- •Contents at a Glance
- •Contents
- •Foreword
- •About the Authors
- •About the Technical Reviewers
- •Acknowledgments
- •Introduction
- •How Drupal Works
- •What Is Drupal?
- •Technology Stack
- •Core
- •Administrative Interface
- •Modules
- •Hooks
- •Themes
- •Nodes
- •Fields
- •Blocks
- •File Layout
- •Serving a Request
- •The Web Server’s Role
- •The Bootstrap Process
- •Processing a Request
- •Theming the Data
- •Summary
- •Writing a Module
- •Creating the Files
- •Implementing a Hook
- •Adding Module-Specific Settings
- •Defining Your Own Administration Section
- •Presenting a Settings Form to the User
- •Validating User-Submitted Settings
- •Storing Settings
- •Using Drupal’s variables Table
- •Retrieving Stored Values with variable_get()
- •Further Steps
- •Summary
- •Hooks, Actions, and Triggers
- •Understanding Events and Triggers
- •Understanding Actions
- •The Trigger User Interface
- •Your First Action
- •Assigning the Action
- •Changing Which Triggers an Action Supports
- •Actions That Support Any Trigger
- •Advanced Actions
- •Using the Context in Actions
- •How the Trigger Module Prepares the Context
- •Changing Existing Actions with action_info_alter()
- •Establishing the Context
- •How Actions Are Stored
- •The actions Table
- •Action IDs
- •Calling an Action Directly with actions_do()
- •Defining Your Own Triggers with hook_trigger_info()
- •Adding Triggers to Existing Hooks
- •Summary
- •The Menu System
- •Callback Mapping
- •Mapping URLs to Functions
- •Creating a Menu Item
- •Page Callback Arguments
- •Page Callbacks in Other Files
- •Adding a Link to the Navigation Block
- •Menu Nesting
- •Access Control
- •Title Localization and Customization
- •Defining a Title Callback
- •Wildcards in Menu Items
- •Basic Wildcards
- •Wildcards and Page Callback Parameters
- •Using the Value of a Wildcard
- •Wildcards and Parameter Replacement
- •Passing Additional Arguments to the Load Function
- •Special, Predefined Load Arguments: %map and %index
- •Building Paths from Wildcards Using to_arg() Functions
- •Special Cases for Wildcards and to_arg() Functions
- •Altering Menu Items from Other Modules
- •Altering Menu Links from Other Modules
- •Kinds of Menu Items
- •Common Tasks
- •Assigning Callbacks Without Adding a Link to the Menu
- •Displaying Menu Items As Tabs
- •Hiding Existing Menu Items
- •Using menu.module
- •Common Mistakes
- •Summary
- •Working with Databases
- •Defining Database Parameters
- •Understanding the Database Abstraction Layer
- •Connecting to the Database
- •Performing Simple Queries
- •Retrieving Query Results
- •Getting a Single Value
- •Getting Multiple Rows
- •Using the Query Builder and Query Objects
- •Getting a Limited Range of Results
- •Getting Results for Paged Display
- •Other Common Queries
- •Inserts and Updates with drupal_write_record()
- •The Schema API
- •Using Module .install Files
- •Creating Tables
- •Using the Schema Module
- •Field Type Mapping from Schema to Database
- •Textual
- •Varchar
- •Char
- •Text
- •Numerical
- •Integer
- •Serial
- •Float
- •Numeric
- •Date and Time: Datetime
- •Binary: Blob
- •Declaring a Specific Column Type with mysql_type
- •Maintaining Tables
- •Deleting Tables on Uninstall
- •Changing Existing Schemas with hook_schema_alter()
- •Modifying Other Modules’ Queries with hook_query_alter()
- •Connecting to Multiple Databases Within Drupal
- •Using a Temporary Table
- •Writing Your Own Database Driver
- •Summary
- •Working with Users
- •The $user Object
- •Testing If a User Is Logged In
- •Introduction to user hooks
- •Understanding hook_user_view($account, $view_mode)
- •The User Registration Process
- •Using profile.module to Collect User Information
- •The Login Process
- •Adding Data to the $user Object at Load Time
- •Providing User Information Categories
- •External Login
- •Summary
- •Working with Nodes
- •So What Exactly Is a Node?
- •Not Everything Is a Node
- •Creating a Node Module
- •Creating the .install File
- •Creating the .info File
- •Creating the .module File
- •Providing Information About Our Node Type
- •Modifying the Menu Callback
- •Defining Node-Type–Specific Permissions with hook_permission()
- •Limiting Access to a Node Type with hook__node_access()
- •Customizing the Node Form for Our Node Type
- •Validating Fields with hook_validate()
- •Saving Our Data with hook_insert()
- •Keeping Data Current with hook_update()
- •Cleaning Up with hook_delete()
- •Modifying Nodes of Our Type with hook_load()
- •Using hook_view()
- •Manipulating Nodes That Are Not Our Type with hook_node_xxxxx()
- •How Nodes Are Stored
- •Creating a Node Type with Custom Content Types
- •Restricting Access to Nodes
- •Defining Node Grants
- •What Is a Realm?
- •What Is a Grant ID?
- •The Node Access Process
- •Summary
- •Working with Fields
- •Creating Content Types
- •Adding Fields to a Content Type
- •Creating a Custom Field
- •Adding Fields Programmatically
- •Summary
- •The Theme System
- •Themes
- •Installing an Off-the-Shelf Theme
- •Building a Theme
- •The .info File
- •Adding Regions to Your Theme
- •Adding CSS Files to Your Theme
- •Adding JavaScript Files
- •Adding Settings to Your Theme
- •Understanding Template Files
- •The Big Picture
- •The html.php.tpl File
- •The page.tpl.php File
- •The region.tpl.php File
- •The node.tpl.php File
- •The field.tpl.php File
- •The block.tpl.php File
- •Overriding Template Files
- •Other Template Files
- •Introducing the theme() Function
- •An Overview of How theme() Works
- •Overriding Themable Items
- •Overriding with Template Files
- •Adding and Manipulating Template Variables
- •Using the Theme Developer Module
- •Summary
- •Working with Blocks
- •What Is a Block?
- •Block Configuration Options
- •Block Placement
- •Defining a Block
- •Using the Block Hooks
- •Building a Block
- •Enabling a Block When a Module Is Installed
- •Block Visibility Examples
- •Displaying a Block to Logged-In Users Only
- •Displaying a Block to Anonymous Users Only
- •Summary
- •The Form API
- •Understanding Form Processing
- •Initializing the Process
- •Setting a Token
- •Setting an ID
- •Collecting All Possible Form Element Definitions
- •Looking for a Validation Function
- •Looking for a Submit Function
- •Allowing Modules to Alter the Form Before It’s Built
- •Building the Form
- •Allowing Functions to Alter the Form After It’s Built
- •Checking If the Form Has Been Submitted
- •Finding a Theme Function for the Form
- •Allowing Modules to Modify the Form Before It’s Rendered
- •Rendering the Form
- •Validating the Form
- •Token Validation
- •Built-In Validation
- •Element-Specific Validation
- •Validation Callbacks
- •Submitting the Form
- •Redirecting the User
- •Creating Basic Forms
- •Form Properties
- •Form IDs
- •Fieldsets
- •Theming Forms
- •Using #prefix, #suffix, and #markup
- •Using a Theme Function
- •Telling Drupal Which Theme Function to Use
- •Specifying Validation and Submission Functions with hook_forms()
- •Call Order of Theme, Validation, and Submission Functions
- •Writing a Validation Function
- •Form Rebuilding
- •Writing a Submit Function
- •Changing Forms with hook_form_alter()
- •Altering Any Form
- •Altering a Specific Form
- •Submitting Forms Programmatically with drupal_form_submit()
- •Dynamic Forms
- •Form API Properties
- •Properties for the Root of the Form
- •#action
- •#built
- •#method
- •Properties Added to All Elements
- •#description
- •#attributes
- •#required
- •#tree
- •Properties Allowed in All Elements
- •#type
- •#access
- •#after_build
- •#array_parents
- •#attached
- •#default_value
- •#disabled
- •#element_validate
- •#parents
- •#post_render
- •#prefix
- •#pre_render
- •#process
- •#states
- •#suffix
- •#theme
- •#theme_wrappers
- •#title
- •#tree
- •#weight
- •Form Elements
- •Text Field
- •Password
- •Password with Confirmation
- •Textarea
- •Select
- •Radio Buttons
- •Check Boxes
- •Value
- •Hidden
- •Date
- •Weight
- •File Upload
- •Fieldset
- •Submit
- •Button
- •Image Button
- •Markup
- •Item
- •#ajax Property
- •Summary
- •Filters
- •Filters and Text formats
- •Installing a Filter
- •Knowing When to Use Filters
- •Creating a Custom Filter
- •Implementing hook_filter_info()
- •The Process Function
- •Helper Function
- •Summary
- •Searching and Indexing Content
- •Building a Custom Search Page
- •The Default Search Form
- •The Advanced Search Form
- •Adding to the Search Form
- •Introducing the Search Hooks
- •Formatting Search Results with hook_search_page()
- •Making Path Aliases Searchable
- •Using the Search HTML Indexer
- •When to Use the Indexer
- •How the Indexer Works
- •Adding Metadata to Nodes: hook_node_update_index()
- •Indexing Content That Isn’t a Node: hook_update_index()
- •Summary
- •Working with Files
- •How Drupal Serves Files
- •Managed and Unmanaged Drupal APIs
- •Public Files
- •Private Files
- •PHP Settings
- •Media Handling
- •Upload Field
- •Video and Audio
- •File API
- •Database Schema
- •Common Tasks and Functions
- •Finding the Default Files URI
- •Copying and Moving Files
- •Checking Directories
- •Uploading Files
- •Getting the URL for a File
- •Finding Files in a Directory
- •Finding the Temp Directory
- •Neutralizing Dangerous Files
- •Checking Disk Space
- •Authentication Hooks for Downloading
- •Summary
- •Working with Taxonomy
- •The Structure of Taxonomy
- •Creating a Vocabulary
- •Creating Terms
- •Assigning a Vocabulary to a Content Type
- •Kinds of Taxonomy
- •Flat
- •Hierarchical
- •Multiple Hierarchical
- •Viewing Content by Term
- •Using AND and OR in URLs
- •Specifying Depth for Hierarchical Vocabularies
- •Automatic RSS Feeds
- •Storing Taxonomies
- •Module-Based Vocabularies
- •Creating a Module-Based Vocabulary
- •Keeping Informed of Vocabulary Changes with Taxonomy Hooks
- •Common Tasks
- •Displaying Taxonomy Terms Associated with a Node
- •Building Your Own Taxonomy Queries
- •Using taxonomy_select_nodes()
- •Taxonomy Functions
- •Retrieving Information About Vocabularies
- •taxonomy_vocabulary_load($vid)
- •taxonomy_get_vocabularies()
- •Adding, Modifying, and Deleting Vocabularies
- •taxonomy_vocabulary_save($vocabulary)
- •taxonomy_vocabulary_delete($vid)
- •Retrieving Information About Terms
- •taxonomy_load_term($tid)
- •taxonomy_get_term_by_name($name)
- •Adding, Modifying, and Deleting Terms
- •taxonomy_term_save($term)
- •taxonomy_term_delete($tid)
- •Retrieving Information About Term Hierarchy
- •taxonomy_get_parents($tid, $key)
- •taxonomy_get_parents_all($tid)
- •taxonomy_get_children($tid, $vid, $key)
- •taxonomy_get_tree($vid, $parent, $max_depth, $load_entities = FALSE)
- •Finding Nodes with Certain Terms
- •Additional Resources
- •Summary
- •Caching
- •Knowing When to Cache
- •How Caching Works
- •How Caching Is Used Within Drupal Core
- •Menu System
- •Caching Filtered Text
- •Administration Variables and Module Settings
- •Disabling Caching
- •Page Caching
- •Static Page Caching
- •Blocks
- •Using the Cache API
- •Caching Data with cache_set()
- •Retrieving Cached Data with cache_get() and cache_get_multiple()
- •Checking to See If Cache Is Empty with cache_is_empty()
- •Clearing Cache with cache_clear_all()
- •Summary
- •Sessions
- •What Are Sessions?
- •Usage
- •Session-Related Settings
- •In .htaccess
- •In settings.php
- •In bootstrap.inc
- •Requiring Cookies
- •Storage
- •Session Life Cycle
- •Session Conversations
- •First Visit
- •Second Visit
- •User with an Account
- •Common Tasks
- •Changing the Length of Time Before a Cookie Expires
- •Changing the Name of the Session
- •Storing Data in the Session
- •Summary
- •Using jQuery
- •What Is jQuery?
- •How jQuery Works
- •Using a CSS ID Selector
- •Using a CSS Class Selector
- •jQuery Within Drupal
- •Your First jQuery Code
- •Targeting an Element by ID
- •Method Chaining
- •Adding or Removing a Class
- •Wrapping Existing Elements
- •Changing Values of CSS Elements
- •Where to Put JavaScript
- •Adding JavaScript via a Theme .info File
- •A Module That Uses jQuery
- •Overridable JavaScript
- •Building a jQuery Voting Widget
- •Building the Module
- •Using Drupal.behaviors
- •Ways to Extend This Module
- •Compatibility
- •Next Steps
- •Summary
- •Localization and Translation
- •Enabling the Locale Module
- •User Interface Translation
- •Strings
- •Translating Strings with t()
- •Replacing Built-In Strings with Custom Strings
- •String Overrides in settings.php
- •Replacing Strings with the Locale Module
- •Exporting Your Translation
- •Starting a New Translation
- •Generating .pot Files with Translation Template Extractor
- •Creating a .pot File for Your Module
- •Using the Command Line
- •Using the Web-Based Extractor
- •Creating .pot Files for an Entire Site
- •Installing a Language Translation
- •Setting Up a Translation at Install Time
- •Installing a Translation on an Existing Site
- •Right-to-Left Language Support
- •Language Negotiation
- •Default
- •User-Preferred Language
- •The Global $language Object
- •Path Prefix Only
- •Path Prefix with Language Fallback
- •URL Only
- •Content Translation
- •Introducing the Content Translation Module
- •Multilingual Support
- •Multilingual Support with Translation
- •Localizationand Translation-Related Files
- •Additional Resources
- •Summary
- •What Is XML-RPC?
- •Prerequisites for XML-RPC
- •XML-RPC Clients
- •XML-RPC Client Example: Getting the Time
- •XML-RPC Client Example: Getting the Name of a State
- •Handling XML-RPC Client Errors
- •Network Errors
- •HTTP Errors
- •Call Syntax Errors
- •A Simple XML-RPC Server
- •Mapping Your Method with hook_xmlrpc()
- •Automatic Parameter Type Validation with hook_xmlrpc()
- •Built-In XML-RPC Methods
- •system.listMethods
- •system.methodSignature
- •system.methodHelp
- •system.getCapabilities
- •system.multiCall
- •Summary
- •Writing Secure Code
- •Handling User Input
- •Thinking About Data Types
- •Plain Text
- •HTML Text
- •Rich Text
- •Using check_plain() and t() to Sanitize Output
- •Using filter_xss() to Prevent Cross-Site Scripting Attacks
- •Using filter_xss_admin()
- •Handling URLs Securely
- •Making Queries Secure with db_query()
- •Keeping Private Data Private with hook_query_alter()
- •Dynamic Queries
- •Permissions and Page Callbacks
- •Cross-Site Request Forgeries (CSRF)
- •File Security
- •File Permissions
- •Protected Files
- •File Uploads
- •Filenames and Paths
- •Encoding Mail Headers
- •Files for Production Environments
- •SSL Support
- •Stand-Alone PHP
- •AJAX Security, a.k.a. Request Replay Attack
- •Form API Security
- •Protecting the Superuser Account
- •Summary
- •Development Best Practices
- •Coding Standards
- •Line Indention and Whitespace
- •Operators
- •Casting
- •Control Structures
- •Function Calls
- •Function Declarations
- •Function Names
- •Class Constructor Calls
- •Arrays
- •Quotes
- •String Concatenators
- •Comments
- •Documentation Examples
- •Documenting Constants
- •Documenting Functions
- •Documenting Hook Implementations
- •Including Code
- •PHP Code Tags
- •Semicolons
- •Example URLs
- •Naming Conventions
- •Checking Your Coding Style with Coder Module
- •Finding Your Way Around Code with grep
- •Summary
- •Optimizing Drupal
- •Caching Is the Key to Drupal Performance
- •Optimizing PHP
- •Setting PHP Opcode Cache File to /dev/zero
- •PHP Process Pool Settings
- •Tuning Apache
- •mod_expires
- •Moving Directives from .htaccess to httpd.conf
- •MPM Prefork vs. Apache MPM Worker
- •Balancing the Apache Pool Size
- •Decreasing Apache Timeout
- •Disabling Unused Apache Modules
- •Using Nginx Instead of Apache
- •Using Pressflow
- •Varnish
- •Normalizing incoming requests for better Varnish hits
- •Varnish: finding extraneous cookies
- •Boost
- •Boost vs. Varnish
- •Linux System Tuning for High Traffic Servers
- •Using Fast File Systems
- •Dedicated Servers vs. Virtual Servers
- •Avoiding Calling External Web Services
- •Decreasing Server Timeouts
- •Database Optimization
- •Enabling MySQL’s Query Cache
- •MySQL InnoDB Performance on Windows
- •Drupal Performance
- •Eliminating 404 Errors
- •Disabling Modules You’re Not Using
- •Drupal-Specific Optimizations
- •Page Caching
- •Bandwidth Optimization
- •Pruning the Sessions Table
- •Managing the Traffic of Authenticated Users
- •Logging to the Database
- •Logging to Syslog
- •Running cron
- •Architectures
- •Single Server
- •Separate Database Server
- •Separate Database Server and a Web Server Cluster
- •Load Balancing
- •File Uploads and Synchronization
- •Multiple Database Servers
- •Database Replication
- •Database Partitioning
- •Finding the Bottleneck
- •Web Server Running Out of CPU
- •Web Server Running Out of RAM
- •Identifying Expensive Database Queries
- •Identifying Expensive Pages
- •Identifying Expensive Code
- •Optimizing Tables
- •Caching Queries Manually
- •Changing the Table Type from MyISAM to InnoDB
- •Summary
- •Installation Profiles
- •Creating a New Installation Profile
- •The enhanced.info File
- •The enhanced.profile File
- •The enhanced.install File
- •Using hook_install_tasks and hook_install_tasks_alter
- •Summary
- •Testing
- •Setting Up the Test Environment
- •How Tests Are Defined
- •Test Functions
- •Test Assertions
- •Summary
- •Database Table Reference
- •Resources
- •Code
- •The Drupal Source Code Repository on GIT
- •Examples
- •Drupal API Reference
- •Security Advisories
- •Updating Modules
- •Updating Themes
- •Handbooks
- •Forums
- •Mailing Lists
- •Development
- •Themes
- •Translations
- •User Groups and Interest Groups
- •Internet Relay Chat
- •North America
- •Europe
- •Asia
- •Latin America / Caribbean
- •Oceania
- •Africa
- •Videocasts
- •Weblogs
- •Conferences
- •Contribute
- •Index
- •Numbers
CHAPTER 14 ■ WORKING WITH FILES
This generic solution probably isn’t robust enough for most people, so let’s see some specific examples in the following section.
Video and Audio
Numerous modules that help to manage media such as video files, Flash content, slideshows, and so on can be found at www.drupal.org/project/modules.
File API
The file API lives in includes/file.inc. We’ll cover some of the commonly used functions in this section. For more, the interested reader is directed to the API documentation to study the API in its current format: http://api.drupal.org/api/group/file/7.
Database Schema
Although Drupal stores files on disk, it still uses the database to store a fair amount of metadata about the files. In addition to authorship, MIME type, and location, it maintains revision information for uploaded files. The schema for the file_managed table is shown in Table 14-1.
Table 14-1. The file_managed Table
Field* |
Type |
Default |
Description |
|
|
|
|
fid |
serial |
|
Primary key |
uid |
int |
0 |
User ID of the user associated with the file |
filename |
varchar(255) |
'' |
Name of the file |
uri |
varchar(255) |
'' |
The URI to access the file (either local or remote) |
filemime |
varchar(255) |
'' |
The MIME type of the file |
filesize |
int |
0 |
Size of the file in bytes |
status |
int |
0 |
Flag indicating whether file is temporary (1) or |
|
|
|
permanent (0) |
timestamp |
int |
0 |
Unix timestamp indicating when file was added |
* Bold indicates a primary key; italics indicate an indexed field.
328
CHAPTER 14 ■ WORKING WITH FILES
The mechanism for associating uploaded files with the content that they are associated with is handled through a field_data_field_file_xxxxxx table, where xxxxx represents the unique name assigned to that form field when it was added to the content type. The schema for all of those tables is identical, as shown in Table 14-2.
Table 14-2. The Upload Table Used by the Upload Module
Field* |
Type |
Default |
Description |
|
|
|
|
etid |
int |
0 |
The entity type id this data is attached to |
bundle |
varchar |
|
The field instance bundle to which this row belongs |
deleted |
tinyint |
0 |
A Boolean indicating whether this data item has been |
|
|
|
deleted |
entity_id |
int |
|
The entity id this data is attached to (e.g., the node |
|
|
|
id) |
revision_id |
int |
NULL |
The entity revision id this data is attached to |
language |
varchar |
|
The language for this data item |
delta |
int |
|
The sequence number for this data item |
field_xxxxxx |
int |
NULL |
The file_managed.id being referenced in this field, |
_fid |
|
|
where xxxxx is replaced with the name of the field from |
|
|
|
the content type |
field_xxxxxx |
tinyint |
1 |
Flag to control whether this file should be displayed |
_display |
|
|
when viewing content |
Field_xxxxxx |
Text |
NULL |
A description of the file |
_description |
|
|
|
* Bold indicates a primary key; italics indicate an indexed field.
Common Tasks and Functions
If you want to do something with a file, chances are that the File API already has a convenient function for you to use. Let’s look at some of these.
Finding the Default Files URI
The file_default_scheme() function returns the default scheme (e.g., public or private) and can be used to define the URI where those files exist. For example, file_default_scheme().”:/” represents the default location where files are written to on file upload.
329
Download from Wow! eBook <www.wowebook.com>
CHAPTER 14 ■ WORKING WITH FILES
Saving Data to a File
Sometimes you just want to save data in a file. That’s what the following function does.
file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME)
The $data parameter will become the contents of the file. The $dest parameter is the URI of the destination. The $replace parameter determines Drupal’s behavior if a file of the same name already exists at the destination. Possible values are shown in Table 14-3.
Table 14-3. Constants That Determine Drupal’s Behavior When a File of the Same Name Exists at the Destination
Name |
Meaning |
FILE_EXISTS_REPLACE Replace the existing file with the current file.
FILE_EXISTS_RENAME Append an underscore and integer to make the new file name unique.
FILE_EXISTS_ERROR |
Abort and return FALSE. |
Here’s a quick example that puts a short string into a file in Drupal’s file system directory:
<?php
$filename = 'testfile.txt';
$dest = file_build_uri($filename);
file_save_data('My data', $dest, FILE_EXISTS_REPLACE);
The $dest variable must contain a valid stream wrapper URI. The foregoing example utilizes the file_build_uri function to create a valid stream wrapper URI that points to the destination directory, which in this case is the default public files directory.
Copying and Moving Files
The following functions help you work with files that are already on the file system. See also file_unmanaged_copy() and file_unmanaged_move().
file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME)
The file_copy() function copies files into Drupal’s file system path (typically sites/default/ files). The $source parameter is a file object, $destination is a string containing the destination of where the file should be copied to—as a valid stream wrapper URI—and $replace is the action that Drupal should take if the file already exists in the destination directory.
330
CHAPTER 14 ■ WORKING WITH FILES
file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME)
The file_move() function works just like the file_copy() function (in fact, it calls file_unmanaged_copy()), but also removes the original file by calling file_delete().
Checking Directories
The file_prepare_directory(&$directory, $options=FILE_MODIFY_PERMISSIONS) function checks to see whether a directory exists and is writeable, which is a good thing to do before you attempt to write to that directory. The following example checks to see if the sites/default/files directory exists and is writeable.
<?php
$directory = 'sites/default/files';
if (file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS)) { echo "The directory exists and is writeable";
} else {
echo "The file does not exist or it is not writeable";
}
Uploading Files
Although field API and its file field offer a full-fledged implementation of file uploading for nodes, sometimes you just want to be able to upload a file that is not associated with a node. The following functions can help in that situation.
file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME)
The $source parameter is a string that specifies the filepath or URI of the uploaded file to save. The $validators parameter is an optional associative array of callback functions used to validate the file. If you don’t specify a validator, then Drupal performs basic validation that the file extension is one of “jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp”. The $destination parameter is a string that contains the URI of where the source should be copied to, and the $replace parameter allows you to specify whether the uploaded file should replace an existing file, rename the file by appending an incrementing number to the end of the file name, or error out. Here is the validation function from the user module that uploads the user’s picture using the file_save_upload function. The function sets three validators: test whether the file is an image, test whether the image resolution is 85 X 85, and validate the size of the file. The image itself comes from the file upload field on the user form named “picture_upload” (see figure 14-4) with the resulting object showing in figure 14-5.
function user_validate_picture(&$form, &$form_state) { // If required, validate the uploaded picture. $validators = array(
'file_validate_is_image' => array(),
'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')),
331
CHAPTER 14 ■ WORKING WITH FILES
'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024),
);
// Save the file as a temporary file.
$file = file_save_upload('picture_upload', $validators); if ($file === FALSE) {
form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures'))));
}
elseif ($file !== NULL) { $form_state['values']['picture_upload'] = $file;
}
}
Figure 14-4. File field for user_picture form element as it appears on the “My account” page
Figure 14-5. Resulting file object after HTTP POST
332
CHAPTER 14 ■ WORKING WITH FILES
The $dest parameter in the file_save_upload() function is optional and may contain the directory to which the file will be copied. For example, when processing files attached to a node, the upload module uses file_directory_path() (which defaults to sites/default/files) as the value for $dest (see Figure 14-6). If $dest is not provided, the temporary directory will be used.
The $replace parameter defines what Drupal should do if a file with the same name already exists. Possible values are listed in Table 14-3.
Figure 14-6. The file object as it exists when passed to file_save_upload() validators
The return value for file_save_upload() is a fully populated file object (as shown in Figure 14-6), or 0 if something went wrong.
After calling file_save_upload(), a new file exists in Drupal’s temporary directory and a new record is written to the files table. The record contains the same values as the file object shown in Figure 14-6.
Notice that the status field is set to 0. That means that as far as Drupal is concerned, this is still a temporary file. It is the caller’s responsibility to make the file permanent. Continuing with our example of uploading a user picture, we see that the user module takes the approach of copying this file to the directory defined in Drupal’s user_picture_path variable and renaming it using the user’s ID:
// Process picture uploads.
if (!empty($edit['picture']->fid)) { $picture = $edit['picture'];
//If the picture is a temporary file move it to its final location and
//make it permanent.
if (($picture->status & FILE_STATUS_PERMANENT) == 0) { $info = image_get_info($picture->uri);
$picture_directory = variable_get('file_default_scheme', 'public') . '://' . variable_get('user_picture_path', 'pictures');
// Prepare the pictures directory. file_prepare_directory($picture_directory, FILE_CREATE_DIRECTORY);
$destination = file_stream_wrapper_uri_normalize($picture_directory . '/picture-'
. $account->uid . '-' . REQUEST_TIME . '.' . $info['extension']);
333
CHAPTER 14 ■ WORKING WITH FILES
if ($picture = file_move($picture, $destination, FILE_EXISTS_RENAME)) { $picture->status |= FILE_STATUS_PERMANENT;
$edit['picture'] = file_save($picture);
}
}
}.
This moves the uploaded image to sites/default/files/pictures/directory and makes the file permanent.
If the $dest parameter was provided and the file was moved to its final destination instead of the temporary directory, the caller can change the status of the record in the files table to permanent by calling file_save($file), with $file set to the full file object (as shown in Figure 14-7) and the status set to FILE_STATUS_PERMANENT. According to includes/file.inc, if you plan to use additional status constants in your own modules, you must start with 256, as 0, 1, 2, 4, 8, 16, 32, 64, and 128 are reserved for core.
Validation functions that may be used with file_save_upload() follow.
file_validate_extensions($file, $extensions)
The $file parameter is a file object. The $extensions parameter is a string of space-delimited file extensions. The function will return an empty array if the file extension is allowed, and an array of error messages like Only files with the following extensions are allowed: jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp if the file extension is disallowed. This function is a possible validator for file_save_upload().
file_validate_is_image($file)
This function takes a file object and attempts to pass $file->filepath to image_get_info(). The function will return an empty array if image_get_info() was able to extract information from the file, or an array containing the error message Only JPEG, PNG and GIF images are allowed if the process failed. This function is a possible validator for file_save_upload().
file_validate_image_resolution($file, $maximum_dimensions = 0, $minimum_ dimensions = 0)
This function takes a file object and uses $file->file path in several operations. If the file is an image, the function will check if the image exceeds $maximum_dimensions and attempt to resize it if possible. If everything goes well, an empty array will be returned and the $file object, which was passed by reference, will have $file->filesize set to the new size if the image was resized. Otherwise, the array will contain an error message, such as The image is too small; the minimum dimensions are 320x240 pixels. The $maximum_dimensions and $minimum_dimensions parameters are strings made up of width and height in pixels with a lowercase x separating them (e.g., 640x480 or 85x85). The default value of 0 indicates no restriction on size. This function is a possible validator for file_save_upload().
334
