New module 'FileField'

This commit is contained in:
Manuel Cillero 2017-07-26 19:47:34 +02:00
parent 673511a713
commit f8571672d1
44 changed files with 5831 additions and 0 deletions

View file

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View file

@ -0,0 +1,44 @@
FileField provides an "File" field type to CCK. It provides many advantages over
the Drupal core "Upload" module including:
* Per-field upload control (file extensions, file size).
* Per-node upload size limits.
* Multiple fields per content type.
* Customizable paths for saving uploads (plus token support for dynamic paths).
* Icons for uploaded file types.
FileField was written by Darrel Opry (dopry).
Maintained by Nathan Haug (quicksketch) and Andrew Morton (drewish).
Dependencies
------------
* Content
FileField also provides additional features when used with the following:
* ImageField (See an image preview during editing.)
* Token (Generate dynamic paths when saving images.)
* ImageCache (Create thumbnails of images on output.)
If your site is any larger than a personal blog, you should definitely install
the following modules to increase the security and stability of your uploads.
* Transliteration (Convert unsafe characters to file system safe names.)
* MimeDetect (Check the content of files to ensure they match the extension.)
Install
-------
1) Copy the filefield folder to the modules folder in your installation.
2) Enable the module using Administer -> Site building -> Modules
(/admin/build/modules).
3) Create a new file field in through CCK's interface. Visit Administer ->
Content management -> Content types (admin/content/types), then click
Manage fields on the type you want to add an file upload field. Select
"File" as the field type and "File" as the widget type to create a new
field.
4) Upload files on the node form for the type that you set up.

View file

@ -0,0 +1,40 @@
Upgrading from previous versions of FileField does not differ from the
recommended approach for other modules.
Upgrading from Drupal 5
-----------------------
1) Your Drupal 5 installation must be running at least FileField 2.3 before
migrating to Drupal 6. If your version of FileField is older than this,
install the latest version of FileField for Drupal 5 on your Drupal 5 site
and run update.php. This gets the database in a format that the Drupal 6
upgrade expects.
2) After ensure FileField 2.3 or higher is on your Drupal 5 site, upgrade your
site normally to Drupal 6. Download the latest version of FileField for
Drupal 6 and place it in your sites/all/modules directory.
3) Run update.php on your Drupal 6 site to upgrade FileField to the latest
database schema.
Upgrading from any previous Drupal 6 version
--------------------------------------------
1) Download the latest version of FileField and the filefield directory in
sites/all/modules.
2) Visit update.php on your site and run the update.
Running update.php
------------------
Never, never, ever expand the "Select versions" fieldset and manually change
the update number. Drupal knows which version you are currently running and
automatically selects the appropriate version. If you change this number, you
risk running an update twice (if you select a lower number) or you'll skip
updates entirely (if you select a higher number). The ability to change the
update number was intended for developers. Due to the confusion caused by this
fieldset, it has been removed entirely in Drupal 7 to prevent such accidental
self-inflicted damage to user sites.

View file

@ -0,0 +1,416 @@
<?php
/**
* @file
* Common functionality for file handling CCK field modules.
*/
/**
* Load a file from the database.
*
* @param $fid
* A numeric file id or string containing the file path.
* @param $reset
* Whether to reset the internal file_load cache.
* @return
* A file array.
*/
function field_file_load($fid, $reset = NULL) {
// Reset internal cache.
if (isset($reset)) {
_field_file_cache(NULL, TRUE);
}
if (empty($fid)) {
return array('fid' => 0, 'filepath' => '', 'filename' => '', 'filemime' => '', 'filesize' => 0);
}
$files = _field_file_cache();
// Serve file from internal cache if available.
if (empty($files[$fid])) {
if (is_numeric($fid)) {
$file = db_fetch_object(db_query('SELECT f.* FROM {files} f WHERE f.fid = %d', $fid));
}
else {
$file = db_fetch_object(db_query("SELECT f.* FROM {files} f WHERE f.filepath = '%s'", $fid));
}
if (!$file) {
$file = (object) array('fid' => 0, 'filepath' => '', 'filename' => '', 'filemime' => '', 'filesize' => 0);
}
foreach (module_implements('file_load') as $module) {
if ($module != 'field') {
$function = $module .'_file_load';
$function($file);
}
}
// Cache the fully loaded file for later use.
$files = _field_file_cache($file);
}
// Cast to an array for the field storage.
// Contrary to fields, hook_file() and core file functions expect objects.
return isset($files[$fid]) ? (array) $files[$fid] : FALSE;
}
/**
* Save a file upload to a new location.
* The source file is validated as a proper upload and handled as such. By
* implementing hook_file($op = 'insert'), modules are able to act on the file
* upload and to add their own properties to the file.
*
* The file will be added to the files table as a temporary file. Temporary
* files are periodically cleaned. To make the file permanent file call
* file_set_status() to change its status.
*
* @param $source
* A string specifying the name of the upload field to save.
* @param $validators
* An optional, associative array of callback functions used to validate the
* file. The keys are function names and the values arrays of callback
* parameters which will be passed in after the user and file objects. The
* functions should return an array of error messages, an empty array
* indicates that the file passed validation. The functions will be called in
* the order specified.
* @param $dest
* A string containing the directory $source should be copied to. If this is
* not provided or is not writable, the temporary directory will be used.
* @return
* An array containing the file information, or 0 in the event of an error.
*/
function field_file_save_upload($source, $validators = array(), $dest = FALSE) {
if (!$file = file_save_upload($source, $validators, $dest, FILE_EXISTS_RENAME)) {
return 0;
}
if (!@chmod($file->filepath, 0664)) {
watchdog('filefield', 'Could not set permissions on destination file: %file', array('%file' => $file->filepath));
}
// Let modules add additional properties to the yet barebone file object.
foreach (module_implements('file_insert') as $module) {
$function = $module .'_file_insert';
$function($file);
}
_field_file_cache($file); // cache the file in order to minimize load queries
return (array)$file;
}
/**
* Save a file into a file node after running all the associated validators.
*
* This function is usually used to move a file from the temporary file
* directory to a permanent location. It may be used by import scripts or other
* modules that want to save an existing file into the database.
*
* @param $filepath
* The local file path of the file to be saved.
* @param $validators
* An optional, associative array of callback functions used to validate the
* file. The keys are function names and the values arrays of callback
* parameters which will be passed in after the user and file objects. The
* functions should return an array of error messages, an empty array
* indicates that the file passed validation. The functions will be called in
* the order specified.
* @param $dest
* A string containing the directory $source should be copied to. If this is
* not provided or is not writable, the temporary directory will be used.
* @param $account
* The user account object that should associated with the uploaded file.
* @return
* An array containing the file information, or 0 in the event of an error.
*/
function field_file_save_file($filepath, $validators = array(), $dest = FALSE, $account = NULL) {
if (!isset($account)) {
$account = $GLOBALS['user'];
}
// Add in our check of the the file name length.
$validators['file_validate_name_length'] = array();
// Begin building file object.
$file = new stdClass();
$file->uid = $account->uid;
$file->filename = basename($filepath);
$file->filepath = $filepath;
$file->filemime = module_exists('mimedetect') ? mimedetect_mime($file) : file_get_mimetype($file->filename);
// Rename potentially executable files, to help prevent exploits.
if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
$file->filemime = 'text/plain';
$file->filepath .= '.txt';
$file->filename .= '.txt';
}
// If the destination is not provided, or is not writable, then use the
// temporary directory.
if (empty($dest) || file_check_path($dest) === FALSE) {
$dest = file_directory_temp();
}
$file->source = 'field_file_save_file';
$file->destination = file_destination(file_create_path($dest .'/'. $file->filename), FILE_EXISTS_RENAME);
$file->filesize = filesize($filepath);
// Call the validation functions.
$errors = array();
foreach ($validators as $function => $args) {
// Add the $file variable to the list of arguments and pass it by
// reference (required for PHP 5.3 and higher).
array_unshift($args, NULL);
$args[0] = &$file;
$errors = array_merge($errors, call_user_func_array($function, $args));
}
// Check for validation errors.
if (!empty($errors)) {
$message = t('The selected file %name could not be saved.', array('%name' => $file->filename));
if (count($errors) > 1) {
$message .= '<ul><li>'. implode('</li><li>', $errors) .'</li></ul>';
}
else {
$message .= ' '. array_pop($errors);
}
form_set_error($file->source, $message);
return 0;
}
if (!file_copy($file, $file->destination, FILE_EXISTS_RENAME)) {
form_set_error($file->source, t('File upload error. Could not move uploaded file.'));
watchdog('file', 'Upload error. Could not move file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->destination));
return 0;
}
// If we made it this far it's safe to record this file in the database.
$file->status = FILE_STATUS_TEMPORARY;
$file->timestamp = time();
// Insert new record to the database.
drupal_write_record('files', $file);
// Let modules add additional properties to the yet barebone file object.
foreach (module_implements('file_insert') as $module) {
$function = $module .'_file_insert';
$function($file);
}
_field_file_cache($file); // cache the file in order to minimize load queries
return (array)$file;
}
/**
* Save a node file. Delete items if necessary and set new items as permanent.
*
* @param $node
* Node object this file is be associated with.
* @param $file
* File to be inserted, passed by reference since fid should be attached.
* @return array
*/
function field_file_save($node, &$file) {
// If this item is marked for deletion.
if (!empty($file['delete']) || !empty($file['_remove'])) {
// If we're creating a new revision, return an empty array so CCK will
// remove the item.
if (!empty($node->old_vid)) {
return array();
}
// Otherwise delete the file and return an empty array.
if (field_file_delete($file)) {
return array();
}
}
// Cast to object since core functions use objects.
$file = (object)$file;
// Set permanent status on files if unset.
if (empty($file->status)) {
file_set_status($file, FILE_STATUS_PERMANENT);
}
// Let modules update their additional file properties too.
foreach (module_implements('file_update') as $module) {
$function = $module .'_file_update';
$function($file);
}
_field_file_cache($file); // update the cache, in case the file has changed
$file = (array)$file;
return $file;
}
/**
* Delete a field file and its database record.
*
* @param $path
* A file object.
* @param $force
* Force File Deletion ignoring reference counting.
* @return mixed
* TRUE for success, Array for reference count block, or FALSE in the event of an error.
*/
function field_file_delete($file, $force = FALSE) {
$file = (object)$file;
// If any module returns a value from the reference hook, the
// file will not be deleted from Drupal, but file_delete will
// return a populated array that tests as TRUE.
if (!$force && $references = module_invoke_all('file_references', $file)) {
$references = array_filter($references); // only keep positive values
if (!empty($references)) {
return $references;
}
}
// Let other modules clean up on delete.
module_invoke_all('file_delete', $file);
// Make sure the file is deleted before removing its row from the
// database, so UIs can still find the file in the database.
if (file_delete($file->filepath)) {
db_query('DELETE FROM {files} WHERE fid = %d', $file->fid);
_field_file_cache(NULL, $file); // delete the file from the cache
return TRUE;
}
return FALSE;
}
/**
* Internal cache, in order to minimize database queries for loading files.
*/
function _field_file_cache($file = NULL, $reset = FALSE) {
static $files = array();
// Reset internal cache.
if (is_object($reset)) { // file object, uncache just that one
unset($files[$reset->fid]);
unset($files[$reset->filepath]);
}
else if ($reset) { // TRUE, delete the whole cache
$files = array();
}
// Cache the file by both fid and filepath.
// Use non-copying objects to save memory.
if (!empty($file->fid)) {
$files[$file->fid] = $file;
$files[$file->filepath] = $file;
}
return $files;
}
/**
* A silent version of file.inc's file_check_directory().
*
* This function differs from file_check_directory in that it checks for
* files when doing the directory check and it does not use drupal_set_message()
* when creating directories. This function may be removed in Drupal 7.
*
* Check that the directory exists and is writable. Directories need to
* have execute permissions to be considered a directory by FTP servers, etc.
*
* @param $directory A string containing the name of a directory path.
* @param $mode A Boolean value to indicate if the directory should be created
* if it does not exist or made writable if it is read-only.
* @param $form_item An optional string containing the name of a form item that
* any errors will be attached to. This is useful for settings forms that
* require the user to specify a writable directory. If it can't be made to
* work, a form error will be set preventing them from saving the settings.
* @return FALSE when directory not found, or TRUE when directory exists.
*/
function field_file_check_directory(&$directory, $mode = 0, $form_item = NULL) {
$directory = rtrim($directory, '/\\');
// Error if the directory is a file.
if (is_file($directory)) {
watchdog('file system', 'The path %directory was checked as a directory, but it is a file.', array('%directory' => $directory), WATCHDOG_ERROR);
if ($form_item) {
form_set_error($form_item, t('The directory %directory is a file and cannot be overwritten.', array('%directory' => $directory)));
}
return FALSE;
}
// Create the directory if it is missing.
if (!is_dir($directory) && $mode & FILE_CREATE_DIRECTORY && !@mkdir($directory, 0775, TRUE)) {
watchdog('file system', 'The directory %directory does not exist.', array('%directory' => $directory), WATCHDOG_ERROR);
if ($form_item) {
form_set_error($form_item, t('The directory %directory does not exist.', array('%directory' => $directory)));
}
return FALSE;
}
// Check to see if the directory is writable.
if (!is_writable($directory) && $mode & FILE_MODIFY_PERMISSIONS && !@chmod($directory, 0775)) {
watchdog('file system', 'The directory %directory is not writable, because it does not have the correct permissions set.', array('%directory' => $directory), WATCHDOG_ERROR);
if ($form_item) {
form_set_error($form_item, t('The directory %directory is not writable', array('%directory' => $directory)));
}
return FALSE;
}
if ((file_directory_path() == $directory || file_directory_temp() == $directory) && !is_file("$directory/.htaccess")) {
$htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks";
if (($fp = fopen("$directory/.htaccess", 'w')) && fputs($fp, $htaccess_lines)) {
fclose($fp);
chmod($directory .'/.htaccess', 0664);
}
else {
$repl = array('%directory' => $directory, '!htaccess' => nl2br(check_plain($htaccess_lines)));
form_set_error($form_item, t("Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines:<br /><code>!htaccess</code>", $repl));
watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines:<br /><code>!htaccess</code>", $repl, WATCHDOG_ERROR);
}
}
return TRUE;
}
/**
* Remove a possible leading file directory path from the given path.
*/
function field_file_strip_path($path) {
$dirpath = file_directory_path();
$dirlen = drupal_strlen($dirpath);
if (drupal_substr($path, 0, $dirlen + 1) == $dirpath .'/') {
$path = drupal_substr($path, $dirlen + 1);
}
return $path;
}
/**
* Encode a file path in a way that is compatible with file_create_url().
*
* This function should be used on the $file->filepath property before any call
* to file_create_url(). This ensures that the file directory path prefix is
* unmodified, but the actual path to the file will be properly URL encoded.
*/
function field_file_urlencode_path($path) {
// Encode the parts of the path to ensure URLs operate within href attributes.
// Private file paths are urlencoded for us inside of file_create_url().
if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) {
$file_directory_path = file_directory_path();
if (strpos($path, $file_directory_path . '/') === 0) {
$path = trim(substr($path, strlen($file_directory_path)), '\\/');
}
$parts = explode('/', $path);
foreach ($parts as $index => $part) {
$parts[$index] = rawurlencode($part);
}
$path = implode('/', $parts);
// Add the file directory path again (not encoded).
$path = $file_directory_path . '/' . $path;
}
return $path;
}
/**
* Return a count of the references to a file by all modules.
*/
function field_file_references($file) {
$references = (array) module_invoke_all('file_references', $file);
$reference_count = 0;
foreach ($references as $module => $count) {
$reference_count += $count;
}
return $reference_count;
}

View file

@ -0,0 +1,20 @@
.filefield-icon {
margin: 0 0 0 2px;
}
.filefield-element .widget-preview {
float: right;
padding: 0 0 0 10px;
border-size: 0 0 0 1px;
margin: 0 0 0 10px;
}
.filefield-element .widget-edit {
float: right;
}
.filefield-element div.ahah-progress,
.filefield-element div.throbber {
padding: 1px 3px 2px 13px;
}

View file

@ -0,0 +1,72 @@
/**
* Formatter styles
*/
.filefield-icon {
margin: 0 2px 0 0; /* RTL */
}
/* End formatter styles. */
/**
* General widget form styles (applicable to all widgets).
*/
.filefield-element {
margin: 1em 0;
white-space: normal;
}
.filefield-element .widget-preview {
float: left; /* RTL */
padding: 0 10px 0 0; /* RTL */
margin: 0 10px 0 0; /* RTL */
border-width: 0 1px 0 0; /* RTL */
border-style: solid;
border-color: #CCC;
max-width: 30%;
}
.filefield-element .widget-edit {
float: left; /* RTL */
max-width: 70%;
}
.filefield-element .filefield-preview {
width: 16em;
overflow: hidden;
}
.filefield-element .widget-edit .form-item {
margin: 0 0 1em 0;
}
.filefield-element input.form-submit,
.filefield-element input.form-file {
margin: 0;
}
.filefield-element input.progress-disabled {
float: none;
display: inline;
}
.filefield-element div.ahah-progress,
.filefield-element div.throbber {
display: inline;
float: none;
padding: 1px 13px 2px 3px; /* RTL */
}
.filefield-element div.ahah-progress-bar {
display: none;
margin-top: 4px;
width: 28em;
padding: 0;
}
.filefield-element div.ahah-progress-bar div.bar {
margin: 0;
}
/* End general widget form styles. */

View file

@ -0,0 +1,126 @@
<?php
/**
* @file
* Utility functions for generating FileField content. Note that image
* generation support requires the GD toolkit.
*/
/**
* Private function used by filefield_content_generate().
*/
function _filefield_content_generate($node, $field) {
if ($source = _filefield_generate_file($field)) {
$file = field_file_save_file($source, array(), filefield_widget_file_path($field));
$item = (array) $file;
$item['list'] = 1;
$item['data']['alt'] = devel_create_greeking(4);
$item['data']['title'] = devel_create_greeking(10);
}
else {
$item = array();
}
return $item;
}
/**
* Generate an image based on the properties of a field.
*
* This is made to work with ImageField, and inspects the minimum and maximum
* image sizes and makes sure the generated image matches the requirements.
*
* @return
* The path to the new file, in the temporary directory.
*/
function _filefield_generate_file($field) {
if (empty($field['widget']['file_extensions'])) {
$field['widget']['file_extensions'] = 'png jpg txt';
}
$extensions = array_intersect(explode(' ', $field['widget']['file_extensions']), array('png', 'jpg', 'txt'));
$extension = array_rand(drupal_map_assoc($extensions));
if ($extension == 'txt') {
$filesize = empty($field['widget']['max_filesize_per_file']) ? 1024 : parse_size($field['widget']['max_filesize_per_file']);
return _filefield_generate_textfile($filesize);
}
elseif (in_array($extension, array('png', 'jpg')) && function_exists('imagecreate')) {
$min_resolution = empty($field['widget']['min_resolution']) ? '100x100' : $field['widget']['min_resolution'];
$max_resolution = empty($field['widget']['max_resolution']) ? '600x600' : $field['widget']['max_resolution'];
return _filefield_generate_image($extension, $min_resolution, $max_resolution);
}
return FALSE;
}
/**
* Private function for generating a random text file.
*/
function _filefield_generate_textfile($filesize = 1024) {
static $filesizes = array();
$temp_file = FALSE;
if (isset($filesizes[$filesize])) {
$temp_file = $filesizes[$filesize];
}
elseif ($temp_file = tempnam(file_directory_temp(), 'filefield_')) {
file_move($temp_file, $temp_file .'.txt');
$fp = fopen($temp_file, 'w');
fwrite($fp, str_repeat('01', $filesize/2));
fclose($fp);
$filesizes[$filesize] = $temp_file;
}
return $temp_file;
}
/**
* Private function for creating a random image.
*
* This function only works with the GD toolkit. ImageMagick is not supported.
*/
function _filefield_generate_image($extension = 'png', $min_resolution, $max_resolution) {
static $images = array();
// Generate a max of 5 different images.
if (!isset($images[$extension][$min_resolution][$max_resolution]) || count($images[$extension][$min_resolution][$max_resolution]) < 5) {
if ($temp_file = tempnam(file_directory_temp(), 'filefield_')) {
file_move($temp_file, $temp_file .'.'. $extension);
$min = explode('x', $min_resolution);
$max = explode('x', $max_resolution);
$width = rand((int)$min[0], (int)$max[0]);
$height = rand((int)$min[0], (int)$max[0]);
// Make a image split into 4 sections with random colors.
$im = imagecreate($width, $height);
for ($n = 0; $n < 4; $n++) {
$color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
$x = $width/2 * ($n % 2);
$y = $height/2 * (int) ($n >= 2);
imagefilledrectangle($im, $x, $y, $x + $width/2, $y + $height/2, $color);
}
// Make a perfect circle in the image middle.
$color = imagecolorallocate($im, rand(0, 255), rand(0, 255), rand(0, 255));
$smaller_dimension = min($width, $height);
$smaller_dimension = ($smaller_dimension % 2) ? $smaller_dimension : $smaller_dimension;
imageellipse($im, $width/2, $height/2, $smaller_dimension, $smaller_dimension, $color);
$save_function = 'image'. ($extension == 'jpg' ? 'jpeg' : $extension);
$save_function($im, $temp_file);
$images[$extension][$min_resolution][$max_resolution][$temp_file] = $temp_file;
}
}
// Select one of the images we've already generated for this field.
else {
$temp_file = array_rand($images[$extension][$min_resolution][$max_resolution]);
}
return $temp_file;
}

View file

@ -0,0 +1,12 @@
name = FileField
description = Defines a file field type.
dependencies[] = content
package = CCK
core = 6.x
php = 5.0
; Information added by Drupal.org packaging script on 2016-02-24
version = "6.x-3.14"
core = "6.x"
project = "filefield"
datestamp = "1456327142"

View file

@ -0,0 +1,363 @@
<?php
/**
* @file
* FileField: Defines a CCK file field type.
*
* Uses content.module to store the fid and field specific metadata,
* and Drupal's {files} table to store the actual file data.
*/
require_once dirname(__FILE__) . '/field_file.inc';
/**
* Implementation of hook_install().
*/
function filefield_install() {
drupal_load('module', 'content');
content_notify('install', 'filefield');
}
/**
* Implementation of hook_uninstall().
*/
function filefield_uninstall() {
drupal_load('module', 'content');
content_notify('uninstall', 'filefield');
}
/**
* Implementation of hook_enable().
*/
function filefield_enable() {
drupal_load('module', 'content');
content_notify('enable', 'filefield');
}
/**
* Implementation of hook_disable().
*/
function filefield_disable() {
drupal_load('module', 'content');
content_notify('disable', 'filefield');
}
/**
* Implementation of hook_requirements().
*
* Display information about getting upload progress bars working.
*/
function filefield_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time
$t = get_t();
// Report Drupal version
if ($phase == 'runtime') {
drupal_load('module', 'filefield');
$implementation = filefield_progress_implementation();
$apache = strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== FALSE;
$fastcgi = strpos($_SERVER['SERVER_SOFTWARE'], 'mod_fastcgi') !== FALSE || strpos($_SERVER["SERVER_SOFTWARE"], 'mod_fcgi') !== FALSE;
$php_52 = version_compare(phpversion(), '5.2.0', '>');
$description = NULL;
if (!$apache || !$php_52) {
$value = $t('Not enabled');
$description = $t('Your server is not capable of displaying file upload progress. File upload progress requires PHP 5.2 and an Apache server.');
$severity = REQUIREMENT_INFO;
}
elseif ($fastcgi) {
$value = $t('Not enabled');
$description = $t('Your server is not capable of displaying file upload progress. File upload progress requires PHP be run with mod_php and not as FastCGI.');
$severity = REQUIREMENT_INFO;
}
elseif (!$implementation && extension_loaded('apc')) {
$value = $t('Not enabled');
$description = $t('Your server is capable of displaying file upload progress through APC, but it is not enabled. Add <code>apc.rfc1867 = 1</code> to your php.ini configuration. Alternatively, it is recommended to use <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>, which supports more than one simultaneous upload.');
$severity = REQUIREMENT_INFO;
}
elseif (!$implementation) {
$value = $t('Not enabled');
$description = t('Your server is capable of displaying file upload progress, but does not have the required libraries. It is recommended to install the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a> (preferred) or to install <a href="http://us2.php.net/apc">APC</a>.');
$severity = REQUIREMENT_INFO;
}
elseif ($implementation == 'apc') {
$value = $t('Enabled (<a href="http://php.net/manual/en/apc.configuration.php#ini.apc.rfc1867">APC RFC1867</a>)');
$description = t('Your server is capable of displaying file upload progress using APC RFC1867. Note that only one upload at a time is supported. It is recommended to use the <a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress library</a> if possible.');
$severity = REQUIREMENT_OK;
}
elseif ($implementation == 'uploadprogress') {
$value = $t('Enabled (<a href="http://pecl.php.net/package/uploadprogress">PECL uploadprogress</a>)');
$severity = REQUIREMENT_OK;
}
$requirements['filefield_progress'] = array(
'title' => $t('Upload progress'),
'value' => $value,
'severity' => $severity,
'description' => $description,
);
}
return $requirements;
}
/**
* Implementation of hook_update_last_removed().
*/
function filefield_update_last_removed() {
return 3;
}
/**
* Upgrade FileField to Drupal 6.
*/
function filefield_update_6001() {
if ($abort = content_check_update('filefield')) {
return $abort;
}
$ret = array();
module_load_include('inc', 'content', 'includes/content.admin');
// Rename the field type from file to filefield. adhere to module namespace.
$ret[] = update_sql("UPDATE {content_node_field} SET type = 'filefield', module = 'filefield', active = 1 WHERE type = 'file'");
// Rename default widget to filefield_widget. adhere to module namespace.
$ret[] = update_sql("UPDATE {content_node_field_instance} SET widget_type = 'filefield_widget', widget_module = 'filefield', widget_active = 1 WHERE widget_type = 'file'");
// Update list default value and force list settings.
$result = db_query("SELECT * FROM {content_node_field} WHERE type = 'filefield'");
while ($field = db_fetch_object($result)) {
$updated = FALSE;
$field_settings = unserialize($field->global_settings);
if (!isset($field_settings['list_default']) || !is_numeric($field_settings['list_default'])) {
$field_settings['list_default'] = 1;
$updated = TRUE;
}
// Set behavior to match old force_list behavior.
if (!empty($field_settings['force_list'])) {
$field_settings['list_default'] = 1;
$field_settings['force_list_default'] = 1;
$updated = TRUE;
}
if ($updated) {
db_query("UPDATE {content_node_field} SET global_settings = '%s' WHERE field_name = '%s'", serialize($field_settings), $field->field_name);
}
}
// Re-enable all the FileFields on the site.
content_associate_fields('filefield');
// Build a list of fields that need data updating.
$fields = array();
foreach (content_types_install() as $type_name => $type_fields) {
foreach ($type_fields as $field) {
if ($field['type'] == 'filefield') {
// We only process a given field once.
$fields[$field['field_name']] = $field;
}
}
}
// Update database storage (add data column, remove description, set NOT NULL).
foreach ($fields as $field) {
$new_field = $field;
// Setup the previous definition.
$field['columns']['description'] = array('type' => 'varchar');
$field['columns']['fid']['not null'] = TRUE;
$field['columns']['list']['not null'] = TRUE;
unset($field['columns']['data']);
// Setup the new definition.
$new_field['columns']['data'] = array('type' => 'text', 'serialize' => TRUE);
$new_field['columns']['fid']['not null'] = FALSE;
$new_field['columns']['list']['size'] = 'tiny';
$new_field['columns']['list']['not null'] = FALSE;
unset($new_field['columns']['description']);
content_alter_db($field, $new_field);
}
// Build a batch that migrates the data in each filefield
$batch = array(
'title' => t('Migrating filefield values'),
'operations' => array(),
'file' => drupal_get_path('module', 'filefield') .'/filefield.install',
);
foreach ($fields as $field_name => $field) {
if ($field['type'] == 'filefield') {
$batch['operations'][] = array('_filefield_update_6001_move_operation', array($field));
$batch['operations'][] = array('_filefield_update_6001_drop_operation', array($field));
}
}
batch_set($batch);
// Clear caches.
cache_clear_all('*', content_cache_tablename(), TRUE);
cache_clear_all('*', 'cache', TRUE);
return $ret;
}
/**
* Migrate field settings from 'force_list_default' and 'show_description'.
*/
function filefield_update_6100() {
$ret = array();
module_load_include('inc', 'content', 'includes/content.crud');
$fields = content_fields();
foreach ($fields as $field) {
if ($field['type'] == 'filefield') {
if (isset($field['force_list_default'])) {
$field['list_field'] = !$field['force_list_default'];
}
if (isset($field['show_description'])) {
$field['description_field'] = $field['show_description'];
}
_content_field_write($field);
$ret[] = array('success' => TRUE, 'query' => t('The file field %field has been updated with new settings.', array('%field' => $field['field_name'])));
}
}
cache_clear_all('*', content_cache_tablename(), TRUE);
cache_clear_all('*', 'cache', TRUE);
return $ret;
}
/**
* Set fid to NULL where files have been deleted.
*
* This is a double-cleanup from Drupal 5 versions, where fid used to be 0 for
* empty rows or sometimes referred to a nonexistent FID altogether.
*/
function filefield_update_6101() {
$ret = array();
module_load_include('inc', 'content', 'includes/content.crud');
$fields = content_fields();
foreach ($fields as $field) {
if ($field['type'] == 'filefield') {
$db_info = content_database_info($field);
if (isset($db_info['columns']['fid'])) {
$table = $db_info['table'];
$fid_column = $db_info['columns']['fid']['column'];
$list_column = $db_info['columns']['list']['column'];
$ret[] = update_sql("UPDATE {" . $table . "} SET $fid_column = NULL, $list_column = NULL WHERE $fid_column NOT IN (SELECT fid FROM {files})");
}
}
}
return $ret;
}
/**
* Fix corrupted serialized data in the "data" column.
*/
function filefield_update_6102(&$sandbox) {
// Update removed. This turned out to be a bug in CCK core, so it is being
// fixed directly in CCK rather than in FileField.
// See http://drupal.org/node/407446.
return array();
}
/**
* Convert "Extensible File" to a normal "File" widget.
*/
function filefield_update_6103() {
$ret = array();
$ret[] = update_sql("UPDATE {". content_instance_tablename() ."} SET widget_type = 'filefield_widget' WHERE widget_type = 'filefield_combo'");
cache_clear_all('*', content_cache_tablename(), TRUE);
cache_clear_all('*', 'cache', TRUE);
return $ret;
}
/**
* Delete the filefield_token module entry in the system table.
*/
function filefield_update_6104() {
$ret = array();
$ret[] = update_sql("DELETE FROM {system} WHERE type = 'module' AND name = 'filefield_token'");
return $ret;
}
/**
* Move the list and descriptions column into the serialized data column.
*/
function _filefield_update_6001_move_operation($field, &$context) {
// Setup the first through
if (!isset($context['sandbox']['processed_files'])) {
$db_info = content_database_info($field);
$context['sandbox']['db_info'] = $db_info;
$context['sandbox']['table'] = $db_info['table'];
$context['sandbox']['col_data'] = $db_info['columns']['data']['column'];
$context['sandbox']['col_desc'] = $db_info['columns']['description']['column'];
$context['sandbox']['max'] = db_result(db_query("SELECT COUNT(*) FROM {". $db_info['table'] ."}"));
$context['sandbox']['current_node'] = 0;
$context['sandbox']['current_delta'] = 0;
$context['sandbox']['processed_files'] = array();
}
// Work our way through the field values 50 rows at a time.
$limit = 50;
$result = NULL;
if ($field['multiple']) {
$result = db_query_range("SELECT * FROM {{$context['sandbox']['table']}} WHERE (vid = %d AND delta > %d) OR vid > %d ORDER BY vid ASC, delta ASC", $context['sandbox']['current_node'], $context['sandbox']['current_delta'], $context['sandbox']['current_node'], 0, $limit);
}
else {
$result = db_query_range("SELECT * FROM {{$context['sandbox']['table']}} WHERE vid >= %d ORDER BY vid ASC", $context['sandbox']['current_node'], 0, $limit);
}
while ($row = db_fetch_array($result)) {
// Do not process the same file twice. This may happen when a node's files
// are split across two separate batch update HTTP requests.
$delta = isset($row['delta']) ? $row['delta'] : 0;
if (isset($context['sandbox']['processed_files'][$row['vid'] . '_' . $delta])) {
continue;
}
// Try to unserialize the data column.
if (!empty($row[$context['sandbox']['col_data']])) {
$data = unserialize($row[$context['sandbox']['col_data']]);
}
if (empty($data)) {
$data = array();
}
// Copy move the values from the columns into the array...
$data['description'] = $row[$context['sandbox']['col_desc']];
// ...serialize it and store it back to the db.
db_query("UPDATE {{$context['sandbox']['table']}} SET {$context['sandbox']['col_data']} = '%s' WHERE vid = %d", serialize($data), $row['vid']);
// Update our progress information.
$context['sandbox']['processed_files'][$row['vid'] . '_' . $delta] = TRUE;
$context['sandbox']['current_node'] = $row['vid'];
$context['sandbox']['current_delta'] = $delta;
}
// Inform the batch engine that we are not finished,
// and provide an estimation of the completion level we reached.
$processed_count = count($context['sandbox']['processed_files']);
if ($processed_count != $context['sandbox']['max']) {
$context['finished'] = $processed_count / $context['sandbox']['max'];
}
}
/**
* Drop the list and description columns.
*/
function _filefield_update_6001_drop_operation($field, &$context) {
$ret = array();
$db_info = content_database_info($field);
// TODO: Now that the data has been migrated we can drop the columns.
db_drop_field($ret, $db_info['table'], $db_info['columns']['description']['column']);
$context['finished'] = 1;
}

View file

@ -0,0 +1,125 @@
/**
* Auto-attach standard client side file input validation.
*/
Drupal.behaviors.filefieldValidateAutoAttach = function(context) {
$("input[type=file]", context).bind('change', Drupal.filefield.validateExtensions);
};
/**
* Prevent FileField uploads when using buttons not intended to upload.
*/
Drupal.behaviors.filefieldButtons = function(context) {
$('input.form-submit', context).bind('mousedown', Drupal.filefield.disableFields);
$('div.filefield-element input.form-submit', context).bind('mousedown', Drupal.filefield.progressBar);
};
/**
* Open links to files within the node form in a new window.
*/
Drupal.behaviors.filefieldPreviewLinks = function(context) {
$('div.filefield-element div.widget-preview a', context).click(Drupal.filefield.openInNewWindow).attr('target', '_blank');
}
/**
* Admin enhancement: only show the "Files listed by default" when needed.
*/
Drupal.behaviors.filefieldAdmin = function(context) {
var $listField = $('div.filefield-list-field', context);
if ($listField.size()) {
$listField.find('input').change(function() {
if (this.checked) {
if (this.value == 0) {
$('#edit-list-default-wrapper').css('display', 'none');
}
else {
$('#edit-list-default-wrapper').css('display', 'block');
}
}
}).change();
}
};
/**
* Utility functions for use by FileField.
* @param {Object} event
*/
Drupal.filefield = {
validateExtensions: function(event) {
// Remove any previous errors.
$('.file-upload-js-error').remove();
var fieldName = this.name.replace(/^files\[([a-z0-9_]+)_\d+\]$/, '$1');
var extensions = '';
if (Drupal.settings.filefield && Drupal.settings.filefield[fieldName]) {
extensions = Drupal.settings.filefield[fieldName].replace(/[, ]+/g, '|');
}
if (extensions.length > 1 && this.value.length > 0) {
var extensionPattern = new RegExp('\\.(' + extensions + ')$', 'gi');
if (!extensionPattern.test(this.value)) {
var error = Drupal.t("The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.",
{ '%filename' : this.value, '%extensions' : extensions.replace(/\|/g, ', ') }
);
$(this).parent().before('<div class="messages error file-upload-js-error">' + error + '</div>');
this.value = '';
return false;
}
}
},
disableFields: function(event) {
var clickedButton = this;
// Only disable upload fields for AHAH buttons.
if (!$(clickedButton).hasClass('ahah-processed')) {
return;
}
// Check if we're working with an "Upload" button.
var $enabledFields = [];
if ($(this).parents('div.filefield-element').size() > 0) {
$enabledFields = $(this).parents('div.filefield-element').find('input.form-file');
}
// Otherwise we're probably dealing with CCK's "Add another item" button.
else if ($(this).parents('div.content-add-more').size() > 0) {
$enabledFields = $(this).parent().parent().find('input.form-file');
}
var $disabledFields = $('div.filefield-element input.form-file').not($enabledFields);
// Disable upload fields other than the one we're currently working with.
$disabledFields.attr('disabled', 'disabled');
// All the other mousedown handlers (like AHAH) are excuted before any
// timeout functions will be called, so this effectively re-enables
// the filefields after the AHAH process is complete even though it only
// has a 1 millisecond timeout.
setTimeout(function(){
$disabledFields.removeAttr('disabled');
}, 1000);
},
progressBar: function(event) {
var clickedButton = this;
var $progressId = $(clickedButton).parents('div.filefield-element').find('input.filefield-progress');
if ($progressId.size()) {
var originalName = $progressId.attr('name');
// Replace the name with the required identifier.
$progressId.attr('name', originalName.match(/APC_UPLOAD_PROGRESS|UPLOAD_IDENTIFIER/)[0]);
// Restore the original name after the upload begins.
setTimeout(function() {
$progressId.attr('name', originalName);
}, 1000);
}
// Show the progress bar if the upload takes longer than 3 seconds.
setTimeout(function() {
$(clickedButton).parents('div.filefield-element').find('div.ahah-progress-bar').slideDown();
}, 500);
},
openInNewWindow: function(event) {
window.open(this.href, 'filefieldPreview', 'toolbar=0,scrollbars=1,location=1,statusbar=1,menubar=0,resizable=1,width=500,height=550');
return false;
}
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,248 @@
<?php
/**
* @file
* Theme functions used for normal file output.
*
* Uses content.module to store the fid and field specific metadata,
* and Drupal's {files} table to store the actual file data.
*/
/**
* Return an image with an appropriate icon for the given file.
*
* @param $file
* A file object for which to make an icon.
*/
function theme_filefield_icon($file) {
if (is_object($file)) {
$file = (array) $file;
}
$mime = check_plain($file['filemime']);
$dashed_mime = strtr($mime, array('/' => '-', '+' => '-', '.' => '-'));
if ($icon_url = filefield_icon_url($file)) {
return '<img class="filefield-icon field-icon-'. $dashed_mime .'" alt="'. t('@mime icon', array('@mime' => $mime)) .'" src="'. $icon_url .'" />';
}
}
/**
* Given a file object, create a URL to a matching icon.
*
* @param $file
* A file object.
* @param $theme
* Optional. The theme to be used for the icon. Defaults to the value of
* the "filefield_icon_theme" variable.
* @return
* A URL string to the icon, or FALSE if an appropriate icon could not be
* found.
*/
function _filefield_icon_url($file, $theme = NULL) {
global $base_url;
if ($icon_path = _filefield_icon_path($file, $theme)) {
return $base_url .'/'. $icon_path;
}
return FALSE;
}
/**
* Given a file object, create a URL to a matching icon.
*
* @param $file
* A file object.
* @param $theme
* Optional. The theme to be used for the icon. Defaults to the value of
* the "filefield_icon_theme" variable.
* @return
* A string to the icon as a local path, or FALSE if an appropriate icon could
* not be found.
*/
function _filefield_icon_path($file, $theme = NULL) {
if (!isset($theme)) {
$theme = variable_get('filefield_icon_theme', 'default');
}
// If there's an icon matching the exact mimetype, go for it.
$dashed_mime = strtr($file['filemime'], array('/' => '-'));
if ($icon_path = _filefield_create_icon_path($dashed_mime, $theme)) {
return $icon_path;
}
// For a couple of mimetypes, we can "manually" tell a generic icon.
if ($generic_name = _filefield_generic_icon_map($file)) {
if ($icon_path = _filefield_create_icon_path($generic_name, $theme)) {
return $icon_path;
}
}
// Use generic icons for each category that provides such icons.
foreach (array('audio', 'image', 'text', 'video') as $category) {
if (strpos($file['filemime'], $category .'/') === 0) {
if ($icon_path = _filefield_create_icon_path($category .'-x-generic', $theme)) {
return $icon_path;
}
}
}
// Try application-octet-stream as last fallback.
if ($icon_path = _filefield_create_icon_path('application-octet-stream', $theme)) {
return $icon_path;
}
// Sorry, no icon can be found...
return FALSE;
}
/**
* Internal function to convert a file icon theme name to a directory.
*/
function _filefield_icon_directory($theme = NULL) {
static $sets;
if (!isset($sets)) {
$sets = module_invoke_all('filefield_icon_sets');
drupal_alter('filefield_icon_sets', $sets);
}
if (!isset($theme) || !isset($sets[$theme])) {
$theme = 'default';
}
return $sets[$theme];
}
function _filefield_create_icon_path($icon_name, $theme = NULL) {
$icons_directory = _filefield_icon_directory($theme);
$icon_path = $icons_directory .'/'. $icon_name .'.png';
if (file_exists($icon_path)) {
return $icon_path;
}
return FALSE;
}
function _filefield_generic_icon_map($file) {
switch ($file['filemime']) {
// Word document types.
case 'application/msword':
case 'application/vnd.ms-word.document.macroEnabled.12':
case 'application/vnd.oasis.opendocument.text':
case 'application/vnd.oasis.opendocument.text-template':
case 'application/vnd.oasis.opendocument.text-master':
case 'application/vnd.oasis.opendocument.text-web':
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
case 'application/vnd.stardivision.writer':
case 'application/vnd.sun.xml.writer':
case 'application/vnd.sun.xml.writer.template':
case 'application/vnd.sun.xml.writer.global':
case 'application/vnd.wordperfect':
case 'application/x-abiword':
case 'application/x-applix-word':
case 'application/x-kword':
case 'application/x-kword-crypt':
return 'x-office-document';
// Spreadsheet document types.
case 'application/vnd.ms-excel':
case 'application/vnd.ms-excel.sheet.macroEnabled.12':
case 'application/vnd.oasis.opendocument.spreadsheet':
case 'application/vnd.oasis.opendocument.spreadsheet-template':
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
case 'application/vnd.stardivision.calc':
case 'application/vnd.sun.xml.calc':
case 'application/vnd.sun.xml.calc.template':
case 'application/vnd.lotus-1-2-3':
case 'application/x-applix-spreadsheet':
case 'application/x-gnumeric':
case 'application/x-kspread':
case 'application/x-kspread-crypt':
return 'x-office-spreadsheet';
// Presentation document types.
case 'application/vnd.ms-powerpoint':
case 'application/vnd.ms-powerpoint.presentation.macroEnabled.12':
case 'application/vnd.oasis.opendocument.presentation':
case 'application/vnd.oasis.opendocument.presentation-template':
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
case 'application/vnd.openxmlformats-officedocument.presentationml.slideshow':
case 'application/vnd.stardivision.impress':
case 'application/vnd.sun.xml.impress':
case 'application/vnd.sun.xml.impress.template':
case 'application/x-kpresenter':
return 'x-office-presentation';
// Compressed archive types.
case 'application/zip':
case 'application/x-zip':
case 'application/stuffit':
case 'application/x-stuffit':
case 'application/x-7z-compressed':
case 'application/x-ace':
case 'application/x-arj':
case 'application/x-bzip':
case 'application/x-bzip-compressed-tar':
case 'application/x-compress':
case 'application/x-compressed-tar':
case 'application/x-cpio-compressed':
case 'application/x-deb':
case 'application/x-gzip':
case 'application/x-java-archive':
case 'application/x-lha':
case 'application/x-lhz':
case 'application/x-lzop':
case 'application/x-rar':
case 'application/x-rpm':
case 'application/x-tzo':
case 'application/x-tar':
case 'application/x-tarz':
case 'application/x-tgz':
return 'package-x-generic';
// Script file types.
case 'application/ecmascript':
case 'application/javascript':
case 'application/mathematica':
case 'application/vnd.mozilla.xul+xml':
case 'application/x-asp':
case 'application/x-awk':
case 'application/x-cgi':
case 'application/x-csh':
case 'application/x-m4':
case 'application/x-perl':
case 'application/x-php':
case 'application/x-ruby':
case 'application/x-shellscript':
case 'text/vnd.wap.wmlscript':
case 'text/x-emacs-lisp':
case 'text/x-haskell':
case 'text/x-literate-haskell':
case 'text/x-lua':
case 'text/x-makefile':
case 'text/x-matlab':
case 'text/x-python':
case 'text/x-sql':
case 'text/x-tcl':
return 'text-x-script';
// HTML aliases.
case 'application/xhtml+xml':
return 'text-html';
// RTF files.
case 'application/rtf':
return 'text-rtf';
// Google earth files.
case 'application/vnd.google-earth.kml+xml':
case 'application/vnd.google-earth.kmz':
return 'application-google-earth';
// Executable types.
case 'application/x-macbinary':
case 'application/x-ms-dos-executable':
case 'application/x-pef-executable':
return 'application-x-executable';
default:
return FALSE;
}
}

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Token hook implementations. Included if token.module is installed.
*/
/**
* Implementation of hook_token_list().
*
* Provide a user readable list of FileField tokens.
*/
function filefield_token_list($type = 'all') {
if ($type == 'field' || $type == 'all') {
$tokens = array();
$tokens['file']['filefield-fid'] = t('File ID');
$tokens['file']['filefield-description'] = t('File description');
$tokens['file']['filefield-filename'] = t('File name');
$tokens['file']['filefield-filepath'] = t('File path');
$tokens['file']['filefield-fileurl'] = t('File URL');
$tokens['file']['filefield-filemime'] = t('File MIME type');
$tokens['file']['filefield-filesize'] = t('File size (in bytes)');
$tokens['file']['filefield-filesize_formatted'] = t('File size (pretty printed)');
$tokens['file']['filefield-view'] = t('Fully formatted HTML file tag');
$tokens['file']['filefield-onlyname'] = t('File name without extension');
$tokens['file']['filefield-extension'] = t('File extension');
return $tokens;
}
}
/**
* Implementation of hook_token_values().
*
* Provide the token values for a given file item.
*/
function filefield_token_values($type, $object = NULL) {
$tokens = array();
if ($type == 'field' && isset($object[0]['fid'])) {
$item = $object[0];
$tokens['filefield-fid'] = $item['fid'];
$tokens['filefield-description'] = isset($item['data']['description']) ? check_plain($item['data']['description']) : '';
$tokens['filefield-filename'] = check_plain($item['filename']);
$tokens['filefield-filepath'] = check_plain($item['filepath']);
$tokens['filefield-fileurl'] = file_create_url($item['filepath']);
$tokens['filefield-filemime'] = $item['filemime'];
$tokens['filefield-filesize'] = $item['filesize'];
$tokens['filefield-filesize_formatted'] = format_size($item['filesize']);
$tokens['filefield-view'] = $item['view'];
$double_extensions = array('tar.gz', 'tar.bz2');
foreach ($double_extensions as $ext) {
$length = strlen($ext) + 1;
$filename = drupal_strtolower($item['filename']);
$pos = strrpos($filename, '.'. $ext);
if ($pos !== FALSE && strlen($filename) - $pos == $length) {
$tokens['filefield-onlyname'] = substr($item['filename'], 0, $pos);
$tokens['filefield-extension'] = $ext;
break;
}
}
if (!isset($tokens['filefield-extension'])) {
$info = pathinfo($item['filename']);
$tokens['filefield-onlyname'] = basename($info['basename'], '.'. $info['extension']);
$tokens['filefield-extension'] = $info['extension'];
}
}
return $tokens;
}

View file

@ -0,0 +1,319 @@
<?php
/**
* @file
* FileField CCK field hooks and callbacks.
*/
/**
* Implementation of CCK's hook_field_settings($op = 'form').
*/
function filefield_field_settings_form($field) {
drupal_add_js(drupal_get_path('module', 'filefield') .'/filefield.js');
$form = array();
$form['list_field'] = array(
'#type' => 'radios',
'#title' => t('List field'),
'#options' => array(0 => t('Disabled'), 1 => t('Enabled')),
'#default_value' => $field['list_field'] === '' ? 0 : (int) $field['list_field'],
'#description' => t('Display a checkbox where users may choose if a file should be shown when viewing the content. Most formatters other than <em>Generic file</em> do not support this option.'),
'#attributes' => array('class' => 'filefield-list-field'),
);
$form['list_default'] = array(
'#type' => 'checkbox',
'#title' => t('Files listed by default'),
'#default_value' => $field['list_default'] === '' ? 1 : (int) $field['list_default'],
);
$form['description_field'] = array(
'#type' => 'radios',
'#title' => t('Description field'),
'#default_value' => $field['description_field'] === '' ? 0 : (int) $field['description_field'],
'#options' => array(0 => t('Disabled'), 1 => t('Enabled')),
'#description' => t('Display a text field where users may enter a description about the uploaded file. The description text is used when using the <em>Generic file</em> formatter to link to the file, the file name will be used otherwise. Most other formatters do not use the description field on output.'),
);
return $form;
}
/**
* Implementation of CCK's hook_field_settings($op = 'save').
*/
function filefield_field_settings_save($field) {
return array('list_field', 'list_default', 'description_field');
}
/**
* Implementation of CCK's hook_field_settings($op = 'database_columns').
*/
function filefield_field_settings_database_columns($field) {
return array(
'fid' => array('type' => 'int', 'not null' => FALSE, 'views' => TRUE),
'list' => array('type' => 'int', 'size' => 'tiny', 'not null' => FALSE, 'views' => TRUE),
'data' => array('type' => 'text', 'serialize' => TRUE, 'views' => TRUE),
);
}
/**
* Implementation of CCK's hook_field_settings($op = 'views_data').
*/
function filefield_field_settings_views_data($field) {
$data = content_views_field_views_data($field);
$db_info = content_database_info($field);
$table_alias = content_views_tablename($field);
// By defining the relationship, we already have a "Has file" filter
// plus all the filters that Views already provides for files.
// No need for having a filter by ourselves.
unset($data[$table_alias][$field['field_name'] .'_fid']['filter']);
// Add a relationship for related file.
$data[$table_alias][$field['field_name'] .'_fid']['relationship'] = array(
'base' => 'files',
'field' => $db_info['columns']['fid']['column'],
'handler' => 'content_handler_relationship',
'label' => t($field['widget']['label']),
'content_field_name' => $field['field_name'],
'skip base' => array('files'),
);
// Use the Views boolean handler for filtering the list value.
$data[$table_alias][$field['field_name'] .'_list']['filter']['handler'] = 'views_handler_filter_boolean_operator';
// Add our special handler for serialized data.
$data[$table_alias][$field['field_name'] .'_data']['field'] = array(
'title' => $data[$table_alias][$field['field_name'] .'_data']['title'],
'title short' => $data[$table_alias][$field['field_name'] .'_data']['title short'],
'help' => t('Can be used to display specific alt, title, or description information. May cause duplicate rows if grouping fields.'),
'field' => $db_info['columns']['data']['column'],
'table' => $db_info['table'],
'handler' => 'filefield_handler_field_data',
'click sortable' => FALSE,
'access callback' => 'content_access',
'access arguments' => array('view', $field),
);
// Remove handlers that are probably unnecessary.
unset($data[$table_alias][$field['field_name'] .'_data']['filter']);
unset($data[$table_alias][$field['field_name'] .'_data']['argument']);
unset($data[$table_alias][$field['field_name'] .'_list']['argument']);
// Set up relationship with file views.
$data[$table_alias]['table']['join']['files'] = array(
'table' => $db_info['table'],
'left_field' => 'fid',
'field' => $db_info['columns']['fid']['column'],
);
$data[$table_alias]['vid'] = array(
'title' => t($field['widget']['label']),
'help' => t('The node the uploaded file is attached to'),
'relationship' => array(
'label' => t($field['widget']['label']),
'base' => 'node',
'base field' => 'vid',
// This allows us to not show this relationship if the base is already
// node so users won't create circular relationships.
'skip base' => array('node', 'node_revisions'),
),
);
return $data;
}
/**
* Implementation of CCK's hook_field($op = 'load').
*/
function filefield_field_load($node, $field, &$items, $teaser, $page) {
if (empty($items)) {
return array();
}
foreach ($items as $delta => $item) {
// Despite hook_content_is_empty(), CCK still doesn't filter out
// empty items from $op = 'load', so we need to do that ourselves.
if (empty($item['fid']) || !($file = field_file_load($item['fid']))) {
$items[$delta] = NULL;
}
else {
if (isset($item['data']) && !empty($item['data'])) {
$item['data'] = unserialize($item['data']);
}
// Temporary fix to unserialize data serialized multiple times.
// See the FileField issue http://drupal.org/node/402860.
// And the CCK issue http://drupal.org/node/407446.
while (!empty($item['data']) && is_string($item['data'])) {
$item['data'] = unserialize($item['data']);
}
// Merge any data added by modules in hook_file_load().
if (isset($file['data']) && isset($item['data'])) {
$file['data'] = array_merge((array) $file['data'], (array) $item['data']);
}
$items[$delta] = array_merge($file, $item);
}
}
return array($field['field_name'] => $items);
}
/**
* Implementation of CCK's hook_field($op = 'insert').
*/
function filefield_field_insert($node, $field, &$items, $teaser, $page) {
return filefield_field_update($node, $field, $items, $teaser, $page);
}
/**
* Implementation of CCK's hook_field($op = 'update').
*/
function filefield_field_update($node, $field, &$items, $teaser, $page) {
// Accumulator to gather current fid to compare with the original node
// for deleting replaced files.
$curfids = array();
foreach ($items as $delta => $item) {
$items[$delta] = field_file_save($node, $item);
// Remove items from the array if they have been deleted.
if (empty($items[$delta]) || empty($items[$delta]['fid'])) {
$items[$delta] = NULL;
}
else {
$curfids[] = $items[$delta]['fid'];
}
}
// If this is a new node there are no old items to worry about.
// On new revisions, old files are always maintained in the previous revision.
if ($node->is_new || !empty($node->revision) || !empty($node->skip_filefield_delete)) {
return;
}
// Delete items from original node.
$orig = node_load($node->nid);
// If there are, figure out which ones must go.
if (!empty($orig->$field['field_name'])) {
foreach ($orig->$field['field_name'] as $oitem) {
if (isset($oitem['fid']) && !in_array($oitem['fid'], $curfids)) {
// For hook_file_references, remember that this is being deleted.
$oitem['field_name'] = $field['field_name'];
$oitem['delete_vid'] = $orig->vid;
filefield_field_delete_file($oitem, $field);
}
}
}
}
/**
* Implementation of CCK's hook_field($op = 'delete_revision').
*/
function filefield_field_delete_revision($node, $field, &$items, $teaser, $page) {
foreach ($items as $delta => $item) {
if (isset($item['fid'])) {
// For hook_file_references, remember that this is being deleted.
$item['field_name'] = $field['field_name'];
$item['delete_vid'] = $node->vid;
if (filefield_field_delete_file($item, $field)) {
$items[$delta] = NULL;
}
}
}
}
/**
* Implementation of CCK's hook_field($op = 'delete').
*/
function filefield_field_delete($node, $field, &$items, $teaser, $page) {
foreach ($items as $delta => $item) {
if (isset($item['fid'])) {
// For hook_file_references(), remember that this is being deleted.
$item['field_name'] = $field['field_name'];
// Pass in the nid of the node that is being removed so all references can
// be counted in hook_file_references().
$item['delete_nid'] = $node->nid;
filefield_field_delete_file($item, $field);
}
}
// Delete all the remaining items present only in older revisions.
$db_info = content_database_info($field);
$result = db_query('SELECT vid, f.* FROM {' . $db_info['table'] . '} t INNER JOIN {files} f ON t.' . $db_info['columns']['fid']['column'] . ' = f.fid WHERE nid = %d AND vid != %d', $node->nid, $node->vid);
while ($item = db_fetch_array($result)) {
if (isset($item['fid'])) {
$item['field_name'] = $field['field_name'];
$item['delete_vid'] = $item['vid'];
filefield_field_delete_file($item, $field);
}
}
}
/**
* Check that FileField controls a file before attempting to deleting it.
*/
function filefield_field_delete_file($file, $field) {
$file = (object) $file;
// Remove the field_name and delete_nid properties so that references can be
// counted including the files to be deleted.
$field_name = isset($file->field_name) ? $file->field_name : NULL;
$delete_nid = isset($file->delete_nid) ? $file->delete_nid : NULL;
unset($file->field_name, $file->delete_nid);
// To prevent FileField from deleting files it doesn't know about, check the
// FileField reference count. Temporary files can be deleted because they
// are not yet associated with any content at all.
if ($file->status == 0 || filefield_get_file_reference_count($file, $field) > 0) {
$file->field_name = $field_name;
$file->delete_nid = $delete_nid;
return field_file_delete($file);
}
// Even if the file is not deleted, return TRUE to indicate the FileField
// record can be removed from the FileField database tables.
return TRUE;
}
/**
* Implementation of CCK's hook_field($op = 'sanitize').
*/
function filefield_field_sanitize($node, $field, &$items, $teaser, $page) {
foreach ($items as $delta => $item) {
// Cleanup $items during node preview.
if (empty($item['fid']) || !empty($item['delete'])) {
// Check for default images at the widget level.
// TODO: Provide an API to ImageField to do this itself?
if (!empty($field['widget']['use_default_image']) && !empty($field['widget']['default_image']['filepath']) && $delta == 0) {
$items[$delta] = $field['widget']['default_image'];
$items[$delta]['default'] = TRUE;
}
else {
$items[$delta] = NULL;
continue;
}
}
// Add nid so formatters can create a link to the node.
$items[$delta]['nid'] = isset($node->nid) ? $node->nid : NULL;
// Get the 'data' column stored by CCK into an array. This is necessary
// for Views, which doesn't call the "load" $op and to fix an issue with
// CCK double-serializing data.
// See the FileField issue http://drupal.org/node/402860.
// And the CCK issue http://drupal.org/node/407446.
while (!empty($items[$delta]['data']) && is_string($items[$delta]['data'])) {
$items[$delta]['data'] = unserialize($items[$delta]['data']);
}
// Load the complete file if a filepath is not available.
if (!empty($item['fid']) && empty($item['filepath'])) {
$file = (array) field_file_load($item['fid']);
if (isset($file['data']) && isset($items[$delta]['data'])) {
$file['data'] = array_merge($file['data'], $items[$delta]['data']);
}
$items[$delta] = array_merge($file, $items[$delta]);
}
// Verify the file exists on the server.
if (!empty($item['filepath']) && !file_exists($item['filepath'])) {
watchdog('filefield', 'FileField was trying to display the file %file, but it does not exist.', array('%file' => $item['filepath']), WATCHDOG_WARNING);
}
}
}

View file

@ -0,0 +1,148 @@
<?php
/**
* @file
* FileField: Defines a CCK file field type.
*
* Uses content.module to store the fid and field specific metadata,
* and Drupal's {files} table to store the actual file data.
*
* This file contains CCK formatter related functionality.
*/
/**
* Theme function for the 'default' filefield formatter.
*/
function theme_filefield_formatter_default($element) {
$file = $element['#item'];
$field = content_fields($element['#field_name']);
$output = theme('filefield_item', $file, $field);
return $output;
}
/**
* Theme function for the 'path_plain' formatter.
*/
function theme_filefield_formatter_path_plain($element) {
// Inside a View this function may be called with null data. In that case,
// just return.
if (empty($element['#item'])) {
return '';
}
$field = content_fields($element['#field_name']);
$item = $element['#item'];
// If there is no image on the database, use default.
if (empty($item['fid']) && $field['use_default_file']) {
$item = $field['default_file'];
}
if (empty($item['filepath']) && !empty($item['fid'])) {
$item = array_merge($item, field_file_load($item['fid']));
}
return empty($item['filepath']) ? '' : check_plain(file_create_path($item['filepath']));
}
/**
* Theme function for the 'url_plain' formatter.
*/
function theme_filefield_formatter_url_plain($element) {
// Inside a View this function may be called with null data. In that case,
// just return.
if (empty($element['#item'])) {
return '';
}
$field = content_fields($element['#field_name']);
$item = $element['#item'];
// If there is no image on the database, use default.
if (empty($item['fid']) && $field['use_default_file']) {
$item = $field['default_file'];
}
if (empty($item['filepath']) && !empty($item['fid'])) {
$item = array_merge($item, field_file_load($item['fid']));
}
if (empty($item['filepath'])) {
return '';
}
// Check for remote filepath, if so return the raw path with protocol prefix
if (strpos($item['filepath'], 'http://') === 0 || strpos($item['filepath'], 'https://' === 0)) {
return l($file['filepath'], $item['filepath']);
}
else {
return file_create_url(field_file_urlencode_path($item['filepath']));
}
}
/**
* Theme function for any file that is managed by FileField.
*
* It doesn't really format stuff by itself but rather redirects to other
* formatters that are telling us they want to handle the concerned file.
*
* This function checks if the file may be shown and returns an empty string
* if viewing the file is not allowed for any reason. If you need to display it
* in any case, please use theme('filefield_file') instead.
*/
function theme_filefield_item($file, $field) {
if (filefield_view_access($field['field_name']) && filefield_file_listed($file, $field)) {
return theme('filefield_file', $file);
}
return '';
}
/**
* Return whether a file should be listed when viewing the node.
*
* @param $file
* A populated FileField item.
* @param $field
* A CCK field instance array.
*/
function filefield_file_listed($file, $field) {
if (!empty($field['list_field'])) {
return !empty($file['list']);
}
return TRUE;
}
/**
* Theme function for the 'generic' single file formatter.
*/
function theme_filefield_file($file) {
// Views may call this function with a NULL value, return an empty string.
if (empty($file['fid'])) {
return '';
}
$path = $file['filepath'];
// Check for remote filepath, if so return the raw path with protocol prefix
if (strpos($path, 'http://') === 0 || strpos($path, 'https://' === 0)) {
return l($file['filename'], $path);
}
else {
$url = file_create_url(field_file_urlencode_path($path));
}
$icon = theme('filefield_icon', $file);
// Set options as per anchor format described at
// http://microformats.org/wiki/file-format-examples
// TODO: Possibly move to until I move to the more complex format described
// at http://darrelopry.com/story/microformats-and-media-rfc-if-you-js-or-css
$options = array(
'attributes' => array(
'type' => $file['filemime'] . '; length=' . $file['filesize'],
),
);
// Use the description as the link text if available.
if (empty($file['data']['description'])) {
$link_text = $file['filename'];
}
else {
$link_text = $file['data']['description'];
$options['attributes']['title'] = $file['filename'];
}
return '<div class="filefield-file">'. $icon . l($link_text, $url, $options) .'</div>';
}

View file

@ -0,0 +1,14 @@
name = FileField Meta
description = Add metadata gathering and storage to FileField.
dependencies[] = filefield
dependencies[] = getid3
package = CCK
core = 6.x
php = 5.0
; Information added by Drupal.org packaging script on 2016-02-24
version = "6.x-3.14"
core = "6.x"
project = "filefield"
datestamp = "1456327142"

View file

@ -0,0 +1,173 @@
<?php
/**
* @file
* FileField Meta: Add Video Support to File Field.
*/
/**
* Implementation of hook_install().
*/
function filefield_meta_install() {
drupal_install_schema('filefield_meta');
}
function filefield_meta_uninstall() {
drupal_uninstall_schema('filefield_meta');
}
/**
* Implementation of hook_schema().
*/
function filefield_meta_schema() {
$schema = array();
// The primary field/index.
$schema['filefield_meta'] = array(
'description' => 'The table for meta data about filefield files.',
'fields' => array(
'fid' => array(
'description' => 'The file id.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
),
'width' => array(
'description' => 'Width of a video or image file in pixels.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
'height' => array(
'description' => 'Height of a video or image file in pixels.',
'type' => 'int',
'unsigned' => TRUE,
'not null' => FALSE,
),
'duration' => array(
'description' => 'The duration of audio or video files, in seconds.',
'type' => 'float',
'size' => 'normal',
'not null' => FALSE,
),
'audio_format' => array(
'description' => 'The audio format.',
'type' => 'varchar',
'length' => 10,
'not null' => TRUE,
'default' => '',
),
'audio_sample_rate' => array(
'description' => 'The sample rate of the audio.',
'type' => 'int',
'size' => 'medium',
'not null' => TRUE,
'default' => 0,
),
'audio_channel_mode' => array(
'description' => 'The number of channels in the audio, by name (stereo or mono).',
'type' => 'varchar',
'length' => 10,
'not null' => TRUE,
'default' => '',
),
'audio_bitrate' => array(
'description' => 'The audio bitrate.',
'type' => 'float',
'size' => 'medium',
'not null' => TRUE,
'default' => 0,
),
'audio_bitrate_mode' => array(
'description' => 'The kind of audio bitrate, such as VBR. Usually empty.',
'type' => 'varchar',
'length' => 4,
'not null' => TRUE,
'default' => '',
),
'tags' => array(
'description' => 'ID3 tags such as artist, album, and genre.',
'type' => 'text',
'serialize' => TRUE,
),
),
'primary key' => array('fid'),
);
return $schema;
}
function filefield_meta_update_1() {
$ret = array();
db_add_field($ret, 'filefield_meta', 'audio_format', array(
'description' => 'The audio format.',
'type' => 'varchar',
'length' => 10,
'not null' => TRUE,
'default' => '',
));
db_add_field($ret, 'filefield_meta', 'audio_sample_rate', array(
'description' => 'The sample rate of the audio.',
'type' => 'int',
'size' => 'medium',
'not null' => TRUE,
'default' => 0,
));
db_add_field($ret, 'filefield_meta', 'audio_channel_mode', array(
'description' => 'The number of channels in the audio, by name.',
'type' => 'varchar',
'length' => 10,
'not null' => TRUE,
'default' => '',
));
db_add_field($ret, 'filefield_meta', 'audio_bitrate', array(
'description' => 'The audio bitrate.',
'type' => 'float',
'size' => 'medium',
'not null' => TRUE,
'default' => 0,
));
db_add_field($ret, 'filefield_meta', 'audio_bitrate_mode', array(
'description' => 'The kind of audio bitrate.',
'type' => 'varchar',
'length' => 4,
'not null' => TRUE,
'default' => '',
));
return $ret;
}
/**
* Add the tags column.
*/
function filefield_meta_update_6100(&$context) {
$ret = array();
// Set up our update and add the tags column.
if (!isset($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['total'] = db_result(db_query("SELECT COUNT(*) FROM {files} f INNER JOIN {filefield_meta} fm ON f.fid = fm.fid WHERE fm.audio_format <> ''"));
$context['sandbox']['current_fid'] = 0;
if (!db_column_exists('filefield_meta', 'tags')) {
db_add_field($ret, 'filefield_meta', 'tags', array('type' => 'text'));
}
// We are done if there are none to update.
if ($context['sandbox']['total'] == 0) {
return $ret;
}
}
// Select and process 200 files at a time.
$limit = 200;
$result = db_query_range("SELECT f.* FROM {files} f INNER JOIN {filefield_meta} fm ON f.fid = fm.fid WHERE f.fid > %d AND fm.audio_format <> '' ORDER BY f.fid ASC", $context['sandbox']['current_fid'], 0, $limit);
// Loop through each file and read in its ID3 tags if applicable.
while ($file = db_fetch_object($result)) {
filefield_meta_file_update($file);
$context['sandbox']['current_fid'] = $file->fid;
$context['sandbox']['progress']++;
}
// Update our progress indicator.
$ret['#finished'] = $context['sandbox']['progress'] / $context['sandbox']['total'];
return $ret;
}

View file

@ -0,0 +1,211 @@
<?php
/**
* @file
* FileField Meta: Add Video Support to File Field.
*/
/**
* Implementation of hook_init().
*/
function filefield_meta_init() {
// Conditional module support.
if (module_exists('token')) {
module_load_include('inc', 'filefield_meta', 'filefield_meta.token');
}
}
/**
* Implementation of hook_theme().
*/
function filefield_meta_theme() {
return array(
'filefield_meta_duration' => array(
'arguments' => array('duration' => NULL),
),
'filefield_meta_samplerate' => array(
'arguments' => array('samplerate' => NULL),
),
'filefield_meta_bitrate' => array(
'arguments' => array('bitrate' => NULL),
),
);
}
/**
* Implementation of hook_cron().
*/
function filefield_meta_cron() {
$result = db_query('SELECT fm.fid FROM {filefield_meta} fm LEFT JOIN {files} f ON fm.fid=f.fid WHERE f.fid IS NULL');
while ($file = db_fetch_object($result)) {
db_query('DELETE FROM {filefield_meta} WHERE fid = %d', $file->fid);
}
}
/**
* Implementation of hook_views_api().
*/
function filefield_meta_views_api() {
return array(
'api' => 2.0,
'path' => drupal_get_path('module', 'filefield_meta') . '/includes',
);
}
/**
* Implementation of FileField's hook_file_load().
*/
function filefield_meta_file_load(&$file) {
$result = db_query("SELECT * FROM {filefield_meta} WHERE fid = %d", $file->fid);
$data = db_fetch_array($result);
// Essentially this is a lazy-loader. If no record exists, read in the file.
if ($data) {
$data['tags'] = isset($data['tags']) ? unserialize($data['tags']) : array();
$file->data = isset($file->data) ? array_merge($file->data, $data) : $data;
}
else {
filefield_meta_file_insert($file);
}
}
/**
* Implementation of FileField's hook_file_insert().
*/
function filefield_meta_file_insert(&$file) {
if (!empty($file->fid)) {
filefield_meta($file);
$record = array_merge($file->data, array('fid' => $file->fid));
drupal_write_record('filefield_meta', $record);
}
}
/**
* Implementation of FileField's hook_file_update().
*/
function filefield_meta_file_update(&$file) {
if (!empty($file->fid)) {
filefield_meta_file_delete($file);
filefield_meta_file_insert($file);
}
}
/**
* Implementation of FileField's hook_file_delete().
*/
function filefield_meta_file_delete($file) {
db_query('DELETE FROM {filefield_meta} WHERE fid = %d', $file->fid);
}
/**
* Adds the width, height and duration to the file's data property.
*/
function filefield_meta(&$file) {
$file->data = !empty($file->data) ? $file->data : array();
// Skip any attempts at adding information if the file is not actually
// located on this server.
if (!file_exists($file->filepath)) {
return;
}
$info = getid3_analyze($file->filepath);
$file->data['width'] = $file->data['height'] = $file->data['duration'] = 0;
if (isset($info['video']['resolution_x'])) {
$file->data['width'] = $info['video']['resolution_x'];
$file->data['height'] = $info['video']['resolution_y'];
}
elseif (isset($info['video']['streams'])) {
foreach ($info['video']['streams'] as $stream) {
$file->data['width'] = max($file->data['width'], $stream['resolution_x']);
$file->data['height'] = max($file->data['height'], $stream['resolution_y']);
}
}
if (isset($info['playtime_seconds'])) {
$file->data['duration'] = $info['playtime_seconds'];
}
// Initialize fields.
$file->data['audio_format'] = $file->data['audio_channel_mode'] = $file->data['audio_bitrate_mode'] = '';
$file->data['audio_sample_rate'] = $file->data['audio_bitrate'] = 0;
if (isset($info['audio'])) {
$file->data['audio_format'] = $info['audio']['dataformat']; //e.g. mp3
$file->data['audio_sample_rate'] = $info['audio']['sample_rate']; //e.g. 44100
$file->data['audio_channel_mode'] = $info['audio']['channelmode']; // e.g. mono
$file->data['audio_bitrate'] = isset($info['audio']['bitrate']) ? $info['audio']['bitrate'] : NULL; //e.g. 64000
$file->data['audio_bitrate_mode'] = isset($info['audio']['bitrate_mode']) ? $info['audio']['bitrate_mode'] : NULL; //e.g. cbr
}
// Add in arbitrary ID3 tags.
if (isset($info['tags_html'])) {
// We use tags_html instead of tags because it is the most reliable data
// source for pulling in non-UTF-8 characters according to getID3 docs.
foreach ($info['tags_html'] as $type => $values) {
// Typically $type may be IDv2 (for MP3s) or quicktime (for AAC).
foreach ($values as $key => $value) {
$value = isset($value[0]) ? (string) $value[0] : (string) $value;
if (!empty($value) && $key != 'coverart' && $key != 'music_cd_identifier') {
$file->data['tags'][$key] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
}
}
}
}
}
/**
* Utility function that simply returns the current list of all known ID3 tags.
*
* If new or different ID3 tags are desired, these may be overridden by adding
* the following to your site's settings.php file.
*
* @code
* $conf['filefield_meta_tags'] = array(
* 'title' => t('Title'),
* 'artist' => t('Artist'),
* 'composer' => t('Composer'),
* // etc...
* );
* @endcode
*/
function filefield_meta_tags() {
$defaults = array(
'title' => t('Title'),
'artist' => t('Artist'),
'album' => t('Album'),
'year' => t('Year'),
'genre' => t('Genre'),
);
return variable_get('filefield_meta_tags', $defaults);
}
/**
* Convert the float duration into a pretty string.
*
* @param $duration
*/
function theme_filefield_meta_duration($duration) {
$seconds = round((($duration / 60) - floor($duration / 60)) * 60);
$minutes = floor($duration / 60);
if ($seconds >= 60) {
$seconds -= 60;
$minutes++;
}
return intval($minutes) . ':' . str_pad($seconds, 2, 0, STR_PAD_LEFT);
}
/**
* Formats audio sample rate.
*/
function theme_filefield_meta_samplerate($samplerate) {
return t('@sampleratekHz', array('@samplerate' => (int) ($samplerate/1000)));
}
/**
* Formats audio bit rate.
*/
function theme_filefield_meta_bitrate($bitrate) {
return t('@bitrateKbps', array('@bitrate' => (int) ($bitrate/1000)));
}

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Token integration for FileField Meta.
*/
/**
* Implementation of hook_token_list().
*
* Provide a user readable list of FileField Meta tokens.
*/
function filefield_meta_token_list($type = 'all') {
if ($type == 'field' || $type == 'all') {
$tokens['file']['filefield-width'] = t('File Video width');
$tokens['file']['filefield-height'] = t('File Video height');
$tokens['file']['filefield-duration'] = t('File Duration');
$tokens['file']['filefield-audio-format'] = t('File Audio Format path');
$tokens['file']['filefield-audio-sample-rate'] = t('File Audio sample rate');
$tokens['file']['filefield-audio-channel-mode'] = t('File Audio channel mode (stereo, mono)');
$tokens['file']['filefield-audio-bitrate'] = t('File Audio bitrate');
$tokens['file']['filefield-audio-bitrate-mode'] = t('File Audio bitrate mode (cbr, vbr, abr...)');
// ID3 tags.
foreach (filefield_meta_tags() as $tag => $label) {
$tokens['file']['filefield-tag-' . $tag] = t('File ID3 @tag tag', array('@tag' => $label));
}
return $tokens;
}
}
/**
* Implementation of hook_token_values().
*
* Provide the token values for a given file item.
*/
function filefield_meta_token_values($type, $object = NULL) {
$tokens = array();
if ($type == 'field' && isset($object[0]['fid'])) {
$item = $object[0];
$tokens['filefield-width'] = $item['data']['width'] ;
$tokens['filefield-height'] = $item['data']['height'] ;
$tokens['filefield-duration'] = $item['data']['duration'] ;
$tokens['filefield-audio-format'] = isset($item['data']['audio_format']) ? check_plain($item['data']['audio_format']) : '';
$tokens['filefield-audio-sample-rate'] = isset($item['data']['sample_rate']) ? check_plain($item['data']['sample_rate']) : '';
$tokens['filefield-audio-channel-mode'] = isset($item['data']['audio_channel_mode']) ? check_plain($item['data']['audio_channel_mode']) : '';
$tokens['filefield-audio-bitrate'] = isset($item['data']['audio_bitrate']) ? check_plain($item['data']['audio_bitrate']) : '';
$tokens['filefield-audio-bitrate-mode'] = isset($item['data']['audio_bitrate_mode']) ? check_plain($item['data']['audio_bitrate_mode']) : '';
// ID3 tags.
foreach (filefield_meta_tags() as $tag => $label) {
$tokens['filefield-tag-title'] = isset($item['data']['tags'][$tag]) ? check_plain($item['data']['tags'][$tag]) : '';
}
}
return $tokens;
}

View file

@ -0,0 +1,205 @@
<?php
/**
* @file
* Provide views data for filefield_meta.module.
*/
/**
* @defgroup views_filefield_meta_module filefield_meta.module handlers
*
* Includes the tables 'node', 'node_revisions' and 'history'.
* @{
*/
/**
* Implementation of hook_views_data()
*/
function filefield_meta_views_data() {
// Define the base group of this table. Fields that don't
// have a group defined will go into this field by default.
$data['filefield_meta']['table']['group'] = t('File');
// For other base tables, explain how we join
$data['filefield_meta']['table']['join'] = array(
// this explains how the 'filefield_meta' table (named in the line above)
// links toward the files table.
'files' => array(
'left_table' => 'files', // Because this is a direct link it could be left out.
'left_field' => 'fid',
'field' => 'fid',
),
);
// ----------------------------------------------------------------
// filefield_meta table -- fields
// width
$data['filefield_meta']['width'] = array(
'title' => t('Video width'),
'help' => t('Width of a video or image file in pixels.'),
'field' => array(
'handler' => 'views_handler_field_numeric',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
// height
$data['filefield_meta']['height'] = array(
'title' => t('Video height'),
'help' => t('Height of a video or image file in pixels.'),
'field' => array(
'handler' => 'views_handler_field_numeric',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
// duration
$data['filefield_meta']['duration'] = array(
'title' => t('Duration'),
'help' => t('The duration of audio or video files, in seconds.'),
'field' => array(
'handler' => 'filefield_meta_handler_field_duration',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
// audio_format
$data['filefield_meta']['audio_format'] = array(
'title' => t('Audio format'),
'help' => t('The audio format.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
// audio_sample_rate
$data['filefield_meta']['audio_sample_rate'] = array(
'title' => t('Audio sample rate'),
'help' => t('The sample rate of the audio.'),
'field' => array(
'handler' => 'filefield_meta_handler_field_samplerate',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
// audio_channel_mode
$data['filefield_meta']['audio_channel_mode'] = array(
'title' => t('Audio channel mode'),
'help' => t('The number of channels in the audio, by name (stereo or mono).'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
// audio_bitrate
$data['filefield_meta']['audio_bitrate'] = array(
'title' => t('Audio bitrate'),
'help' => t('The audio bitrate.'),
'field' => array(
'handler' => 'filefield_meta_handler_field_bitrate',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_numeric',
),
);
// audio_bitrate_mode
$data['filefield_meta']['audio_bitrate_mode'] = array(
'title' => t('Audio bitrate mode'),
'help' => t('The kind of audio bitrate, such as VBR. Usually empty.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
);
// Tags.
$data['filefield_meta']['tags'] = array(
'title' => t('ID3 tags'),
'help' => t('ID3 tags include embedded information such as artist, album, year, genre and other information.'),
'field' => array(
'handler' => 'filefield_meta_handler_field_tags',
'click sortable' => FALSE,
),
);
return $data;
}
/**
* Implementation of hook_views_handlers().
*/
function filefield_meta_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'filefield_meta') . '/includes',
),
'handlers' => array(
// field handlers
'filefield_meta_handler_field_bitrate' => array(
'parent' => 'views_handler_field_numeric',
),
'filefield_meta_handler_field_duration' => array(
'parent' => 'views_handler_field_numeric',
),
'filefield_meta_handler_field_samplerate' => array(
'parent' => 'views_handler_field_numeric',
),
'filefield_meta_handler_field_tags' => array(
'parent' => 'views_handler_field_prerender_list',
),
),
);
}
/**
* @}
*/

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* A special handler that properly formats bit rate fields as Kbps.
*/
/**
* Render a field as a readable value in hours, minutes, and seconds.
*
* @ingroup views_field_handlers
*/
class filefield_meta_handler_field_bitrate extends views_handler_field_numeric {
function option_definition() {
$options = parent::option_definition();
$options['format'] = array('default' => 'default', 'translatable' => TRUE);
// Remove the separator options since we don't need them.
unset($options['separator']);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Remove the separator and alter options since we don't need them.
unset($form['separator']);
$form['prefix']['#weight'] = 10;
$form['suffix']['#weight'] = 10;
$form['format'] = array(
'#type' => 'select',
'#title' => t('Format'),
'#default_value' => $this->options['format'],
'#options' => array(
'default' => t('Default (Mbps or Kbps)'),
'raw' => t('Raw numberic value'),
),
);
}
function render($values) {
$value = $values->{$this->field_alias};
// Check to see if hiding should happen before adding prefix and suffix.
if ($this->options['hide_empty'] && empty($value) && ($value !== 0 || $this->options['empty_zero'])) {
return '';
}
switch ($this->options['format']) {
case 'raw':
$output = $value;
break;
default:
$output = theme('filefield_meta_bitrate', $value);
}
return check_plain($this->options['prefix']) . $output . check_plain($this->options['suffix']);
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* @file
* A special handler that properly formats duration fields as minutes:seconds.
*/
/**
* Render a field as a readable value in hours, minutes, and seconds.
*
* @ingroup views_field_handlers
*/
class filefield_meta_handler_field_duration extends views_handler_field_numeric {
function option_definition() {
$options = parent::option_definition();
$options['format'] = array('default' => 'default', 'translatable' => TRUE);
// Remove the separator options since we don't need them.
unset($options['separator']);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Remove the separator options since we don't need them.
unset($form['separator']);
$form['prefix']['#weight'] = 10;
$form['suffix']['#weight'] = 10;
$form['format'] = array(
'#type' => 'select',
'#title' => t('Time format'),
'#default_value' => $this->options['format'],
'#options' => array(
'default' => t('Default (usually mm:ss)'),
'hours' => t('Hours: h'),
'minutes' => t('Minutes: mm'),
'seconds' => t('Seconds: ss'),
'total' => t('Total seconds'),
),
);
}
function render($values) {
$value = $values->{$this->field_alias};
switch ($this->options['format']) {
case 'hours':
$output = date('g', (int) $value);
break;
case 'minutes':
$output = date('i', (int) $value);
break;
case 'seconds':
$output = date('s', (int) $value);
break;
case 'total':
$output = check_plain($value);
break;
default:
$output = theme('filefield_meta_duration', $value);
}
// Check to see if hiding should happen before adding prefix and suffix.
if ($this->options['hide_empty'] && empty($value) && ($value !== 0 || $this->options['empty_zero'])) {
return '';
}
return check_plain($this->options['prefix']) . $output . check_plain($this->options['suffix']);
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* A special handler that properly formats bit rate fields as kHz.
*/
/**
* Render a field as a readable value in hours, minutes, and seconds.
*
* @ingroup views_field_handlers
*/
class filefield_meta_handler_field_samplerate extends views_handler_field_numeric {
function option_definition() {
$options = parent::option_definition();
$options['format'] = array('default' => 'default', 'translatable' => TRUE);
// Remove the separator options since we don't need them.
unset($options['separator']);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Remove the separator options since we don't need them.
unset($form['separator']);
$form['prefix']['#weight'] = 10;
$form['suffix']['#weight'] = 10;
$form['format'] = array(
'#type' => 'select',
'#title' => t('Format'),
'#default_value' => $this->options['format'],
'#options' => array(
'default' => t('Default (kHz)'),
'raw' => t('Raw numberic value'),
),
);
}
function render($values) {
$value = $values->{$this->field_alias};
// Check to see if hiding should happen before adding prefix and suffix.
if ($this->options['hide_empty'] && empty($value) && ($value !== 0 || $this->options['empty_zero'])) {
return '';
}
switch ($this->options['format']) {
case 'raw':
$output = $value;
break;
default:
$output = theme('filefield_meta_samplerate', $value);
}
return check_plain($this->options['prefix']) . $output . check_plain($this->options['suffix']);
}
}

View file

@ -0,0 +1,43 @@
<?php
/**
* @file
* A special handler that renders ID3 tags attached to a file.
*/
/**
* Render a field as a readable value in hours, minutes, and seconds.
*
* @ingroup views_field_handlers
*/
class filefield_meta_handler_field_tags extends views_handler_field {
function option_definition() {
$options = parent::option_definition();
$default = reset(array_keys(filefield_meta_tags()));
$options['tag'] = array('tag' => $default, 'required' => TRUE, 'translatable' => TRUE);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$form['relationship']['#weight'] = -2;
$form['tag'] = array(
'#type' => 'select',
'#title' => t('ID3 tag'),
'#required' => TRUE,
'#default_value' => $this->options['tag'],
'#options' => filefield_meta_tags(),
'#description' => t('Select the tag to be rendered. If needing multiple tags, add another ID3 tags field.'),
'#weight' => -1,
);
}
function render($values) {
$value = unserialize($values->{$this->field_alias});
$tag = $this->options['tag'];
if (isset($value[$tag])) {
return check_plain($value[$tag]);
}
}
}

View file

@ -0,0 +1,588 @@
<?php
/**
* @file
* This file contains CCK widget related functionality.
*
* Uses content.module to store the fid and field specific metadata,
* and Drupal's files table to store the actual file data.
*/
/**
* Implementation of CCK's hook_widget_settings($op == 'form').
*/
function filefield_widget_settings_form($widget) {
$form = array();
// Convert the extensions list to be a human-friendly comma-separated list.
$extensions = is_string($widget['file_extensions']) ? $widget['file_extensions'] : 'txt';
$form['file_extensions'] = array(
'#type' => 'textfield',
'#title' => t('Permitted upload file extensions'),
'#default_value' => $extensions,
'#size' => 64,
'#maxlength' => 512,
'#description' => t('Extensions a user can upload to this field. Separate extensions with a space and do not include the leading dot. Leaving this blank will allow users to upload a file with any extension.'),
'#element_validate' => array('_filefield_widget_settings_extensions_validate'),
'#pre_render' => array('_filefield_widget_settings_extensions_value'),
'#weight' => 1,
);
$form['progress_indicator'] = array(
'#type' => 'radios',
'#title' => t('Progress indicator'),
'#options' => array(
'bar' => t('Bar with progress meter'),
'throbber' => t('Throbber'),
),
'#default_value' => empty($widget['progress_indicator']) ? 'bar' : $widget['progress_indicator'],
'#description' => t('Your server supports upload progress capabilities. The "throbber" display does not indicate progress but takes up less room on the form, you may want to use it if you\'ll only be uploading small files or if experiencing problems with the progress bar.'),
'#weight' => 5,
'#access' => filefield_progress_implementation(),
);
$form['path_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Path settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => 6,
);
$form['path_settings']['file_path'] = array(
'#type' => 'textfield',
'#title' => t('File path'),
'#default_value' => is_string($widget['file_path']) ? $widget['file_path'] : '',
'#description' => t('Optional subdirectory within the "%directory" directory where files will be stored. Do not include preceding or trailing slashes.', array('%directory' => variable_get('file_directory_path', 'files') . '/')),
'#element_validate' => array('_filefield_widget_settings_file_path_validate'),
'#suffix' => theme('token_help', 'user'),
);
$form['max_filesize'] = array(
'#type' => 'fieldset',
'#title' => t('File size restrictions'),
'#description' => t('Limits for the size of files that a user can upload. Note that these settings only apply to newly uploaded files, whereas existing files are not affected.'),
'#weight' => 6,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['max_filesize']['max_filesize_per_file'] = array(
'#type' => 'textfield',
'#title' => t('Maximum upload size per file'),
'#default_value' => is_string($widget['max_filesize_per_file'])
? $widget['max_filesize_per_file']
: '',
'#description' => t('Specify the size limit that applies to each file separately. Enter a value like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes) in order to restrict the allowed file size. If you leave this empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit <strong>%limit</strong>).', array('%limit' => format_size(file_upload_max_size()))),
'#element_validate' => array('_filefield_widget_settings_max_filesize_per_file_validate'),
);
$form['max_filesize']['max_filesize_per_node'] = array(
'#type' => 'textfield',
'#title' => t('Maximum upload size per node'),
'#default_value' => is_string($widget['max_filesize_per_node'])
? $widget['max_filesize_per_node']
: '',
'#description' => t('Specify the total size limit for all files in field on a given node. Enter a value like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes) in order to restrict the total size of a node. Leave this empty if there should be no size restriction.'),
'#element_validate' => array('_filefield_widget_settings_max_filesize_per_node_validate'),
);
return $form;
}
/**
* Implementation of CCK's hook_widget_settings($op == 'save').
*/
function filefield_widget_settings_save($widget) {
return array('file_extensions', 'file_path', 'progress_indicator', 'max_filesize_per_file', 'max_filesize_per_node');
}
/**
* A FAPI #pre_render() function to set a cosmetic default value for extensions.
*/
function _filefield_widget_settings_extensions_value($element) {
$element['#value'] = implode(', ', array_filter(explode(' ', str_replace(',', ' ', $element['#value']))));
return $element;
}
/**
* A FAPI #element_validate callback to strip commas from extension lists.
*/
function _filefield_widget_settings_extensions_validate($element, &$form_state) {
// Remove commas and leading dots from file extensions.
$value = str_replace(',', ' ', $element['#value']);
$value = str_replace(' .', ' ', $value);
$value = array_filter(explode(' ', $value));
$value = implode(' ', $value);
form_set_value($element, $value, $form_state);
}
function _filefield_widget_settings_file_path_validate($element, &$form_state) {
// Strip slashes from the beginning and end of $widget['file_path']
$form_state['values']['file_path'] = trim($form_state['values']['file_path'], '\\/');
// Do not allow the file path to be the same as the file_directory_path().
// This causes all sorts of problems with things like file_create_url().
if (strpos($form_state['values']['file_path'], file_directory_path()) === 0) {
form_error($element, t('The file path (@file_path) cannot start with the system files directory (@files_directory), as this may cause conflicts when building file URLs.', array('@file_path' => $form_state['values']['file_path'], '@files_directory' => file_directory_path())));
}
}
function _filefield_widget_settings_max_filesize_per_file_validate($element, &$form_state) {
if (empty($form_state['values']['max_filesize_per_file'])) {
return; // Empty means no size restrictions, so don't throw an error.
}
elseif (!is_numeric(parse_size($form_state['values']['max_filesize_per_file']))) {
form_error($element, t('The "@field" option must contain a valid value. You can either leave the text field empty or enter a string like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes).', array('@field' => t('Maximum upload size per file'))));
}
}
function _filefield_widget_settings_max_filesize_per_node_validate($element, &$form_state) {
if (empty($form_state['values']['max_filesize_per_node'])) {
return; // Empty means no size restrictions, so don't throw an error.
}
elseif (!is_numeric(parse_size($form_state['values']['max_filesize_per_node']))) {
form_error($element, t('The "@field" option must contain a valid value. You can either leave the text field empty or enter a string like "512" (bytes), "80K" (kilobytes) or "50M" (megabytes).', array('@field' => t('Maximum upload size per node'))));
}
}
/**
* Determine the widget's files directory
*
* @param $field
* A CCK field array.
* @param $account
* The user account object to calculate the file path for.
* @return
* The files directory path, with any tokens replaced.
*/
function filefield_widget_file_path($field, $account = NULL) {
$account = isset($account) ? $account : $GLOBALS['user'];
$dest = $field['widget']['file_path'];
// Replace user level tokens.
// Node level tokens require a lot of complexity like temporary storage
// locations when values don't exist. See the filefield_paths module.
if (module_exists('token')) {
$dest = token_replace($dest, 'user', $account);
}
// Replace nasty characters in the path if possible.
if (module_exists('transliteration')) {
module_load_include('inc', 'transliteration');
$dest_array = array_filter(explode('/', $dest));
foreach ($dest_array as $key => $directory) {
$dest_array[$key] = transliteration_clean_filename($directory);
}
$dest = implode('/', $dest_array);
}
return file_directory_path() .'/'. $dest;
}
/**
* Given a FAPI element, save any files that may have been uploaded into it.
*
* This function should only be called during validate, submit, or
* value_callback functions.
*
* @param $element
* The FAPI element whose values are being saved.
*/
function filefield_save_upload($element) {
$upload_name = implode('_', $element['#array_parents']);
$field = content_fields($element['#field_name'], $element['#type_name']);
if (empty($_FILES['files']['name'][$upload_name])) {
return 0;
}
$dest = filefield_widget_file_path($field);
if (!field_file_check_directory($dest, FILE_CREATE_DIRECTORY)) {
watchdog('filefield', 'The upload directory %directory for the file field %field (content type %type) could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $dest, '%field' => $element['#field_name'], '%type' => $element['#type_name']));
form_set_error($upload_name, t('The file could not be uploaded.'));
return 0;
}
if (!$file = field_file_save_upload($upload_name, $element['#upload_validators'], $dest)) {
watchdog('filefield', 'The file upload failed. %upload', array('%upload' => $upload_name));
form_set_error($upload_name, t('The file in the @field field was unable to be uploaded.', array('@field' => $element['#title'])));
return 0;
}
return $file['fid'];
}
/**
* The #value_callback for the filefield_widget type element.
*/
function filefield_widget_value($element, $edit = FALSE) {
if (!$edit) {
$file = field_file_load($element['#default_value']['fid']);
$item = $element['#default_value'];
}
else {
$item = $edit;
$field = content_fields($element['#field_name'], $element['#type_name']);
// Uploads take priority over value of fid text field.
if ($fid = filefield_save_upload($element)) {
$item['fid'] = $fid;
}
// Check for #filefield_value_callback values.
// Because FAPI does not allow multiple #value_callback values like it does
// for #element_validate and #process, this fills the missing functionality
// to allow FileField to be extended purely through FAPI.
elseif (isset($element['#filefield_value_callback'])) {
foreach ($element['#filefield_value_callback'] as $callback) {
$callback($element, $item);
}
}
// Load file if the FID has changed so that it can be saved by CCK.
$file = isset($item['fid']) ? field_file_load($item['fid']) : NULL;
// If the file entry doesn't exist, don't save anything.
if (empty($file)) {
$item = array();
$file = array();
}
// Checkboxes loose their value when empty.
// If the list field is present make sure its unchecked value is saved.
if (!empty($field['list_field']) && empty($edit['list'])) {
$item['list'] = 0;
}
}
// Merge file and item data so it is available to all widgets.
if (isset($item['data']) && isset($file['data'])) {
$file['data'] = array_merge($item['data'], $file['data']);
}
$item = array_merge($item, $file);
return $item;
}
/**
* An element #process callback for the filefield_widget field type.
*
* Expands the filefield_widget type to include the upload field, upload and
* remove buttons, and the description field.
*/
function filefield_widget_process($element, $edit, &$form_state, $form) {
static $settings_added;
$item = $element['#value'];
$field_name = $element['#field_name'];
$delta = $element['#delta'];
$element['#theme'] = 'filefield_widget_item';
$field = $form['#field_info'][$field_name];
// The widget is being presented, so apply the JavaScript.
drupal_add_js(drupal_get_path('module', 'filefield') .'/filefield.js');
if (!isset($settings_added[$field_name]) && isset($element['#upload_validators']['filefield_validate_extensions'])) {
$settings_added[$field_name] = TRUE;
$settings = array(
'filefield' => array(
$field_name => $element['#upload_validators']['filefield_validate_extensions'][0],
),
);
drupal_add_js($settings, 'setting');
}
// Title is not necessary for each individual field.
if ($field['multiple'] > 0) {
unset($element['#title']);
}
// Set up the buttons first since we need to check if they were clicked.
$element['filefield_upload'] = array(
'#type' => 'submit',
'#value' => t('Upload'),
'#submit' => array('node_form_submit_build_node'),
'#ahah' => array( // with JavaScript
'path' => 'filefield/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'],
'wrapper' => $element['#id'] .'-ahah-wrapper',
'method' => 'replace',
'effect' => 'fade',
),
'#field_name' => $element['#field_name'],
'#delta' => $element['#delta'],
'#type_name' => $element['#type_name'],
'#upload_validators' => $element['#upload_validators'],
'#weight' => 100,
'#post' => $element['#post'],
);
$element['filefield_remove'] = array(
// With default CCK edit forms, $element['#parents'] is array($element['#field_name'], $element['#delta']).
// However, if some module (for example, flexifield) places our widget deeper in the tree, we want to
// use that information in constructing the button name.
'#name' => implode('_', $element['#parents']) .'_filefield_remove',
'#type' => 'submit',
'#value' => t('Remove'),
'#submit' => array('node_form_submit_build_node'),
'#ahah' => array( // with JavaScript
'path' => 'filefield/ahah/'. $element['#type_name'] .'/'. $element['#field_name'] .'/'. $element['#delta'],
'wrapper' => $element['#id'] .'-ahah-wrapper',
'method' => 'replace',
'effect' => 'fade',
),
'#field_name' => $element['#field_name'],
'#delta' => $element['#delta'],
'#weight' => 101,
'#post' => $element['#post'],
);
// Because the output of this field changes depending on the button clicked,
// we need to ask FAPI immediately if the remove button was clicked.
// It's not good that we call this private function, but
// $form_state['clicked_button'] is only available after this #process
// callback is finished.
if (_form_button_was_clicked($element['filefield_remove'])) {
// Delete the file if it is currently unused. Note that field_file_delete()
// does a reference check in addition to our basic status check.
if (isset($edit['fid'])) {
$removed_file = field_file_load($edit['fid']);
// Users are only allowed to remove their own files.
if ($removed_file['status'] == 0 && $GLOBALS['user']->uid == $removed_file['uid']) {
field_file_delete($removed_file);
}
}
$item = array('fid' => 0, 'list' => $field['list_default'], 'data' => array('description' => ''));
}
// Add progress bar support to the upload if possible.
$progress_indicator = isset($field['widget']['progress_indicator']) ? $field['widget']['progress_indicator'] : 'bar';
if ($progress_indicator != 'throbber' && $implementation = filefield_progress_implementation()) {
$upload_progress_key = md5(mt_rand());
if ($implementation == 'uploadprogress') {
$element['UPLOAD_IDENTIFIER'] = array(
'#type' => 'hidden',
'#value' => $upload_progress_key,
'#attributes' => array('class' => 'filefield-progress'),
);
}
elseif ($implementation == 'apc') {
$element['APC_UPLOAD_PROGRESS'] = array(
'#type' => 'hidden',
'#value' => $upload_progress_key,
'#attributes' => array('class' => 'filefield-progress'),
);
}
// Add the upload progress callback.
$element['filefield_upload']['#ahah']['progress']['type'] = 'bar';
$element['filefield_upload']['#ahah']['progress']['path'] = 'filefield/progress/' . $upload_progress_key;
}
// Set the FID.
$element['fid'] = array(
'#type' => 'hidden',
'#value' => isset($item['fid']) ? $item['fid'] : 0,
);
if (!empty($item['fid'])) {
if (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE && function_exists('file_download_access') && !empty($item['status']) && !file_download_access($item['filepath'])) {
$element['preview'] = array(
'#type' => 'markup',
'#value' => t('Access denied'),
);
}
else {
$element['preview'] = array(
'#type' => 'markup',
'#value' => theme('filefield_widget_preview', $item),
);
}
}
// Grant access to temporary files.
if (!empty($item['fid']) && isset($item['status']) && $item['status'] == 0 && variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE) {
$_SESSION['filefield_access'][] = $item['fid'];
}
// placeholder.. will be serialized into the data column. this is a place for widgets
// to put additional data.
$element['data'] = array(
'#tree' => 'true',
'#access' => !empty($item['fid']),
);
if (!empty($field['description_field'])) {
$element['data']['description'] = array(
'#title' => t('Description'),
'#value' => isset($item['data']['description']) ? $item['data']['description'] : '',
'#type' => variable_get('filefield_description_type', 'textfield'),
'#maxlength' => variable_get('filefield_description_length', 128),
);
}
if (!empty($field['list_field'])) {
$element['list'] = array(
'#type' => empty($item['fid']) ? 'hidden' : 'checkbox',
'#title' => t('List'),
'#value' => isset($item['list']) && !empty($item['fid']) ? $item['list'] : $field['list_default'],
'#attributes' => array('class' => 'filefield-list'),
);
}
else {
$element['list'] = array(
'#type' => 'hidden',
'#value' => '1',
);
}
foreach ($element['#upload_validators'] as $callback => $arguments) {
$help_func = $callback .'_help';
if (function_exists($help_func)) {
$desc[] = call_user_func_array($help_func, $arguments);
}
}
$element['upload'] = array(
'#name' => 'files[' . implode('_', $element['#array_parents']) . ']',
'#type' => 'file',
'#description' => implode('<br />', $desc),
'#size' => 22,
'#access' => empty($item['fid']),
);
$element['#attributes']['id'] = $element['#id'] .'-ahah-wrapper';
$element['#prefix'] = '<div '. drupal_attributes($element['#attributes']) .'>';
$element['#suffix'] = '</div>';
return $element;
}
function filefield_widget_after_build($element, &$form_state) {
// Set access on the buttons.
$element['filefield_upload']['#access'] = empty($element['fid']['#value']);
$element['filefield_remove']['#access'] = !empty($element['fid']['#value']);
return $element;
}
/**
* An #element_validate callback for the filefield_widget field.
*/
function filefield_widget_validate(&$element, &$form_state) {
// If referencing an existing file, only allow if there are existing
// references. This prevents unmanaged files (outside of FileField) from
// being deleted if this node were to be deleted.
if (!empty($element['fid']['#value'])) {
$field = content_fields($element['#field_name'], $element['#type_name']);
if ($file = field_file_load($element['fid']['#value'])) {
$file = (object) $file;
if ($file->status == FILE_STATUS_PERMANENT) {
if (field_file_references($file) == 0 || (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PRIVATE && function_exists('file_download_access') && !file_download_access($file->filepath))) {
form_error($element, t('Referencing to the file used in the %field field is not allowed.', array('%field' => $element['#title'])));
}
}
}
else {
form_error($element, t('The file referenced by the %field field does not exist.', array('%field' => $element['#title'])));
}
}
}
/**
* FormAPI theme function. Theme the output of an image field.
*/
function theme_filefield_widget($element) {
$element['#id'] .= '-upload'; // Link the label to the upload field.
return theme('form_element', $element, $element['#children']);
}
function theme_filefield_widget_preview($item) {
// Remove the current description so that we get the filename as the link.
if (isset($item['data']) && is_array($item['data']) && isset($item['data']['description'])) {
unset($item['data']['description']);
}
return '<div class="filefield-file-info">'.
'<div class="filename">'. theme('filefield_file', $item) .'</div>'.
'<div class="filesize">'. format_size($item['filesize']) .'</div>'.
'<div class="filemime">'. $item['filemime'] .'</div>'.
'</div>';
}
function theme_filefield_widget_item($element) {
// Put the upload button directly after the upload field.
$element['upload']['#field_suffix'] = drupal_render($element['filefield_upload']);
$element['upload']['#theme'] = 'filefield_widget_file';
$output = '';
$output .= '<div class="filefield-element clear-block">';
if ($element['fid']['#value'] != 0) {
$output .= '<div class="widget-preview">';
$output .= drupal_render($element['preview']);
$output .= '</div>';
}
$output .= '<div class="widget-edit">';
$output .= drupal_render($element);
$output .= '</div>';
$output .= '</div>';
return $output;
}
/**
* Custom theme function for FileField upload elements.
*
* This function allows us to put the "Upload" button immediately after the
* file upload field by respecting the #field_suffix property.
*/
function theme_filefield_widget_file($element) {
$output = '';
$output .= '<div class="filefield-upload clear-block">';
if (isset($element['#field_prefix'])) {
$output .= $element['#field_prefix'];
}
_form_set_class($element, array('form-file'));
$output .= '<input type="file" name="'. $element['#name'] .'"'. ($element['#attributes'] ? ' '. drupal_attributes($element['#attributes']) : '') .' id="'. $element['#id'] .'" size="'. $element['#size'] ."\" />\n";
if (isset($element['#field_suffix'])) {
$output .= $element['#field_suffix'];
}
$output .= '</div>';
return theme('form_element', $element, $output);
}
/**
* Additional #validate handler for the node form.
*
* This function checks the #required properties on file fields and calculates
* node upload totals for all file fields. The #required property is not
* properly supported on file fields by Drupal core, so we do this manually.
*/
function filefield_node_form_validate($form, &$form_state) {
foreach ($form['#field_info'] as $field_name => $field) {
if (!(in_array($field['module'], array('imagefield', 'filefield')))) continue;
$empty = $field['module'] .'_content_is_empty';
$valid = FALSE;
$total_filesize = 0;
if (!empty($form_state['values'][$field_name])) {
foreach ($form_state['values'][$field_name] as $delta => $item) {
if ($empty($item, $field)) continue;
$valid = TRUE;
$total_filesize += (int)$item['filesize'];
}
}
if (!$valid && $field['required'] && filefield_edit_access($field['type_name'], $field_name, $form['#node'])) {
form_set_error($field_name, t('%title field is required.', array('%title' => t($field['widget']['label']))));
}
$max_filesize = parse_size($field['widget']['max_filesize_per_node']);
if ($max_filesize && $total_filesize > $max_filesize) {
form_set_error($field_name, t('Total filesize for %title, %tsize, exceeds field settings of %msize.',
array(
'%title' => t($field['widget']['label']),
'%tsize' => format_size($total_filesize),
'%msize' => format_size($max_filesize)
)
));
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 B

View file

@ -0,0 +1,637 @@
<?php
class FileFieldTestCase extends DrupalWebTestCase {
protected $admin_user;
/**
* Implementation of setUp().
*/
function setUp() {
// Views is included here just so that it doesn't whine when CCK tries to
// clear the caches.
$modules = array_merge(func_get_args(), array('content', 'filefield', 'filefield_meta', 'getid3', 'mimedetect', 'token', 'views'));
call_user_func_array(array('parent', 'setUp'), $modules);
// Create and login user
$this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'administer files'));
$this->drupalLogin($this->admin_user);
}
/**
* Get a sample file of the specified type.
*/
function getTestFile($type, $size = NULL) {
// Get a file to upload.
$file = current($this->drupalGetTestFiles($type, $size));
// SimpleTest files incorrectly use "filename" instead of "filepath".
$file->filepath = $file->filename;
$file->filename = basename($file->filepath);
$file->filesize = filesize($file->filepath);
return $file;
}
/**
* Create a new file field.
*
* @param $name
* The name of the new field (all lowercase), exclude the "field_" prefix.
* @param $type
* The node type that this field will be added to.
* @param $field_options
* A list of field options that will be added to the defaults.
* @param $widget_options
* A list of widget options that will be added to the widget defaults.
*/
function createFileField($name, $type, $field_options = array(), $widget_options = array()) {
module_load_include('inc', 'content', 'includes/content.crud');
$field = array(
'label' => $name,
'field_name' => $name,
'type' => 'filefield',
'widget_type' => 'filefield_widget',
'weight' => 0,
'parent' => 0,
'type_name' => $type,
'list_field' => 0,
'list_default' => 1,
'description_field' => 0,
);
$field = array_merge($field, $field_options);
$field = content_field_instance_create($field);
$widget = array(
'type' => 'filefield_widget',
'file_extensions' => '',
);
$field['widget'] = array_merge($field['widget'], $widget, $widget_options);
$field = content_field_instance_update($field);
return $field;
}
/**
* Update an existing FileField with new settings.
*/
function updateFileField($name, $type, $field_options = array(), $widget_options = array()) {
$field = content_fields($name, $type);
$field = array_merge($field, $field_options);
$field['widget'] = array_merge($field['widget'], $widget_options);
return content_field_instance_update($field);
}
/**
* Upload a file to a node.
*/
function uploadNodeFile($file, $field, $nid_or_type, $new_revision = TRUE) {
$field_name = $field['field_name'];
$edit = array(
'title' => $this->randomName(),
'revision' => (string) (int) $new_revision,
);
if (is_numeric($nid_or_type)) {
$node = node_load($nid_or_type);
$delta = isset($node->$field_name) ? count($node->$field_name) : 0;
$edit['files[' . $field_name . '_' . $delta . ']'] = realpath($file->filepath);
$this->drupalPost('node/' . $nid_or_type . '/edit', $edit, t('Save'));
}
else {
$delta = '0';
$edit['files[' . $field_name . '_' . $delta . ']'] = realpath($file->filepath);
$type = str_replace('_', '-', $nid_or_type);
$this->drupalPost('node/add/' . $type, $edit, t('Save'));
}
$matches = array();
preg_match('/node\/([0-9]+)/', $this->getUrl(), $matches);
$nid = isset($matches[1]) ? $matches[1] : FALSE;
// Edit the node and add a description if possible.
if ($nid && $field['description_field']) {
$edit = array(
'revision' => 0,
$field_name . '[' . $delta . '][data][description]' => $this->randomString(),
);
$this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));
}
return $nid;
}
/**
* Remove a file from a node.
*
* Note that if replacing a file, it must first be removed then added again.
*/
function removeNodeFile($nid, $new_revision = TRUE) {
$edit = array(
'revision' => (string) (int) $new_revision,
);
$this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
$this->drupalPost(NULL, $edit, t('Save'));
}
/**
* Replace a file within a node.
*/
function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) {
$edit = array(
'files[' . $field_name . '_0]' => realpath($file->filepath),
'revision' => (string) (int) $new_revision,
);
$this->drupalPost('node/' . $nid . '/edit', array(), t('Remove'));
$this->drupalPost(NULL, $edit, t('Save'));
}
/**
* Assert that a file exists physically on disk.
*/
function assertFileExists($file, $message = NULL) {
$message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file['filepath']));
$this->assertTrue(is_file($file['filepath']), $message);
}
/**
* Assert that a file exists in the database.
*/
function assertFileEntryExists($file, $message = NULL) {
module_load_include('inc', 'filefield', 'field_file');
$db_file = field_file_load($file['fid'], TRUE);
$message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file['filepath']));
$this->assertEqual($db_file['filepath'], $file['filepath'], $message);
}
/**
* Assert that a file does not exist on disk.
*/
function assertFileNotExists($file, $message = NULL) {
$message = isset($message) ? $message : t('File %file exists on the disk.', array('%file' => $file['filepath']));
$this->assertFalse(is_file($file['filepath']), $message);
}
/**
* Assert that a file does not exist in the database.
*/
function assertFileEntryNotExists($file, $message) {
module_load_include('inc', 'filefield', 'field_file');
$message = isset($message) ? $message : t('File %file exists in database at the correct path.', array('%file' => $file['filepath']));
$this->assertFalse(field_file_load($file['fid'], TRUE), $message);
}
}
/**
* Test class to test file handling with node revisions.
*/
class FileFieldRevisionTestCase extends FileFieldTestCase {
function getInfo() {
return array(
'name' => t('FileField revision test'),
'description' => t('Test creating and deleting revisions with files attached.'),
'group' => t('FileField'),
);
}
/**
* Test creating multiple revisions of a node and managing the attached files.
*
* Expected behaviors:
* - Adding a new revision will make another entry in the field table, but
* the original file will not be duplicated.
* - Deleting a revision should not delete the original file if the file
* is in use by another revision.
* - When the last revision that uses a file is deleted, the original file
* should be deleted also.
*/
function testRevisions() {
$field_name = 'field_' . strtolower($this->randomName());
$type = $this->drupalCreateContentType();
$field_options = array(
'description_field' => '1',
);
$field = $this->createFileField($field_name, $type->name, $field_options);
$test_file = $this->getTestFile('text');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
// Check that the file exists on disk and in the database.
$node = node_load($nid, NULL, TRUE);
$node_file_r1 = $node->{$field['field_name']}[0];
$node_vid_r1 = $node->vid;
$this->assertFileExists($node_file_r1, t('New file saved to disk on node creation.'));
$this->assertFileEntryExists($node_file_r1, t('File entry exists in database on node creation.'));
// Upload another file to the same node in a new revision.
$this->replaceNodeFile($test_file, $field_name, $nid);
$node = node_load($nid, NULL, TRUE);
$node_file_r2 = $node->{$field['field_name']}[0];
$node_vid_r2 = $node->vid;
$this->assertFileExists($node_file_r2, t('Replacement file exists on disk after creating new revision.'));
$this->assertFileEntryExists($node_file_r2, t('Replacement file entry exists in database after creating new revision.'));
// Check that the original file is still in place on the first revision.
$node = node_load($nid, $node_vid_r1, TRUE);
$this->assertEqual($node_file_r1, $node->{$field['field_name']}[0], t('Original file still in place after replacing file in new revision.'));
$this->assertFileExists($node_file_r1, t('Original file still in place after replacing file in new revision.'));
$this->assertFileEntryExists($node_file_r1, t('Original file entry still in place after replacing file in new revision'));
// Save a new version of the node without any changes.
// Check that the file is still the same as the previous revision.
$this->drupalPost('node/' . $nid . '/edit', array('revision' => '1'), t('Save'));
$node = node_load($nid, NULL, TRUE);
$node_file_r3 = $node->{$field['field_name']}[0];
$node_vid_r3 = $node->vid;
// FileField Meta's extensive meta data can be difficult to match up exactly
// (mostly differences between strings and integers). Just compare the
// descriptions.
$node_file_r2['data'] = array('description' => $node_file_r2['data']['description']);
$node_file_r3['data'] = array('description' => $node_file_r3['data']['description']);
$this->assertEqual($node_file_r2, $node_file_r3, t('Previous revision file still in place after creating a new revision without a new file.'));
// Revert to the first revision and check that the original file is active.
$this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r1 . '/revert', array(), t('Revert'));
$node = node_load($nid, NULL, TRUE);
$node_file_r4 = $node->{$field['field_name']}[0];
$node_vid_r4 = $node->vid;
$this->assertEqual($node_file_r1, $node_file_r4, t('Original revision file still in place after reverting to the original revision.'));
// Delete the second revision and check that the file is kept (since it is
// still being used by the third revision).
$this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r2 . '/delete', array(), t('Delete'));
$this->assertFileExists($node_file_r3, t('Second file is still available after deleting second revision, since it is being used by the third revision.'));
$this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting second revision, since it is being used by the third revision.'));
// Delete the third revision and check that the file is deleted also.
$this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete'));
$this->assertFileNotExists($node_file_r3, t('Second file is now deleted after deleting third revision, since it is no longer being used by any other nodes.'));
$this->assertFileEntryNotExists($node_file_r3, t('Second file entry is now deleted after deleting third revision, since it is no longer being used by any other nodes.'));
// Delete the entire node and check that the original file is deleted.
$this->drupalPost('node/' . $nid . '/delete', array(), t('Delete'));
$this->assertFileNotExists($node_file_r1, t('Original file is deleted after deleting the entire node with two revisions remaining.'));
$this->assertFileEntryNotExists($node_file_r1, t('Original file entry is deleted after deleting the entire node with two revisions remaining.'));
}
}
/**
* Test class to check that formatters are working properly.
*/
class FileFieldDisplayTestCase extends FileFieldTestCase {
function getInfo() {
return array(
'name' => t('FileField display tests'),
'description' => t('Test the display of file fields in node and views.'),
'group' => t('FileField'),
);
}
/**
* Test normal formatter display on node display.
*/
function testNodeDisplay() {
$field_name = 'field_' . strtolower($this->randomName());
$type = $this->drupalCreateContentType();
$field_options = array(
'description_field' => '1',
'list_field' => '1',
'list_default' => '1',
);
$field = $this->createFileField($field_name, $type->name, $field_options);
$test_file = $this->getTestFile('text');
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
$this->drupalGet('node/' . $nid);
// Check that the default formatter is displaying with the file name.
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$default_output = theme('filefield_file', $node_file);
$this->assertRaw($default_output, t('Default formatter displaying correctly on full node view.'));
// Turn the "list" option off and check that the file is no longer listed.
$edit = array($field['field_name'] . '[0][list]' => FALSE);
$this->drupalPost('node/' . $nid . '/edit', $edit, t('Save'));
$this->assertNoRaw($default_output, t('Field is hidden when "list" option is unchecked.'));
}
}
/**
* Test class to check for various validations.
*/
class FileFieldValidateTestCase extends FileFieldTestCase {
protected $field;
protected $node_type;
function getInfo() {
return array(
'name' => t('FileField validation tests'),
'description' => t('Tests validation functions such as file type, max file size, max size per node, and required.'),
'group' => t('FileField'),
);
}
/**
* Implementation of setUp().
*/
function setUp() {
$modules = array_merge(func_get_args(), array('content', 'filefield', 'filefield_meta', 'getid3', 'mimedetect', 'token', 'views'));
call_user_func_array(array('parent', 'setUp'), $modules);
$this->node_type = $this->drupalCreateContentType();
$this->node_type->url_name = str_replace('_', '-', $this->node_type->name);
$field_name = 'field_' . strtolower($this->randomName());
$this->field = $this->createFileField($field_name, $this->node_type->name);
}
/**
* Test required property on file fields.
*/
function testRequired() {
$type = $this->node_type;
$field = $this->field;
// Make our field required.
$this->updateFileField($field['field_name'], $type->name, array('required' => '1'));
$test_file = $this->getTestFile('image');
// Try to post a new node without uploading a file.
$edit = array('title' => $this->randomName());
$this->drupalPost('node/add/' . $type->url_name, $edit, t('Save'));
$this->assertRaw(t('%title field is required.', array('%title' => $field['widget']['label'])), t('Node save failed when required file field was empty.'));
// Create a new node with the uploaded file.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading to the required field.'));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required field.'));
// Try again with a multiple value field.
$this->updateFileField($field['field_name'], $type->name, array('multiple' => '0', 'required' => '1'));
// Try to post a new node without uploading a file in the multivalue field.
$edit = array('title' => $this->randomName());
$this->drupalPost('node/add/' . $type->url_name, $edit, t('Save'));
$this->assertRaw(t('%title field is required.', array('%title' => $field['widget']['label'])), t('Node save failed when required multiple value file field was empty.'));
// Create a new node with the uploaded file into the multivalue field.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading to the required multiple value field.'));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading to the required multipel value field.'));
// Set the field back to not required.
$this->updateFileField($field['field_name'], $type->name, array('multiple' => '0', 'required' => '1'));
}
/**
* Test the max file size validator.
*/
function testFileMaxSize() {
$type = $this->node_type;
$field = $this->field;
$small_file = $this->getTestFile('text', 131072); // 128KB.
$large_file = $this->getTestFile('text', 1310720); // 1.2MB
// Test uploading both a large and small file with different increments.
$sizes = array(
'1M' => 1048576,
'1024K' => 1048576,
'1048576' => 1048576,
);
foreach ($sizes as $max_filesize_per_file => $file_limit) {
// Set the max file upload size.
$this->updateFileField($field['field_name'], $type->name, array(), array('max_filesize_per_file' => $max_filesize_per_file));
// Create a new node with the small file, which should pass.
$nid = $this->uploadNodeFile($small_file, $field, $type->name);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize_per_file)));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) under the max limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_filesize_per_file)));
// Check that uploading the large file fails (1M limit).
$nid = $this->uploadNodeFile($large_file, $field, $type->name);
$error_message = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($large_file->filesize), '%maxsize' => format_size($file_limit)));
$this->assertRaw($error_message, t('Node save failed when file (%filesize) exceeded the max upload size (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_filesize_per_file)));
}
// Turn off the max filesize.
$this->updateFileField($field['field_name'], $type->name, array(), array('max_filesize_per_file' => ''));
// Upload the big file successfully.
$nid = $this->uploadNodeFile($large_file, $field, $type->name);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) with no max limit.', array('%filesize' => format_size($large_file->filesize))));
}
/**
* Test the max file size per node validator.
*/
function testNodeMaxSize() {
$type = $this->node_type;
$field = $this->field;
$small_file = $this->getTestFile('text', 131072); // 128KB.
$large_file = $this->getTestFile('text', 1310720); // 1.2MB
// Set the max file upload size.
$max_node_limit = '256K';
$file_limit = 262144;
$this->updateFileField($field['field_name'], $type->name, array('multiple' => '1'), array('max_filesize_per_node' => $max_node_limit));
// Create a new node with the small file, which should pass.
$nid = $this->uploadNodeFile($small_file, $field, $type->name);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) under the max node limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_node_limit)));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) under the max node limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_node_limit)));
// Add a second file to the same node which should pass.
$nid = $this->uploadNodeFile($small_file, $field, $nid);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading a file (%filesize) under the max node limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_node_limit)));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading a file (%filesize) under the max node limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_node_limit)));
// Add a third file to the same node which should fail.
$nid = $this->uploadNodeFile($small_file, $field, $nid);
$error_message = t('exceeds field settings of %msize.', array('%msize' => format_size($file_limit)));
$this->assertRaw($error_message, t('File not uploaded as the file (%filesize) exceeds the max node limit (%maxsize).', array('%filesize' => format_size($small_file->filesize), '%maxsize' => $max_node_limit)));
// Check that uploading the large file fails (1M limit).
$nid = $this->uploadNodeFile($large_file, $field, $type->name);
$error_message = t('exceeds field settings of %msize.', array('%msize' => format_size($file_limit)));
$this->assertRaw($error_message, t('File not uploaded as the file (%filesize) exceeds the max node limit (%maxsize).', array('%filesize' => format_size($large_file->filesize), '%maxsize' => $max_node_limit)));
// Turn off the max filesize per node.
$this->updateFileField($field['field_name'], $type->name, array('multiple' => '0'), array('max_filesize_per_node' => ''));
}
/**
* Test the file extension, do additional checks if mimedetect is installed.
*/
function testFileExtension() {
$type = $this->node_type;
$field = $this->field;
// Setup files for extension checking.
$test_file = $this->getTestFile('image');
preg_match('/(?<=\.)[^\.]*$/', $test_file->filename, $matches);
$extention = current($matches);
$wrong_extension_file = drupal_clone($test_file);
$wrong_extension_file->filename = str_replace(".$extention", '.jpg', $test_file->filename);
$wrong_extension_file->filepath = file_directory_path() .'/'. $wrong_extension_file->filename;
$original_path = $test_file->filepath;
file_copy($original_path, $wrong_extension_file->filepath);
$this->updateFileField($field['field_name'], $type->name, array(), array('file_extensions' => ''));
// Check that the file can be uploaded with no extension checking.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading a file with no extension checking.'));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with no extension checking.'));
// Enable extension checking.
$this->updateFileField($field['field_name'], $type->name, array(), array('file_extensions' => "txt png jpg $extention"));
// Check that the file can be uploaded with extension checking.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertFileExists($node_file, t('File exists after uploading a file with extension checking.'));
$this->assertFileEntryExists($node_file, t('File entry exists after uploading a file with extension checking.'));
// Check that a mismatched extension cannot be uploaded.
$mimedetect = FALSE;
if (module_exists('mimedetect')) {
$mimedetect = TRUE;
module_load_include('install', 'mimedetect');
if (!extension_loaded('fileinfo')) {
variable_set('mimedetect_enable_file_binary', 1);
}
$requirements = mimedetect_requirements('runtime');
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
$mimedetect = FALSE;
}
}
}
if ($mimedetect) {
$this->uploadNodeFile($wrong_extension_file, $field, $type->name);
$error_pattern = "/The file contents \([a-z\-\/]+\) do not match its extension \([a-z]+\)\./";
$this->assertPattern($error_pattern, t('File prevented from uploading because its extension does not match its content.'));
}
else {
$this->assertTrue(TRUE, t('Mime type checking and extension spoofing skipped because the mimedetect module is not available.'));
}
// Disable the extension checking.
$this->updateFileField($field['field_name'], $type->name, array(), array('file_extensions' => ''));
}
}
/**
* Test class to check that files are uploaded to proper locations.
*/
class FileFieldPathTestCase extends FileFieldTestCase {
function getInfo() {
return array(
'name' => t('FileField file path tests'),
'description' => t('Test that files are uploaded to the proper location, extra testing if Token module is available.'),
'group' => t('FileField'),
);
}
/**
* Test normal formatter display on node display.
*/
function testUploadPath() {
$field_name = 'field_' . strtolower($this->randomName());
$type = $this->drupalCreateContentType();
$field = $this->createFileField($field_name, $type->name);
$test_file = $this->getTestFile('text');
// Create a new node.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
// Check that the file was uploaded to the file root.
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertPathMatch(file_directory_path() . '/' . $test_file->filename, $node_file['filepath'], t('The file %file was uploaded to the correct path.', array('%file' => $node_file['filepath'])));
// Change the path to contain multiple subdirectories.
$field = $this->updateFileField($field['field_name'], $type->name, array(), array('file_path' => 'foo/bar/baz'));
// Upload a new file into the subdirectories.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
// Check that the file was uploaded into the subdirectory.
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$this->assertPathMatch(file_directory_path() . '/foo/bar/baz/' . $test_file->filename, $node_file['filepath'], t('The file %file was uploaded to the correct path.', array('%file' => $node_file['filepath'])));
// Check the path when used with tokens.
if (module_exists('token')) {
// Change the path to contain multiple token directories.
$field = $this->updateFileField($field['field_name'], $type->name, array(), array('file_path' => '[uid]/[user-raw]'));
// Upload a new file into the token subdirectories.
$nid = $this->uploadNodeFile($test_file, $field, $type->name);
// Check that the file was uploaded into the subdirectory.
$node = node_load($nid, NULL, TRUE);
$node_file = $node->{$field['field_name']}[0];
$subdirectory = token_replace('[uid]/[user-raw]', 'user', $this->admin_user);
$this->assertPathMatch(file_directory_path() . '/' . $subdirectory . '/' . $test_file->filename, $node_file['filepath'], t('The file %file was uploaded to the correct path with token replacements.', array('%file' => $node_file['filepath'])));
}
else {
$this->assertTrue(TRUE, t('File path token test skipped because the Token module is not available.'));
}
}
/**
* A loose assertion to check that a file is uploaded to the right location.
*
* @param $expected_path
* The location where the file is expected to be uploaded. Duplicate file
* names to not need to be taken into account.
* @param $actual_path
* Where the file was actually uploaded.
* @param $message
* The message to display with this assertion.
*/
function assertPathMatch($expected_path, $actual_path, $message) {
// Strip off the extension of the expected path to allow for _0, _1, etc.
// suffixes when the file hits a duplicate name.
$pos = strrpos($expected_path, '.');
$base_path = substr($expected_path, 0, $pos);
$extension = substr($expected_path, $pos + 1);
$result = preg_match('/' . preg_quote($base_path, '/') . '(_[0-9]+)?\.' . preg_quote($extension, '/') . '/', $actual_path);
$this->assertTrue($result, $message);
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Provide views data for filefield.module.
*/
/**
* @defgroup views_filefield_module filefield.module handlers
*
* Includes the handler for the FileField data column.
* @{
*/
/**
* Implementation of hook_views_data()
*/
function filefield_views_data() {
$data = array();
// Extend the files table with an icon field.
$data['files']['icon'] = array(
'title' => t('Icon'),
'help' => t('An icon corresponding to the file MIME type.'),
'real field' => 'filemime',
'field' => array(
'handler' => 'filefield_handler_field_icon',
'click sortable' => FALSE,
),
);
return $data;
}
/**
* Implementation of hook_views_handlers().
*/
function filefield_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'filefield') . '/views',
),
'handlers' => array(
// field handlers
'filefield_handler_field_data' => array(
'parent' => 'views_handler_field_node',
),
'filefield_handler_field_icon' => array(
'parent' => 'views_handler_field',
),
),
);
}
/**
* @}
*/

View file

@ -0,0 +1,81 @@
<?php
/**
* @file
* Field conversion for fields handled by this module.
*/
/**
* Implementation of hook_views_convert().
*/
function filefield_views_convert($display, $type, &$view, $field, $id = NULL) {
static $filefield_fields;
static $filefield_filters;
static $filefield_arguments;
if (!isset($filefield_fields)) {
$filefield_fields = $filefield_filters = $filefield_arguments = array();
foreach (content_fields() as $content_field) {
if ($content_field['module'] == 'filefield') {
$result = module_invoke('filefield', 'field_settings', 'views data', $content_field);
if (!empty($result)) {
foreach ($result as $table => $fields) {
unset($fields['table']);
foreach ($fields as $filefield_field => $definition) {
switch ($filefield_field) {
case $content_field['field_name'] .'_fid':
$filefield_fields[$filefield_field] = array(
'table' => $table,
'field' => $filefield_field,
);
$filefield_arguments['content: '. $content_field['field_name']] = &$filefield_fields[$filefield_field];
break;
case $content_field['field_name'] .'_list':
$filefield_filters[$content_field['field_name'] .'_fid_not null'] = array(
'table' => $table,
'field' => $filefield_field,
);
break;
}
}
}
}
}
}
}
switch ($type) {
case 'field':
if (isset($filefield_fields[$field['field']])) {
$multiple = array();
switch ($field['handler']) {
case 'content_views_field_handler_ungroup':
$view->set_item_option($display, 'field', $id, 'multiple', array('group' => FALSE));
break;
case 'content_views_field_handler_last':
$multiple['multiple_reversed'] = TRUE;
case 'content_views_field_handler_first':
$multiple['multiple_number'] = 1;
$view->set_item_option($display, 'field', $id, 'multiple', $multiple);
break;
}
$view->set_item_option($display, 'field', $id, 'format', $field['options']);
}
break;
case 'filter':
if (isset($filefield_filters[$field['field']])) {
$filter = $filefield_filters[$field['field']];
$item = $view->get_item($display, 'filter', $id);
$item['value'] = $field['value'];
$item['table'] = $filter['table'];
$item['field'] = $filter['field'];
$view->set_item($display, 'filter', $id, $item);
}
break;
case 'argument':
if (isset($filefield_arguments[$field['type']])) {
$argument = $filefield_arguments[$field['type']];
$options = $field['argoptions'];
$view->add_item($display, 'argument', $argument['table'], $argument['field'], $options, $field['id']);
}
break;
}
}

View file

@ -0,0 +1,54 @@
<?php
/**
* @file
* filefield_handler_field_data.inc
*
* Provides a handler for displaying values within the serialized data column.
*/
class filefield_handler_field_data extends views_handler_field_node {
function option_definition() {
$options = parent::option_definition();
$options['data_key'] = array('default' => 'description');
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$options = array();
$info = filefield_data_info();
foreach ($info as $key => $data) {
$options[$key] = $data['title'] . ' (' . $data['module'] .')';
}
$form['data_key'] = array(
'#title' => t('Data key'),
'#type' => 'radios',
'#options' => $options,
'#required' => TRUE,
'#default_value' => $this->options['data_key'],
'#description' => t('The data column may contain only a few or none any of these data options. The name of the module that provides the data is shown in parentheses.'),
'#weight' => 4,
);
}
function admin_summary() {
// Display the data to be displayed.
$info = filefield_data_info();
return isset($info[$this->options['data_key']]['title']) ? $info[$this->options['data_key']]['title'] : $this->options['data_key'];
}
function render($values) {
$values = drupal_clone($values); // Prevent affecting the original.
$data = unserialize($values->{$this->field_alias});
$values->{$this->field_alias} = filefield_data_value($this->options['data_key'], $data[$this->options['data_key']]);
// Copied from views_handler_field_node(). We just remove the call to
// sanitize_value() from the original call, becaue our value has already
// been cleaned by filefield_data_value().
return $this->render_link($values->{$this->field_alias}, $values);
}
}

View file

@ -0,0 +1,24 @@
<?php
/**
* @file
* Returns a file icon based on its MIME type.
*/
class filefield_handler_field_icon extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['fid'] = 'fid';
$this->additional_fields['filename'] = 'filename';
$this->additional_fields['filesize'] = 'filesize';
}
function render($values) {
$pseudo_file = array(
'fid' => $values->{$this->aliases['fid']},
'filemime' => $values->{$this->field_alias},
'filename' => $values->{$this->aliases['filename']},
'filesize' => $values->{$this->aliases['filesize']},
);
return theme('filefield_icon', $pseudo_file);
}
}