PHP File Management System - php

We need to create a file management interface that integrates with the clients current website built on cakephp.
The file manager must only let the users see the files that they have permissions too.
The user will be able to upload files(of any size) and have other users download those files (if permissions allow).
This is no problem for many file management systems that we can purchase but none that I can find will integrate with their current log in system. The client only wants the users to log in once to access their user CP and their files. So as far as I can see our only option is to build a file management interface ourselves.
What are some of the options that we have to allow uploads using PHP? I understand that we need to increase the php upload limit, but is there a ceiling that php/apache will allow?
The files will likely cap out at approximately 150MB if that is relevant, however there may be situations where the file sizes will be larger.
Also what are the do's and don'ts of file upload/control on a Linux server?
I suppose I have no real 'specific' questions but would like some advise on where to start and some of the typical pitfalls we will run into.

File management is quite easy, actually. Here are some suggestions that may point you in the right direction.
First of all, if this is a load balanced web server situation, you'll need to step up the complexity a little in order to put the files in one common place. If that's the case, ping me and I'll be happy to send you our super-light file server/client we use for that same situation.
There are a few variables you will want to affect in order to allow larger uploads. I recommend using apache directives to limit these changes to a particular file:
<Directory /home/deploy/project/uploader>
php_value max_upload_size "200M"
php_value post_max_size "200M"
php_value max_input_time "1800"
# this one depends on how much processing you are doing to the file
php_value memory_limit "32M"
</Directory>
Architecture:
Create a database table that stores some information about each file.
CREATE TABLE `File` (
`File_MNID` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Owner_Field` enum('User.User_ID', 'Resource.Resource_ID') NOT NULL,
`Owner_Key` int(10) unsigned NOT NULL,
`ContentType` varchar(64) NOT NULL,
`Size` int(10) NOT NULL,
`Hash` varchar(40) NOT NULL,
`Name` varchar(128) NOT NULL,
PRIMARY KEY (`File_MNID`),
KEY `Owner` (`Owner_Field`,`Owner_Key`)
) ENGINE=InnoDB
What is Owner_Field and Owner_Key? A simple way to say what "entity" owns the file. In this specific case there were multiple types of files being uploaded. In your case, a simple User_ID field may be adequate.
The purpose of storing the owner is so that you can restrict who can download and delete the file. That will be crucial for protecting the downloads.
Here is a sample class that can be used to accept the file uploads from the browser. You'll need to modify it to suit, of course.
There are a few things to note about the following code. Since this is used with an Application Server and a File Server, there are a few things to "replace".
Any occurrences of App::CallAPI(...) will need to be replaced with a query or set of queries that do the "same thing".
Any occurrences of App::$FS->... will need to be replaced with the correct file handling functions in PHP such as move_uploaded_file, readfile, etc...
Here it is. Keep in mind that there are functions here which allow you to see files owned by a given user, delete files, and so on and so forth. More explanation at the bottom...
<?php
class FileClient
{
public static $DENY = '/\.ade$|\.adp$|\.asp$|\.bas$|\.bat$|\.chm$|\.cmd$|\.com$|\.cpl$|\.crt$|\.exe$|\.hlp$|\.hta$|\.inf$|\.ins$|\.isp$|\.its$| \.js$|\.jse$|\.lnk$|\.mda$|\.mdb$|\.mde$|\.mdt,\. mdw$|\.mdz$|\.msc$|\.msi$|\.msp$|\.mst$|\.pcd$|\.pif$|\.reg$|\.scr$|\.sct$|\.shs$|\.tmp$|\.url$|\.vb$|\.vbe$|\.vbs$|vsmacros$|\.vss$|\.vst$|\.vsw$|\.ws$|\.wsc$|\.wsf$|\.wsh$/i';
public static $MAX_SIZE = 5000000;
public static function SelectList($Owner_Field, $Owner_Key)
{
$tmp = App::CallAPI
(
'File.List',
array
(
'Owner_Field' => $Owner_Field,
'Owner_Key' => $Owner_Key,
)
);
return $tmp['Result'];
}
public static function HandleUpload($Owner_Field, $Owner_Key, $FieldName)
{
$aError = array();
if(! isset($_FILES[$FieldName]))
return false;
elseif(! is_array($_FILES[$FieldName]))
return false;
elseif(! $_FILES[$FieldName]['tmp_name'])
return false;
elseif($_FILES[$FieldName]['error'])
return array('An unknown upload error has occured.');
$sPath = $_FILES[$FieldName]['tmp_name'];
$sHash = sha1_file($sPath);
$sType = $_FILES[$FieldName]['type'];
$nSize = (int) $_FILES[$FieldName]['size'];
$sName = $_FILES[$FieldName]['name'];
if(preg_match(self::$DENY, $sName))
{
$aError[] = "File type not allowed for security reasons. If this file must be attached, please add it to a .zip file first...";
}
if($nSize > self::$MAX_SIZE)
{
$aError[] = 'File too large at $nSize bytes.';
}
// Any errors? Bail out.
if($aError)
{
return $aError;
}
$File = App::CallAPI
(
'File.Insert',
array
(
'Owner_Field' => $Owner_Field,
'Owner_Key' => $Owner_Key,
'ContentType' => $sType,
'Size' => $nSize,
'Hash' => $sHash,
'Name' => $sName,
)
);
App::InitFS();
App::$FS->PutFile("File_" . $File['File_MNID'], $sPath);
return $File['File_MNID'];
}
public static function Serve($Owner_Field, $Owner_Key, $File_MNID)
{
//Also returns the name, content-type, and ledger_MNID
$File = App::CallAPI
(
'File.Select',
array
(
'Owner_Field' => $Owner_Field,
'Owner_Key' => $Owner_Key,
'File_MNID' => $File_MNID
)
);
$Name = 'File_' . $File['File_MNID'] ;
//Content Header for that given file
header('Content-disposition: attachment; filename="' . $File['Name'] . '"');
header("Content-type:'" . $File['ContentType'] . "'");
App::InitFS();
#TODO
echo App::$FS->GetString($Name);
}
public static function Delete($Owner_Field, $Owner_Key, $File_MNID)
{
$tmp = App::CallAPI
(
'File.Delete',
array
(
'Owner_Field' => $Owner_Field,
'Owner_Key' => $Owner_Key,
'File_MNID' => $File_MNID,
)
);
App::InitFS();
App::$FS->DelFile("File_" . $File_MNID);
}
public static function DeleteAll($Owner_Field, $Owner_Key)
{
foreach(self::SelectList($Owner_Field, $Owner_Key) as $aRow)
{
self::Delete($Owner_Field, $Owner_Key, $aRow['File_MNID']);
}
}
}
Notes:
Please keep in mind that this class DOES NOT implement security. It assumes that the caller has an authenticated Owner_Field and Owner_Key before calling FileClient::Serve(...) etc...
It's a bit late, so if some of this doesn't make sense, just leave a comment. Have a great evening, and I hope this helps some.
PS. The user interface can be simple tables and file upload fields, etc.. Or you could be fancy and use a flash uploader...

I recommend having a look at the following community-contributed CakePHP code examples:
Uploader Plugin
WhoDidIt Behavior
Traceable Behavior

here is good choice. It requires you install a PC client and a single file php server. But it runs fast!
http://the-sync-star.com/

Related

File Move Google Drive API v3 PHP

Using Google API v3 I try to move a file from one folder to another. I am using a wrapper class in Laravel, the file and parent IDs are correct. Developing from the documentation, I have tried code as:
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
$fromParentID = $fromFile->getParents();
$blankFile = new Google_Service_Drive_DriveFile();
$this->service->files->update($fileID, $blankFile, [
"removeParents" => $fromParentID,
"addParents" => $toParentID
]);
}
However, this seems to move the file but strips out all the meta data.
I have also tried
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
$fromParentID = $fromFile->getParents();
$fromFile->setParents($fromParentID);
$this->service->files->update($fileID, $fromFile);
}
However, this gives the error:
Google\Service\Exception
{ "error": { "errors": [ { "domain": "global", "reason":
"fieldNotWritable", "message": "The resource body includes fields
which are not directly writable." } ], "code": 403, "message": "The
resource body includes fields which are not directly writable." } }
I wish to simply move the file and retain all its metadata. From the documentation, it seems either a new empty file is required in update (really weird) or I must somehow strip out the fields of the object used in the second argument ($fromFile) it does not like to be written to (even though I am simply updating the files parents - which is writable).
See also https://issuetracker.google.com/issues/199921300
Problems with Answers so far:
but grateful for responses
$fromFile = $this->service->files->get($fileID, ['fields' => 'parents, id']);
returns all ~75 attributes a lot of which are not writeable.
Instead of the expected 2 as per PHPStorm debug (note the break is at the statement immediately following the GET request so irrelevant at this point
using
unset($fromFile->shared);
still leaves other writable attributes
and indeed the file is not actually shared
UPDATE TO MY CODING
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ["fields" => "id,parents"]);
$fromFile = $this->getParsedWritableFile($fromFile);
$fromFile->setParents($toParentID);
$this->service->files->update($fileID, $fromFile, ['addParents' => $toParentID]);
}
getParsedWritableFile is trying to just set writable attributes on a new Google Drive file object:
public function getParsedWritableFile($gdrivefile) {
$gdrivefile = new \Google_Service_Drive_DriveFile();//comment or delete, just here to check auto complete function names
$parsedFile = new \Google_Service_Drive_DriveFile();
//$parsedFile=$gdrivefile;
// currently only allow these according to https://developers.google.com/drive/api/v3/reference/files#resource-representations
$parsedFile->setName($gdrivefile->getName());//name
$parsedFile->setMimeType($gdrivefile->getMimeType());//mimeType
$parsedFile->setDescription($gdrivefile->getDescription());//description
$parsedFile->setStarred($gdrivefile->getStarred());//starred
$parsedFile->setTrashed($gdrivefile->getTrashed());//trashed
$parsedFile->setParents($gdrivefile->getParents());//parents
$parsedFile->setProperties($gdrivefile->getProperties());//properties [object]
$parsedFile->setAppProperties($gdrivefile->getAppProperties());//appProperties [object]
$parsedFile->setCreatedTime($gdrivefile->getCreatedTime());//createdTime
$parsedFile->setModifiedTime($gdrivefile->getModifiedTime());//modifiedTime
$parsedFile->setWritersCanShare($gdrivefile->getWritersCanShare());//writersCanShare
$parsedFile->setViewedByMeTime($gdrivefile->getViewedByMeTime());//viewedByMeTime
$parsedFile->setFolderColorRgb($gdrivefile->getFolderColorRgb());//folderColorRgb
$parsedFile->setOriginalFilename($gdrivefile->getOriginalFilename());//originalFilename
$parsedFile->setCopyRequiresWriterPermission($gdrivefile->getCopyRequiresWriterPermission());//copyRequiresWriterPermission
/*complex permissions*/
/*
contentHints.thumbnail.image
contentHints.thumbnail.mimeType
contentHints.indexableText
*/
$contenthints=$gdrivefile->getContentHints();//could be null meaning further functions eg getThumbnail cause exception
if($contenthints){
$parsedFile->setContentHints($contenthints->getThumbnail()->getImage());
$parsedFile->setContentHints($contenthints->getThumbnail()->getMimeType());
$parsedFile->setContentHints($contenthints->getIndexableText());
}
/*no function to get indiviual attributes*/
/*
contentRestrictions[].readOnly
ccontentRestrictions[].reason
*/
$parsedFile->setContentRestrictions($gdrivefile->getContentRestrictions());
//</ end>
return $parsedFile;
}
This is proving a bit successful but this is original meta
the above code does move it, with seemingly proper meta data, created time and EXIF data is now intact
The problem is that you are using file.update which uses HTTP PATCH methodology. By using a PATCH its going to try to update all of the properties in the file object that you send.
You did a file.get and you included fileds *
$fromFile = $this->service->files->get($fileID, ['fields' => '*']);
By including 'fields' => '*' you told the API to return to you all of the properties that the file has. A file resource has a lot of properties and they are not all writeable.
By sending the file.update method all of the fields you are telling it you want to update everything including some of the properties that you are not allowed to update. To which the api responds with fieldNotWritable
The soultion would be to do the following
$fromFile = $this->service->files->get($fileID, ['fields' => 'parents, id']);
Which will cause the method to only return the two parameters that you actual need. The id and the parents parameter. (TBH you may just need the parents part, I would need to test that.)
{
"id": "1x8-vD-XiA5Spf3qp8x2wltablGF22Lpwup8VtxNY",
"parents": ["0B1bbSFgVLpoXcVDRFRF8tTkU"
]
}
You should then be allowed to update the parent and move your file.
I don't know how to do in laravel but the problem might be
I have also faced the same problem some time and after searching internet for over months found nothing and one day Referring to documentation, seen that shared
isn't a writable field.
That's it, if you are sharing the File and trying to move the file it wouldn't move. Try to un-share the File and then try to move the File it would be done.
I ended up creating a custom function to only include writeable attributes as specified in the Google Documentation:
public function getParsedWritableFile($gdrivefile) {
$gdrivefile = new \Google_Service_Drive_DriveFile();//comment or delete, just here to check auto complete function names
$parsedFile = new \Google_Service_Drive_DriveFile();
// currently only allow these according to https://developers.google.com/drive/api/v3/reference/files#resource-representations
$parsedFile->setName($gdrivefile->getName());//name
$parsedFile->setMimeType($gdrivefile->getMimeType());//mimeType
$parsedFile->setDescription($gdrivefile->getDescription());//description
$parsedFile->setStarred($gdrivefile->getStarred());//starred
$parsedFile->setTrashed($gdrivefile->getTrashed());//trashed
$parsedFile->setParents($gdrivefile->getParents());//parents
$parsedFile->setProperties($gdrivefile->getProperties());//properties [object]
$parsedFile->setAppProperties($gdrivefile->getAppProperties());//appProperties [object]
$parsedFile->setCreatedTime($gdrivefile->getCreatedTime());//createdTime
$parsedFile->setModifiedTime($gdrivefile->getModifiedTime());//modifiedTime
$parsedFile->setWritersCanShare($gdrivefile->getWritersCanShare());//writersCanShare
$parsedFile->setViewedByMeTime($gdrivefile->getViewedByMeTime());//viewedByMeTime
$parsedFile->setFolderColorRgb($gdrivefile->getFolderColorRgb());//folderColorRgb
$parsedFile->setOriginalFilename($gdrivefile->getOriginalFilename());//originalFilename
$parsedFile->setCopyRequiresWriterPermission($gdrivefile->getCopyRequiresWriterPermission());//copyRequiresWriterPermission
/*complex permissions*/
/*
contentHints.thumbnail.image
contentHints.thumbnail.mimeType
contentHints.indexableText
*/
$contenthints=$gdrivefile->getContentHints();//could be null meaning further functions eg getThumbnail cause exception
if($contenthints){
$parsedFile->setContentHints($contenthints->getThumbnail()->getImage());
$parsedFile->setContentHints($contenthints->getThumbnail()->getMimeType());
$parsedFile->setContentHints($contenthints->getIndexableText());
}
/*no function to get indiviual attributes*/
/*
contentRestrictions[].readOnly
ccontentRestrictions[].reason
*/
$parsedFile->setContentRestrictions($gdrivefile->getContentRestrictions());
return $parsedFile;
}
and called with
public function moveFileFromTo($fileID, $toParentID) {
$fromFile = $this->service->files->get($fileID, ["fields" => "id,parents"]);
$fromFile = $this->getParsedWritableFile($fromFile);
$fromFile->setParents($toParentID);
$this->service->files->update($fileID, $fromFile, ['addParents' => $toParentID]);
}

SilverStripe default content author permissions

The default value for Manage site configuration is off
security > groups > content authors > permissions
Although it's possible to simply check the box and activate this, I would rather have this on by default for every SS installation.
How can the default value for this be set to on?
This should do as required, make an extension of Group and add a requireDefaultRecords function, this is called on every dev build.
This function is to look for that permission and if not existing create it...
class GroupExtension extends DataExtension {
function requireDefaultRecords() {
//get the content-authors group
if ($group = Group::get()->filter('Code','content-authors')->first()) {
//expected permission record content
$arrPermissionData = array(
'Arg' => 0,
'Type' => 1,
'Code' => 'EDIT_SITECONFIG',
'GroupID' => $group->ID
);
//if the permission is not found, then create it
if (!Permission::get()->filter($arrPermissionData)->first())
Permission::create($arrPermissionData)->write();
}
}
}
As ever to register the extension add this to your config.yml...
Group:
extensions:
- GroupExtension

PHP file exist & Wordpress Shortcode

I'm trying to retrieve the number of parking lots from a .txt file, its working on the static site iframe but I want to make a shortcode and place it on wordpress theme function file.
For some reason it's not reading the data...
function GenerateLOT($atts = array()) {
// Default Parameters
extract(shortcode_atts(array(
'id' => 'id'
), $atts));
// Create Default Park / Help
if ($id == '') {
$id = 'PARK IDs: bahnhofgarage;';
}
// Create Park Bahnhofgarage
if ($id == 'bahnhofgarage') {
$completeBahnhof = "//xxx.de/bahnhof.txt";
if(file_exists($completeBahnhof )) {
$fp=file($completeBahnhof );
$Garage = $fp[0];
$valmpl=explode(" ",$Garage);
$Bahnhof_Platz = $valmpl[0];
$Bahnhof_Tendenz = $valmpl[1];
}
$id = $Bahnhof_Platz;
}
return $id;
}
add_shortcode('parking', 'GenerateLOT');
[parking id='bahnhofgarage']
PS: The .txt is working properly retrieving like this: 000 - //bahnhof 27.12.15 12:46:59
For some reason its only displaying the $park == '' text and not the parking lots according shortcode param.
I've used this tutorial: sitepoint.com/wordpress-shortcodes-tutorial/
EDIT: There are 6 parking lots.
EDIT2: Changed park to id on all instances
The problem is that you can't meaningfully use file_exists on remote path. See SO answer to "file_exists() returns false even if file exist (remote URL)" question for details.
You should probably just call file() on that path. It will return FALSE if it encounter an error.
if ($id == 'bahnhofgarage') {
$completeBahnhof = "//xxx.de/bahnhof.txt";
$fp=file($completeBahnhof );
if ($fp !== false) {
$Garage = $fp[0];
// rest of code
On a side note, shortcode_atts() is used to provide default values for shortcode attributes, while you seem to be using it as some sort of mapping between shortcode attributes and internal variable names.
Accessing file on remote server inside shortcode is asking for trouble. Just think what will happen if this server is overloaded, slow to respond or not available anymore. You should really access that file asynchronously. If it is located on your server, access it through file-system path.

How to count the number of user downloads

This code for extract files dir and title. The user download when clicked the file link. I need to count then number of downloads. How to do this with cakephp or php?
downloads_controller.php:
function index() {
$this->set('downloads', $this->paginate());
}
downloads/index.ctp:
<?php
$title = $download['Download']['title'];
// output filetitle
$filename = '/files/'.$download['Download']['filename'];
// output http://localhost/tet/files/un5titled0.rar
echo $this->Html->link($title, $filename,array('escape' => false));
?>
not this way i am afraid.
you either need to redirect from an action (which counts before redirecting) or use Media view to pass it through.
thats how I do it.
In the action you can then raise the count prior to sending the file.
If you want to count downloads, you should create a function that serves those downloads and create a field in your database that increments downloads each time this function is called.. For example
Call the following function from your view passing the $filename and the $id
To try out at first use, taking ID=4 as one of the downloads ID in your DB
http://www.yourdomain.com/downloads/download/4
And Then your controller would be...
Class DownloadsController extends AppController{
// All your other functions here
function download($id = null){
$this->Article->updateAll(
array('Download.count' => 'Download.count+1'),
array('Download.id' => $id)
);
$download = $this->Download->findById($id);
$this->set('filename', $download['Download']['filename']);
//$filename is an array that can then be used in your view to pass the file for download. to find out what is has you can use debug($filename) in your view
}
}
Then you need to create a special layout so the browser knows that the request file is a download and you will also need to create a view for download.ctp. Basically when you click the file link on your page, it will call this function which will use its view to serve the file.
You can access the following which will provide some insight on what needs to be done..
TUTORIAL LINK
There are lots of techniques, though in the simplest way, you can use a text file to do this.
Create a txt file and write 0 (zero) into it.
In your index function, read the content of your file.
$counter = file_read_contents('counter.txt');
Increase the read value by 1.
$counter++;
Write new value into file again.
file_put_contents('counter.txt', $counter);
So, it counts downloads and keep number in that file.
function download($id = null) {
$download = $this->Download->findById($id);
$this->set(compact('download'));
if(!empty($id)) {
// increment the number of items downloads
$this->Download->save(array(
'Download' => array(
'id' => $id,
'counts' => ($download['Download']['counts'] + 1)
)
));
}
header('Location: http://localhost/tet/files/'.$download['Download']['filename']);
exit();
}

Protect a directory in lighttpd using a php script

I would like to protect a directory and authenticate users against a mysql database. I am using lighttpd and haven't been able to find a way of doing so. Is it possible?
You could use mod_auth, here is the relevant doc page
Since it has no direct access to a database, i would recommend using the 'htdigest' method, and regenerating the file from your database users.
the 'htdigest' format is just: "user:realm:md5(password)", as explained in the page.
Generating a file like this from a php script should be extremely simple.
pseudo-code:
foreach ($users as $user) {
// $user['md5pass'] = md5($user['password']);
$line = sprintf("%s:%s:%s\n", $user['username'], 'protected', $user['md5pass']);
file_put_contents('htdigest-file', $line, FILE_APPEND);
}
Also, from the same page, here is a sample lighttpd configuration for mod_auth:
auth.backend = "htdigest"
auth.backend.htdigest.userfile = "lighttpd-htdigest.user"
auth.require = ( "/download/" =>
(
# method must be either basic or digest
"method" => "digest",
"realm" => "download archiv",
"require" => "user=agent007|user=agent008"
),
"/server-info" =>
(
# limit access to server information
"method" => "digest",
"realm" => "download archiv",
"require" => "valid-user"
)
)

Categories