Initial code using Drupal 6.38

This commit is contained in:
Manuel Cillero 2017-07-24 15:21:05 +02:00
commit 4824608a33
467 changed files with 90887 additions and 0 deletions

116
.htaccess Normal file
View file

@ -0,0 +1,116 @@
#
# Apache/PHP/Drupal settings:
#
# Protect files and directories from prying eyes.
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl|svn-base)$|^(code-style\.pl|Entries.*|Repository|Root|Tag|Template|all-wcprops|entries|format)$">
Order allow,deny
</FilesMatch>
# Don't show directory listings for URLs which map to a directory.
Options -Indexes
# Follow symbolic links in this directory.
Options +FollowSymLinks
# Make Drupal handle any 404 errors.
ErrorDocument 404 /index.php
# Force simple error message for requests for non-existent favicon.ico.
<Files favicon.ico>
# There is no end quote below, for compatibility with Apache 1.3.
ErrorDocument 404 "The requested file favicon.ico was not found.
</Files>
# Set the default handler.
DirectoryIndex index.php
# Override PHP settings. More in sites/default/settings.php
# but the following cannot be changed at runtime.
# PHP 4, Apache 1.
<IfModule mod_php4.c>
php_value magic_quotes_gpc 0
php_value register_globals 0
php_value session.auto_start 0
php_value mbstring.http_input pass
php_value mbstring.http_output pass
php_value mbstring.encoding_translation 0
</IfModule>
# PHP 4, Apache 2.
<IfModule sapi_apache2.c>
php_value magic_quotes_gpc 0
php_value register_globals 0
php_value session.auto_start 0
php_value mbstring.http_input pass
php_value mbstring.http_output pass
php_value mbstring.encoding_translation 0
</IfModule>
# PHP 5, Apache 1 and 2.
<IfModule mod_php5.c>
php_value magic_quotes_gpc 0
php_value register_globals 0
php_value session.auto_start 0
php_value mbstring.http_input pass
php_value mbstring.http_output pass
php_value mbstring.encoding_translation 0
</IfModule>
# Requires mod_expires to be enabled.
<IfModule mod_expires.c>
# Enable expirations.
ExpiresActive On
# Cache all files for 2 weeks after access (A).
ExpiresDefault A1209600
<FilesMatch \.php$>
# Do not allow PHP scripts to be cached unless they explicitly send cache
# headers themselves. Otherwise all scripts would have to overwrite the
# headers set by mod_expires if they want another caching behavior. This may
# fail if an error occurs early in the bootstrap process, and it may cause
# problems if a non-Drupal PHP file is installed in a subdirectory.
ExpiresActive Off
</FilesMatch>
</IfModule>
# Various rewrite rules.
<IfModule mod_rewrite.c>
RewriteEngine on
# If your site can be accessed both with and without the 'www.' prefix, you
# can use one of the following settings to redirect users to your preferred
# URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
#
# To redirect all users to access the site WITH the 'www.' prefix,
# (http://example.com/... will be redirected to http://www.example.com/...)
# adapt and uncomment the following:
# RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
# RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
#
# To redirect all users to access the site WITHOUT the 'www.' prefix,
# (http://www.example.com/... will be redirected to http://example.com/...)
# uncomment and adapt the following:
# RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
# RewriteRule ^(.*)$ http://example.com/$1 [L,R=301]
# Modify the RewriteBase if you are using Drupal in a subdirectory or in a
# VirtualDocumentRoot and the rewrite rules are not working properly.
# For example if your site is at http://example.com/drupal uncomment and
# modify the following line:
# RewriteBase /drupal
#
# If your site is running in a VirtualDocumentRoot at http://example.com/,
# uncomment the following line:
# RewriteBase /
# Rewrite URLs of the form 'x' to the form 'index.php?q=x'.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
</IfModule>
# $Id$

1138
CHANGELOG.txt Normal file

File diff suppressed because it is too large Load diff

29
COPYRIGHT.txt Normal file
View file

@ -0,0 +1,29 @@
All Drupal code is Copyright 2001 - 2012 by the original authors.
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.
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 as the file LICENSE.txt; if not, please see
http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
Drupal is a registered trademark of Dries Buytaert.
Drupal includes works under other copyright notices and distributed
according to the terms of the GNU General Public License or a compatible
license, including:
Javascript
Farbtastic - Copyright (c) 2007 Matt Farina
jQuery - Copyright (c) 2008 John Resig
jQuery Form - Copyright (c) 2007 Mike Alsup

39
INSTALL.mysql.txt Normal file
View file

@ -0,0 +1,39 @@
CREATE THE MySQL DATABASE
--------------------------
This step is only necessary if you don't already have a database set-up (e.g. by
your host). In the following examples, 'username' is an example MySQL user which
has the CREATE and GRANT privileges. Use the appropriate user name for your
system.
First, you must create a new database for your Drupal site (here, 'databasename'
is the name of the new database):
mysqladmin -u username -p create databasename
MySQL will prompt for the 'username' database password and then create the
initial database files. Next you must login and set the access database rights:
mysql -u username -p
Again, you will be asked for the 'username' database password. At the MySQL
prompt, enter following command:
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
CREATE TEMPORARY TABLES ON databasename.*
TO 'username'@'localhost' IDENTIFIED BY 'password';
where
'databasename' is the name of your database
'username@localhost' is the username of your MySQL account
'password' is the password required for that username
Note: Unless your database user has all of the privileges listed above, you will
not be able to run Drupal.
If successful, MySQL will reply with:
Query OK, 0 rows affected

27
INSTALL.pgsql.txt Normal file
View file

@ -0,0 +1,27 @@
CREATE THE PostgreSQL DATABASE
------------------------------
Note that the database must be created with UTF-8 (Unicode) encoding.
1. CREATE DATABASE USER
This step is only necessary if you don't already have a user set up (e.g.
by your host) or you want to create new user for use with Drupal only. The
following command creates a new user named "username" and asks for a
password for that user:
createuser --pwprompt --encrypted --no-adduser --no-createdb username
If everything works correctly, you'll see a "CREATE USER" notice.
2. CREATE THE DRUPAL DATABASE
This step is only necessary if you don't already have a database set up (e.g.
by your host) or you want to create new database for use with Drupal only.
The following command creates a new database named "databasename", which is
owned by previously created "username":
createdb --encoding=UNICODE --owner=username databasename
If everything works correctly, you'll see a "CREATE DATABASE" notice.

349
INSTALL.txt Normal file
View file

@ -0,0 +1,349 @@
CONTENTS OF THIS FILE
---------------------
* Requirements
* Optional requirements
* Installation
* Drupal administration
* Customizing your theme(s)
* Multisite Configuration
* More Information
REQUIREMENTS
------------
Drupal requires a web server, PHP 4 (4.3.5 or greater) or PHP 5
(http://www.php.net/) and either MySQL (http://www.mysql.com/) or PostgreSQL
(http://www.postgresql.org/). The Apache web server and MySQL database are
recommended; other web server and database combinations such as IIS and
PostgreSQL have been tested to a lesser extent. When using MySQL, version 4.1.1
or greater is recommended to assure you can safely transfer the database.
For more detailed information about Drupal requirements, see "Requirements"
(http://drupal.org/requirements) in the Drupal handbook.
For detailed information on how to configure a test server environment using
a variety of operating systems and web servers, see "Local server setup"
(http://drupal.org/node/157602) in the Drupal handbook.
OPTIONAL TASKS
--------------
- To use XML-based services such as the Blogger API and RSS syndication,
you will need PHP's XML extension. This extension is enabled by default.
- To use Drupal's "Clean URLs" feature on an Apache web server, you will need
the mod_rewrite module and the ability to use local .htaccess files. For
Clean URLs support on IIS, see "Using Clean URLs with IIS"
(http://drupal.org/node/3854) in the Drupal handbook.
- Various Drupal features require that the web server process (for
example, httpd) be able to initiate outbound connections. This is usually
possible, but some hosting providers or server configurations forbid such
connections. The features that depend on this functionality include the
integrated "Update status" module (which downloads information about
available updates of Drupal core and any installed contributed modules and
themes), the ability to log in via OpenID, fetching aggregator feeds, or
other network-dependent services.
INSTALLATION
------------
1. DOWNLOAD DRUPAL AND OPTIONALLY A TRANSLATION
You can obtain the latest Drupal release from http://drupal.org/. The files
are in .tar.gz format and can be extracted using most compression tools. On a
typical Unix command line, use:
wget http://drupal.org/files/projects/drupal-x.x.tar.gz
tar -zxvf drupal-x.x.tar.gz
This will create a new directory drupal-x.x/ containing all Drupal files
and directories. Move the contents of that directory into a directory within
your web server's document root or your public HTML directory:
mv drupal-x.x/* drupal-x.x/.htaccess /var/www/html
If you would like to have the default English interface translated to a
different language, we have good news. You can install and use Drupal in
other languages from the start. Check whether a released package of the
language desired is available for this Drupal version at
http://localize.drupal.org and download the package. Extract
the contents to the same directory where you extracted Drupal into.
2. CREATE THE CONFIGURATION FILE AND GRANT WRITE PERMISSIONS
Drupal comes with a default.settings.php file in the sites/default
directory. The installer uses this file as a template to create your
settings file using the details you provide through the install process.
To avoid problems when upgrading, Drupal is not packaged with an actual
settings file. You must create a file named settings.php. You may do so
by making a copy of default.settings.php (or create an empty file with
this name in the same directory). For example, (from the installation
directory) make a copy of the default.settings.php file with the command:
cp sites/default/default.settings.php sites/default/settings.php
Next, give the web server write privileges to the sites/default/settings.php
file with the command (from the installation directory):
chmod o+w sites/default/settings.php
So that the files directory can be created automatically, give the web server
write privileges to the sites/default directory with the command (from the
installation directory):
chmod o+w sites/default
3. CREATE THE DRUPAL DATABASE
Drupal requires access to a database in order to be installed. Your database
user will need sufficient privileges to run Drupal. Additional information
about privileges, and instructions to create a database using the command
line are available in INSTALL.mysql.txt (for MySQL) or INSTALL.pgsql.txt
(for PostgreSQL).
To create a database using PHPMyAdmin or a web-based control panel consult
the documentation or ask your webhost service provider.
Take note of the username, password, database name and hostname as you
create the database. You will enter these items in the install script.
4. RUN THE INSTALL SCRIPT
To run the install script point your browser to the base URL of your website
(e.g., http://www.example.com).
You will be guided through several screens to set up the database,
create tables, add the first user account and provide basic web
site settings.
The install script will attempt to create a files storage directory
in the default location at sites/default/files (the location of the
files directory may be changed after Drupal is installed). In some
cases, you may need to create the directory and modify its permissions
manually. Use the following commands (from the installation directory)
to create the files directory and grant the web server write privileges to it:
mkdir sites/default/files
chmod o+w sites/default/files
The install script will attempt to write-protect the settings.php file and
the sites/default directory after saving your configuration. However, you
may need to manually write-protect them using the commands (from the
installation directory):
chmod a-w sites/default/settings.php
chmod a-w sites/default
If you make manual changes to the file later, be sure to protect it again
after making your modifications. Failure to remove write permissions to that
file is a security risk. Although the default location for the settings.php
file is at sites/default/settings.php, it may be in another location
if you use the multi-site setup, as explained below.
5. CONFIGURE DRUPAL
When the install script succeeds, you will be directed to the "Welcome"
page, and you will be logged in as the administrator already. Proceed with
the initial configuration steps suggested on the "Welcome" page.
If the default Drupal theme is not displaying properly and links on the page
result in "Page Not Found" errors, try manually setting the $base_url variable
in the settings.php file if not already set. It's currently known that servers
running FastCGI can run into problems if the $base_url variable is left
commented out (see http://bugs.php.net/bug.php?id=19656).
6. REVIEW FILE SYSTEM STORAGE SETTINGS AND FILE PERMISSIONS
The files directory created in step 4 is the default file system path used
to store all uploaded files, as well as some temporary files created by Drupal.
After installation, the settings for the file system path may be modified
to store uploaded files in a different location.
It is not necessary to modify this path, but you may wish to change it if:
* your site runs multiple Drupal installations from a single codebase
(modify the file system path of each installation to a different
directory so that uploads do not overlap between installations); or,
* your site runs a number of web server front-ends behind a load
balancer or reverse proxy (modify the file system path on each
server to point to a shared file repository).
To modify the file system path:
* Ensure that the new location for the path exists or create it if
necessary. To create a new directory named uploads, for example,
use the following command from a shell or system prompt (while in
the installation directory):
mkdir uploads
* Ensure that the new location for the path is writable by the web
server process. To grant write permissions for a directory named
uploads, you may need to use the following command from a shell
or system prompt (while in the installation directory):
chmod o+w uploads
* Access the file system path settings in Drupal by selecting these
menu items from the Navigation menu:
Administer > Site configuration > File system
Enter the path to the new location (e.g.: uploads) at the File
System Path prompt.
Changing the file system path after files have been uploaded may cause
unexpected problems on an existing site. If you modify the file system path
on an existing site, remember to copy all files from the original location
to the new location.
Some administrators suggest making the documentation files, especially
CHANGELOG.txt, non-readable so that the exact version of Drupal you are
running is slightly more difficult to determine. If you wish to implement
this optional security measure, use the following command from a shell or
system prompt (while in the installation directory):
chmod a-r CHANGELOG.txt
Note that the example only affects CHANGELOG.txt. To completely hide
all documentation files from public view, repeat this command for each of
the Drupal documentation files in the installation directory, substituting the
name of each file for CHANGELOG.txt in the example.
For more information on setting file permissions, see "Modifying Linux, Unix,
and Mac file permissions" (http://drupal.org/node/202483) or "Modifying
Windows file permissions" (http://drupal.org/node/202491) in the online
handbook.
7. CRON MAINTENANCE TASKS
Many Drupal modules have periodic tasks that must be triggered by a cron
maintenance task, including search module (to build and update the index
used for keyword searching), aggregator module (to retrieve feeds from other
sites), ping module (to notify other sites about new or updated content), and
system module (to perform routine maintenance and pruning on system tables).
To activate these tasks, call the cron page by visiting
http://www.example.com/cron.php, which, in turn, executes tasks on behalf
of installed modules.
Most systems support the crontab utility for scheduling tasks like this. The
following example crontab line will activate the cron tasks automatically on
the hour:
0 * * * * wget -O - -q -t 1 http://www.example.com/cron.php
More information about cron maintenance tasks are available in the help pages
and in Drupal's online handbook at http://drupal.org/cron. Example scripts can
be found in the scripts/ directory.
DRUPAL ADMINISTRATION
---------------------
A new installation of Drupal defaults to a very basic configuration with only a
few active modules and minimal user access rights.
Use your administration panel to enable and configure services. For example:
General Settings Administer > Site configuration > Site information
Enable Modules Administer > Site building > Modules
Configure Themes Administer > Site building > Themes
Set User Permissions Administer > User management > Permissions
For more information on configuration options, read the instructions which
accompany the different configuration settings and consult the various help
pages available in the administration panel.
Community-contributed modules and themes are available at http://drupal.org/.
CUSTOMIZING YOUR THEME(S)
-------------------------
Now that your installation is running, you will want to customize the look of
your site. Several sample themes are included and more can be downloaded from
drupal.org.
Simple customization of your theme can be done using only CSS. Further changes
require understanding the phptemplate engine that is part of Drupal. See
http://drupal.org/handbook/customization to find out more.
MULTISITE CONFIGURATION
-----------------------
A single Drupal installation can host several Drupal-powered sites, each with
its own individual configuration.
Additional site configurations are created in subdirectories within the 'sites'
directory. Each subdirectory must have a 'settings.php' file which specifies the
configuration settings. The easiest way to create additional sites is to copy
the 'default' directory and modify the 'settings.php' file as appropriate. The
new directory name is constructed from the site's URL. The configuration for
www.example.com could be in 'sites/example.com/settings.php' (note that 'www.'
should be omitted if users can access your site at http://example.com/).
Sites do not have to have a different domain. You can also use subdomains and
subdirectories for Drupal sites. For example, example.com, sub.example.com,
and sub.example.com/site3 can all be defined as independent Drupal sites. The
setup for a configuration such as this would look like the following:
sites/default/settings.php
sites/example.com/settings.php
sites/sub.example.com/settings.php
sites/sub.example.com.site3/settings.php
When searching for a site configuration (for example www.sub.example.com/site3),
Drupal will search for configuration files in the following order, using the
first configuration it finds:
sites/www.sub.example.com.site3/settings.php
sites/sub.example.com.site3/settings.php
sites/example.com.site3/settings.php
sites/www.sub.example.com/settings.php
sites/sub.example.com/settings.php
sites/example.com/settings.php
sites/default/settings.php
If you are installing on a non-standard port, the port number is treated as the
deepest subdomain. For example: http://www.example.com:8080/ could be loaded
from sites/8080.www.example.com/. The port number will be removed according to
the pattern above if no port-specific configuration is found, just like a real
subdomain.
Each site configuration can have its own site-specific modules and themes in
addition to those installed in the standard 'modules' and 'themes' directories.
To use site-specific modules or themes, simply create a 'modules' or 'themes'
directory within the site configuration directory. For example, if
sub.example.com has a custom theme and a custom module that should not be
accessible to other sites, the setup would look like this:
sites/sub.example.com/:
settings.php
themes/custom_theme
modules/custom_module
NOTE: for more information about multiple virtual hosts or the configuration
settings, consult the Drupal handbook at drupal.org.
For more information on configuring Drupal's file system path in a multi-site
configuration, see step 6 above.
MORE INFORMATION
----------------
- For additional documentation, see the online Drupal handbook at
http://drupal.org/handbook.
- For a list of security announcements, see the "Security announcements" page
at http://drupal.org/security (available as an RSS feed). This page also
describes how to subscribe to these announcements via e-mail.
- For information about the Drupal security process, or to find out how to report
a potential security issue to the Drupal security team, see the "Security team"
page at http://drupal.org/security-team.
- For information about the wide range of available support options, see the
"Support" page at http://drupal.org/support.

339
LICENSE.txt Normal file
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.

85
MAINTAINERS.txt Normal file
View file

@ -0,0 +1,85 @@
List of maintainers
--------------------------------------------------------------------------------
LEGEND
======
- M: the maintainer
- S: status:
"supported" : someone is actually paid to look after this.
"maintained" : someone actually looks after it.
"fixes/patches" : it has a maintainer but they don't have time to
do much other than throw the odd patch in.
"orphan" : no current maintainer, but maybe you could take
the role as you write new code?
--------------------------------------------------------------------------------
BLOG API
M: James Walker <walkah@walkah.net>
S: maintained
DISTRIBUTED AUTHENTICATION MODULES
M: Moshe Weitzman <weitzman@tejasa.com>
S: maintained
DOCUMENTATION COORDINATOR
M: Steven Peck <speck@blkmtn.org>
S: maintained
FILTER SYSTEM
M: Steven Wittens <unconed@drupal.org>
S: maintained
FORM SYSTEM
M: Károly Négyesi <chx@mail.tvnet.hu>
S: maintained
LOCALE MODULE
M: Gabor Hojtsy <goba@php.net>
S: maintained
LOGGING
M: Khalid Baheyeldin <drupal@2bits.com>
S: maintained
MENU SYSTEM
M: Károly Négyesi <chx@mail.tvnet.hu>
S: maintained
PATH MODULE
M: Matt Westgate <drupal@asitis.org>
S: maintained
POSTGRESQL
M: Sammy Spets <sammys-drupal@synerger.com>
S: maintained
SECURITY COORDINATOR
M: Greg Knaddison <http://drupal.org/user/36762>
S: maintained
STATISTICS MODULE
M: Jeremy Andrews <jeremy@kerneltrap.com>
S: maintained
THEME SYSTEM
M: Earl Miles <merlin@logrus.com>
Joon Park <joon@dvessel.com>
S: maintained
UPDATE MODULE
M: Derek Wright <http://drupal.org/user/46549/contact>
S: maintained
XML-RPC SERVER/CLIENT
M: John VanDyk <http://drupal.org/user/2375/contact>
S: maintained
TRANSLATIONS COORDINATOR
M: Gerhard Killesreiter <gerhard@killesreiter.de>
S: maintained
THE REST:
M: Dries <dries@drupal.org>

107
UPGRADE.txt Normal file
View file

@ -0,0 +1,107 @@
UPGRADING
---------
Prior to upgrading, you should ensure that:
* Your system meets or exceeds Drupal's minimum requirements as shown at
http://drupal.org/requirements.
* You have a backup of all your relevant data (#1).
* Custom and contributed modules have been checked for compatibility (#11).
* Custom and contributed themes have been checked for compatibility (#11).
* You have read through this entire document.
Let's begin!
1. Back up your Drupal database and site root directory. Be especially sure
to back up your "sites" directory which contains your configuration file,
added modules and themes, and your site's uploaded files. If other files
have modifications, such as .htaccess or robots.txt, back those up as well.
Note: for a single site setup, the configuration file is the "settings.php"
file located at sites/default/settings.php. The default.settings.php file
contains a clean copy for restoration purposes, if required.
For multisite configurations, the configuration file is located in a
structure like the following:
sites/default/settings.php
sites/example.com/settings.php
sites/sub.example.com/settings.php
sites/sub.example.com.path/settings.php
More information on multisite configuration is located in INSTALL.txt.
2. If possible, log on as the user with user ID 1, which is the first account
created and the main administrator account. User ID 1 will be able to
automatically access update.php in step #10. There are special instructions
in step #10 if you are unable to log on as user ID 1. Do not close your
browser until the final step is complete.
3. Place the site in "Off-line" mode, to let the database updates run without
interruption and avoid displaying errors to end users of the site. This
option is at http://www.example.com/?q=admin/settings/site-maintenance
(replace www.example.com with your installation's domain name and path).
4. If using a custom or contributed theme, switch
to a core theme, such as Garland or Bluemarine.
5. Disable all custom and contributed modules.
6. Remove all old files and directories from the Drupal installation directory.
7. Unpack the new files and directories into the Drupal installation directory.
8. Copy your backed up "files" and "sites" directories to the Drupal
installation directory. If other system files such as .htaccess or
robots.txt were customized, re-create the modifications in the new
versions of the files using the backups taken in step #1.
9. Verify the new configuration file to make sure it has correct information.
10. Run update.php by visiting http://www.example.com/update.php (replace
www.example.com with your Drupal installation's domain name and path). This
step will update the core database tables to the new Drupal installation.
Note: if you are unable to access update.php do the following:
- Open your settings.php with a text editor.
- There is a line that says $update_free_access = FALSE;
Change it to $update_free_access = TRUE;
- Once update.php is done, you must change the settings.php file
back to its original form with $update_free_access = FALSE;
11. Ensure that the versions of all custom and contributed modules match the
new Drupal version to which you have updated. For a major update, such as
from 5.x to 6.x, modules from previous versions will not be compatible
and updated versions will be required.
- For contributed modules, check http://drupal.org/project/modules
for the version of a module matching your version of Drupal.
- For custom modules, review http://drupal.org/update/modules to
ensure that a custom module is compatible with the current version.
12. Re-enable custom and contributed modules and re-run update.php
to update custom and contributed database tables.
13. Return the site to its original theme (if you switched to a core
theme like Garland or Bluemarine in step #4). If your site uses a
custom or contributed theme, make sure it is compatible with your
version of Drupal.
- For contributed themes, check http://drupal.org/project/themes
for the version of a theme matching your version of Drupal.
- For custom themes, review http://drupal.org/update/theme to ensure
that a custom theme is compatible with the current version.
14. Finally, return your site to "Online" mode so your visitors may resume
browsing. As in step #3, this option is available in your administration
screens at http://www.example.com/?q=admin/settings/site-maintenance
(replace www.example.com with your installation's domain name and path).
For more information on upgrading visit
the Drupal handbook at http://drupal.org/upgrade

10
cron.php Normal file
View file

@ -0,0 +1,10 @@
<?php
/**
* @file
* Handles incoming requests to fire off regularly-scheduled tasks (cron jobs).
*/
include_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
drupal_cron_run();

404
includes/actions.inc Normal file
View file

@ -0,0 +1,404 @@
<?php
/**
* @file
* This is the actions engine for executing stored actions.
*/
/**
* @defgroup actions Actions
* @{
* Functions that perform an action on a certain system object.
*
* All modules should declare their action functions to be in this group and
* each action function should reference its configuration form, validate, and
* submit functions using \@see. Conversely, form, validate, and submit
* functions should reference the action function using \@see. For examples of
* this see comment_unpublish_by_keyword_action(), which has the following in
* its doxygen documentation:
*
* \@ingroup actions
* \@see comment_unpublish_by_keyword_action_form().
* \@see comment_unpublish_by_keyword_action_submit().
*
* @} End of "defgroup actions".
*/
/**
* Perform a given list of actions by executing their callback functions.
*
* Given the IDs of actions to perform, find out what the callbacks
* for the actions are by querying the database. Then call each callback
* using the function call $function($object, $context, $a1, $a2)
* where $function is the name of a function written in compliance with
* the action specification; that is, foo($object, $context).
*
* @param $action_ids
* The ID of the action to perform. Can be a single action ID or an array
* of IDs. IDs of instances will be numeric; IDs of singletons will be
* function names.
* @param $object
* Parameter that will be passed along to the callback. Typically the
* object that the action will act on; a node, user or comment object.
* If the action does not act on an object, pass a dummy object. This
* is necessary to support PHP 4 object referencing.
* @param $context
* Parameter that will be passed along to the callback. $context is a
* keyed array containing extra information about what is currently
* happening at the time of the call. Typically $context['hook'] and
* $context['op'] will tell which hook-op combination resulted in this
* call to actions_do().
* @param $a1
* Parameter that will be passed along to the callback.
* @param $a2
* Parameter that will be passed along to the callback.
*
* @return
* An associative array containing the result of the function that
* performs the action, keyed on action ID.
*/
function actions_do($action_ids, &$object, $context = NULL, $a1 = NULL, $a2 = NULL) {
// $stack tracks the number of recursive calls.
static $stack;
$stack++;
if ($stack > variable_get('actions_max_stack', 35)) {
watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR);
return;
}
$actions = array();
$available_actions = actions_list();
$result = array();
if (is_array($action_ids)) {
$where = array();
$where_values = array();
foreach ($action_ids as $action_id) {
if (is_numeric($action_id)) {
$where[] = "OR aid = '%s'";
$where_values[] = $action_id;
}
elseif (isset($available_actions[$action_id])) {
$actions[$action_id] = $available_actions[$action_id];
}
}
// When we have action instances we must go to the database to
// retrieve instance data.
if ($where) {
$where_clause = implode(' ', $where);
// Strip off leading 'OR '.
$where_clause = '('. strstr($where_clause, " ") .')';
$result_db = db_query('SELECT * FROM {actions} WHERE '. $where_clause, $where_values);
while ($action = db_fetch_object($result_db)) {
$actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array();
$actions[$action->aid]['callback'] = $action->callback;
$actions[$action->aid]['type'] = $action->type;
}
}
// Fire actions, in no particular order.
foreach ($actions as $action_id => $params) {
if (is_numeric($action_id)) { // Configurable actions need parameters.
$function = $params['callback'];
if (function_exists($function)) {
$context = array_merge($context, $params);
$actions_result[$action_id] = $function($object, $context, $a1, $a2);
}
else {
$actions_result[$action_id] = FALSE;
}
}
// Singleton action; $action_id is the function name.
else {
$result[$action_id] = $action_id($object, $context, $a1, $a2);
}
}
}
// Optimized execution of single action.
else {
// If it's a configurable action, retrieve stored parameters.
if (is_numeric($action_ids)) {
$action = db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = '%s'", $action_ids));
$function = $action->callback;
if (function_exists($function)) {
$context = array_merge($context, unserialize($action->parameters));
$actions_result[$action_ids] = $function($object, $context, $a1, $a2);
}
else {
$actions_result[$action_ids] = FALSE;
}
}
// Singleton action; $action_ids is the function name.
else {
$result[$action_ids] = $action_ids($object, $context, $a1, $a2);
}
}
$stack--;
return $result;
}
/**
* Discover all action functions by invoking hook_action_info().
*
* @code
* mymodule_action_info() {
* return array(
* 'mymodule_functiondescription_action' => array(
* 'type' => 'node',
* 'description' => t('Save node'),
* 'configurable' => FALSE,
* 'hooks' => array(
* 'nodeapi' => array('delete', 'insert', 'update', 'view'),
* 'comment' => array('delete', 'insert', 'update', 'view'),
* )
* )
* );
* }
* @endcode
*
* The description is used in presenting possible actions to the user for
* configuration. The type is used to present these actions in a logical
* grouping and to denote context. Some types are 'node', 'user', 'comment',
* and 'system'. If an action is configurable it will provide form,
* validation and submission functions. The hooks the action supports
* are declared in the 'hooks' array.
*
* @param $reset
* Reset the action info static cache.
*
* @return
* An associative array keyed on function name. The value of each key is
* an array containing information about the action, such as type of
* action and description of the action, e.g.,
*
* @code
* $actions['node_publish_action'] = array(
* 'type' => 'node',
* 'description' => t('Publish post'),
* 'configurable' => FALSE,
* 'hooks' => array(
* 'nodeapi' => array('presave', 'insert', 'update', 'view'),
* 'comment' => array('delete', 'insert', 'update', 'view'),
* ),
* );
* @endcode
*/
function actions_list($reset = FALSE) {
static $actions;
if (!isset($actions) || $reset) {
$actions = module_invoke_all('action_info');
drupal_alter('action_info', $actions);
}
// See module_implements for explanations of this cast.
return (array)$actions;
}
/**
* Retrieves all action instances from the database.
*
* Compare with actions_list(), which gathers actions by invoking
* hook_action_info(). The actions returned by this function and the actions
* returned by actions_list() are partially synchronized. Non-configurable
* actions from hook_action_info() implementations are put into the database
* when actions_synchronize() is called, which happens when
* admin/settings/actions is visited. Configurable actions are not added to
* the database until they are configured in the user interface, in which case
* a database row is created for each configuration of each action.
*
* @return
* Associative array keyed by action ID. Each value is an
* associative array with keys 'callback', 'description', 'type' and
* 'configurable'.
*/
function actions_get_all_actions() {
$actions = array();
$result = db_query("SELECT * FROM {actions}");
while ($action = db_fetch_object($result)) {
$actions[$action->aid] = array(
'callback' => $action->callback,
'description' => $action->description,
'type' => $action->type,
'configurable' => (bool) $action->parameters,
);
}
return $actions;
}
/**
* Create an associative array keyed by md5 hashes of function names.
*
* Hashes are used to prevent actual function names from going out into
* HTML forms and coming back.
*
* @param $actions
* An associative array with function names as keys and associative
* arrays with keys 'description', 'type', etc. as values. Generally
* the output of actions_list() or actions_get_all_actions() is given
* as input to this function.
*
* @return
* An associative array keyed on md5 hash of function name. The value of
* each key is an associative array of function, description, and type
* for the action.
*/
function actions_actions_map($actions) {
$actions_map = array();
foreach ($actions as $callback => $array) {
$key = md5($callback);
$actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
$actions_map[$key]['description'] = $array['description'];
$actions_map[$key]['type'] = $array['type'];
$actions_map[$key]['configurable'] = $array['configurable'];
}
return $actions_map;
}
/**
* Given an md5 hash of a function name, return the function name.
*
* Faster than actions_actions_map() when you only need the function name.
*
* @param $hash
* MD5 hash of a function name
*
* @return
* Function name
*/
function actions_function_lookup($hash) {
$actions_list = actions_list();
foreach ($actions_list as $function => $array) {
if (md5($function) == $hash) {
return $function;
}
}
// Must be an instance; must check database.
$aid = db_result(db_query("SELECT aid FROM {actions} WHERE MD5(aid) = '%s' AND parameters <> ''", $hash));
return $aid;
}
/**
* Synchronize actions that are provided by modules.
*
* They are synchronized with actions that are stored in the actions table.
* This is necessary so that actions that do not require configuration can
* receive action IDs. This is not necessarily the best approach,
* but it is the most straightforward.
*/
function actions_synchronize($actions_in_code = array(), $delete_orphans = FALSE) {
if (!$actions_in_code) {
$actions_in_code = actions_list(TRUE);
}
$actions_in_db = array();
$result = db_query("SELECT * FROM {actions} WHERE parameters = ''");
while ($action = db_fetch_object($result)) {
$actions_in_db[$action->callback] = array('aid' => $action->aid, 'description' => $action->description);
}
// Go through all the actions provided by modules.
foreach ($actions_in_code as $callback => $array) {
// Ignore configurable actions since their instances get put in
// when the user adds the action.
if (!$array['configurable']) {
// If we already have an action ID for this action, no need to assign aid.
if (array_key_exists($callback, $actions_in_db)) {
unset($actions_in_db[$callback]);
}
else {
// This is a new singleton that we don't have an aid for; assign one.
db_query("INSERT INTO {actions} (aid, type, callback, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $callback, $array['type'], $callback, '', $array['description']);
watchdog('actions', "Action '%action' added.", array('%action' => $array['description']));
}
}
}
// Any actions that we have left in $actions_in_db are orphaned.
if ($actions_in_db) {
$orphaned = array();
$placeholder = array();
foreach ($actions_in_db as $callback => $array) {
$orphaned[] = $callback;
$placeholder[] = "'%s'";
}
$orphans = implode(', ', $orphaned);
if ($delete_orphans) {
$placeholders = implode(', ', $placeholder);
$results = db_query("SELECT a.aid, a.description FROM {actions} a WHERE callback IN ($placeholders)", $orphaned);
while ($action = db_fetch_object($results)) {
actions_delete($action->aid);
watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => $action->description));
}
}
else {
$link = l(t('Remove orphaned actions'), 'admin/settings/actions/orphan');
$count = count($actions_in_db);
watchdog('actions', format_plural($count, 'One orphaned action (%orphans) exists in the actions table. !link', '@count orphaned actions (%orphans) exist in the actions table. !link'), array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_INFO);
}
}
}
/**
* Save an action and its associated user-supplied parameter values to the database.
*
* @param $function
* The name of the function to be called when this action is performed.
* @param $type
* The type of action, to describe grouping and/or context, e.g., 'node',
* 'user', 'comment', or 'system'.
* @param $params
* An associative array with parameter names as keys and parameter values
* as values.
* @param $desc
* A user-supplied description of this particular action, e.g., 'Send
* e-mail to Jim'.
* @param $aid
* The ID of this action. If omitted, a new action is created.
*
* @return
* The ID of the action.
*/
function actions_save($function, $type, $params, $desc, $aid = NULL) {
$serialized = serialize($params);
if ($aid) {
db_query("UPDATE {actions} SET callback = '%s', type = '%s', parameters = '%s', description = '%s' WHERE aid = '%s'", $function, $type, $serialized, $desc, $aid);
watchdog('actions', 'Action %action saved.', array('%action' => $desc));
}
else {
// aid is the callback for singleton actions so we need to keep a
// separate table for numeric aids.
db_query('INSERT INTO {actions_aid} VALUES (default)');
$aid = db_last_insert_id('actions_aid', 'aid');
db_query("INSERT INTO {actions} (aid, callback, type, parameters, description) VALUES ('%s', '%s', '%s', '%s', '%s')", $aid, $function, $type, $serialized, $desc);
watchdog('actions', 'Action %action created.', array('%action' => $desc));
}
return $aid;
}
/**
* Retrieve a single action from the database.
*
* @param $aid
* integer The ID of the action to retrieve.
*
* @return
* The appropriate action row from the database as an object.
*/
function actions_load($aid) {
return db_fetch_object(db_query("SELECT * FROM {actions} WHERE aid = '%s'", $aid));
}
/**
* Delete a single action from the database.
*
* @param $aid
* integer The ID of the action to delete.
*/
function actions_delete($aid) {
db_query("DELETE FROM {actions} WHERE aid = '%s'", $aid);
module_invoke_all('actions_delete', $aid);
}

354
includes/batch.inc Normal file
View file

@ -0,0 +1,354 @@
<?php
/**
* @file Batch processing API for processes to run in multiple HTTP requests.
*/
/**
* State-based dispatcher for the batch processing page.
*/
function _batch_page() {
$batch =& batch_get();
// Retrieve the current state of batch from db.
if (isset($_REQUEST['id']) && $data = db_result(db_query("SELECT batch FROM {batch} WHERE bid = %d AND token = '%s'", $_REQUEST['id'], drupal_get_token($_REQUEST['id'])))) {
$batch = unserialize($data);
}
else {
return FALSE;
}
// Register database update for end of processing.
register_shutdown_function('_batch_shutdown');
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
$output = NULL;
switch ($op) {
case 'start':
$output = _batch_start();
break;
case 'do':
// JS-version AJAX callback.
_batch_do();
break;
case 'do_nojs':
// Non-JS progress page.
$output = _batch_progress_page_nojs();
break;
case 'finished':
$output = _batch_finished();
break;
}
return $output;
}
/**
* Initiate the batch processing
*/
function _batch_start() {
// Choose between the JS and non-JS version.
// JS-enabled users are identified through the 'has_js' cookie set in drupal.js.
// If the user did not visit any JS enabled page during his browser session,
// he gets the non-JS version...
if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
return _batch_progress_page_js();
}
else {
return _batch_progress_page_nojs();
}
}
/**
* Batch processing page with JavaScript support.
*/
function _batch_progress_page_js() {
$batch = batch_get();
// The first batch set gets to set the page title
// and the initialization and error messages.
$current_set = _batch_current_set();
drupal_set_title($current_set['title']);
drupal_add_js('misc/progress.js', 'core', 'header', FALSE, FALSE);
$url = url($batch['url'], array('query' => array('id' => $batch['id'])));
$js_setting = array(
'batch' => array(
'errorMessage' => $current_set['error_message'] .'<br/>'. $batch['error_message'],
'initMessage' => $current_set['init_message'],
'uri' => $url,
),
);
drupal_add_js($js_setting, 'setting');
drupal_add_js('misc/batch.js', 'core', 'header', FALSE, FALSE);
$output = '<div id="progress"></div>';
return $output;
}
/**
* Do one pass of execution and inform back the browser about progression
* (used for JavaScript-mode only).
*/
function _batch_do() {
// HTTP POST required
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
drupal_set_message(t('HTTP POST is required.'), 'error');
drupal_set_title(t('Error'));
return '';
}
// Perform actual processing.
list($percentage, $message) = _batch_process();
drupal_json(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
}
/**
* Batch processing page without JavaScript support.
*/
function _batch_progress_page_nojs() {
$batch =& batch_get();
$current_set = _batch_current_set();
drupal_set_title($current_set['title']);
$new_op = 'do_nojs';
if (!isset($batch['running'])) {
// This is the first page so we return some output immediately.
$percentage = 0;
$message = $current_set['init_message'];
$batch['running'] = TRUE;
}
else {
// This is one of the later requests: do some processing first.
// Error handling: if PHP dies due to a fatal error (e.g. non-existant
// function), it will output whatever is in the output buffer,
// followed by the error message.
ob_start();
$fallback = $current_set['error_message'] .'<br/>'. $batch['error_message'];
drupal_maintenance_theme();
$fallback = theme('maintenance_page', $fallback, FALSE, FALSE);
// We strip the end of the page using a marker in the template, so any
// additional HTML output by PHP shows up inside the page rather than
// below it. While this causes invalid HTML, the same would be true if
// we didn't, as content is not allowed to appear after </html> anyway.
list($fallback) = explode('<!--partial-->', $fallback);
print $fallback;
// Perform actual processing.
list($percentage, $message) = _batch_process($batch);
if ($percentage == 100) {
$new_op = 'finished';
}
// PHP did not die : remove the fallback output.
ob_end_clean();
}
$url = url($batch['url'], array('query' => array('id' => $batch['id'], 'op' => $new_op)));
drupal_set_html_head('<meta http-equiv="Refresh" content="0; URL='. $url .'">');
$output = theme('progress_bar', $percentage, $message);
return $output;
}
/**
* Advance batch processing for 1 second (or process the whole batch if it
* was not set for progressive execution - e.g forms submitted by drupal_execute).
*/
function _batch_process() {
$batch =& batch_get();
$current_set =& _batch_current_set();
$set_changed = TRUE;
if ($batch['progressive']) {
timer_start('batch_processing');
}
while (!$current_set['success']) {
// If this is the first time we iterate this batch set in the current
// request, we check if it requires an additional file for functions
// definitions.
if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) {
include_once($current_set['file']);
}
$finished = 1;
$task_message = '';
if ((list($function, $args) = reset($current_set['operations'])) && function_exists($function)) {
// Build the 'context' array, execute the function call,
// and retrieve the user message.
$batch_context = array('sandbox' => &$current_set['sandbox'], 'results' => &$current_set['results'], 'finished' => &$finished, 'message' => &$task_message);
// Process the current operation.
call_user_func_array($function, array_merge($args, array(&$batch_context)));
}
if ($finished >= 1) {
// Make sure this step isn't counted double when computing $current.
$finished = 0;
// Remove the operation and clear the sandbox.
array_shift($current_set['operations']);
$current_set['sandbox'] = array();
}
// If the batch set is completed, browse through the remaining sets,
// executing 'control sets' (stored form submit handlers) along the way -
// this might in turn insert new batch sets.
// Stop when we find a set that actually has operations.
$set_changed = FALSE;
$old_set = $current_set;
while (empty($current_set['operations']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
$current_set =& _batch_current_set();
$set_changed = TRUE;
}
// At this point, either $current_set is a 'real' batch set (has operations),
// or all sets have been completed.
// If we're in progressive mode, stop after 1 second.
if ($batch['progressive'] && timer_read('batch_processing') > 1000) {
break;
}
}
if ($batch['progressive']) {
// Gather progress information.
// Reporting 100% progress will cause the whole batch to be considered
// processed. If processing was paused right after moving to a new set,
// we have to use the info from the new (unprocessed) one.
if ($set_changed && isset($current_set['operations'])) {
// Processing will continue with a fresh batch set.
$remaining = count($current_set['operations']);
$total = $current_set['total'];
$progress_message = $current_set['init_message'];
$task_message = '';
}
else {
$remaining = count($old_set['operations']);
$total = $old_set['total'];
$progress_message = $old_set['progress_message'];
}
$current = $total - $remaining + $finished;
$percentage = $total ? floor($current / $total * 100) : 100;
$values = array(
'@remaining' => $remaining,
'@total' => $total,
'@current' => floor($current),
'@percentage' => $percentage,
);
$message = strtr($progress_message, $values) .'<br/>';
$message .= $task_message ? $task_message : '&nbsp;';
return array($percentage, $message);
}
else {
// If we're not in progressive mode, the whole batch has been processed by now.
return _batch_finished();
}
}
/**
* Retrieve the batch set being currently processed.
*/
function &_batch_current_set() {
$batch =& batch_get();
return $batch['sets'][$batch['current_set']];
}
/**
* Move execution to the next batch set if any, executing the stored
* form _submit handlers along the way (thus possibly inserting
* additional batch sets).
*/
function _batch_next_set() {
$batch =& batch_get();
if (isset($batch['sets'][$batch['current_set'] + 1])) {
$batch['current_set']++;
$current_set =& _batch_current_set();
if (isset($current_set['form_submit']) && ($function = $current_set['form_submit']) && function_exists($function)) {
// We use our stored copies of $form and $form_state, to account for
// possible alteration by the submit handlers.
$function($batch['form'], $batch['form_state']);
}
return TRUE;
}
}
/**
* End the batch processing:
* Call the 'finished' callbacks to allow custom handling of results,
* and resolve page redirection.
*/
function _batch_finished() {
$batch =& batch_get();
// Execute the 'finished' callbacks for each batch set.
foreach ($batch['sets'] as $key => $batch_set) {
if (isset($batch_set['finished'])) {
// Check if the set requires an additional file for functions definitions.
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once($batch_set['file']);
}
if (function_exists($batch_set['finished'])) {
$batch_set['finished']($batch_set['success'], $batch_set['results'], $batch_set['operations']);
}
}
}
// Cleanup the batch table and unset the global $batch variable.
if ($batch['progressive']) {
db_query("DELETE FROM {batch} WHERE bid = %d", $batch['id']);
}
$_batch = $batch;
$batch = NULL;
// Redirect if needed.
if ($_batch['progressive']) {
// Put back the 'destination' that was saved in batch_process().
if (isset($_batch['destination'])) {
$_REQUEST['destination'] = $_batch['destination'];
}
// Use $_batch['form_state']['redirect'], or $_batch['redirect'],
// or $_batch['source_page'].
if (isset($_batch['form_state']['redirect'])) {
$redirect = $_batch['form_state']['redirect'];
}
elseif (isset($_batch['redirect'])) {
$redirect = $_batch['redirect'];
}
else {
$redirect = $_batch['source_page'];
}
// Let drupal_redirect_form handle redirection logic.
$form = isset($batch['form']) ? $batch['form'] : array();
if (empty($_batch['form_state']['rebuild']) && empty($_batch['form_state']['storage'])) {
drupal_redirect_form($form, $redirect);
}
// We get here if $form['#redirect'] was FALSE, or if the form is a
// multi-step form. We save the final $form_state value to be retrieved
// by drupal_get_form, and we redirect to the originating page.
$_SESSION['batch_form_state'] = $_batch['form_state'];
drupal_goto($_batch['source_page']);
}
}
/**
* Shutdown function: store the batch data for next request,
* or clear the table if the batch is finished.
*/
function _batch_shutdown() {
if ($batch = batch_get()) {
db_query("UPDATE {batch} SET batch = '%s' WHERE bid = %d", serialize($batch), $batch['id']);
}
}

1605
includes/bootstrap.inc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
<?php
/**
* A stub cache implementation to be used during the installation
* process when database access is not yet available. Because Drupal's
* caching system never requires that cached data be present, these
* stub functions can short-circuit the process and sidestep the
* need for any persistent storage. Obviously, using this cache
* implementation during normal operations would have a negative impact
* on performance.
*/
function cache_get($key, $table = 'cache') {
return FALSE;
}
function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
return;
}
function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
return;
}

186
includes/cache.inc Normal file
View file

@ -0,0 +1,186 @@
<?php
/**
* Return data from the persistent cache. Data may be stored as either plain text or as serialized data.
* cache_get will automatically return unserialized objects and arrays.
*
* @param $cid
* The cache ID of the data to retrieve.
* @param $table
* The table $table to store the data in. Valid core values are 'cache_filter',
* 'cache_menu', 'cache_page', or 'cache' for the default cache.
*
* @see cache_set()
*/
function cache_get($cid, $table = 'cache') {
global $user;
// Garbage collection necessary when enforcing a minimum cache lifetime
$cache_flush = variable_get('cache_flush_'. $table, 0);
if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= time())) {
// Reset the variable immediately to prevent a meltdown in heavy load situations.
variable_set('cache_flush_'. $table, 0);
// Time to flush old cache data
db_query("DELETE FROM {". $table ."} WHERE expire != %d AND expire <= %d", CACHE_PERMANENT, $cache_flush);
}
$cache = db_fetch_object(db_query("SELECT data, created, headers, expire, serialized FROM {". $table ."} WHERE cid = '%s'", $cid));
if (isset($cache->data)) {
// If the data is permanent or we're not enforcing a minimum cache lifetime
// always return the cached data.
if ($cache->expire == CACHE_PERMANENT || !variable_get('cache_lifetime', 0)) {
$cache->data = db_decode_blob($cache->data);
if ($cache->serialized) {
$cache->data = unserialize($cache->data);
}
}
// If enforcing a minimum cache lifetime, validate that the data is
// currently valid for this user before we return it by making sure the
// cache entry was created before the timestamp in the current session's
// cache timer. The cache variable is loaded into the $user object by
// sess_read() in session.inc.
else {
if (isset($user->cache) && $user->cache > $cache->created) {
// This cache data is too old and thus not valid for us, ignore it.
return 0;
}
else {
$cache->data = db_decode_blob($cache->data);
if ($cache->serialized) {
$cache->data = unserialize($cache->data);
}
}
}
return $cache;
}
return 0;
}
/**
* Store data in the persistent cache.
*
* The persistent cache is split up into four database
* tables. Contributed modules can add additional tables.
*
* 'cache_page': This table stores generated pages for anonymous
* users. This is the only table affected by the page cache setting on
* the administrator panel.
*
* 'cache_menu': Stores the cachable part of the users' menus.
*
* 'cache_filter': Stores filtered pieces of content. This table is
* periodically cleared of stale entries by cron.
*
* 'cache': Generic cache storage table.
*
* The reasons for having several tables are as follows:
*
* - smaller tables allow for faster selects and inserts
* - we try to put fast changing cache items and rather static
* ones into different tables. The effect is that only the fast
* changing tables will need a lot of writes to disk. The more
* static tables will also be better cachable with MySQL's query cache
*
* @param $cid
* The cache ID of the data to store.
* @param $data
* The data to store in the cache. Complex data types will be automatically serialized before insertion.
* Strings will be stored as plain text and not serialized.
* @param $table
* The table $table to store the data in. Valid core values are 'cache_filter',
* 'cache_menu', 'cache_page', or 'cache'.
* @param $expire
* One of the following values:
* - CACHE_PERMANENT: Indicates that the item should never be removed unless
* explicitly told to using cache_clear_all() with a cache ID.
* - CACHE_TEMPORARY: Indicates that the item should be removed at the next
* general cache wipe.
* - A Unix timestamp: Indicates that the item should be kept at least until
* the given time, after which it behaves like CACHE_TEMPORARY.
* @param $headers
* A string containing HTTP header information for cached pages.
*
* @see cache_get()
*/
function cache_set($cid, $data, $table = 'cache', $expire = CACHE_PERMANENT, $headers = NULL) {
$serialized = 0;
if (is_object($data) || is_array($data)) {
$data = serialize($data);
$serialized = 1;
}
$created = time();
db_query("UPDATE {". $table ."} SET data = %b, created = %d, expire = %d, headers = '%s', serialized = %d WHERE cid = '%s'", $data, $created, $expire, $headers, $serialized, $cid);
if (!db_affected_rows()) {
@db_query("INSERT INTO {". $table ."} (cid, data, created, expire, headers, serialized) VALUES ('%s', %b, %d, %d, '%s', %d)", $cid, $data, $created, $expire, $headers, $serialized);
}
}
/**
*
* Expire data from the cache. If called without arguments, expirable
* entries will be cleared from the cache_page and cache_block tables.
*
* @param $cid
* If set, the cache ID to delete. Otherwise, all cache entries that can
* expire are deleted.
*
* @param $table
* If set, the table $table to delete from. Mandatory
* argument if $cid is set.
*
* @param $wildcard
* If $wildcard is TRUE, cache IDs starting with $cid are deleted in
* addition to the exact cache ID specified by $cid. If $wildcard is
* TRUE and $cid is '*' then the entire table $table is emptied.
*/
function cache_clear_all($cid = NULL, $table = NULL, $wildcard = FALSE) {
global $user;
if (!isset($cid) && !isset($table)) {
// Clear the block cache first, so stale data will
// not end up in the page cache.
cache_clear_all(NULL, 'cache_block');
cache_clear_all(NULL, 'cache_page');
return;
}
if (empty($cid)) {
if (variable_get('cache_lifetime', 0)) {
// We store the time in the current user's $user->cache variable which
// will be saved into the sessions table by sess_write(). We then
// simulate that the cache was flushed for this user by not returning
// cached data that was cached before the timestamp.
$user->cache = time();
$cache_flush = variable_get('cache_flush_'. $table, 0);
if ($cache_flush == 0) {
// This is the first request to clear the cache, start a timer.
variable_set('cache_flush_'. $table, time());
}
else if (time() > ($cache_flush + variable_get('cache_lifetime', 0))) {
// Clear the cache for everyone, cache_lifetime seconds have
// passed since the first request to clear the cache.
db_query("DELETE FROM {". $table ."} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time());
variable_set('cache_flush_'. $table, 0);
}
}
else {
// No minimum cache lifetime, flush all temporary cache entries now.
db_query("DELETE FROM {". $table ."} WHERE expire != %d AND expire < %d", CACHE_PERMANENT, time());
}
}
else {
if ($wildcard) {
if ($cid == '*') {
db_query("TRUNCATE TABLE {". $table ."}");
}
else {
db_query("DELETE FROM {". $table ."} WHERE cid LIKE '%s%%'", $cid);
}
}
else {
db_query("DELETE FROM {". $table ."} WHERE cid = '%s'", $cid);
}
}
}

3882
includes/common.inc Normal file

File diff suppressed because it is too large Load diff

623
includes/database.inc Normal file
View file

@ -0,0 +1,623 @@
<?php
/**
* @file
* Wrapper for database interface code.
*/
/**
* A hash value to check when outputting database errors, md5('DB_ERROR').
*
* @see drupal_error_handler()
*/
define('DB_ERROR', 'a515ac9c2796ca0e23adbe92c68fc9fc');
/**
* @defgroup database Database abstraction layer
* @{
* Allow the use of different database servers using the same code base.
*
* Drupal provides a slim database abstraction layer to provide developers with
* the ability to support multiple database servers easily. The intent of this
* layer is to preserve the syntax and power of SQL as much as possible, while
* letting Drupal control the pieces of queries that need to be written
* differently for different servers and provide basic security checks.
*
* Most Drupal database queries are performed by a call to db_query() or
* db_query_range(). Module authors should also consider using pager_query() for
* queries that return results that need to be presented on multiple pages, and
* tablesort_sql() for generating appropriate queries for sortable tables.
*
* For example, one might wish to return a list of the most recent 10 nodes
* authored by a given user. Instead of directly issuing the SQL query
* @code
* SELECT n.nid, n.title, n.created FROM node n WHERE n.uid = $uid LIMIT 0, 10;
* @endcode
* one would instead call the Drupal functions:
* @code
* $result = db_query_range('SELECT n.nid, n.title, n.created
* FROM {node} n WHERE n.uid = %d', $uid, 0, 10);
* while ($node = db_fetch_object($result)) {
* // Perform operations on $node->body, etc. here.
* }
* @endcode
* Curly braces are used around "node" to provide table prefixing via
* db_prefix_tables(). The explicit use of a user ID is pulled out into an
* argument passed to db_query() so that SQL injection attacks from user input
* can be caught and nullified. The LIMIT syntax varies between database servers,
* so that is abstracted into db_query_range() arguments. Finally, note the
* common pattern of iterating over the result set using db_fetch_object().
*/
/**
* Perform an SQL query and return success or failure.
*
* @param $sql
* A string containing a complete SQL query. %-substitution
* parameters are not supported.
* @return
* An array containing the keys:
* success: a boolean indicating whether the query succeeded
* query: the SQL query executed, passed through check_plain()
*/
function update_sql($sql) {
$result = db_query($sql, true);
return array('success' => $result !== FALSE, 'query' => check_plain($sql));
}
/**
* Append a database prefix to all tables in a query.
*
* Queries sent to Drupal should wrap all table names in curly brackets. This
* function searches for this syntax and adds Drupal's table prefix to all
* tables, allowing Drupal to coexist with other systems in the same database if
* necessary.
*
* @param $sql
* A string containing a partial or entire SQL query.
* @return
* The properly-prefixed string.
*/
function db_prefix_tables($sql) {
global $db_prefix;
if (is_array($db_prefix)) {
if (array_key_exists('default', $db_prefix)) {
$tmp = $db_prefix;
unset($tmp['default']);
foreach ($tmp as $key => $val) {
$sql = strtr($sql, array('{'. $key .'}' => $val . $key));
}
return strtr($sql, array('{' => $db_prefix['default'], '}' => ''));
}
else {
foreach ($db_prefix as $key => $val) {
$sql = strtr($sql, array('{'. $key .'}' => $val . $key));
}
return strtr($sql, array('{' => '', '}' => ''));
}
}
else {
return strtr($sql, array('{' => $db_prefix, '}' => ''));
}
}
/**
* Activate a database for future queries.
*
* If it is necessary to use external databases in a project, this function can
* be used to change where database queries are sent. If the database has not
* yet been used, it is initialized using the URL specified for that name in
* Drupal's configuration file. If this name is not defined, a duplicate of the
* default connection is made instead.
*
* Be sure to change the connection back to the default when done with custom
* code.
*
* @param $name
* The key in the $db_url global variable from settings.php. If omitted, the
* default connection will be made active.
*
* @return
* The name of the previously active database, or FALSE if none was found.
*/
function db_set_active($name = 'default') {
global $db_url, $db_type, $active_db;
static $db_conns, $active_name = FALSE;
if (empty($db_url)) {
include_once 'includes/install.inc';
install_goto('install.php');
}
if (!isset($db_conns[$name])) {
// Initiate a new connection, using the named DB URL specified.
if (is_array($db_url)) {
$connect_url = array_key_exists($name, $db_url) ? $db_url[$name] : $db_url['default'];
}
else {
$connect_url = $db_url;
}
$db_type = substr($connect_url, 0, strpos($connect_url, '://'));
$handler = "./includes/database.$db_type.inc";
if (is_file($handler)) {
include_once $handler;
}
else {
_db_error_page("The database type '". $db_type ."' is unsupported. Please use either 'mysql' or 'mysqli' for MySQL, or 'pgsql' for PostgreSQL databases.");
}
$db_conns[$name] = db_connect($connect_url);
}
$previous_name = $active_name;
// Set the active connection.
$active_name = $name;
$active_db = $db_conns[$name];
return $previous_name;
}
/**
* Helper function to show fatal database errors.
*
* Prints a themed maintenance page with the 'Site off-line' text,
* adding the provided error message in the case of 'display_errors'
* set to on. Ends the page request; no return.
*
* @param $error
* The error message to be appended if 'display_errors' is on.
*/
function _db_error_page($error = '') {
global $db_type;
drupal_init_language();
drupal_maintenance_theme();
drupal_set_header($_SERVER['SERVER_PROTOCOL'] .' 503 Service Unavailable');
drupal_set_title('Site off-line');
$message = '<p>The site is currently not available due to technical problems. Please try again later. Thank you for your understanding.</p>';
$message .= '<hr /><p><small>If you are the maintainer of this site, please check your database settings in the <code>settings.php</code> file and ensure that your hosting provider\'s database server is running. For more help, see the <a href="http://drupal.org/node/258">handbook</a>, or contact your hosting provider.</small></p>';
if ($error && ini_get('display_errors')) {
$message .= '<p><small>The '. theme('placeholder', $db_type) .' error was: '. theme('placeholder', $error) .'.</small></p>';
}
print theme('maintenance_page', $message);
exit;
}
/**
* Returns a boolean depending on the availability of the database.
*/
function db_is_active() {
global $active_db;
return !empty($active_db);
}
/**
* Helper function for db_query().
*/
function _db_query_callback($match, $init = FALSE) {
static $args = NULL;
if ($init) {
$args = $match;
return;
}
switch ($match[1]) {
case '%d': // We must use type casting to int to convert FALSE/NULL/(TRUE?)
$value = array_shift($args);
// Do we need special bigint handling?
if ($value > PHP_INT_MAX) {
$precision = ini_get('precision');
@ini_set('precision', 16);
$value = sprintf('%.0f', $value);
@ini_set('precision', $precision);
}
else {
$value = (int) $value;
}
// We don't need db_escape_string as numbers are db-safe.
return $value;
case '%s':
return db_escape_string(array_shift($args));
case '%n':
// Numeric values have arbitrary precision, so can't be treated as float.
// is_numeric() allows hex values (0xFF), but they are not valid.
$value = trim(array_shift($args));
return is_numeric($value) && !preg_match('/x/i', $value) ? $value : '0';
case '%%':
return '%';
case '%f':
return (float) array_shift($args);
case '%b': // binary data
return db_encode_blob(array_shift($args));
}
}
/**
* Generate placeholders for an array of query arguments of a single type.
*
* Given a Schema API field type, return correct %-placeholders to
* embed in a query
*
* @param $arguments
* An array with at least one element.
* @param $type
* The Schema API type of a field (e.g. 'int', 'text', or 'varchar').
*/
function db_placeholders($arguments, $type = 'int') {
$placeholder = db_type_placeholder($type);
return implode(',', array_fill(0, count($arguments), $placeholder));
}
/**
* Indicates the place holders that should be replaced in _db_query_callback().
*/
define('DB_QUERY_REGEXP', '/(%d|%s|%%|%f|%b|%n)/');
/**
* Helper function for db_rewrite_sql.
*
* Collects JOIN and WHERE statements via hook_db_rewrite_sql()
* Decides whether to select primary_key or DISTINCT(primary_key)
*
* @param $query
* Query to be rewritten.
* @param $primary_table
* Name or alias of the table which has the primary key field for this query.
* Typical table names would be: {blocks}, {comments}, {forum}, {node},
* {menu}, {term_data} or {vocabulary}. However, in most cases the usual
* table alias (b, c, f, n, m, t or v) is used instead of the table name.
* @param $primary_field
* Name of the primary field.
* @param $args
* Array of additional arguments.
* @return
* An array: join statements, where statements, field or DISTINCT(field).
*/
function _db_rewrite_sql($query = '', $primary_table = 'n', $primary_field = 'nid', $args = array()) {
$where = array();
$join = array();
$distinct = FALSE;
foreach (module_implements('db_rewrite_sql') as $module) {
$result = module_invoke($module, 'db_rewrite_sql', $query, $primary_table, $primary_field, $args);
if (isset($result) && is_array($result)) {
if (isset($result['where'])) {
$where[] = $result['where'];
}
if (isset($result['join'])) {
$join[] = $result['join'];
}
if (isset($result['distinct']) && $result['distinct']) {
$distinct = TRUE;
}
}
elseif (isset($result)) {
$where[] = $result;
}
}
$where = empty($where) ? '' : '('. implode(') AND (', $where) .')';
$join = empty($join) ? '' : implode(' ', $join);
return array($join, $where, $distinct);
}
/**
* Rewrites node, taxonomy and comment queries. Use it for listing queries. Do not
* use FROM table1, table2 syntax, use JOIN instead.
*
* @param $query
* Query to be rewritten.
* @param $primary_table
* Name or alias of the table which has the primary key field for this query.
* Typical table names would be: {blocks}, {comments}, {forum}, {node},
* {menu}, {term_data} or {vocabulary}. However, it is more common to use the
* the usual table aliases: b, c, f, n, m, t or v.
* @param $primary_field
* Name of the primary field.
* @param $args
* An array of arguments, passed to the implementations of hook_db_rewrite_sql.
* @return
* The original query with JOIN and WHERE statements inserted from
* hook_db_rewrite_sql implementations. nid is rewritten if needed.
*/
function db_rewrite_sql($query, $primary_table = 'n', $primary_field = 'nid', $args = array()) {
list($join, $where, $distinct) = _db_rewrite_sql($query, $primary_table, $primary_field, $args);
if ($distinct) {
$query = db_distinct_field($primary_table, $primary_field, $query);
}
if (!empty($where) || !empty($join)) {
$pattern = '{
# Beginning of the string
^
((?P<anonymous_view>
# Everything within this set of parentheses is named "anonymous view"
(?:
[^()]++ # anything not parentheses
|
\( (?P>anonymous_view) \) # an open parenthesis, more "anonymous view" and finally a close parenthesis.
)*
)[^()]+WHERE)
}x';
preg_match($pattern, $query, $matches);
if (!$where) {
$where = '1 = 1';
}
if ($matches) {
$n = strlen($matches[1]);
$second_part = substr($query, $n);
$first_part = substr($matches[1], 0, $n - 5) ." $join WHERE $where AND ( ";
// PHP 4 does not support strrpos for strings. We emulate it.
$haystack_reverse = strrev($second_part);
}
else {
$haystack_reverse = strrev($query);
}
// No need to use strrev on the needle, we supply GROUP, ORDER, LIMIT
// reversed.
foreach (array('PUORG', 'REDRO', 'TIMIL') as $needle_reverse) {
$pos = strpos($haystack_reverse, $needle_reverse);
if ($pos !== FALSE) {
// All needles are five characters long.
$pos += 5;
break;
}
}
if ($matches) {
if ($pos === FALSE) {
$query = $first_part . $second_part .')';
}
else {
$query = $first_part . substr($second_part, 0, -$pos) .')'. substr($second_part, -$pos);
}
}
elseif ($pos === FALSE) {
$query .= " $join WHERE $where";
}
else {
$query = substr($query, 0, -$pos) . " $join WHERE $where " . substr($query, -$pos);
}
}
return $query;
}
/**
* Adds the DISTINCT flag to the supplied query and returns the altered query.
*
* The supplied query should not contain a DISTINCT flag. This will not, and
* never did guarantee that you will obtain distinct values of $table.$field.
*
* @param $table
* Unused. Kept to retain API compatibility.
* @param $field
* Unused. Kept to retain API compatibility.
* @param $query
* Query to which the DISTINCT flag should be applied.
*
* @return
* SQL query with the DISTINCT flag set.
*/
function db_distinct_field($table, $field, $query) {
$matches = array();
if (!preg_match('/^SELECT\s*DISTINCT/i', $query, $matches)) {
// Only add distinct to the outer SELECT to avoid messing up subqueries.
$query = preg_replace('/^SELECT/i', 'SELECT DISTINCT', $query);
}
return $query;
}
/**
* Restrict a dynamic table, column or constraint name to safe characters.
*
* Only keeps alphanumeric and underscores.
*/
function db_escape_table($string) {
return preg_replace('/[^A-Za-z0-9_]+/', '', $string);
}
/**
* @} End of "defgroup database".
*/
/**
* @defgroup schemaapi Schema API
* @{
*
* A Drupal schema definition is an array structure representing one or
* more tables and their related keys and indexes. A schema is defined by
* hook_schema(), which usually lives in a modulename.install file.
*
* By implementing hook_schema() and specifying the tables your module
* declares, you can easily create and drop these tables on all
* supported database engines. You don't have to deal with the
* different SQL dialects for table creation and alteration of the
* supported database engines.
*
* hook_schema() should return an array with a key for each table that
* the module defines.
*
* The following keys are defined:
*
* - 'description': A string describing this table and its purpose.
* References to other tables should be enclosed in
* curly-brackets. For example, the node_revisions table
* description field might contain "Stores per-revision title and
* body data for each {node}."
* - 'fields': An associative array ('fieldname' => specification)
* that describes the table's database columns. The specification
* is also an array. The following specification parameters are defined:
* - 'description': A string describing this field and its purpose.
* References to other tables should be enclosed in
* curly-brackets. For example, the node table vid field
* description might contain "Always holds the largest (most
* recent) {node_revisions}.vid value for this nid."
* - 'type': The generic datatype: 'varchar', 'int', 'serial'
* 'float', 'numeric', 'text', 'blob' or 'datetime'. Most types
* just map to the according database engine specific
* datatypes. Use 'serial' for auto incrementing fields. This
* will expand to 'int auto_increment' on mysql.
* - 'serialize': A boolean indicating whether the field will be stored
as a serialized string.
* - 'size': The data size: 'tiny', 'small', 'medium', 'normal',
* 'big'. This is a hint about the largest value the field will
* store and determines which of the database engine specific
* datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT).
* 'normal', the default, selects the base type (e.g. on MySQL,
* INT, VARCHAR, BLOB, etc.).
* Not all sizes are available for all data types. See
* db_type_map() for possible combinations.
* - 'not null': If true, no NULL values will be allowed in this
* database column. Defaults to false.
* - 'default': The field's default value. The PHP type of the
* value matters: '', '0', and 0 are all different. If you
* specify '0' as the default value for a type 'int' field it
* will not work because '0' is a string containing the
* character "zero", not an integer.
* - 'length': The maximal length of a type 'char', 'varchar' or 'text'
* field. Ignored for other field types.
* - 'unsigned': A boolean indicating whether a type 'int', 'float'
* and 'numeric' only is signed or unsigned. Defaults to
* FALSE. Ignored for other field types.
* - 'precision', 'scale': For type 'numeric' fields, indicates
* the precision (total number of significant digits) and scale
* (decimal digits right of the decimal point). Both values are
* mandatory. Ignored for other field types.
* All parameters apart from 'type' are optional except that type
* 'numeric' columns must specify 'precision' and 'scale'.
* - 'primary key': An array of one or more key column specifiers (see below)
* that form the primary key.
* - 'unique keys': An associative array of unique keys ('keyname' =>
* specification). Each specification is an array of one or more
* key column specifiers (see below) that form a unique key on the table.
* - 'indexes': An associative array of indexes ('indexame' =>
* specification). Each specification is an array of one or more
* key column specifiers (see below) that form an index on the
* table.
*
* A key column specifier is either a string naming a column or an
* array of two elements, column name and length, specifying a prefix
* of the named column.
*
* As an example, here is a SUBSET of the schema definition for
* Drupal's 'node' table. It show four fields (nid, vid, type, and
* title), the primary key on field 'nid', a unique key named 'vid' on
* field 'vid', and two indexes, one named 'nid' on field 'nid' and
* one named 'node_title_type' on the field 'title' and the first four
* bytes of the field 'type':
*
* @code
* $schema['node'] = array(
* 'fields' => array(
* 'nid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE),
* 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
* 'type' => array('type' => 'varchar', 'length' => 32, 'not null' => TRUE, 'default' => ''),
* 'title' => array('type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => ''),
* ),
* 'primary key' => array('nid'),
* 'unique keys' => array(
* 'vid' => array('vid')
* ),
* 'indexes' => array(
* 'nid' => array('nid'),
* 'node_title_type' => array('title', array('type', 4)),
* ),
* );
* @endcode
*
* @see drupal_install_schema()
*/
/**
* Create a new table from a Drupal table definition.
*
* @param $ret
* Array to which query results will be added.
* @param $name
* The name of the table to create.
* @param $table
* A Schema API table definition array.
*/
function db_create_table(&$ret, $name, $table) {
$statements = db_create_table_sql($name, $table);
foreach ($statements as $statement) {
$ret[] = update_sql($statement);
}
}
/**
* Return an array of field names from an array of key/index column specifiers.
*
* This is usually an identity function but if a key/index uses a column prefix
* specification, this function extracts just the name.
*
* @param $fields
* An array of key/index column specifiers.
* @return
* An array of field names.
*/
function db_field_names($fields) {
$ret = array();
foreach ($fields as $field) {
if (is_array($field)) {
$ret[] = $field[0];
}
else {
$ret[] = $field;
}
}
return $ret;
}
/**
* Given a Schema API field type, return the correct %-placeholder.
*
* Embed the placeholder in a query to be passed to db_query and and pass as an
* argument to db_query a value of the specified type.
*
* @param $type
* The Schema API type of a field.
* @return
* The placeholder string to embed in a query for that type.
*/
function db_type_placeholder($type) {
switch ($type) {
case 'varchar':
case 'char':
case 'text':
case 'datetime':
return "'%s'";
case 'numeric':
// Numeric values are arbitrary precision numbers. Syntacically, numerics
// should be specified directly in SQL. However, without single quotes
// the %s placeholder does not protect against non-numeric characters such
// as spaces which would expose us to SQL injection.
return '%n';
case 'serial':
case 'int':
return '%d';
case 'float':
return '%f';
case 'blob':
return '%b';
}
// There is no safe value to return here, so return something that
// will cause the query to fail.
return 'unsupported type '. $type .'for db_type_placeholder';
}
/**
* @} End of "defgroup schemaapi".
*/

View file

@ -0,0 +1,542 @@
<?php
/**
* @file
* Functions shared between mysql and mysqli database engines.
*/
/**
* Runs a basic query in the active database.
*
* User-supplied arguments to the query should be passed in as separate
* parameters so that they can be properly escaped to avoid SQL injection
* attacks.
*
* @param $query
* A string containing an SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. Instead of a variable number of query arguments,
* you may also pass a single array containing the query arguments.
*
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
*
* @return
* Successful SELECT, SHOW, DESCRIBE, EXPLAIN, or other queries which return a
* set of results will return a database query result resource. Other
* successful queries will return TRUE and failing queries will return FALSE.
*/
function db_query($query) {
$args = func_get_args();
array_shift($args);
$query = db_prefix_tables($query);
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
return _db_query($query);
}
/**
* @ingroup schemaapi
* @{
*/
/**
* Generate SQL to create a new table from a Drupal schema definition.
*
* @param $name
* The name of the table to create.
* @param $table
* A Schema API table definition array.
* @return
* An array of SQL statements to create the table.
*/
function db_create_table_sql($name, $table) {
if (empty($table['mysql_suffix'])) {
$table['mysql_suffix'] = '/*!40100 DEFAULT CHARACTER SET utf8';
// By default, MySQL uses the default collation for new tables, which is
// 'utf8_general_ci' for utf8. If an alternate collation has been set, it
// needs to be explicitly specified.
// @see db_connect()
$collation = (!empty($table['collation']) ? $table['collation'] : (!empty($GLOBALS['db_collation']) ? $GLOBALS['db_collation'] : ''));
if ($collation) {
$table['mysql_suffix'] .= ' COLLATE ' . $collation;
}
$table['mysql_suffix'] .= ' */';
}
$sql = "CREATE TABLE {". $name ."} (\n";
// Add the SQL statement for each field.
foreach ($table['fields'] as $field_name => $field) {
$sql .= _db_create_field_sql($field_name, _db_process_field($field)) .", \n";
}
// Process keys & indexes.
$keys = _db_create_keys_sql($table);
if (count($keys)) {
$sql .= implode(", \n", $keys) .", \n";
}
// Remove the last comma and space.
$sql = substr($sql, 0, -3) ."\n) ";
$sql .= $table['mysql_suffix'];
return array($sql);
}
function _db_create_keys_sql($spec) {
$keys = array();
if (!empty($spec['primary key'])) {
$keys[] = 'PRIMARY KEY ('. _db_create_key_sql($spec['primary key']) .')';
}
if (!empty($spec['unique keys'])) {
foreach ($spec['unique keys'] as $key => $fields) {
$keys[] = 'UNIQUE KEY '. $key .' ('. _db_create_key_sql($fields) .')';
}
}
if (!empty($spec['indexes'])) {
foreach ($spec['indexes'] as $index => $fields) {
$keys[] = 'INDEX '. $index .' ('. _db_create_key_sql($fields) .')';
}
}
return $keys;
}
function _db_create_key_sql($fields) {
$ret = array();
foreach ($fields as $field) {
if (is_array($field)) {
$ret[] = $field[0] .'('. $field[1] .')';
}
else {
$ret[] = $field;
}
}
return implode(', ', $ret);
}
/**
* Set database-engine specific properties for a field.
*
* @param $field
* A field description array, as specified in the schema documentation.
*/
function _db_process_field($field) {
if (!isset($field['size'])) {
$field['size'] = 'normal';
}
// Set the correct database-engine specific datatype.
if (!isset($field['mysql_type'])) {
$map = db_type_map();
$field['mysql_type'] = $map[$field['type'] .':'. $field['size']];
}
if ($field['type'] == 'serial') {
$field['auto_increment'] = TRUE;
}
return $field;
}
/**
* Create an SQL string for a field to be used in table creation or alteration.
*
* Before passing a field out of a schema definition into this function it has
* to be processed by _db_process_field().
*
* @param $name
* Name of the field.
* @param $spec
* The field specification, as per the schema data structure format.
*/
function _db_create_field_sql($name, $spec) {
$sql = "`". $name ."` ". $spec['mysql_type'];
if (in_array($spec['type'], array('varchar', 'char', 'text')) && isset($spec['length'])) {
$sql .= '('. $spec['length'] .')';
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$sql .= '('. $spec['precision'] .', '. $spec['scale'] .')';
}
if (!empty($spec['unsigned'])) {
$sql .= ' unsigned';
}
if (!empty($spec['not null'])) {
$sql .= ' NOT NULL';
}
if (!empty($spec['auto_increment'])) {
$sql .= ' auto_increment';
}
if (isset($spec['default'])) {
if (is_string($spec['default'])) {
$spec['default'] = "'". $spec['default'] ."'";
}
$sql .= ' DEFAULT '. $spec['default'];
}
if (empty($spec['not null']) && !isset($spec['default'])) {
$sql .= ' DEFAULT NULL';
}
return $sql;
}
/**
* This maps a generic data type in combination with its data size
* to the engine-specific data type.
*/
function db_type_map() {
// Put :normal last so it gets preserved by array_flip. This makes
// it much easier for modules (such as schema.module) to map
// database types back into schema types.
$map = array(
'varchar:normal' => 'VARCHAR',
'char:normal' => 'CHAR',
'text:tiny' => 'TINYTEXT',
'text:small' => 'TINYTEXT',
'text:medium' => 'MEDIUMTEXT',
'text:big' => 'LONGTEXT',
'text:normal' => 'TEXT',
'serial:tiny' => 'TINYINT',
'serial:small' => 'SMALLINT',
'serial:medium' => 'MEDIUMINT',
'serial:big' => 'BIGINT',
'serial:normal' => 'INT',
'int:tiny' => 'TINYINT',
'int:small' => 'SMALLINT',
'int:medium' => 'MEDIUMINT',
'int:big' => 'BIGINT',
'int:normal' => 'INT',
'float:tiny' => 'FLOAT',
'float:small' => 'FLOAT',
'float:medium' => 'FLOAT',
'float:big' => 'DOUBLE',
'float:normal' => 'FLOAT',
'numeric:normal' => 'DECIMAL',
'blob:big' => 'LONGBLOB',
'blob:normal' => 'BLOB',
'datetime:normal' => 'DATETIME',
);
return $map;
}
/**
* Rename a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be renamed.
* @param $new_name
* The new name for the table.
*/
function db_rename_table(&$ret, $table, $new_name) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} RENAME TO {'. $new_name .'}');
}
/**
* Drop a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be dropped.
*/
function db_drop_table(&$ret, $table) {
$ret[] = update_sql('DROP TABLE {'. $table .'}');
}
/**
* Add a new field to a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* Name of the table to be altered.
* @param $field
* Name of the field to be added.
* @param $spec
* The field specification array, as taken from a schema definition.
* The specification may also contain the key 'initial', the newly
* created field will be set to the value of the key in all rows.
* This is most useful for creating NOT NULL columns with no default
* value in existing tables.
* @param $keys_new
* (optional) Keys and indexes specification to be created on the
* table along with adding the field. The format is the same as a
* table specification but without the 'fields' element. If you are
* adding a type 'serial' field, you MUST specify at least one key
* or index including it in this array. See db_change_field() for more
* explanation why.
*/
function db_add_field(&$ret, $table, $field, $spec, $keys_new = array()) {
$fixnull = FALSE;
if (!empty($spec['not null']) && !isset($spec['default'])) {
$fixnull = TRUE;
$spec['not null'] = FALSE;
}
$query = 'ALTER TABLE {'. $table .'} ADD ';
$query .= _db_create_field_sql($field, _db_process_field($spec));
if (count($keys_new)) {
$query .= ', ADD '. implode(', ADD ', _db_create_keys_sql($keys_new));
}
$ret[] = update_sql($query);
if (isset($spec['initial'])) {
// All this because update_sql does not support %-placeholders.
$sql = 'UPDATE {'. $table .'} SET '. $field .' = '. db_type_placeholder($spec['type']);
$result = db_query($sql, $spec['initial']);
$ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql .' ('. $spec['initial'] .')'));
}
if ($fixnull) {
$spec['not null'] = TRUE;
db_change_field($ret, $table, $field, $field, $spec);
}
}
/**
* Drop a field.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be dropped.
*/
function db_drop_field(&$ret, $table, $field) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP '. $field);
}
/**
* Set the default value for a field.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be altered.
* @param $default
* Default value to be set. NULL for 'default NULL'.
*/
function db_field_set_default(&$ret, $table, $field, $default) {
if ($default === NULL) {
$default = 'NULL';
}
else {
$default = is_string($default) ? "'$default'" : $default;
}
$ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default);
}
/**
* Set a field to have no default value.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be altered.
*/
function db_field_set_no_default(&$ret, $table, $field) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT');
}
/**
* Add a primary key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $fields
* Fields for the primary key.
*/
function db_add_primary_key(&$ret, $table, $fields) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('.
_db_create_key_sql($fields) .')');
}
/**
* Drop the primary key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
*/
function db_drop_primary_key(&$ret, $table) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP PRIMARY KEY');
}
/**
* Add a unique key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the key.
* @param $fields
* An array of field names.
*/
function db_add_unique_key(&$ret, $table, $name, $fields) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ADD UNIQUE KEY '.
$name .' ('. _db_create_key_sql($fields) .')');
}
/**
* Drop a unique key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the key.
*/
function db_drop_unique_key(&$ret, $table, $name) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP KEY '. $name);
}
/**
* Add an index.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the index.
* @param $fields
* An array of field names.
*/
function db_add_index(&$ret, $table, $name, $fields) {
$query = 'ALTER TABLE {'. $table .'} ADD INDEX '. $name .' ('. _db_create_key_sql($fields) .')';
$ret[] = update_sql($query);
}
/**
* Drop an index.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the index.
*/
function db_drop_index(&$ret, $table, $name) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP INDEX '. $name);
}
/**
* Change a field definition.
*
* IMPORTANT NOTE: To maintain database portability, you have to explicitly
* recreate all indices and primary keys that are using the changed field.
*
* That means that you have to drop all affected keys and indexes with
* db_drop_{primary_key,unique_key,index}() before calling db_change_field().
* To recreate the keys and indices, pass the key definitions as the
* optional $keys_new argument directly to db_change_field().
*
* For example, suppose you have:
* @code
* $schema['foo'] = array(
* 'fields' => array(
* 'bar' => array('type' => 'int', 'not null' => TRUE)
* ),
* 'primary key' => array('bar')
* );
* @endcode
* and you want to change foo.bar to be type serial, leaving it as the
* primary key. The correct sequence is:
* @code
* db_drop_primary_key($ret, 'foo');
* db_change_field($ret, 'foo', 'bar', 'bar',
* array('type' => 'serial', 'not null' => TRUE),
* array('primary key' => array('bar')));
* @endcode
*
* The reasons for this are due to the different database engines:
*
* On PostgreSQL, changing a field definition involves adding a new field
* and dropping an old one which* causes any indices, primary keys and
* sequences (from serial-type fields) that use the changed field to be dropped.
*
* On MySQL, all type 'serial' fields must be part of at least one key
* or index as soon as they are created. You cannot use
* db_add_{primary_key,unique_key,index}() for this purpose because
* the ALTER TABLE command will fail to add the column without a key
* or index specification. The solution is to use the optional
* $keys_new argument to create the key or index at the same time as
* field.
*
* You could use db_add_{primary_key,unique_key,index}() in all cases
* unless you are converting a field to be type serial. You can use
* the $keys_new argument in all cases.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* Name of the table.
* @param $field
* Name of the field to change.
* @param $field_new
* New name for the field (set to the same as $field if you don't want to change the name).
* @param $spec
* The field specification for the new field.
* @param $keys_new
* (optional) Keys and indexes specification to be created on the
* table along with changing the field. The format is the same as a
* table specification but without the 'fields' element.
*/
function db_change_field(&$ret, $table, $field, $field_new, $spec, $keys_new = array()) {
$sql = 'ALTER TABLE {'. $table .'} CHANGE `'. $field .'` '.
_db_create_field_sql($field_new, _db_process_field($spec));
if (count($keys_new)) {
$sql .= ', ADD '. implode(', ADD ', _db_create_keys_sql($keys_new));
}
$ret[] = update_sql($sql);
}
/**
* Returns the last insert id.
*
* @param $table
* The name of the table you inserted into.
* @param $field
* The name of the autoincrement field.
*/
function db_last_insert_id($table, $field) {
return db_result(db_query('SELECT LAST_INSERT_ID()'));
}

376
includes/database.mysql.inc Normal file
View file

@ -0,0 +1,376 @@
<?php
/**
* @file
* Database interface code for MySQL database servers.
*/
/**
* @ingroup database
* @{
*/
// Include functions shared between mysql and mysqli.
require_once './includes/database.mysql-common.inc';
/**
* Report database status.
*/
function db_status_report($phase) {
$t = get_t();
$version = db_version();
$form['mysql'] = array(
'title' => $t('MySQL database'),
'value' => ($phase == 'runtime') ? l($version, 'admin/reports/status/sql') : $version,
);
if (version_compare($version, DRUPAL_MINIMUM_MYSQL) < 0) {
$form['mysql']['severity'] = REQUIREMENT_ERROR;
$form['mysql']['description'] = $t('Your MySQL Server is too old. Drupal requires at least MySQL %version.', array('%version' => DRUPAL_MINIMUM_MYSQL));
}
return $form;
}
/**
* Returns the version of the database server currently in use.
*
* @return Database server version
*/
function db_version() {
list($version) = explode('-', mysql_get_server_info());
return $version;
}
/**
* Initialize a database connection.
*/
function db_connect($url) {
$url = parse_url($url);
// Check if MySQL support is present in PHP
if (!function_exists('mysql_connect')) {
_db_error_page('Unable to use the MySQL database because the MySQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
}
// Decode urlencoded information in the db connection string
$url['user'] = urldecode($url['user']);
// Test if database URL has a password.
$url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
$url['host'] = urldecode($url['host']);
$url['path'] = urldecode($url['path']);
// Allow for non-standard MySQL port.
if (isset($url['port'])) {
$url['host'] = $url['host'] .':'. $url['port'];
}
// - TRUE makes mysql_connect() always open a new link, even if
// mysql_connect() was called before with the same parameters.
// This is important if you are using two databases on the same
// server.
// - 2 means CLIENT_FOUND_ROWS: return the number of found
// (matched) rows, not the number of affected rows.
$connection = @mysql_connect($url['host'], $url['user'], $url['pass'], TRUE, 2);
if (!$connection || !mysql_select_db(substr($url['path'], 1))) {
// Show error screen otherwise
_db_error_page(mysql_error());
}
// Force MySQL to use the UTF-8 character set. Also set the collation, if a
// certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
// for UTF-8.
if (!empty($GLOBALS['db_collation'])) {
mysql_query('SET NAMES utf8 COLLATE '. $GLOBALS['db_collation'], $connection);
}
else {
mysql_query('SET NAMES utf8', $connection);
}
return $connection;
}
/**
* Helper function for db_query().
*/
function _db_query($query, $debug = 0) {
global $active_db, $queries, $user;
if (variable_get('dev_query', 0)) {
list($usec, $sec) = explode(' ', microtime());
$timer = (float)$usec + (float)$sec;
// If devel.module query logging is enabled, prepend a comment with the username and calling function
// to the SQL string. This is useful when running mysql's SHOW PROCESSLIST to learn what exact
// code is issueing the slow query.
$bt = debug_backtrace();
// t() may not be available yet so we don't wrap 'Anonymous'.
$name = $user->uid ? $user->name : variable_get('anonymous', 'Anonymous');
// str_replace() to prevent SQL injection via username or anonymous name.
$name = str_replace(array('*', '/'), '', $name);
$query = '/* '. $name .' : '. $bt[2]['function'] .' */ '. $query;
}
$result = mysql_query($query, $active_db);
if (variable_get('dev_query', 0)) {
$query = $bt[2]['function'] ."\n". $query;
list($usec, $sec) = explode(' ', microtime());
$stop = (float)$usec + (float)$sec;
$diff = $stop - $timer;
$queries[] = array($query, $diff);
}
if ($debug) {
print '<p>query: '. $query .'<br />error:'. mysql_error($active_db) .'</p>';
}
if (!mysql_errno($active_db)) {
return $result;
}
else {
// Indicate to drupal_error_handler that this is a database error.
${DB_ERROR} = TRUE;
trigger_error(check_plain(mysql_error($active_db) ."\nquery: ". $query), E_USER_WARNING);
return FALSE;
}
}
/**
* Fetch one result row from the previous query as an object.
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* An object representing the next row of the result, or FALSE. The attributes
* of this object are the table fields selected by the query.
*/
function db_fetch_object($result) {
if ($result) {
return mysql_fetch_object($result);
}
}
/**
* Fetch one result row from the previous query as an array.
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* An associative array representing the next row of the result, or FALSE.
* The keys of this object are the names of the table fields selected by the
* query, and the values are the field values for this result row.
*/
function db_fetch_array($result) {
if ($result) {
return mysql_fetch_array($result, MYSQL_ASSOC);
}
}
/**
* Return an individual result field from the previous query.
*
* Only use this function if exactly one field is being selected; otherwise,
* use db_fetch_object() or db_fetch_array().
*
* @param $result
* A database query result resource, as returned from db_query().
*
* @return
* The resulting field or FALSE.
*/
function db_result($result) {
if ($result && mysql_num_rows($result) > 0) {
// The mysql_fetch_row function has an optional second parameter $row
// but that can't be used for compatibility with Oracle, DB2, etc.
$array = mysql_fetch_row($result);
return $array[0];
}
return FALSE;
}
/**
* Determine whether the previous query caused an error.
*/
function db_error() {
global $active_db;
return mysql_errno($active_db);
}
/**
* Determine the number of rows changed by the preceding query.
*/
function db_affected_rows() {
global $active_db;
return mysql_affected_rows($active_db);
}
/**
* Runs a limited-range query in the active database.
*
* Use this as a substitute for db_query() when a subset of the query is to be
* returned.
* User-supplied arguments to the query should be passed in as separate parameters
* so that they can be properly escaped to avoid SQL injection attacks.
*
* @param $query
* A string containing an SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. The query arguments can be enclosed in one
* array instead.
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
*
* @param $from
* The first result row to return.
* @param $count
* The maximum number of result rows to return.
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*/
function db_query_range($query) {
$args = func_get_args();
$count = array_pop($args);
$from = array_pop($args);
array_shift($args);
$query = db_prefix_tables($query);
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
$query .= ' LIMIT '. (int)$from .', '. (int)$count;
return _db_query($query);
}
/**
* Runs a SELECT query and stores its results in a temporary table.
*
* Use this as a substitute for db_query() when the results need to be stored
* in a temporary table.
*
* User-supplied arguments to the query should be passed in as separate parameters
* so that they can be properly escaped to avoid SQL injection attacks.
*
* Note that if you need to know how many results were returned, you should do
* a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
* not give consistent result across different database types in this case.
*
* @param $query
* A string containing a normal SELECT SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. The query arguments can be enclosed in one
* array instead.
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
* @param $table
* The name of the temporary table to select into. This name will not be
* prefixed as there is no risk of collision.
*
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*/
function db_query_temporary($query) {
$args = func_get_args();
$tablename = array_pop($args);
array_shift($args);
$query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' Engine=HEAP SELECT', db_prefix_tables($query));
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
return _db_query($query);
}
/**
* Returns a properly formatted Binary Large OBject value.
*
* @param $data
* Data to encode.
* @return
* Encoded data.
*/
function db_encode_blob($data) {
global $active_db;
return "'". mysql_real_escape_string($data, $active_db) ."'";
}
/**
* Returns text from a Binary Large Object value.
*
* @param $data
* Data to decode.
* @return
* Decoded data.
*/
function db_decode_blob($data) {
return $data;
}
/**
* Prepare user input for use in a database query, preventing SQL injection attacks.
*/
function db_escape_string($text) {
global $active_db;
return mysql_real_escape_string($text, $active_db);
}
/**
* Lock a table.
*/
function db_lock_table($table) {
db_query('LOCK TABLES {'. db_escape_table($table) .'} WRITE');
}
/**
* Unlock all locked tables.
*/
function db_unlock_tables() {
db_query('UNLOCK TABLES');
}
/**
* Check if a table exists.
*
* @param $table
* The name of the table.
*
* @return
* TRUE if the table exists, and FALSE if the table does not exist.
*/
function db_table_exists($table) {
return (bool) db_fetch_object(db_query("SHOW TABLES LIKE '{". db_escape_table($table) ."}'"));
}
/**
* Check if a column exists in the given table.
*
* @param $table
* The name of the table.
* @param $column
* The name of the column.
*
* @return
* TRUE if the column exists, and FALSE if the column does not exist.
*/
function db_column_exists($table, $column) {
return (bool) db_fetch_object(db_query("SHOW COLUMNS FROM {". db_escape_table($table) ."} LIKE '". db_escape_table($column) ."'"));
}
/**
* @} End of "ingroup database".
*/

View file

@ -0,0 +1,377 @@
<?php
/**
* @file
* Database interface code for MySQL database servers using the mysqli client libraries. mysqli is included in PHP 5 by default and allows developers to use the advanced features of MySQL 4.1.x, 5.0.x and beyond.
*/
// Maintainers of this file should consult:
// http://www.php.net/manual/en/ref.mysqli.php
/**
* @ingroup database
* @{
*/
// Include functions shared between mysql and mysqli.
require_once './includes/database.mysql-common.inc';
/**
* Report database status.
*/
function db_status_report($phase) {
$t = get_t();
$version = db_version();
$form['mysql'] = array(
'title' => $t('MySQL database'),
'value' => ($phase == 'runtime') ? l($version, 'admin/reports/status/sql') : $version,
);
if (version_compare($version, DRUPAL_MINIMUM_MYSQL) < 0) {
$form['mysql']['severity'] = REQUIREMENT_ERROR;
$form['mysql']['description'] = $t('Your MySQL Server is too old. Drupal requires at least MySQL %version.', array('%version' => DRUPAL_MINIMUM_MYSQL));
}
return $form;
}
/**
* Returns the version of the database server currently in use.
*
* @return Database server version
*/
function db_version() {
global $active_db;
list($version) = explode('-', mysqli_get_server_info($active_db));
return $version;
}
/**
* Initialise a database connection.
*
* Note that mysqli does not support persistent connections.
*/
function db_connect($url) {
// Check if MySQLi support is present in PHP
if (!function_exists('mysqli_init') && !extension_loaded('mysqli')) {
_db_error_page('Unable to use the MySQLi database because the MySQLi extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
}
$url = parse_url($url);
// Decode urlencoded information in the db connection string
$url['user'] = urldecode($url['user']);
// Test if database URL has a password.
$url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
$url['host'] = urldecode($url['host']);
$url['path'] = urldecode($url['path']);
if (!isset($url['port'])) {
$url['port'] = NULL;
}
$connection = mysqli_init();
@mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS);
if (mysqli_connect_errno() > 0) {
_db_error_page(mysqli_connect_error());
}
// Force MySQL to use the UTF-8 character set. Also set the collation, if a
// certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
// for UTF-8.
if (!empty($GLOBALS['db_collation'])) {
mysqli_query($connection, 'SET NAMES utf8 COLLATE ' . $GLOBALS['db_collation']);
}
else {
mysqli_query($connection, 'SET NAMES utf8');
}
return $connection;
}
/**
* Helper function for db_query().
*/
function _db_query($query, $debug = 0) {
global $active_db, $queries, $user;
if (variable_get('dev_query', 0)) {
list($usec, $sec) = explode(' ', microtime());
$timer = (float)$usec + (float)$sec;
// If devel.module query logging is enabled, prepend a comment with the username and calling function
// to the SQL string. This is useful when running mysql's SHOW PROCESSLIST to learn what exact
// code is issueing the slow query.
$bt = debug_backtrace();
// t() may not be available yet so we don't wrap 'Anonymous'
$name = $user->uid ? $user->name : variable_get('anonymous', 'Anonymous');
// str_replace() to prevent SQL injection via username or anonymous name.
$name = str_replace(array('*', '/'), '', $name);
$query = '/* '. $name .' : '. $bt[2]['function'] .' */ '. $query;
}
$result = mysqli_query($active_db, $query);
if (variable_get('dev_query', 0)) {
$query = $bt[2]['function'] ."\n". $query;
list($usec, $sec) = explode(' ', microtime());
$stop = (float)$usec + (float)$sec;
$diff = $stop - $timer;
$queries[] = array($query, $diff);
}
if ($debug) {
print '<p>query: '. $query .'<br />error:'. mysqli_error($active_db) .'</p>';
}
if (!mysqli_errno($active_db)) {
return $result;
}
else {
// Indicate to drupal_error_handler that this is a database error.
${DB_ERROR} = TRUE;
trigger_error(check_plain(mysqli_error($active_db) ."\nquery: ". $query), E_USER_WARNING);
return FALSE;
}
}
/**
* Fetch one result row from the previous query as an object.
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* An object representing the next row of the result, or FALSE. The attributes
* of this object are the table fields selected by the query.
*/
function db_fetch_object($result) {
if ($result) {
$object = mysqli_fetch_object($result);
return isset($object) ? $object : FALSE;
}
}
/**
* Fetch one result row from the previous query as an array.
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* An associative array representing the next row of the result, or FALSE.
* The keys of this object are the names of the table fields selected by the
* query, and the values are the field values for this result row.
*/
function db_fetch_array($result) {
if ($result) {
$array = mysqli_fetch_array($result, MYSQLI_ASSOC);
return isset($array) ? $array : FALSE;
}
}
/**
* Return an individual result field from the previous query.
*
* Only use this function if exactly one field is being selected; otherwise,
* use db_fetch_object() or db_fetch_array().
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* The resulting field or FALSE.
*/
function db_result($result) {
if ($result && mysqli_num_rows($result) > 0) {
// The mysqli_fetch_row function has an optional second parameter $row
// but that can't be used for compatibility with Oracle, DB2, etc.
$array = mysqli_fetch_row($result);
return $array[0];
}
return FALSE;
}
/**
* Determine whether the previous query caused an error.
*/
function db_error() {
global $active_db;
return mysqli_errno($active_db);
}
/**
* Determine the number of rows changed by the preceding query.
*/
function db_affected_rows() {
global $active_db; /* mysqli connection resource */
return mysqli_affected_rows($active_db);
}
/**
* Runs a limited-range query in the active database.
*
* Use this as a substitute for db_query() when a subset of the query is to be
* returned.
* User-supplied arguments to the query should be passed in as separate parameters
* so that they can be properly escaped to avoid SQL injection attacks.
*
* @param $query
* A string containing an SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. The query arguments can be enclosed in one
* array instead.
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
*
* @param $from
* The first result row to return.
* @param $count
* The maximum number of result rows to return.
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*/
function db_query_range($query) {
$args = func_get_args();
$count = array_pop($args);
$from = array_pop($args);
array_shift($args);
$query = db_prefix_tables($query);
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
$query .= ' LIMIT '. (int)$from .', '. (int)$count;
return _db_query($query);
}
/**
* Runs a SELECT query and stores its results in a temporary table.
*
* Use this as a substitute for db_query() when the results need to be stored
* in a temporary table.
*
* User-supplied arguments to the query should be passed in as separate parameters
* so that they can be properly escaped to avoid SQL injection attacks.
*
* Note that if you need to know how many results were returned, you should do
* a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
* not give consistent result across different database types in this case.
*
* @param $query
* A string containing a normal SELECT SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. The query arguments can be enclosed in one
* array instead.
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
* @param $table
* The name of the temporary table to select into. This name will not be
* prefixed as there is no risk of collision.
*
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*/
function db_query_temporary($query) {
$args = func_get_args();
$tablename = array_pop($args);
array_shift($args);
$query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' Engine=HEAP SELECT', db_prefix_tables($query));
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
return _db_query($query);
}
/**
* Returns a properly formatted Binary Large Object value.
*
* @param $data
* Data to encode.
* @return
* Encoded data.
*/
function db_encode_blob($data) {
global $active_db;
return "'". mysqli_real_escape_string($active_db, $data) ."'";
}
/**
* Returns text from a Binary Large OBject value.
*
* @param $data
* Data to decode.
* @return
* Decoded data.
*/
function db_decode_blob($data) {
return $data;
}
/**
* Prepare user input for use in a database query, preventing SQL injection attacks.
*/
function db_escape_string($text) {
global $active_db;
return mysqli_real_escape_string($active_db, $text);
}
/**
* Lock a table.
*/
function db_lock_table($table) {
db_query('LOCK TABLES {'. db_escape_table($table) .'} WRITE');
}
/**
* Unlock all locked tables.
*/
function db_unlock_tables() {
db_query('UNLOCK TABLES');
}
/**
* Check if a table exists.
*
* @param $table
* The name of the table.
*
* @return
* TRUE if the table exists, and FALSE if the table does not exist.
*/
function db_table_exists($table) {
return (bool) db_fetch_object(db_query("SHOW TABLES LIKE '{". db_escape_table($table) ."}'"));
}
/**
* Check if a column exists in the given table.
*
* @param $table
* The name of the table.
* @param $column
* The name of the column.
*
* @return
* TRUE if the column exists, and FALSE if the column does not exist.
*/
function db_column_exists($table, $column) {
return (bool) db_fetch_object(db_query("SHOW COLUMNS FROM {". db_escape_table($table) ."} LIKE '". db_escape_table($column) ."'"));
}
/**
* @} End of "ingroup database".
*/

928
includes/database.pgsql.inc Normal file
View file

@ -0,0 +1,928 @@
<?php
/**
* @file
* Database interface code for PostgreSQL database servers.
*/
/**
* @ingroup database
* @{
*/
/**
* Report database status.
*/
function db_status_report() {
$t = get_t();
$version = db_version();
$form['pgsql'] = array(
'title' => $t('PostgreSQL database'),
'value' => $version,
);
if (version_compare($version, DRUPAL_MINIMUM_PGSQL) < 0) {
$form['pgsql']['severity'] = REQUIREMENT_ERROR;
$form['pgsql']['description'] = $t('Your PostgreSQL Server is too old. Drupal requires at least PostgreSQL %version.', array('%version' => DRUPAL_MINIMUM_PGSQL));
}
return $form;
}
/**
* Returns the version of the database server currently in use.
*
* @return Database server version
*/
function db_version() {
return db_result(db_query("SHOW SERVER_VERSION"));
}
/**
* Initialize a database connection.
*/
function db_connect($url) {
// Check if PostgreSQL support is present in PHP
if (!function_exists('pg_connect')) {
_db_error_page('Unable to use the PostgreSQL database because the PostgreSQL extension for PHP is not installed. Check your <code>php.ini</code> to see how you can enable it.');
}
$url = parse_url($url);
$conn_string = '';
// Decode urlencoded information in the db connection string
if (isset($url['user'])) {
$conn_string .= ' user='. urldecode($url['user']);
}
if (isset($url['pass'])) {
$conn_string .= ' password='. urldecode($url['pass']);
}
if (isset($url['host'])) {
$conn_string .= ' host='. urldecode($url['host']);
}
if (isset($url['path'])) {
$conn_string .= ' dbname='. substr(urldecode($url['path']), 1);
}
if (isset($url['port'])) {
$conn_string .= ' port='. urldecode($url['port']);
}
// pg_last_error() does not return a useful error message for database
// connection errors. We must turn on error tracking to get at a good error
// message, which will be stored in $php_errormsg.
$track_errors_previous = ini_get('track_errors');
ini_set('track_errors', 1);
$connection = @pg_connect($conn_string);
if (!$connection) {
require_once './includes/unicode.inc';
_db_error_page(decode_entities($php_errormsg));
}
// Restore error tracking setting
ini_set('track_errors', $track_errors_previous);
pg_query($connection, "set client_encoding=\"UTF8\"");
return $connection;
}
/**
* Runs a basic query in the active database.
*
* User-supplied arguments to the query should be passed in as separate
* parameters so that they can be properly escaped to avoid SQL injection
* attacks.
*
* @param $query
* A string containing an SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. Instead of a variable number of query arguments,
* you may also pass a single array containing the query arguments.
*
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
*
* @return
* A database query result resource, or FALSE if the query was not
* executed correctly.
*/
function db_query($query) {
$args = func_get_args();
array_shift($args);
$query = db_prefix_tables($query);
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
return _db_query($query);
}
/**
* Helper function for db_query().
*/
function _db_query($query, $debug = 0) {
global $active_db, $last_result, $queries;
if (variable_get('dev_query', 0)) {
list($usec, $sec) = explode(' ', microtime());
$timer = (float)$usec + (float)$sec;
}
$last_result = pg_query($active_db, $query);
if (variable_get('dev_query', 0)) {
$bt = debug_backtrace();
$query = $bt[2]['function'] ."\n". $query;
list($usec, $sec) = explode(' ', microtime());
$stop = (float)$usec + (float)$sec;
$diff = $stop - $timer;
$queries[] = array($query, $diff);
}
if ($debug) {
print '<p>query: '. $query .'<br />error:'. pg_last_error($active_db) .'</p>';
}
if ($last_result !== FALSE) {
return $last_result;
}
else {
// Indicate to drupal_error_handler that this is a database error.
${DB_ERROR} = TRUE;
trigger_error(check_plain(pg_last_error($active_db) ."\nquery: ". $query), E_USER_WARNING);
return FALSE;
}
}
/**
* Fetch one result row from the previous query as an object.
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* An object representing the next row of the result, or FALSE. The attributes
* of this object are the table fields selected by the query.
*/
function db_fetch_object($result) {
if ($result) {
return pg_fetch_object($result);
}
}
/**
* Fetch one result row from the previous query as an array.
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* An associative array representing the next row of the result, or FALSE.
* The keys of this object are the names of the table fields selected by the
* query, and the values are the field values for this result row.
*/
function db_fetch_array($result) {
if ($result) {
return pg_fetch_assoc($result);
}
}
/**
* Return an individual result field from the previous query.
*
* Only use this function if exactly one field is being selected; otherwise,
* use db_fetch_object() or db_fetch_array().
*
* @param $result
* A database query result resource, as returned from db_query().
* @return
* The resulting field or FALSE.
*/
function db_result($result) {
if ($result && pg_num_rows($result) > 0) {
$array = pg_fetch_row($result);
return $array[0];
}
return FALSE;
}
/**
* Determine whether the previous query caused an error.
*/
function db_error() {
global $active_db;
return pg_last_error($active_db);
}
/**
* Returns the last insert id. This function is thread safe.
*
* @param $table
* The name of the table you inserted into.
* @param $field
* The name of the autoincrement field.
*/
function db_last_insert_id($table, $field) {
return db_result(db_query("SELECT CURRVAL('{". db_escape_table($table) ."}_". db_escape_table($field) ."_seq')"));
}
/**
* Determine the number of rows changed by the preceding query.
*/
function db_affected_rows() {
global $last_result;
return empty($last_result) ? 0 : pg_affected_rows($last_result);
}
/**
* Runs a limited-range query in the active database.
*
* Use this as a substitute for db_query() when a subset of the query
* is to be returned.
* User-supplied arguments to the query should be passed in as separate
* parameters so that they can be properly escaped to avoid SQL injection
* attacks.
*
* @param $query
* A string containing an SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. Instead of a variable number of query arguments,
* you may also pass a single array containing the query arguments.
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
*
* @param $from
* The first result row to return.
* @param $count
* The maximum number of result rows to return.
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*/
function db_query_range($query) {
$args = func_get_args();
$count = array_pop($args);
$from = array_pop($args);
array_shift($args);
$query = db_prefix_tables($query);
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
$query .= ' LIMIT '. (int)$count .' OFFSET '. (int)$from;
return _db_query($query);
}
/**
* Runs a SELECT query and stores its results in a temporary table.
*
* Use this as a substitute for db_query() when the results need to be stored
* in a temporary table.
*
* User-supplied arguments to the query should be passed in as separate parameters
* so that they can be properly escaped to avoid SQL injection attacks.
*
* Note that if you need to know how many results were returned, you should do
* a SELECT COUNT(*) on the temporary table afterwards. db_affected_rows() does
* not give consistent result across different database types in this case.
*
* @param $query
* A string containing a normal SELECT SQL query.
* @param ...
* A variable number of arguments which are substituted into the query
* using printf() syntax. The query arguments can be enclosed in one
* array instead.
* Valid %-modifiers are: %s, %d, %f, %b (binary data, do not enclose
* in '') and %%.
*
* NOTE: using this syntax will cast NULL and FALSE values to decimal 0,
* and TRUE values to decimal 1.
* @param $table
* The name of the temporary table to select into. This name will not be
* prefixed as there is no risk of collision.
*
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*/
function db_query_temporary($query) {
$args = func_get_args();
$tablename = array_pop($args);
array_shift($args);
$query = preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE '. $tablename .' AS SELECT', db_prefix_tables($query));
if (isset($args[0]) and is_array($args[0])) { // 'All arguments in one array' syntax
$args = $args[0];
}
_db_query_callback($args, TRUE);
$query = preg_replace_callback(DB_QUERY_REGEXP, '_db_query_callback', $query);
return _db_query($query);
}
/**
* Returns a properly formatted Binary Large OBject value.
* In case of PostgreSQL encodes data for insert into bytea field.
*
* @param $data
* Data to encode.
* @return
* Encoded data.
*/
function db_encode_blob($data) {
return "'". pg_escape_bytea($data) ."'";
}
/**
* Returns text from a Binary Large OBject value.
* In case of PostgreSQL decodes data after select from bytea field.
*
* @param $data
* Data to decode.
* @return
* Decoded data.
*/
function db_decode_blob($data) {
return pg_unescape_bytea($data);
}
/**
* Prepare user input for use in a database query, preventing SQL injection attacks.
* Note: This function requires PostgreSQL 7.2 or later.
*/
function db_escape_string($text) {
return pg_escape_string($text);
}
/**
* Lock a table.
* This function automatically starts a transaction.
*/
function db_lock_table($table) {
db_query('BEGIN; LOCK TABLE {'. db_escape_table($table) .'} IN EXCLUSIVE MODE');
}
/**
* Unlock all locked tables.
* This function automatically commits a transaction.
*/
function db_unlock_tables() {
db_query('COMMIT');
}
/**
* Check if a table exists.
*
* @param $table
* The name of the table.
*
* @return
* TRUE if the table exists, and FALSE if the table does not exist.
*/
function db_table_exists($table) {
return (bool) db_result(db_query("SELECT COUNT(*) FROM pg_class WHERE relname = '{". db_escape_table($table) ."}'"));
}
/**
* Check if a column exists in the given table.
*
* @param $table
* The name of the table.
* @param $column
* The name of the column.
*
* @return
* TRUE if the column exists, and FALSE if the column does not exist.
*/
function db_column_exists($table, $column) {
return (bool) db_result(db_query("SELECT COUNT(pg_attribute.attname) FROM pg_class, pg_attribute WHERE pg_attribute.attrelid = pg_class.oid AND pg_class.relname = '{". db_escape_table($table) ."}' AND attname = '". db_escape_table($column) ."'"));
}
/**
* Verify if the database is set up correctly.
*/
function db_check_setup() {
$t = get_t();
$encoding = db_result(db_query('SHOW server_encoding'));
if (!in_array(strtolower($encoding), array('unicode', 'utf8'))) {
drupal_set_message($t('Your PostgreSQL database is set up with the wrong character encoding (%encoding). It is possible it will not work as expected. It is advised to recreate it with UTF-8/Unicode encoding. More information can be found in the <a href="@url">PostgreSQL documentation</a>.', array('%encoding' => $encoding, '@url' => 'http://www.postgresql.org/docs/7.4/interactive/multibyte.html')), 'status');
}
}
/**
* @} End of "ingroup database".
*/
/**
* @ingroup schemaapi
* @{
*/
/**
* This maps a generic data type in combination with its data size
* to the engine-specific data type.
*/
function db_type_map() {
// Put :normal last so it gets preserved by array_flip. This makes
// it much easier for modules (such as schema.module) to map
// database types back into schema types.
$map = array(
'varchar:normal' => 'varchar',
'char:normal' => 'character',
'text:tiny' => 'text',
'text:small' => 'text',
'text:medium' => 'text',
'text:big' => 'text',
'text:normal' => 'text',
'int:tiny' => 'smallint',
'int:small' => 'smallint',
'int:medium' => 'int',
'int:big' => 'bigint',
'int:normal' => 'int',
'float:tiny' => 'real',
'float:small' => 'real',
'float:medium' => 'real',
'float:big' => 'double precision',
'float:normal' => 'real',
'numeric:normal' => 'numeric',
'blob:big' => 'bytea',
'blob:normal' => 'bytea',
'datetime:normal' => 'timestamp without time zone',
'serial:tiny' => 'serial',
'serial:small' => 'serial',
'serial:medium' => 'serial',
'serial:big' => 'bigserial',
'serial:normal' => 'serial',
);
return $map;
}
/**
* Generate SQL to create a new table from a Drupal schema definition.
*
* @param $name
* The name of the table to create.
* @param $table
* A Schema API table definition array.
* @return
* An array of SQL statements to create the table.
*/
function db_create_table_sql($name, $table) {
$sql_fields = array();
foreach ($table['fields'] as $field_name => $field) {
$sql_fields[] = _db_create_field_sql($field_name, _db_process_field($field));
}
$sql_keys = array();
if (isset($table['primary key']) && is_array($table['primary key'])) {
$sql_keys[] = 'PRIMARY KEY ('. implode(', ', $table['primary key']) .')';
}
if (isset($table['unique keys']) && is_array($table['unique keys'])) {
foreach ($table['unique keys'] as $key_name => $key) {
$sql_keys[] = 'CONSTRAINT {'. $name .'}_'. $key_name .'_key UNIQUE ('. implode(', ', $key) .')';
}
}
$sql = "CREATE TABLE {". $name ."} (\n\t";
$sql .= implode(",\n\t", $sql_fields);
if (count($sql_keys) > 0) {
$sql .= ",\n\t";
}
$sql .= implode(",\n\t", $sql_keys);
$sql .= "\n)";
$statements[] = $sql;
if (isset($table['indexes']) && is_array($table['indexes'])) {
foreach ($table['indexes'] as $key_name => $key) {
$statements[] = _db_create_index_sql($name, $key_name, $key);
}
}
return $statements;
}
function _db_create_index_sql($table, $name, $fields) {
$query = 'CREATE INDEX {'. $table .'}_'. $name .'_idx ON {'. $table .'} (';
$query .= _db_create_key_sql($fields) .')';
return $query;
}
function _db_create_key_sql($fields) {
$ret = array();
foreach ($fields as $field) {
if (is_array($field)) {
$ret[] = 'substr('. $field[0] .', 1, '. $field[1] .')';
}
else {
$ret[] = $field;
}
}
return implode(', ', $ret);
}
function _db_create_keys(&$ret, $table, $new_keys) {
if (isset($new_keys['primary key'])) {
db_add_primary_key($ret, $table, $new_keys['primary key']);
}
if (isset($new_keys['unique keys'])) {
foreach ($new_keys['unique keys'] as $name => $fields) {
db_add_unique_key($ret, $table, $name, $fields);
}
}
if (isset($new_keys['indexes'])) {
foreach ($new_keys['indexes'] as $name => $fields) {
db_add_index($ret, $table, $name, $fields);
}
}
}
/**
* Set database-engine specific properties for a field.
*
* @param $field
* A field description array, as specified in the schema documentation.
*/
function _db_process_field($field) {
if (!isset($field['size'])) {
$field['size'] = 'normal';
}
// Set the correct database-engine specific datatype.
if (!isset($field['pgsql_type'])) {
$map = db_type_map();
$field['pgsql_type'] = $map[$field['type'] .':'. $field['size']];
}
if ($field['type'] == 'serial') {
unset($field['not null']);
}
return $field;
}
/**
* Create an SQL string for a field to be used in table creation or alteration.
*
* Before passing a field out of a schema definition into this function it has
* to be processed by _db_process_field().
*
* @param $name
* Name of the field.
* @param $spec
* The field specification, as per the schema data structure format.
*/
function _db_create_field_sql($name, $spec) {
$sql = $name .' '. $spec['pgsql_type'];
if ($spec['type'] == 'serial') {
unset($spec['not null']);
}
if (in_array($spec['type'], array('varchar', 'char', 'text')) && isset($spec['length'])) {
$sql .= '('. $spec['length'] .')';
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$sql .= '('. $spec['precision'] .', '. $spec['scale'] .')';
}
if (!empty($spec['unsigned'])) {
$sql .= " CHECK ($name >= 0)";
}
if (isset($spec['not null']) && $spec['not null']) {
$sql .= ' NOT NULL';
}
if (isset($spec['default'])) {
$default = is_string($spec['default']) ? "'". $spec['default'] ."'" : $spec['default'];
$sql .= " default $default";
}
return $sql;
}
/**
* Rename a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be renamed.
* @param $new_name
* The new name for the table.
*/
function db_rename_table(&$ret, $table, $new_name) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} RENAME TO {'. $new_name .'}');
}
/**
* Drop a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be dropped.
*/
function db_drop_table(&$ret, $table) {
$ret[] = update_sql('DROP TABLE {'. $table .'}');
}
/**
* Add a new field to a table.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* Name of the table to be altered.
* @param $field
* Name of the field to be added.
* @param $spec
* The field specification array, as taken from a schema definition.
* The specification may also contain the key 'initial', the newly
* created field will be set to the value of the key in all rows.
* This is most useful for creating NOT NULL columns with no default
* value in existing tables.
* @param $new_keys
* (optional) Keys and indexes specification to be created on the
* table along with adding the field. The format is the same as a
* table specification but without the 'fields' element. If you are
* adding a type 'serial' field, you MUST specify at least one key
* or index including it in this array. See db_change_field() for more
* explanation why.
*/
function db_add_field(&$ret, $table, $field, $spec, $new_keys = array()) {
$fixnull = FALSE;
if (!empty($spec['not null']) && !isset($spec['default'])) {
$fixnull = TRUE;
$spec['not null'] = FALSE;
}
$query = 'ALTER TABLE {'. $table .'} ADD COLUMN ';
$query .= _db_create_field_sql($field, _db_process_field($spec));
$ret[] = update_sql($query);
if (isset($spec['initial'])) {
// All this because update_sql does not support %-placeholders.
$sql = 'UPDATE {'. $table .'} SET '. $field .' = '. db_type_placeholder($spec['type']);
$result = db_query($sql, $spec['initial']);
$ret[] = array('success' => $result !== FALSE, 'query' => check_plain($sql .' ('. $spec['initial'] .')'));
}
if ($fixnull) {
$ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $field SET NOT NULL");
}
if (isset($new_keys)) {
_db_create_keys($ret, $table, $new_keys);
}
}
/**
* Drop a field.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be dropped.
*/
function db_drop_field(&$ret, $table, $field) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP COLUMN '. $field);
}
/**
* Set the default value for a field.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be altered.
* @param $default
* Default value to be set. NULL for 'default NULL'.
*/
function db_field_set_default(&$ret, $table, $field, $default) {
if ($default == NULL) {
$default = 'NULL';
}
else {
$default = is_string($default) ? "'$default'" : $default;
}
$ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' SET DEFAULT '. $default);
}
/**
* Set a field to have no default value.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $field
* The field to be altered.
*/
function db_field_set_no_default(&$ret, $table, $field) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ALTER COLUMN '. $field .' DROP DEFAULT');
}
/**
* Add a primary key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $fields
* Fields for the primary key.
*/
function db_add_primary_key(&$ret, $table, $fields) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} ADD PRIMARY KEY ('.
implode(',', $fields) .')');
}
/**
* Drop the primary key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
*/
function db_drop_primary_key(&$ret, $table) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT {'. $table .'}_pkey');
}
/**
* Add a unique key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the key.
* @param $fields
* An array of field names.
*/
function db_add_unique_key(&$ret, $table, $name, $fields) {
$name = '{'. $table .'}_'. $name .'_key';
$ret[] = update_sql('ALTER TABLE {'. $table .'} ADD CONSTRAINT '.
$name .' UNIQUE ('. implode(',', $fields) .')');
}
/**
* Drop a unique key.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the key.
*/
function db_drop_unique_key(&$ret, $table, $name) {
$name = '{'. $table .'}_'. $name .'_key';
$ret[] = update_sql('ALTER TABLE {'. $table .'} DROP CONSTRAINT '. $name);
}
/**
* Add an index.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the index.
* @param $fields
* An array of field names.
*/
function db_add_index(&$ret, $table, $name, $fields) {
$ret[] = update_sql(_db_create_index_sql($table, $name, $fields));
}
/**
* Drop an index.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* The table to be altered.
* @param $name
* The name of the index.
*/
function db_drop_index(&$ret, $table, $name) {
$name = '{'. $table .'}_'. $name .'_idx';
$ret[] = update_sql('DROP INDEX '. $name);
}
/**
* Change a field definition.
*
* IMPORTANT NOTE: To maintain database portability, you have to explicitly
* recreate all indices and primary keys that are using the changed field.
*
* That means that you have to drop all affected keys and indexes with
* db_drop_{primary_key,unique_key,index}() before calling db_change_field().
* To recreate the keys and indices, pass the key definitions as the
* optional $new_keys argument directly to db_change_field().
*
* For example, suppose you have:
* @code
* $schema['foo'] = array(
* 'fields' => array(
* 'bar' => array('type' => 'int', 'not null' => TRUE)
* ),
* 'primary key' => array('bar')
* );
* @endcode
* and you want to change foo.bar to be type serial, leaving it as the
* primary key. The correct sequence is:
* @code
* db_drop_primary_key($ret, 'foo');
* db_change_field($ret, 'foo', 'bar', 'bar',
* array('type' => 'serial', 'not null' => TRUE),
* array('primary key' => array('bar')));
* @endcode
*
* The reasons for this are due to the different database engines:
*
* On PostgreSQL, changing a field definition involves adding a new field
* and dropping an old one which* causes any indices, primary keys and
* sequences (from serial-type fields) that use the changed field to be dropped.
*
* On MySQL, all type 'serial' fields must be part of at least one key
* or index as soon as they are created. You cannot use
* db_add_{primary_key,unique_key,index}() for this purpose because
* the ALTER TABLE command will fail to add the column without a key
* or index specification. The solution is to use the optional
* $new_keys argument to create the key or index at the same time as
* field.
*
* You could use db_add_{primary_key,unique_key,index}() in all cases
* unless you are converting a field to be type serial. You can use
* the $new_keys argument in all cases.
*
* @param $ret
* Array to which query results will be added.
* @param $table
* Name of the table.
* @param $field
* Name of the field to change.
* @param $field_new
* New name for the field (set to the same as $field if you don't want to change the name).
* @param $spec
* The field specification for the new field.
* @param $new_keys
* (optional) Keys and indexes specification to be created on the
* table along with changing the field. The format is the same as a
* table specification but without the 'fields' element.
*/
function db_change_field(&$ret, $table, $field, $field_new, $spec, $new_keys = array()) {
$ret[] = update_sql('ALTER TABLE {'. $table .'} RENAME "'. $field .'" TO "'. $field .'_old"');
$not_null = isset($spec['not null']) ? $spec['not null'] : FALSE;
unset($spec['not null']);
if (!array_key_exists('size', $spec)) {
$spec['size'] = 'normal';
}
db_add_field($ret, $table, "$field_new", $spec);
// We need to type cast the new column to best transfer the data
// db_type_map will return possiblities that are not 'cast-able'
// such as serial - they must be made 'int' instead.
$map = db_type_map();
$typecast = $map[$spec['type'] .':'. $spec['size']];
if (in_array($typecast, array('serial', 'bigserial', 'numeric'))) {
$typecast = 'int';
}
$ret[] = update_sql('UPDATE {'. $table .'} SET '. $field_new .' = CAST('. $field .'_old AS '. $typecast .')');
if ($not_null) {
$ret[] = update_sql("ALTER TABLE {". $table ."} ALTER $field_new SET NOT NULL");
}
db_drop_field($ret, $table, $field .'_old');
if (isset($new_keys)) {
_db_create_keys($ret, $table, $new_keys);
}
}
/**
* @} End of "ingroup schemaapi".
*/

1550
includes/file.inc Normal file

File diff suppressed because it is too large Load diff

2684
includes/form.inc Normal file

File diff suppressed because it is too large Load diff

220
includes/image.gd.inc Normal file
View file

@ -0,0 +1,220 @@
<?php
/**
* @file
* GD2 toolkit for image manipulation within Drupal.
*/
/**
* @ingroup image
* @{
*/
/**
* Retrieve information about the toolkit.
*/
function image_gd_info() {
return array('name' => 'gd', 'title' => t('GD2 image manipulation toolkit'));
}
/**
* Retrieve settings for the GD2 toolkit.
*/
function image_gd_settings() {
if (image_gd_check_settings()) {
$form = array();
$form['status'] = array(
'#value' => t('The GD toolkit is installed and working properly.')
);
$form['image_jpeg_quality'] = array(
'#type' => 'textfield',
'#title' => t('JPEG quality'),
'#description' => t('Define the image quality for JPEG manipulations. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
'#size' => 10,
'#maxlength' => 3,
'#default_value' => variable_get('image_jpeg_quality', 75),
'#field_suffix' => t('%'),
);
$form['#element_validate'] = array('image_gd_settings_validate');
return $form;
}
else {
form_set_error('image_toolkit', t('The GD image toolkit requires that the GD module for PHP be installed and configured properly. For more information see <a href="@url">PHP\'s image documentation</a>.', array('@url' => 'http://php.net/image')));
return FALSE;
}
}
/**
* Validate the submitted GD settings.
*/
function image_gd_settings_validate($form, &$form_state) {
// Validate image quality range.
$value = $form_state['values']['image_jpeg_quality'];
if (!is_numeric($value) || $value < 0 || $value > 100) {
form_set_error('image_jpeg_quality', t('JPEG quality must be a number between 0 and 100.'));
}
}
/**
* Verify GD2 settings (that the right version is actually installed).
*
* @return
* A boolean indicating if the GD toolkit is avaiable on this machine.
*/
function image_gd_check_settings() {
if ($check = get_extension_funcs('gd')) {
if (in_array('imagegd2', $check)) {
// GD2 support is available.
return TRUE;
}
}
return FALSE;
}
/**
* Scale an image to the specified size using GD.
*/
function image_gd_resize($source, $destination, $width, $height) {
if (!file_exists($source)) {
return FALSE;
}
$info = image_get_info($source);
if (!$info) {
return FALSE;
}
$im = image_gd_open($source, $info['extension']);
if (!$im) {
return FALSE;
}
$res = imagecreatetruecolor($width, $height);
if ($info['extension'] == 'png') {
$transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
imagealphablending($res, FALSE);
imagefilledrectangle($res, 0, 0, $width, $height, $transparency);
imagealphablending($res, TRUE);
imagesavealpha($res, TRUE);
}
elseif ($info['extension'] == 'gif') {
// If we have a specific transparent color.
$transparency_index = imagecolortransparent($im);
if ($transparency_index >= 0) {
// Get the original image's transparent color's RGB values.
$transparent_color = imagecolorsforindex($im, $transparency_index);
// Allocate the same color in the new image resource.
$transparency_index = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
// Completely fill the background of the new image with allocated color.
imagefill($res, 0, 0, $transparency_index);
// Set the background color for new image to transparent.
imagecolortransparent($res, $transparency_index);
// Find number of colors in the images palette.
$number_colors = imagecolorstotal($im);
// Convert from true color to palette to fix transparency issues.
imagetruecolortopalette($res, TRUE, $number_colors);
}
}
imagecopyresampled($res, $im, 0, 0, 0, 0, $width, $height, $info['width'], $info['height']);
$result = image_gd_close($res, $destination, $info['extension']);
imagedestroy($res);
imagedestroy($im);
return $result;
}
/**
* Rotate an image the given number of degrees.
*/
function image_gd_rotate($source, $destination, $degrees, $background = 0x000000) {
if (!function_exists('imageRotate')) {
return FALSE;
}
$info = image_get_info($source);
if (!$info) {
return FALSE;
}
$im = image_gd_open($source, $info['extension']);
if (!$im) {
return FALSE;
}
$res = imageRotate($im, $degrees, $background);
$result = image_gd_close($res, $destination, $info['extension']);
return $result;
}
/**
* Crop an image using the GD toolkit.
*/
function image_gd_crop($source, $destination, $x, $y, $width, $height) {
$info = image_get_info($source);
if (!$info) {
return FALSE;
}
$im = image_gd_open($source, $info['extension']);
$res = imageCreateTrueColor($width, $height);
imageCopy($res, $im, 0, 0, $x, $y, $width, $height);
$result = image_gd_close($res, $destination, $info['extension']);
imageDestroy($res);
imageDestroy($im);
return $result;
}
/**
* GD helper function to create an image resource from a file.
*
* @param $file
* A string file path where the iamge should be saved.
* @param $extension
* A string containing one of the following extensions: gif, jpg, jpeg, png.
* @return
* An image resource, or FALSE on error.
*/
function image_gd_open($file, $extension) {
$extension = str_replace('jpg', 'jpeg', $extension);
$open_func = 'imageCreateFrom'. $extension;
if (!function_exists($open_func)) {
return FALSE;
}
return $open_func($file);
}
/**
* GD helper to write an image resource to a destination file.
*
* @param $res
* An image resource created with image_gd_open().
* @param $destination
* A string file path where the iamge should be saved.
* @param $extension
* A string containing one of the following extensions: gif, jpg, jpeg, png.
* @return
* Boolean indicating success.
*/
function image_gd_close($res, $destination, $extension) {
$extension = str_replace('jpg', 'jpeg', $extension);
$close_func = 'image'. $extension;
if (!function_exists($close_func)) {
return FALSE;
}
if ($extension == 'jpeg') {
return $close_func($res, $destination, variable_get('image_jpeg_quality', 75));
}
else {
return $close_func($res, $destination);
}
}
/**
* @} End of "ingroup image".
*/

271
includes/image.inc Normal file
View file

@ -0,0 +1,271 @@
<?php
/**
* @file
* API for manipulating images.
*/
/**
* @defgroup image Image toolkits
* @{
* Drupal's image toolkits provide an abstraction layer for common image file
* manipulations like scaling, cropping, and rotating. The abstraction frees
* module authors from the need to support multiple image libraries, and it
* allows site administrators to choose the library that's best for them.
*
* PHP includes the GD library by default so a GD toolkit is installed with
* Drupal. Other toolkits like ImageMagic are available from contrib modules.
* GD works well for small images, but using it with larger files may cause PHP
* to run out of memory. In contrast the ImageMagick library does not suffer
* from this problem, but it requires the ISP to have installed additional
* software.
*
* Image toolkits are installed by copying the image.ToolkitName.inc file into
* Drupal's includes directory. The toolkit must then be enabled using the
* admin/settings/image-toolkit form.
*
* Only one toolkit maybe selected at a time. If a module author wishes to call
* a specific toolkit they can check that it is installed by calling
* image_get_available_toolkits(), and then calling its functions directly.
*/
/**
* Return a list of available toolkits.
*
* @return
* An array of toolkit name => descriptive title.
*/
function image_get_available_toolkits() {
$toolkits = file_scan_directory('includes', 'image\..*\.inc$');
$output = array();
foreach ($toolkits as $file => $toolkit) {
include_once "./$file";
$function = str_replace('.', '_', $toolkit->name) .'_info';
if (function_exists($function)) {
$info = $function();
$output[$info['name']] = $info['title'];
}
}
return $output;
}
/**
* Retrieve the name of the currently used toolkit.
*
* @return
* String containing the name of the selected toolkit, or FALSE on error.
*/
function image_get_toolkit() {
static $toolkit;
if (!$toolkit) {
$toolkit = variable_get('image_toolkit', 'gd');
$toolkit_file = './includes/image.'. $toolkit .'.inc';
if (isset($toolkit) && file_exists($toolkit_file)) {
include_once $toolkit_file;
}
elseif (!image_gd_check_settings()) {
$toolkit = FALSE;
}
}
return $toolkit;
}
/**
* Invokes the given method using the currently selected toolkit.
*
* @param $method
* A string containing the method to invoke.
* @param $params
* An optional array of parameters to pass to the toolkit method.
* @return
* Mixed values (typically Boolean indicating successful operation).
*/
function image_toolkit_invoke($method, $params = array()) {
if ($toolkit = image_get_toolkit()) {
$function = 'image_'. $toolkit .'_'. $method;
if (function_exists($function)) {
return call_user_func_array($function, $params);
}
else {
watchdog('php', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $toolkit, '%function' => $function), WATCHDOG_ERROR);
return FALSE;
}
}
}
/**
* Get details about an image.
*
* Drupal only supports GIF, JPG and PNG file formats.
*
* @return
* FALSE, if the file could not be found or is not an image. Otherwise, a
* keyed array containing information about the image:
* 'width' - Width in pixels.
* 'height' - Height in pixels.
* 'extension' - Commonly used file extension for the image.
* 'mime_type' - MIME type ('image/jpeg', 'image/gif', 'image/png').
* 'file_size' - File size in bytes.
*/
function image_get_info($file) {
// Proceed no further if this file doesn't exist. Some web servers (IIS) may
// not pass is_file() for newly uploaded files, so we need two checks here.
if (!is_file($file) && !is_uploaded_file($file)) {
return FALSE;
}
$details = FALSE;
$data = @getimagesize($file);
$file_size = @filesize($file);
if (isset($data) && is_array($data)) {
$extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
$extension = array_key_exists($data[2], $extensions) ? $extensions[$data[2]] : '';
$details = array('width' => $data[0],
'height' => $data[1],
'extension' => $extension,
'file_size' => $file_size,
'mime_type' => $data['mime']);
}
return $details;
}
/**
* Scales an image to the exact width and height given. Achieves the
* target aspect ratio by cropping the original image equally on both
* sides, or equally on the top and bottom. This function is, for
* example, useful to create uniform sized avatars from larger images.
*
* The resulting image always has the exact target dimensions.
*
* @param $source
* The file path of the source image.
* @param $destination
* The file path of the destination image.
* @param $width
* The target width, in pixels.
* @param $height
* The target height, in pixels.
* @return
* TRUE or FALSE, based on success.
*/
function image_scale_and_crop($source, $destination, $width, $height) {
$info = image_get_info($source);
$scale = max($width / $info['width'], $height / $info['height']);
$x = round(($info['width'] * $scale - $width) / 2);
$y = round(($info['height'] * $scale - $height) / 2);
if (image_toolkit_invoke('resize', array($source, $destination, $info['width'] * $scale, $info['height'] * $scale))) {
return image_toolkit_invoke('crop', array($destination, $destination, $x, $y, $width, $height));
}
return FALSE;
}
/**
* Scales an image to the given width and height while maintaining aspect
* ratio.
*
* The resulting image can be smaller for one or both target dimensions.
*
* @param $source
* The file path of the source image.
* @param $destination
* The file path of the destination image.
* @param $width
* The target width, in pixels.
* @param $height
* The target height, in pixels.
* @return
* TRUE or FALSE, based on success.
*/
function image_scale($source, $destination, $width, $height) {
$info = image_get_info($source);
// Don't scale up.
if ($width >= $info['width'] && $height >= $info['height']) {
return FALSE;
}
$aspect = $info['height'] / $info['width'];
if ($aspect < $height / $width) {
$width = (int)min($width, $info['width']);
$height = (int)round($width * $aspect);
}
else {
$height = (int)min($height, $info['height']);
$width = (int)round($height / $aspect);
}
return image_toolkit_invoke('resize', array($source, $destination, $width, $height));
}
/**
* Resize an image to the given dimensions (ignoring aspect ratio).
*
* @param $source
* The file path of the source image.
* @param $destination
* The file path of the destination image.
* @param $width
* The target width, in pixels.
* @param $height
* The target height, in pixels.
* @return
* TRUE or FALSE, based on success.
*/
function image_resize($source, $destination, $width, $height) {
return image_toolkit_invoke('resize', array($source, $destination, $width, $height));
}
/**
* Rotate an image by the given number of degrees.
*
* @param $source
* The file path of the source image.
* @param $destination
* The file path of the destination image.
* @param $degrees
* The number of (clockwise) degrees to rotate the image.
* @param $background
* An hexidecimal integer specifying the background color to use for the
* uncovered area of the image after the rotation. E.g. 0x000000 for black,
* 0xff00ff for magenta, and 0xffffff for white.
* @return
* TRUE or FALSE, based on success.
*/
function image_rotate($source, $destination, $degrees, $background = 0x000000) {
return image_toolkit_invoke('rotate', array($source, $destination, $degrees, $background));
}
/**
* Crop an image to the rectangle specified by the given rectangle.
*
* @param $source
* The file path of the source image.
* @param $destination
* The file path of the destination image.
* @param $x
* The top left co-ordinate, in pixels, of the crop area (x axis value).
* @param $y
* The top left co-ordinate, in pixels, of the crop area (y axis value).
* @param $width
* The target width, in pixels.
* @param $height
* The target height, in pixels.
* @return
* TRUE or FALSE, based on success.
*/
function image_crop($source, $destination, $x, $y, $width, $height) {
return image_toolkit_invoke('crop', array($source, $destination, $x, $y, $width, $height));
}
/**
* @} End of "defgroup image".
*/

736
includes/install.inc Normal file
View file

@ -0,0 +1,736 @@
<?php
define('SCHEMA_UNINSTALLED', -1);
define('SCHEMA_INSTALLED', 0);
define('REQUIREMENT_INFO', -1);
define('REQUIREMENT_OK', 0);
define('REQUIREMENT_WARNING', 1);
define('REQUIREMENT_ERROR', 2);
define('FILE_EXIST', 1);
define('FILE_READABLE', 2);
define('FILE_WRITABLE', 4);
define('FILE_EXECUTABLE', 8);
define('FILE_NOT_EXIST', 16);
define('FILE_NOT_READABLE', 32);
define('FILE_NOT_WRITABLE', 64);
define('FILE_NOT_EXECUTABLE', 128);
/**
* Initialize the update system by loading all installed module's .install files.
*/
function drupal_load_updates() {
foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) {
if ($schema_version > -1) {
module_load_install($module);
}
}
}
/**
* Returns an array of available schema versions for a module.
*
* @param $module
* A module name.
* @return
* If the module has updates, an array of available updates sorted by version.
* Otherwise, FALSE.
*/
function drupal_get_schema_versions($module) {
$updates = array();
$functions = get_defined_functions();
foreach ($functions['user'] as $function) {
if (strpos($function, $module .'_update_') === 0) {
$version = substr($function, strlen($module .'_update_'));
if (is_numeric($version)) {
$updates[] = $version;
}
}
}
if (count($updates) == 0) {
return FALSE;
}
sort($updates, SORT_NUMERIC);
return $updates;
}
/**
* Returns the currently installed schema version for a module.
*
* @param $module
* A module name.
* @param $reset
* Set to TRUE after modifying the system table.
* @param $array
* Set to TRUE if you want to get information about all modules in the
* system.
* @return
* The currently installed schema version.
*/
function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) {
static $versions = array();
if ($reset) {
$versions = array();
}
if (!$versions) {
$versions = array();
$result = db_query("SELECT name, schema_version FROM {system} WHERE type = '%s'", 'module');
while ($row = db_fetch_object($result)) {
$versions[$row->name] = $row->schema_version;
}
}
return $array ? $versions : $versions[$module];
}
/**
* Update the installed version information for a module.
*
* @param $module
* A module name.
* @param $version
* The new schema version.
*/
function drupal_set_installed_schema_version($module, $version) {
db_query("UPDATE {system} SET schema_version = %d WHERE name = '%s'", $version, $module);
}
/**
* Loads the profile definition, extracting the profile's defined name.
*
* @return
* The name defined in the profile's _profile_details() hook.
*/
function drupal_install_profile_name() {
global $profile;
static $name = NULL;
if (!isset($name)) {
// Load profile details.
$function = $profile .'_profile_details';
if (function_exists($function)) {
$details = $function();
}
$name = isset($details['name']) ? $details['name'] : 'Drupal';
}
return $name;
}
/**
* Auto detect the base_url with PHP predefined variables.
*
* @param $file
* The name of the file calling this function so we can strip it out of
* the URI when generating the base_url.
*
* @return
* The auto-detected $base_url that should be configured in settings.php
*/
function drupal_detect_baseurl($file = 'install.php') {
global $profile;
$proto = $_SERVER['HTTPS'] ? 'https://' : 'http://';
$host = $_SERVER['SERVER_NAME'];
$port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':'. $_SERVER['SERVER_PORT']);
$uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']);
$dir = str_replace("/$file", '', $uri);
return "$proto$host$port$dir";
}
/**
* Detect all databases supported by Drupal that are compiled into the current
* PHP installation.
*
* @return
* An array of database types compiled into PHP.
*/
function drupal_detect_database_types() {
$databases = array();
foreach (array('mysql', 'mysqli', 'pgsql') as $type) {
if (file_exists('./includes/install.'. $type .'.inc')) {
include_once './includes/install.'. $type .'.inc';
$function = $type .'_is_available';
if ($function()) {
$databases[$type] = $type;
}
}
}
return $databases;
}
/**
* Read settings.php into a buffer line by line, changing values specified in
* $settings array, then over-writing the old settings.php file.
*
* @param $settings
* An array of settings that need to be updated.
*/
function drupal_rewrite_settings($settings = array(), $prefix = '') {
$default_settings = './sites/default/default.settings.php';
$settings_file = './'. conf_path(FALSE, TRUE) .'/'. $prefix .'settings.php';
// Build list of setting names and insert the values into the global namespace.
$keys = array();
foreach ($settings as $setting => $data) {
$GLOBALS[$setting] = $data['value'];
$keys[] = $setting;
}
$buffer = NULL;
$first = TRUE;
if ($fp = fopen($default_settings, 'r')) {
// Step line by line through settings.php.
while (!feof($fp)) {
$line = fgets($fp);
if ($first && substr($line, 0, 5) != '<?php') {
$buffer = "<?php\n\n";
}
$first = FALSE;
// Check for constants.
if (substr($line, 0, 7) == 'define(') {
preg_match('/define\(\s*[\'"]([A-Z_-]+)[\'"]\s*,(.*?)\);/', $line, $variable);
if (in_array($variable[1], $keys)) {
$setting = $settings[$variable[1]];
$buffer .= str_replace($variable[2], " '". $setting['value'] ."'", $line);
unset($settings[$variable[1]]);
unset($settings[$variable[2]]);
}
else {
$buffer .= $line;
}
}
// Check for variables.
elseif (substr($line, 0, 1) == '$') {
preg_match('/\$([^ ]*) /', $line, $variable);
if (in_array($variable[1], $keys)) {
// Write new value to settings.php in the following format:
// $'setting' = 'value'; // 'comment'
$setting = $settings[$variable[1]];
$buffer .= '$'. $variable[1] ." = '". $setting['value'] ."';". (!empty($setting['comment']) ? ' // '. $setting['comment'] ."\n" : "\n");
unset($settings[$variable[1]]);
}
else {
$buffer .= $line;
}
}
else {
$buffer .= $line;
}
}
fclose($fp);
// Add required settings that were missing from settings.php.
foreach ($settings as $setting => $data) {
if ($data['required']) {
$buffer .= "\$$setting = '". $data['value'] ."';\n";
}
}
$fp = fopen($settings_file, 'w');
if ($fp && fwrite($fp, $buffer) === FALSE) {
drupal_set_message(st('Failed to modify %settings, please verify the file permissions.', array('%settings' => $settings_file)), 'error');
}
}
else {
drupal_set_message(st('Failed to open %settings, please verify the file permissions.', array('%settings' => $default_settings)), 'error');
}
}
/**
* Get list of all .install files.
*
* @param $module_list
* An array of modules to search for their .install files.
*/
function drupal_get_install_files($module_list = array()) {
$installs = array();
foreach ($module_list as $module) {
$installs = array_merge($installs, drupal_system_listing($module .'.install$', 'modules'));
}
return $installs;
}
/**
* Verify a profile for installation.
*
* @param profile
* Name of profile to verify.
* @param locale
* Name of locale used (if any).
* @return
* The list of modules to install.
*/
function drupal_verify_profile($profile, $locale) {
include_once './includes/file.inc';
include_once './includes/common.inc';
$profile_file = "./profiles/$profile/$profile.profile";
if (!isset($profile) || !file_exists($profile_file)) {
install_no_profile_error();
}
require_once($profile_file);
// Get a list of modules required by this profile.
$function = $profile .'_profile_modules';
$module_list = array_merge(drupal_required_modules(), $function(), ($locale != 'en' && !empty($locale) ? array('locale') : array()));
// Get a list of modules that exist in Drupal's assorted subdirectories.
$present_modules = array();
foreach (drupal_system_listing('\.module$', 'modules', 'name', 0) as $present_module) {
$present_modules[] = $present_module->name;
}
// Verify that all of the profile's required modules are present.
$missing_modules = array_diff($module_list, $present_modules);
if (count($missing_modules)) {
foreach ($missing_modules as $module) {
drupal_set_message(st('The %module module is required but was not found. Please move it into the <em>modules</em> subdirectory.', array('%module' => $module)), 'error');
}
}
else {
return $module_list;
}
}
/**
* Calls the install function and updates the system table for a given list of
* modules.
*
* @param module_list
* The modules to install.
*/
function drupal_install_modules($module_list = array()) {
$files = module_rebuild_cache();
$module_list = array_flip(array_values($module_list));
do {
$moved = FALSE;
foreach ($module_list as $module => $weight) {
$file = $files[$module];
if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
foreach ($file->info['dependencies'] as $dependency) {
if (isset($module_list[$dependency]) && $module_list[$module] < $module_list[$dependency] +1) {
$module_list[$module] = $module_list[$dependency] +1;
$moved = TRUE;
}
}
}
}
} while ($moved);
asort($module_list);
$module_list = array_keys($module_list);
array_filter($module_list, '_drupal_install_module');
module_enable($module_list);
}
/**
* Callback to install an individual profile module.
*
* Used during installation to install modules one at a time and then
* enable them, or to install a number of modules at one time
* from admin/build/modules.
*/
function _drupal_install_module($module) {
if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) {
module_load_install($module);
module_invoke($module, 'install');
$versions = drupal_get_schema_versions($module);
drupal_set_installed_schema_version($module, $versions ? max($versions) : SCHEMA_INSTALLED);
return TRUE;
}
}
/**
* Callback to install the system module.
*
* Separated from the installation of other modules so core system
* functions can be made available while other modules are installed.
*/
function drupal_install_system() {
$system_path = dirname(drupal_get_filename('module', 'system', NULL));
require_once './'. $system_path .'/system.install';
module_invoke('system', 'install');
$system_versions = drupal_get_schema_versions('system');
$system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED;
db_query("INSERT INTO {system} (filename, name, type, owner, status, throttle, bootstrap, schema_version) VALUES('%s', '%s', '%s', '%s', %d, %d, %d, %d)", $system_path .'/system.module', 'system', 'module', '', 1, 0, 0, $system_version);
// Now that we've installed things properly, bootstrap the full Drupal environment
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
module_rebuild_cache();
}
/**
* Calls the uninstall function and updates the system table for a given module.
*
* @param $module
* The module to uninstall.
*/
function drupal_uninstall_module($module) {
// First, retrieve all the module's menu paths from db.
drupal_load('module', $module);
$paths = module_invoke($module, 'menu');
// Uninstall the module(s).
module_load_install($module);
module_invoke($module, 'uninstall');
// Now remove the menu links for all paths declared by this module.
if (!empty($paths)) {
$paths = array_keys($paths);
// Clean out the names of load functions.
foreach ($paths as $index => $path) {
$parts = explode('/', $path, MENU_MAX_PARTS);
foreach ($parts as $k => $part) {
if (preg_match('/^%[a-z_]*$/', $part)) {
$parts[$k] = '%';
}
}
$paths[$index] = implode('/', $parts);
}
$placeholders = implode(', ', array_fill(0, count($paths), "'%s'"));
$result = db_query('SELECT * FROM {menu_links} WHERE router_path IN ('. $placeholders .') AND external = 0 ORDER BY depth DESC', $paths);
// Remove all such items. Starting from those with the greatest depth will
// minimize the amount of re-parenting done by menu_link_delete().
while ($item = db_fetch_array($result)) {
_menu_delete_item($item, TRUE);
}
}
drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
}
/**
* Verify the state of the specified file.
*
* @param $file
* The file to check for.
* @param $mask
* An optional bitmask created from various FILE_* constants.
* @param $type
* The type of file. Can be file (default), dir, or link.
* @return
* TRUE on success or FALSE on failure. A message is set for the latter.
*/
function drupal_verify_install_file($file, $mask = NULL, $type = 'file') {
$return = TRUE;
// Check for files that shouldn't be there.
if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) {
return FALSE;
}
// Verify that the file is the type of file it is supposed to be.
if (isset($type) && file_exists($file)) {
$check = 'is_'. $type;
if (!function_exists($check) || !$check($file)) {
$return = FALSE;
}
}
// Verify file permissions.
if (isset($mask)) {
$masks = array(FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
foreach ($masks as $current_mask) {
if ($mask & $current_mask) {
switch ($current_mask) {
case FILE_EXIST:
if (!file_exists($file)) {
if ($type == 'dir') {
drupal_install_mkdir($file, $mask);
}
if (!file_exists($file)) {
$return = FALSE;
}
}
break;
case FILE_READABLE:
if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) {
$return = FALSE;
}
break;
case FILE_WRITABLE:
if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) {
$return = FALSE;
}
break;
case FILE_EXECUTABLE:
if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) {
$return = FALSE;
}
break;
case FILE_NOT_READABLE:
if (is_readable($file) && !drupal_install_fix_file($file, $mask)) {
$return = FALSE;
}
break;
case FILE_NOT_WRITABLE:
if (is_writable($file) && !drupal_install_fix_file($file, $mask)) {
$return = FALSE;
}
break;
case FILE_NOT_EXECUTABLE:
if (is_executable($file) && !drupal_install_fix_file($file, $mask)) {
$return = FALSE;
}
break;
}
}
}
}
return $return;
}
/**
* Create a directory with specified permissions.
*
* @param file
* The name of the directory to create;
* @param mask
* The permissions of the directory to create.
* @param $message
* (optional) Whether to output messages. Defaults to TRUE.
*
* @return
* TRUE/FALSE whether or not the directory was successfully created.
*/
function drupal_install_mkdir($file, $mask, $message = TRUE) {
$mod = 0;
$masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
foreach ($masks as $m) {
if ($mask & $m) {
switch ($m) {
case FILE_READABLE:
$mod += 444;
break;
case FILE_WRITABLE:
$mod += 222;
break;
case FILE_EXECUTABLE:
$mod += 111;
break;
}
}
}
if (@mkdir($file, intval("0$mod", 8))) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Attempt to fix file permissions.
*
* The general approach here is that, because we do not know the security
* setup of the webserver, we apply our permission changes to all three
* digits of the file permission (i.e. user, group and all).
*
* To ensure that the values behave as expected (and numbers don't carry
* from one digit to the next) we do the calculation on the octal value
* using bitwise operations. This lets us remove, for example, 0222 from
* 0700 and get the correct value of 0500.
*
* @param $file
* The name of the file with permissions to fix.
* @param $mask
* The desired permissions for the file.
* @param $message
* (optional) Whether to output messages. Defaults to TRUE.
*
* @return
* TRUE/FALSE whether or not we were able to fix the file's permissions.
*/
function drupal_install_fix_file($file, $mask, $message = TRUE) {
$mod = fileperms($file) & 0777;
$masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE);
// FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings
// can theoretically be 0400, 0200, and 0100 respectively, but to be safe
// we set all three access types in case the administrator intends to
// change the owner of settings.php after installation.
foreach ($masks as $m) {
if ($mask & $m) {
switch ($m) {
case FILE_READABLE:
if (!is_readable($file)) {
$mod |= 0444;
}
break;
case FILE_WRITABLE:
if (!is_writable($file)) {
$mod |= 0222;
}
break;
case FILE_EXECUTABLE:
if (!is_executable($file)) {
$mod |= 0111;
}
break;
case FILE_NOT_READABLE:
if (is_readable($file)) {
$mod &= ~0444;
}
break;
case FILE_NOT_WRITABLE:
if (is_writable($file)) {
$mod &= ~0222;
}
break;
case FILE_NOT_EXECUTABLE:
if (is_executable($file)) {
$mod &= ~0111;
}
break;
}
}
}
// chmod() will work if the web server is running as owner of the file.
// If PHP safe_mode is enabled the currently executing script must also
// have the same owner.
if (@chmod($file, $mod)) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Send the user to a different installer page. This issues an on-site HTTP
* redirect. Messages (and errors) are erased.
*
* @param $path
* An installer path.
*/
function install_goto($path) {
global $base_url;
header('Location: '. $base_url .'/'. $path);
header('Cache-Control: no-cache'); // Not a permanent redirect.
exit();
}
/**
* Hardcoded function for doing the equivalent of t() during
* the install process, when database, theme, and localization
* system is possibly not yet available.
*/
function st($string, $args = array()) {
static $locale_strings = NULL;
global $profile, $install_locale;
if (!isset($locale_strings)) {
$locale_strings = array();
$filename = './profiles/'. $profile .'/translations/'. $install_locale .'.po';
if (file_exists($filename)) {
require_once './includes/locale.inc';
$file = (object) array('filepath' => $filename);
_locale_import_read_po('mem-store', $file);
$locale_strings = _locale_import_one_string('mem-report');
}
}
require_once './includes/theme.inc';
// Transform arguments before inserting them
foreach ($args as $key => $value) {
switch ($key[0]) {
// Escaped only
case '@':
$args[$key] = check_plain($value);
break;
// Escaped and placeholder
case '%':
default:
$args[$key] = '<em>'. check_plain($value) .'</em>';
break;
// Pass-through
case '!':
}
}
return strtr((!empty($locale_strings[$string]) ? $locale_strings[$string] : $string), $args);
}
/**
* Check a profile's requirements.
*
* @param profile
* Name of profile to check.
*/
function drupal_check_profile($profile) {
include_once './includes/file.inc';
$profile_file = "./profiles/$profile/$profile.profile";
if (!isset($profile) || !file_exists($profile_file)) {
install_no_profile_error();
}
require_once($profile_file);
// Get a list of modules required by this profile.
$function = $profile .'_profile_modules';
$module_list = array_unique(array_merge(drupal_required_modules(), $function()));
// Get a list of all .install files.
$installs = drupal_get_install_files($module_list);
// Collect requirement testing results
$requirements = array();
foreach ($installs as $install) {
require_once $install->filename;
if (module_hook($install->name, 'requirements')) {
$requirements = array_merge($requirements, module_invoke($install->name, 'requirements', 'install'));
}
}
return $requirements;
}
/**
* Extract highest severity from requirements array.
*/
function drupal_requirements_severity(&$requirements) {
$severity = REQUIREMENT_OK;
foreach ($requirements as $requirement) {
if (isset($requirement['severity'])) {
$severity = max($severity, $requirement['severity']);
}
}
return $severity;
}
/**
* Check a module's requirements.
*/
function drupal_check_module($module) {
// Include install file
$install = drupal_get_install_files(array($module));
if (isset($install[$module])) {
require_once $install[$module]->filename;
// Check requirements
$requirements = module_invoke($module, 'requirements', 'install');
if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) {
// Print any error messages
foreach ($requirements as $requirement) {
if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) {
$message = $requirement['description'];
if (isset($requirement['value']) && $requirement['value']) {
$message .= ' ('. t('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) .')';
}
drupal_set_message($message, 'error');
}
}
return FALSE;
}
}
return TRUE;
}

116
includes/install.mysql.inc Normal file
View file

@ -0,0 +1,116 @@
<?php
// MySQL specific install functions
/**
* Check if MySQL is available.
*
* @return
* TRUE/FALSE
*/
function mysql_is_available() {
return function_exists('mysql_connect');
}
/**
* Check if we can connect to MySQL.
*
* @return
* TRUE/FALSE
*/
function drupal_test_mysql($url, &$success) {
if (!mysql_is_available()) {
drupal_set_message(st('PHP MySQL support not enabled.'), 'error');
return FALSE;
}
$url = parse_url($url);
// Decode urlencoded information in the db connection string.
$url['user'] = urldecode($url['user']);
$url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
$url['host'] = urldecode($url['host']);
$url['path'] = urldecode($url['path']);
// Allow for non-standard MySQL port.
if (isset($url['port'])) {
$url['host'] = $url['host'] .':'. $url['port'];
}
// Test connecting to the database.
$connection = @mysql_connect($url['host'], $url['user'], $url['pass'], TRUE, 2);
if (!$connection) {
drupal_set_message(st('Failed to connect to your MySQL database server. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysql_error())), 'error');
return FALSE;
}
// Test selecting the database.
if (!mysql_select_db(substr($url['path'], 1))) {
drupal_set_message(st('Failed to select your database on your MySQL database server, which means the connection username and password are valid, but there is a problem accessing your data. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct database name?</li><li>Are you sure the database exists?</li><li>Are you sure the username has permission to access the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysql_error())), 'error');
return FALSE;
}
$success = array('CONNECT');
// Test CREATE.
$query = 'CREATE TABLE drupal_install_test (id int NULL)';
$result = mysql_query($query);
if ($error = mysql_error()) {
drupal_set_message(st('Failed to create a test table on your MySQL database server with the command %query. MySQL reports the following message: %error.<ul><li>Are you sure the configured username has the necessary MySQL permissions to create tables in the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%query' => $query, '%error' => $error)), 'error');
return FALSE;
}
$err = FALSE;
$success[] = 'SELECT';
$success[] = 'CREATE';
// Test INSERT.
$query = 'INSERT INTO drupal_install_test (id) VALUES (1)';
$result = mysql_query($query);
if ($error = mysql_error()) {
drupal_set_message(st('Failed to insert a value into a test table on your MySQL database server. We tried inserting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'INSERT';
}
// Test UPDATE.
$query = 'UPDATE drupal_install_test SET id = 2';
$result = mysql_query($query);
if ($error = mysql_error()) {
drupal_set_message(st('Failed to update a value in a test table on your MySQL database server. We tried updating a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'UPDATE';
}
// Test DELETE.
$query = 'DELETE FROM drupal_install_test';
$result = mysql_query($query);
if ($error = mysql_error()) {
drupal_set_message(st('Failed to delete a value from a test table on your MySQL database server. We tried deleting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'DELETE';
}
// Test DROP.
$query = 'DROP TABLE drupal_install_test';
$result = mysql_query($query);
if ($error = mysql_error()) {
drupal_set_message(st('Failed to drop a test table from your MySQL database server. We tried dropping a table with the command %query and MySQL reported the following error %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'DROP';
}
if ($err) {
return FALSE;
}
mysql_close($connection);
return TRUE;
}

111
includes/install.mysqli.inc Normal file
View file

@ -0,0 +1,111 @@
<?php
// MySQLi specific install functions
/**
* Check if MySQLi is available.
*
* @return
* TRUE/FALSE
*/
function mysqli_is_available() {
return function_exists('mysqli_connect');
}
/**
* Check if we can connect to MySQL.
*
* @return
* TRUE/FALSE
*/
function drupal_test_mysqli($url, &$success) {
if (!mysqli_is_available()) {
drupal_set_message(st('PHP MySQLi support not enabled.'), 'error');
return FALSE;
}
$url = parse_url($url);
// Decode urlencoded information in the db connection string.
$url['user'] = urldecode($url['user']);
$url['pass'] = isset($url['pass']) ? urldecode($url['pass']) : '';
$url['host'] = urldecode($url['host']);
$url['path'] = urldecode($url['path']);
$connection = mysqli_init();
@mysqli_real_connect($connection, $url['host'], $url['user'], $url['pass'], substr($url['path'], 1), $url['port'], NULL, MYSQLI_CLIENT_FOUND_ROWS);
if (mysqli_connect_errno() >= 2000 || mysqli_connect_errno() == 1045) {
drupal_set_message(st('Failed to connect to your MySQL database server. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysqli_connect_error())), 'error');
return FALSE;
}
// Test selecting the database.
if (mysqli_connect_errno() > 0) {
drupal_set_message(st('Failed to select your database on your MySQL database server, which means the connection username and password are valid, but there is a problem accessing your data. MySQL reports the following message: %error.<ul><li>Are you sure you have the correct database name?</li><li>Are you sure the database exists?</li><li>Are you sure the username has permission to access the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => mysqli_connect_error())), 'error');
return FALSE;
}
$success = array('CONNECT');
// Test CREATE.
$query = 'CREATE TABLE drupal_install_test (id int NULL)';
$result = mysqli_query($connection, $query);
if ($error = mysqli_error($connection)) {
drupal_set_message(st('Failed to create a test table on your MySQL database server with the command %query. MySQL reports the following message: %error.<ul><li>Are you sure the configured username has the necessary MySQL permissions to create tables in the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%query' => $query, '%error' => $error)), 'error');
return FALSE;
}
$err = FALSE;
$success[] = 'SELECT';
$success[] = 'CREATE';
// Test INSERT.
$query = 'INSERT INTO drupal_install_test (id) VALUES (1)';
$result = mysqli_query($connection, $query);
if ($error = mysqli_error($connection)) {
drupal_set_message(st('Failed to insert a value into a test table on your MySQL database server. We tried inserting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'INSERT';
}
// Test UPDATE.
$query = 'UPDATE drupal_install_test SET id = 2';
$result = mysqli_query($connection, $query);
if ($error = mysqli_error($connection)) {
drupal_set_message(st('Failed to update a value in a test table on your MySQL database server. We tried updating a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'UPDATE';
}
// Test DELETE.
$query = 'DELETE FROM drupal_install_test';
$result = mysqli_query($connection, $query);
if ($error = mysqli_error($connection)) {
drupal_set_message(st('Failed to delete a value from a test table on your MySQL database server. We tried deleting a value with the command %query and MySQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'DELETE';
}
// Test DROP.
$query = 'DROP TABLE drupal_install_test';
$result = mysqli_query($connection, $query);
if ($error = mysqli_error($connection)) {
drupal_set_message(st('Failed to drop a test table from your MySQL database server. We tried dropping a table with the command %query and MySQL reported the following error %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'DROP';
}
if ($err) {
return FALSE;
}
mysqli_close($connection);
return TRUE;
}

139
includes/install.pgsql.inc Normal file
View file

@ -0,0 +1,139 @@
<?php
// PostgreSQL specific install functions
/**
* Check if PostgreSQL is available.
*
* @return
* TRUE/FALSE
*/
function pgsql_is_available() {
return function_exists('pg_connect');
}
/**
* Check if we can connect to PostgreSQL.
*
* @return
* TRUE/FALSE
*/
function drupal_test_pgsql($url, &$success) {
if (!pgsql_is_available()) {
drupal_set_message(st('PHP PostgreSQL support not enabled.'), 'error');
return FALSE;
}
$url = parse_url($url);
$conn_string = '';
// Decode urlencoded information in the db connection string
if (isset($url['user'])) {
$conn_string .= ' user='. urldecode($url['user']);
}
if (isset($url['pass'])) {
$conn_string .= ' password='. urldecode($url['pass']);
}
if (isset($url['host'])) {
$conn_string .= ' host='. urldecode($url['host']);
}
if (isset($url['path'])) {
$conn_string .= ' dbname='. substr(urldecode($url['path']), 1);
}
if (isset($url['port'])) {
$conn_string .= ' port='. urldecode($url['port']);
}
// Test connecting to the database.
$connection = @pg_connect($conn_string);
if (!$connection) {
drupal_set_message(st('Failed to connect to your PostgreSQL database server. PostgreSQL reports the following message: %error.<ul><li>Are you sure you have the correct username and password?</li><li>Are you sure that you have typed the correct database hostname?</li><li>Are you sure that the database server is running?</li><li>Are you sure you typed the correct database name?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%error' => 'Connection failed. See log file for failure reason')), 'error');
return FALSE;
}
$success = array('CONNECT');
// Test CREATE.
$query = 'CREATE TABLE drupal_install_test (id integer NOT NULL)';
$result = pg_query($connection, $query);
if ($error = pg_result_error($result)) {
drupal_set_message(st('Failed to create a test table on your PostgreSQL database server with the command %query. PostgreSQL reports the following message: %error.<ul><li>Are you sure the configured username has the necessary PostgreSQL permissions to create tables in the database?</li></ul>For more help, see the <a href="http://drupal.org/node/258">Installation and upgrading handbook</a>. If you are unsure what these terms mean you should probably contact your hosting provider.', array('%query' => $query, '%error' => $error)), 'error');
return FALSE;
}
$err = FALSE;
$success[] = 'SELECT';
$success[] = 'CREATE';
// Test INSERT.
$query = 'INSERT INTO drupal_install_test (id) VALUES (1)';
$result = pg_query($connection, $query);
if ($error = pg_result_error($result)) {
drupal_set_message(st('Failed to insert a value into a test table on your PostgreSQL database server. We tried inserting a value with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'INSERT';
}
// Test UPDATE.
$query = 'UPDATE drupal_install_test SET id = 2';
$result = pg_query($connection, $query);
if ($error = pg_result_error($result)) {
drupal_set_message(st('Failed to update a value in a test table on your PostgreSQL database server. We tried updating a value with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'UPDATE';
}
// Test LOCK.
$query = 'BEGIN; LOCK drupal_install_test IN SHARE ROW EXCLUSIVE MODE';
$result = pg_query($connection, $query);
if ($error = pg_result_error($result)) {
drupal_set_message(st('Failed to lock a test table on your PostgreSQL database server. We tried locking a table with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'LOCK';
}
// Test UNLOCK, which is done automatically upon transaction end in PostgreSQL
$query = 'COMMIT';
$result = pg_query($connection, $query);
if ($error = pg_result_error()) {
drupal_set_message(st('Failed to unlock a test table on your PostgreSQL database server. We tried unlocking a table with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'UNLOCK';
}
// Test DELETE.
$query = 'DELETE FROM drupal_install_test';
$result = pg_query($connection, $query);
if ($error = pg_result_error()) {
drupal_set_message(st('Failed to delete a value from a test table on your PostgreSQL database server. We tried deleting a value with the command %query and PostgreSQL reported the following error: %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'DELETE';
}
// Test DROP.
$query = 'DROP TABLE drupal_install_test';
$result = pg_query($connection, $query);
if ($error = pg_result_error()) {
drupal_set_message(st('Failed to drop a test table from your PostgreSQL database server. We tried dropping a table with the command %query and PostgreSQL reported the following error %error.', array('%query' => $query, '%error' => $error)), 'error');
$err = TRUE;
}
else {
$success[] = 'DROP';
}
if ($err) {
return FALSE;
}
pg_close($connection);
return TRUE;
}

143
includes/language.inc Normal file
View file

@ -0,0 +1,143 @@
<?php
/**
* @file
* Multiple language handling functionality.
*/
/**
* Choose a language for the page, based on language negotiation settings.
*/
function language_initialize() {
global $user;
// Configured presentation language mode.
$mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
// Get a list of enabled languages.
$languages = language_list('enabled');
$languages = $languages[1];
switch ($mode) {
case LANGUAGE_NEGOTIATION_NONE:
return language_default();
case LANGUAGE_NEGOTIATION_DOMAIN:
foreach ($languages as $language) {
$parts = parse_url($language->domain);
if (!empty($parts['host']) && ($_SERVER['HTTP_HOST'] == $parts['host'])) {
return $language;
}
}
return language_default();
case LANGUAGE_NEGOTIATION_PATH_DEFAULT:
case LANGUAGE_NEGOTIATION_PATH:
// $_GET['q'] might not be available at this time, because
// path initialization runs after the language bootstrap phase.
$args = isset($_GET['q']) ? explode('/', $_GET['q']) : array();
$prefix = array_shift($args);
// Search prefix within enabled languages.
foreach ($languages as $language) {
if (!empty($language->prefix) && $language->prefix == $prefix) {
// Rebuild $GET['q'] with the language removed.
$_GET['q'] = implode('/', $args);
return $language;
}
}
if ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) {
// If we did not found the language by prefix, choose the default.
return language_default();
}
break;
}
// User language.
if ($user->uid && isset($languages[$user->language])) {
return $languages[$user->language];
}
// Browser accept-language parsing.
if ($language = language_from_browser()) {
return $language;
}
// Fall back on the default if everything else fails.
return language_default();
}
/**
* Identify language from the Accept-language HTTP header we got.
*/
function language_from_browser() {
// Specified by the user via the browser's Accept Language setting
// Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
$browser_langs = array();
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
for ($i = 0; $i < count($browser_accept); $i++) {
// The language part is either a code or a code with a quality.
// We cannot do anything with a * code, so it is skipped.
// If the quality is missing, it is assumed to be 1 according to the RFC.
if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($browser_accept[$i]), $found)) {
$browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0);
}
}
}
// Order the codes by quality
arsort($browser_langs);
// Try to find the first preferred language we have
$languages = language_list('enabled');
foreach ($browser_langs as $langcode => $q) {
if (isset($languages['1'][$langcode])) {
return $languages['1'][$langcode];
}
}
}
/**
* Rewrite URL's with language based prefix. Parameters are the same
* as those of the url() function.
*/
function language_url_rewrite(&$path, &$options) {
global $language;
// Only modify relative (insite) URLs.
if (empty($options['external'])) {
// Language can be passed as an option, or we go for current language.
if (!isset($options['language'])) {
$options['language'] = $language;
}
switch (variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) {
case LANGUAGE_NEGOTIATION_NONE:
// No language dependent path allowed in this mode.
unset($options['language']);
break;
case LANGUAGE_NEGOTIATION_DOMAIN:
if ($options['language']->domain) {
// Ask for an absolute URL with our modified base_url.
$options['absolute'] = TRUE;
$options['base_url'] = $options['language']->domain;
}
break;
case LANGUAGE_NEGOTIATION_PATH_DEFAULT:
$default = language_default();
if ($options['language']->language == $default->language) {
break;
}
// Intentionally no break here.
case LANGUAGE_NEGOTIATION_PATH:
if (!empty($options['language']->prefix)) {
$options['prefix'] = $options['language']->prefix .'/';
}
break;
}
}
}

2658
includes/locale.inc Normal file

File diff suppressed because it is too large Load diff

62
includes/lock-install.inc Normal file
View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* A stub lock implementation to be used during the installation
* process when database access is not yet available. Because Drupal's
* install system should never be running in more than on concurrant
* request, we can bypass any need for locking.
*/
/**
* Initialize the locking system.
*/
function lock_init() {
}
/**
* Acquire (or renew) a lock, but do not block if it fails.
*
* @return
* TRUE if the lock was acquired, FALSE if it failed.
*/
function lock_acquire($name, $timeout = 30.0) {
return TRUE;
}
/**
* Check if lock acquired by a different process may be available.
*
* @return
* TRUE if there is no lock or it was removed, FALSE otherwise.
*/
function lock_may_be_available($name) {
return TRUE;
}
/**
* Wait for a lock to be available.
*
* @return
* TRUE if the lock holds, FALSE if it is available.
*/
function lock_wait($name, $delay = 30) {
return FALSE;
}
/**
* Release a lock previously acquired by lock_acquire().
*
* This will release the named lock if it is still held by the current request.
*
* @param $name
* The name of the lock.
*/
function lock_release($name) {
}
/**
* Release all previously acquired locks.
*/
function lock_release_all($lock_id = NULL) {
}

229
includes/lock.inc Normal file
View file

@ -0,0 +1,229 @@
<?php
/**
* @file
* A database-mediated implementation of a locking mechanism.
*/
/**
* @defgroup lock Functions to coordinate long-running operations across requests.
* @{
* In most environments, multiple Drupal page requests (a.k.a. threads or
* processes) will execute in parallel. This leads to potential conflicts or
* race conditions when two requests execute the same code at the same time. A
* common example of this is a rebuild like menu_rebuild() where we invoke many
* hook implementations to get and process data from all active modules, and
* then delete the current data in the database to insert the new afterwards.
*
* This is a cooperative, advisory lock system. Any long-running operation
* that could potentially be attempted in parallel by multiple requests should
* try to acquire a lock before proceeding. By obtaining a lock, one request
* notifies any other requests that a specific opertation is in progress which
* must not be executed in parallel.
*
* To use this API, pick a unique name for the lock. A sensible choice is the
* name of the function performing the operation. A very simple example use of
* this API:
* @code
* function mymodule_long_operation() {
* if (lock_acquire('mymodule_long_operation')) {
* // Do the long operation here.
* // ...
* lock_release('mymodule_long_operation');
* }
* }
* @endcode
*
* If a function acquires a lock it should always release it when the
* operation is complete by calling lock_release(), as in the example.
*
* A function that has acquired a lock may attempt to renew a lock (extend the
* duration of the lock) by calling lock_acquire() again during the operation.
* Failure to renew a lock is indicative that another request has acquired
* the lock, and that the current operation may need to be aborted.
*
* If a function fails to acquire a lock it may either immediately return, or
* it may call lock_wait() if the rest of the current page request requires
* that the operation in question be complete. After lock_wait() returns,
* the function may again attempt to acquire the lock, or may simply allow the
* page request to proceed on the assumption that a parallel request completed
* the operation.
*
* lock_acquire() and lock_wait() will automatically break (delete) a lock
* whose duration has exceeded the timeout specified when it was acquired.
*
* Alternative implementations of this API (such as APC) may be substituted
* by setting the 'lock_inc' variable to an alternate include filepath. Since
* this is an API intended to support alternative implementations, code using
* this API should never rely upon specific implementation details (for example
* no code should look for or directly modify a lock in the {semaphore} table).
*/
/**
* Initialize the locking system.
*/
function lock_init() {
global $locks;
$locks = array();
}
/**
* Helper function to get this request's unique id.
*/
function _lock_id() {
static $lock_id;
if (!isset($lock_id)) {
// Assign a unique id.
$lock_id = uniqid(mt_rand(), TRUE);
// We only register a shutdown function if a lock is used.
register_shutdown_function('lock_release_all', $lock_id);
}
return $lock_id;
}
/**
* Acquire (or renew) a lock, but do not block if it fails.
*
* @param $name
* The name of the lock.
* @param $timeout
* A number of seconds (float) before the lock expires.
* @return
* TRUE if the lock was acquired, FALSE if it failed.
*/
function lock_acquire($name, $timeout = 30.0) {
global $locks;
// Insure that the timeout is at least 1 ms.
$timeout = max($timeout, 0.001);
list($usec, $sec) = explode(' ', microtime());
$expire = (float)$usec + (float)$sec + $timeout;
if (isset($locks[$name])) {
// Try to extend the expiration of a lock we already acquired.
db_query("UPDATE {semaphore} SET expire = %f WHERE name = '%s' AND value = '%s'", $expire, $name, _lock_id());
if (!db_affected_rows()) {
// The lock was broken.
unset($locks[$name]);
}
}
else {
// Optimistically try to acquire the lock, then retry once if it fails.
// The first time through the loop cannot be a retry.
$retry = FALSE;
// We always want to do this code at least once.
do {
if (@db_query("INSERT INTO {semaphore} (name, value, expire) VALUES ('%s', '%s', %f)", $name, _lock_id(), $expire)) {
// We track all acquired locks in the global variable.
$locks[$name] = TRUE;
// We never need to try again.
$retry = FALSE;
}
else {
// Suppress the error. If this is our first pass through the loop,
// then $retry is FALSE. In this case, the insert must have failed
// meaning some other request acquired the lock but did not release it.
// We decide whether to retry by checking lock_may_be_available()
// Since this will break the lock in case it is expired.
$retry = $retry ? FALSE : lock_may_be_available($name);
}
// We only retry in case the first attempt failed, but we then broke
// an expired lock.
} while ($retry);
}
return isset($locks[$name]);
}
/**
* Check if lock acquired by a different process may be available.
*
* If an existing lock has expired, it is removed.
*
* @param $name
* The name of the lock.
* @return
* TRUE if there is no lock or it was removed, FALSE otherwise.
*/
function lock_may_be_available($name) {
$lock = db_fetch_array(db_query("SELECT expire, value FROM {semaphore} WHERE name = '%s'", $name));
if (!$lock) {
return TRUE;
}
$expire = (float) $lock['expire'];
list($usec, $sec) = explode(' ', microtime());
$now = (float)$usec + (float)$sec;
if ($now > $lock['expire']) {
// We check two conditions to prevent a race condition where another
// request acquired the lock and set a new expire time. We add a small
// number to $expire to avoid errors with float to string conversion.
db_query("DELETE FROM {semaphore} WHERE name = '%s' AND value = '%s' AND expire <= %f", $name, $lock['value'], 0.0001 + $expire);
return (bool)db_affected_rows();
}
return FALSE;
}
/**
* Wait for a lock to be available.
*
* This function may be called in a request that fails to acquire a desired
* lock. This will block further execution until the lock is available or the
* specified delay in seconds is reached. This should not be used with locks
* that are acquired very frequently, since the lock is likely to be acquired
* again by a different request during the sleep().
*
* @param $name
* The name of the lock.
* @param $delay
* The maximum number of seconds to wait, as an integer.
* @return
* TRUE if the lock holds, FALSE if it is available.
*/
function lock_wait($name, $delay = 30) {
while ($delay--) {
// This function should only be called by a request that failed to get a
// lock, so we sleep first to give the parallel request a chance to finish
// and release the lock.
sleep(1);
if (lock_may_be_available($name)) {
// No longer need to wait.
return FALSE;
}
}
// The caller must still wait longer to get the lock.
return TRUE;
}
/**
* Release a lock previously acquired by lock_acquire().
*
* This will release the named lock if it is still held by the current request.
*
* @param $name
* The name of the lock.
*/
function lock_release($name) {
global $locks;
unset($locks[$name]);
db_query("DELETE FROM {semaphore} WHERE name = '%s' AND value = '%s'", $name, _lock_id());
}
/**
* Release all previously acquired locks.
*/
function lock_release_all($lock_id = NULL) {
global $locks;
$locks = array();
if (empty($lock_id)) {
$lock_id = _lock_id();
}
db_query("DELETE FROM {semaphore} WHERE value = '%s'", _lock_id());
}
/**
* @} End of "defgroup locks".
*/

482
includes/mail.inc Normal file
View file

@ -0,0 +1,482 @@
<?php
/**
* Compose and optionally send an e-mail message.
*
* Sending an e-mail works with defining an e-mail template (subject, text
* and possibly e-mail headers) and the replacement values to use in the
* appropriate places in the template. Processed e-mail templates are
* requested from hook_mail() from the module sending the e-mail. Any module
* can modify the composed e-mail message array using hook_mail_alter().
* Finally drupal_mail_send() sends the e-mail, which can be reused
* if the exact same composed e-mail is to be sent to multiple recipients.
*
* Finding out what language to send the e-mail with needs some consideration.
* If you send e-mail to a user, her preferred language should be fine, so
* use user_preferred_language(). If you send email based on form values
* filled on the page, there are two additional choices if you are not
* sending the e-mail to a user on the site. You can either use the language
* used to generate the page ($language global variable) or the site default
* language. See language_default(). The former is good if sending e-mail to
* the person filling the form, the later is good if you send e-mail to an
* address previously set up (like contact addresses in a contact form).
*
* Taking care of always using the proper language is even more important
* when sending e-mails in a row to multiple users. Hook_mail() abstracts
* whether the mail text comes from an administrator setting or is
* static in the source code. It should also deal with common mail tokens,
* only receiving $params which are unique to the actual e-mail at hand.
*
* An example:
*
* @code
* function example_notify($accounts) {
* foreach ($accounts as $account) {
* $params['account'] = $account;
* // example_mail() will be called based on the first drupal_mail() parameter.
* drupal_mail('example', 'notice', $account->mail, user_preferred_language($account), $params);
* }
* }
*
* function example_mail($key, &$message, $params) {
* $language = $message['language'];
* $variables = user_mail_tokens($params['account'], $language);
* switch($key) {
* case 'notice':
* $message['subject'] = t('Notification from !site', $variables, $language->language);
* $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, $language->language);
* break;
* }
* }
* @endcode
*
* @param $module
* A module name to invoke hook_mail() on. The {$module}_mail() hook will be
* called to complete the $message structure which will already contain common
* defaults.
* @param $key
* A key to identify the e-mail sent. The final e-mail id for e-mail altering
* will be {$module}_{$key}.
* @param $to
* The e-mail address or addresses where the message will be sent to. The
* formatting of this string must comply with
* @link http://tools.ietf.org/html/rfc5322 RFC 5322 @endlink.
* Some examples are:
* - user@example.com
* - user@example.com, anotheruser@example.com
* - User <user@example.com>
* - User <user@example.com>, Another User <anotheruser@example.com>
* @param $language
* Language object to use to compose the e-mail.
* @param $params
* Optional parameters to build the e-mail.
* @param $from
* Sets From to this value, if given.
* @param $send
* Send the message directly, without calling drupal_mail_send() manually.
*
* @return
* The $message array structure containing all details of the
* message. If already sent ($send = TRUE), then the 'result' element
* will contain the success indicator of the e-mail, failure being already
* written to the watchdog. (Success means nothing more than the message being
* accepted at php-level, which still doesn't guarantee it to be delivered.)
*/
function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) {
$default_from = variable_get('site_mail', ini_get('sendmail_from'));
// Bundle up the variables into a structured array for altering.
$message = array(
'id' => $module .'_'. $key,
'to' => $to,
'from' => isset($from) ? $from : $default_from,
'language' => $language,
'params' => $params,
'subject' => '',
'body' => array()
);
// Build the default headers
$headers = array(
'MIME-Version' => '1.0',
'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes',
'Content-Transfer-Encoding' => '8Bit',
'X-Mailer' => 'Drupal'
);
if ($default_from) {
// To prevent e-mail from looking like spam, the addresses in the Sender and
// Return-Path headers should have a domain authorized to use the originating
// SMTP server. Errors-To is redundant, but shouldn't hurt.
$headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $default_from;
}
if ($from) {
$headers['From'] = $from;
}
$message['headers'] = $headers;
// Build the e-mail (get subject and body, allow additional headers) by
// invoking hook_mail() on this module. We cannot use module_invoke() as
// we need to have $message by reference in hook_mail().
if (function_exists($function = $module .'_mail')) {
$function($key, $message, $params);
}
// Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail.
drupal_alter('mail', $message);
// Concatenate and wrap the e-mail body.
$message['body'] = is_array($message['body']) ? drupal_wrap_mail(implode("\n\n", $message['body'])) : drupal_wrap_mail($message['body']);
// Optionally send e-mail.
if ($send) {
$message['result'] = drupal_mail_send($message);
// Log errors
if (!$message['result']) {
watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR);
drupal_set_message(t('Unable to send e-mail. Please contact the site administrator if the problem persists.'), 'error');
}
}
return $message;
}
/**
* Send an e-mail message, using Drupal variables and default settings.
* More information in the <a href="http://php.net/manual/en/function.mail.php">
* PHP function reference for mail()</a>. See drupal_mail() for information on
* how $message is composed.
*
* @param $message
* Message array with at least the following elements:
* - id: A unique identifier of the e-mail type. Examples:
* 'contact_user_copy', 'user_password_reset'.
* - to: The mail address or addresses where the message will be sent to. The
* formatting of this string must comply with
* @link http://tools.ietf.org/html/rfc5322 RFC 5322 @endlink.
* Some examples are:
* - user@example.com
* - user@example.com, anotheruser@example.com
* - User <user@example.com>
* - User <user@example.com>, Another User <anotheruser@example.com>
* - subject: Subject of the e-mail to be sent. This must not contain any
* newline characters, or the mail may not be sent properly.
* - body: Message to be sent. Accepts both CRLF and LF line-endings.
* E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
* smart plain text wrapping.
* - headers: Associative array containing all mail headers.
*
* @return
* Returns TRUE if the mail was successfully accepted for delivery,
* FALSE otherwise.
*/
function drupal_mail_send($message) {
// Allow for a custom mail backend.
if (variable_get('smtp_library', '') && file_exists(variable_get('smtp_library', ''))) {
include_once './'. variable_get('smtp_library', '');
return drupal_mail_wrapper($message);
}
else {
$mimeheaders = array();
foreach ($message['headers'] as $name => $value) {
$mimeheaders[] = $name .': '. mime_header_encode($value);
}
return mail(
$message['to'],
mime_header_encode($message['subject']),
// Note: e-mail uses CRLF for line-endings, but PHP's API requires LF.
// They will appear correctly in the actual e-mail that is sent.
str_replace("\r", '', $message['body']),
// For headers, PHP's API suggests that we use CRLF normally,
// but some MTAs incorrecly replace LF with CRLF. See #234403.
join("\n", $mimeheaders)
);
}
}
/**
* Perform format=flowed soft wrapping for mail (RFC 3676).
*
* We use delsp=yes wrapping, but only break non-spaced languages when
* absolutely necessary to avoid compatibility issues.
*
* We deliberately use LF rather than CRLF, see drupal_mail().
*
* @param $text
* The plain text to process.
* @param $indent (optional)
* A string to indent the text with. Only '>' characters are repeated on
* subsequent wrapped lines. Others are replaced by spaces.
*/
function drupal_wrap_mail($text, $indent = '') {
// Convert CRLF into LF.
$text = str_replace("\r", '', $text);
// See if soft-wrapping is allowed.
$clean_indent = _drupal_html_to_text_clean($indent);
$soft = strpos($clean_indent, ' ') === FALSE;
// Check if the string has line breaks.
if (strpos($text, "\n") !== FALSE) {
// Remove trailing spaces to make existing breaks hard.
$text = preg_replace('/ +\n/m', "\n", $text);
// Wrap each line at the needed width.
$lines = explode("\n", $text);
array_walk($lines, '_drupal_wrap_mail_line', array('soft' => $soft, 'length' => strlen($indent)));
$text = implode("\n", $lines);
}
else {
// Wrap this line.
_drupal_wrap_mail_line($text, 0, array('soft' => $soft, 'length' => strlen($indent)));
}
// Empty lines with nothing but spaces.
$text = preg_replace('/^ +\n/m', "\n", $text);
// Space-stuff special lines.
$text = preg_replace('/^(>| |From)/m', ' $1', $text);
// Apply indentation. We only include non-'>' indentation on the first line.
$text = $indent . substr(preg_replace('/^/m', $clean_indent, $text), strlen($indent));
return $text;
}
/**
* Transform an HTML string into plain text, preserving the structure of the
* markup. Useful for preparing the body of a node to be sent by e-mail.
*
* The output will be suitable for use as 'format=flowed; delsp=yes' text
* (RFC 3676) and can be passed directly to drupal_mail() for sending.
*
* We deliberately use LF rather than CRLF, see drupal_mail().
*
* This function provides suitable alternatives for the following tags:
* <a> <em> <i> <strong> <b> <br> <p> <blockquote> <ul> <ol> <li> <dl> <dt>
* <dd> <h1> <h2> <h3> <h4> <h5> <h6> <hr>
*
* @param $string
* The string to be transformed.
* @param $allowed_tags (optional)
* If supplied, a list of tags that will be transformed. If omitted, all
* all supported tags are transformed.
*
* @return
* The transformed string.
*/
function drupal_html_to_text($string, $allowed_tags = NULL) {
// Cache list of supported tags.
static $supported_tags;
if (empty($supported_tags)) {
$supported_tags = array('a', 'em', 'i', 'strong', 'b', 'br', 'p', 'blockquote', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr');
}
// Make sure only supported tags are kept.
$allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags;
// Make sure tags, entities and attributes are well-formed and properly nested.
$string = _filter_htmlcorrector(filter_xss($string, $allowed_tags));
// Apply inline styles.
$string = preg_replace('!</?(em|i)((?> +)[^>]*)?>!i', '/', $string);
$string = preg_replace('!</?(strong|b)((?> +)[^>]*)?>!i', '*', $string);
// Replace inline <a> tags with the text of link and a footnote.
// 'See <a href="http://drupal.org">the Drupal site</a>' becomes
// 'See the Drupal site [1]' with the URL included as a footnote.
_drupal_html_to_mail_urls(NULL, TRUE);
$pattern = '@(<a[^>]+?href="([^"]*)"[^>]*?>(.+?)</a>)@i';
$string = preg_replace_callback($pattern, '_drupal_html_to_mail_urls', $string);
$urls = _drupal_html_to_mail_urls();
$footnotes = '';
if (count($urls)) {
$footnotes .= "\n";
for ($i = 0, $max = count($urls); $i < $max; $i++) {
$footnotes .= '['. ($i + 1) .'] '. $urls[$i] ."\n";
}
}
// Split tags from text.
$split = preg_split('/<([^>]+?)>/', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
// Note: PHP ensures the array consists of alternating delimiters and literals
// and begins and ends with a literal (inserting $null as required).
$tag = FALSE; // Odd/even counter (tag or no tag)
$casing = NULL; // Case conversion function
$output = '';
$indent = array(); // All current indentation string chunks
$lists = array(); // Array of counters for opened lists
foreach ($split as $value) {
$chunk = NULL; // Holds a string ready to be formatted and output.
// Process HTML tags (but don't output any literally).
if ($tag) {
list($tagname) = explode(' ', strtolower($value), 2);
switch ($tagname) {
// List counters
case 'ul':
array_unshift($lists, '*');
break;
case 'ol':
array_unshift($lists, 1);
break;
case '/ul':
case '/ol':
array_shift($lists);
$chunk = ''; // Ensure blank new-line.
break;
// Quotation/list markers, non-fancy headers
case 'blockquote':
// Format=flowed indentation cannot be mixed with lists.
$indent[] = count($lists) ? ' "' : '>';
break;
case 'li':
$indent[] = is_numeric($lists[0]) ? ' '. $lists[0]++ .') ' : ' * ';
break;
case 'dd':
$indent[] = ' ';
break;
case 'h3':
$indent[] = '.... ';
break;
case 'h4':
$indent[] = '.. ';
break;
case '/blockquote':
if (count($lists)) {
// Append closing quote for inline quotes (immediately).
$output = rtrim($output, "> \n") ."\"\n";
$chunk = ''; // Ensure blank new-line.
}
// Fall-through
case '/li':
case '/dd':
array_pop($indent);
break;
case '/h3':
case '/h4':
array_pop($indent);
case '/h5':
case '/h6':
$chunk = ''; // Ensure blank new-line.
break;
// Fancy headers
case 'h1':
$indent[] = '======== ';
$casing = 'drupal_strtoupper';
break;
case 'h2':
$indent[] = '-------- ';
$casing = 'drupal_strtoupper';
break;
case '/h1':
case '/h2':
$casing = NULL;
// Pad the line with dashes.
$output = _drupal_html_to_text_pad($output, ($tagname == '/h1') ? '=' : '-', ' ');
array_pop($indent);
$chunk = ''; // Ensure blank new-line.
break;
// Horizontal rulers
case 'hr':
// Insert immediately.
$output .= drupal_wrap_mail('', implode('', $indent)) ."\n";
$output = _drupal_html_to_text_pad($output, '-');
break;
// Paragraphs and definition lists
case '/p':
case '/dl':
$chunk = ''; // Ensure blank new-line.
break;
}
}
// Process blocks of text.
else {
// Convert inline HTML text to plain text.
$value = trim(preg_replace('/\s+/', ' ', decode_entities($value)));
if (strlen($value)) {
$chunk = $value;
}
}
// See if there is something waiting to be output.
if (isset($chunk)) {
// Apply any necessary case conversion.
if (isset($casing)) {
$chunk = $casing($chunk);
}
// Format it and apply the current indentation.
$output .= drupal_wrap_mail($chunk, implode('', $indent)) ."\n";
// Remove non-quotation markers from indentation.
$indent = array_map('_drupal_html_to_text_clean', $indent);
}
$tag = !$tag;
}
return $output . $footnotes;
}
/**
* Helper function for array_walk in drupal_wrap_mail().
*
* Wraps words on a single line.
*/
function _drupal_wrap_mail_line(&$line, $key, $values) {
// Use soft-breaks only for purely quoted or unindented text.
$line = wordwrap($line, 77 - $values['length'], $values['soft'] ? " \n" : "\n");
// Break really long words at the maximum width allowed.
$line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n");
}
/**
* Helper function for drupal_html_to_text().
*
* Keeps track of URLs and replaces them with placeholder tokens.
*/
function _drupal_html_to_mail_urls($match = NULL, $reset = FALSE) {
global $base_url, $base_path;
static $urls = array(), $regexp;
if ($reset) {
// Reset internal URL list.
$urls = array();
}
else {
if (empty($regexp)) {
$regexp = '@^'. preg_quote($base_path, '@') .'@';
}
if ($match) {
list(, , $url, $label) = $match;
// Ensure all URLs are absolute.
$urls[] = strpos($url, '://') ? $url : preg_replace($regexp, $base_url .'/', $url);
return $label .' ['. count($urls) .']';
}
}
return $urls;
}
/**
* Helper function for drupal_wrap_mail() and drupal_html_to_text().
*
* Replace all non-quotation markers from a given piece of indentation with spaces.
*/
function _drupal_html_to_text_clean($indent) {
return preg_replace('/[^>]/', ' ', $indent);
}
/**
* Helper function for drupal_html_to_text().
*
* Pad the last line with the given character.
*/
function _drupal_html_to_text_pad($text, $pad, $prefix = '') {
// Remove last line break.
$text = substr($text, 0, -1);
// Calculate needed padding space and add it.
if (($p = strrpos($text, "\n")) === FALSE) {
$p = -1;
}
$n = max(0, 79 - (strlen($text) - $p) - strlen($prefix));
// Add prefix and padding, and restore linebreak.
return $text . $prefix . str_repeat($pad, $n) ."\n";
}

2543
includes/menu.inc Normal file

File diff suppressed because it is too large Load diff

518
includes/module.inc Normal file
View file

@ -0,0 +1,518 @@
<?php
/**
* @file
* API for loading and interacting with Drupal modules.
*/
/**
* Load all the modules that have been enabled in the system table.
*/
function module_load_all() {
foreach (module_list(TRUE, FALSE) as $module) {
drupal_load('module', $module);
}
}
/**
* Call a function repeatedly with each module in turn as an argument.
*/
function module_iterate($function, $argument = '') {
foreach (module_list() as $name) {
$function($name, $argument);
}
}
/**
* Collect a list of all loaded modules. During the bootstrap, return only
* vital modules. See bootstrap.inc
*
* @param $refresh
* Whether to force the module list to be regenerated (such as after the
* administrator has changed the system settings).
* @param $bootstrap
* Whether to return the reduced set of modules loaded in "bootstrap mode"
* for cached pages. See bootstrap.inc.
* @param $sort
* By default, modules are ordered by weight and filename, settings this option
* to TRUE, module list will be ordered by module name.
* @param $fixed_list
* (Optional) Override the module list with the given modules. Stays until the
* next call with $refresh = TRUE.
* @return
* An associative array whose keys and values are the names of all loaded
* modules.
*/
function module_list($refresh = FALSE, $bootstrap = TRUE, $sort = FALSE, $fixed_list = NULL) {
static $list, $sorted_list;
if ($refresh || $fixed_list) {
$list = array();
$sorted_list = NULL;
if ($fixed_list) {
foreach ($fixed_list as $name => $module) {
drupal_get_filename('module', $name, $module['filename']);
$list[$name] = $name;
}
}
else {
if ($bootstrap) {
$result = db_query("SELECT name, filename, throttle FROM {system} WHERE type = 'module' AND status = 1 AND bootstrap = 1 ORDER BY weight ASC, filename ASC");
}
else {
$result = db_query("SELECT name, filename, throttle FROM {system} WHERE type = 'module' AND status = 1 ORDER BY weight ASC, filename ASC");
}
while ($module = db_fetch_object($result)) {
if (file_exists($module->filename)) {
// Determine the current throttle status and see if the module should be
// loaded based on server load. We have to directly access the throttle
// variables, since throttle.module may not be loaded yet.
$throttle = ($module->throttle && variable_get('throttle_level', 0) > 0);
if (!$throttle) {
drupal_get_filename('module', $module->name, $module->filename);
$list[$module->name] = $module->name;
}
}
}
}
}
if ($sort) {
if (!isset($sorted_list)) {
$sorted_list = $list;
ksort($sorted_list);
}
return $sorted_list;
}
return $list;
}
/**
* Rebuild the database cache of module files.
*
* @return
* The array of filesystem objects used to rebuild the cache.
*/
function module_rebuild_cache() {
$write_database = TRUE;
// If lock not acquired, return $files data without writing to database.
if (!lock_acquire('module_rebuild_cache')) {
$write_database = FALSE;
// Wait for the parallel thread to be done so we are more likely
// to get updated and consistent data.
lock_wait('module_rebuild_cache');
}
// Get current list of modules
$files = drupal_system_listing('\.module$', 'modules', 'name', 0);
// Extract current files from database.
system_get_files_database($files, 'module');
ksort($files);
// Set defaults for module info
$defaults = array(
'dependencies' => array(),
'dependents' => array(),
'description' => '',
'version' => NULL,
'php' => DRUPAL_MINIMUM_PHP,
);
foreach ($files as $filename => $file) {
// Look for the info file.
$file->info = drupal_parse_info_file(dirname($file->filename) .'/'. $file->name .'.info');
// Skip modules that don't provide info.
if (empty($file->info)) {
unset($files[$filename]);
continue;
}
// Invoke hook_system_info_alter() to give installed modules a chance to
// modify the data in the .info files if necessary.
drupal_alter('system_info', $files[$filename]->info, $files[$filename]);
// Merge in defaults and save.
$files[$filename]->info = $file->info + $defaults;
}
// If lock not acquired, return $files data without writing to database.
if ($write_database) {
foreach ($files as $filename => $file) {
// Log the critical hooks implemented by this module.
$bootstrap = 0;
foreach (bootstrap_hooks() as $hook) {
if (module_hook($file->name, $hook)) {
$bootstrap = 1;
break;
}
}
// Update the contents of the system table:
if (isset($file->status)) {
db_query("UPDATE {system} SET info = '%s', name = '%s', filename = '%s', bootstrap = %d WHERE filename = '%s'", serialize($files[$filename]->info), $file->name, $file->filename, $bootstrap, $file->old_filename);
}
else {
// This is a new module.
$files[$filename]->status = 0;
$files[$filename]->throttle = 0;
db_query("INSERT INTO {system} (name, info, type, filename, status, throttle, bootstrap) VALUES ('%s', '%s', '%s', '%s', %d, %d, %d)", $file->name, serialize($files[$filename]->info), 'module', $file->filename, 0, 0, $bootstrap);
}
}
lock_release('module_rebuild_cache');
}
$files = _module_build_dependencies($files);
return $files;
}
/**
* Find dependencies any level deep and fill in dependents information too.
*
* If module A depends on B which in turn depends on C then this function will
* add C to the list of modules A depends on. This will be repeated until
* module A has a list of all modules it depends on. If it depends on itself,
* called a circular dependency, that's marked by adding a nonexistent module,
* called -circular- to this list of modules. Because this does not exist,
* it'll be impossible to switch module A on.
*
* Also we fill in a dependents array in $file->info. Using the names above,
* the dependents array of module B lists A.
*
* @param $files
* The array of filesystem objects used to rebuild the cache.
* @return
* The same array with dependencies and dependents added where applicable.
*/
function _module_build_dependencies($files) {
do {
$new_dependency = FALSE;
foreach ($files as $filename => $file) {
// We will modify this object (module A, see doxygen for module A, B, C).
$file = &$files[$filename];
if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) {
foreach ($file->info['dependencies'] as $dependency_name) {
// This is a nonexistent module.
if ($dependency_name == '-circular-' || !isset($files[$dependency_name])) {
continue;
}
// $dependency_name is module B (again, see doxygen).
$files[$dependency_name]->info['dependents'][$filename] = $filename;
$dependency = $files[$dependency_name];
if (isset($dependency->info['dependencies']) && is_array($dependency->info['dependencies'])) {
// Let's find possible C modules.
foreach ($dependency->info['dependencies'] as $candidate) {
if (array_search($candidate, $file->info['dependencies']) === FALSE) {
// Is this a circular dependency?
if ($candidate == $filename) {
// As a module name can not contain dashes, this makes
// impossible to switch on the module.
$candidate = '-circular-';
// Do not display the message or add -circular- more than once.
if (array_search($candidate, $file->info['dependencies']) !== FALSE) {
continue;
}
drupal_set_message(t('%module is part of a circular dependency. This is not supported and you will not be able to switch it on.', array('%module' => $file->info['name'])), 'error');
}
else {
// We added a new dependency to module A. The next loop will
// be able to use this as "B module" thus finding even
// deeper dependencies.
$new_dependency = TRUE;
}
$file->info['dependencies'][] = $candidate;
}
}
}
}
}
// Don't forget to break the reference.
unset($file);
}
} while ($new_dependency);
return $files;
}
/**
* Determine whether a given module exists.
*
* @param $module
* The name of the module (without the .module extension).
* @return
* TRUE if the module is both installed and enabled.
*/
function module_exists($module) {
$list = module_list();
return array_key_exists($module, $list);
}
/**
* Load a module's installation hooks.
*/
function module_load_install($module) {
// Make sure the installation API is available
include_once './includes/install.inc';
module_load_include('install', $module);
}
/**
* Load a module include file.
*
* Examples:
* @code
* // Load node.admin.inc from the node module.
* module_load_include('inc', 'node', 'node.admin');
* // Load content_types.inc from the node module.
* module_load_include('inc', 'node', 'content_types');
* @endcode
*
* Do not use this function to load an install file. Use module_load_install()
* instead.
*
* @param $type
* The include file's type (file extension).
* @param $module
* The module to which the include file belongs.
* @param $name
* Optionally, specify the base file name (without the $type extension).
* If not set, $module is used.
*/
function module_load_include($type, $module, $name = NULL) {
if (empty($name)) {
$name = $module;
}
$file = './'. drupal_get_path('module', $module) ."/$name.$type";
if (is_file($file)) {
require_once $file;
}
else {
return FALSE;
}
}
/**
* Load an include file for each of the modules that have been enabled in
* the system table.
*/
function module_load_all_includes($type, $name = NULL) {
$modules = module_list();
foreach ($modules as $module) {
module_load_include($type, $module, $name);
}
}
/**
* Enable a given list of modules.
*
* @param $module_list
* An array of module names.
*/
function module_enable($module_list) {
$invoke_modules = array();
foreach ($module_list as $module) {
$existing = db_fetch_object(db_query("SELECT status FROM {system} WHERE type = '%s' AND name = '%s'", 'module', $module));
if ($existing->status == 0) {
module_load_install($module);
db_query("UPDATE {system} SET status = %d, throttle = %d WHERE type = '%s' AND name = '%s'", 1, 0, 'module', $module);
drupal_load('module', $module);
$invoke_modules[] = $module;
}
}
if (!empty($invoke_modules)) {
// Refresh the module list to include the new enabled module.
module_list(TRUE, FALSE);
// Force to regenerate the stored list of hook implementations.
module_implements('', FALSE, TRUE);
}
foreach ($invoke_modules as $module) {
module_invoke($module, 'enable');
// Check if node_access table needs rebuilding.
// We check for the existence of node_access_needs_rebuild() since
// at install time, module_enable() could be called while node.module
// is not enabled yet.
if (function_exists('node_access_needs_rebuild') && !node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
node_access_needs_rebuild(TRUE);
}
}
}
/**
* Disable a given set of modules.
*
* @param $module_list
* An array of module names.
*/
function module_disable($module_list) {
$invoke_modules = array();
foreach ($module_list as $module) {
if (module_exists($module)) {
// Check if node_access table needs rebuilding.
if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) {
node_access_needs_rebuild(TRUE);
}
module_load_install($module);
module_invoke($module, 'disable');
db_query("UPDATE {system} SET status = %d, throttle = %d WHERE type = '%s' AND name = '%s'", 0, 0, 'module', $module);
$invoke_modules[] = $module;
}
}
if (!empty($invoke_modules)) {
// Refresh the module list to exclude the disabled modules.
module_list(TRUE, FALSE);
// Force to regenerate the stored list of hook implementations.
module_implements('', FALSE, TRUE);
}
// If there remains no more node_access module, rebuilding will be
// straightforward, we can do it right now.
if (node_access_needs_rebuild() && count(module_implements('node_grants')) == 0) {
node_access_rebuild();
}
}
/**
* @defgroup hooks Hooks
* @{
* Allow modules to interact with the Drupal core.
*
* Drupal's module system is based on the concept of "hooks". A hook is a PHP
* function that is named foo_bar(), where "foo" is the name of the module
* (whose filename is thus foo.module) and "bar" is the name of the hook. Each
* hook has a defined set of parameters and a specified result type.
*
* To extend Drupal, a module need simply implement a hook. When Drupal wishes
* to allow intervention from modules, it determines which modules implement a
* hook and calls that hook in all enabled modules that implement it.
*
* The available hooks to implement are explained here in the Hooks section of
* the developer documentation. The string "hook" is used as a placeholder for
* the module name in the hook definitions. For example, if the module file is
* called example.module, then hook_help() as implemented by that module would
* be defined as example_help().
*/
/**
* Determine whether a module implements a hook.
*
* @param $module
* The name of the module (without the .module extension).
* @param $hook
* The name of the hook (e.g. "help" or "menu").
* @return
* TRUE if the module is both installed and enabled, and the hook is
* implemented in that module.
*/
function module_hook($module, $hook) {
return function_exists($module .'_'. $hook);
}
/**
* Determine which modules are implementing a hook.
*
* @param $hook
* The name of the hook (e.g. "help" or "menu").
* @param $sort
* By default, modules are ordered by weight and filename, settings this option
* to TRUE, module list will be ordered by module name.
* @param $refresh
* For internal use only: Whether to force the stored list of hook
* implementations to be regenerated (such as after enabling a new module,
* before processing hook_enable).
* @return
* An array with the names of the modules which are implementing this hook.
*/
function module_implements($hook, $sort = FALSE, $refresh = FALSE) {
static $implementations;
if ($refresh) {
$implementations = array();
return;
}
if (!isset($implementations[$hook])) {
$implementations[$hook] = array();
$list = module_list(FALSE, TRUE, $sort);
foreach ($list as $module) {
if (module_hook($module, $hook)) {
$implementations[$hook][] = $module;
}
}
}
// The explicit cast forces a copy to be made. This is needed because
// $implementations[$hook] is only a reference to an element of
// $implementations and if there are nested foreaches (due to nested node
// API calls, for example), they would both manipulate the same array's
// references, which causes some modules' hooks not to be called.
// See also http://www.zend.com/zend/art/ref-count.php.
return (array)$implementations[$hook];
}
/**
* Invoke a hook in a particular module.
*
* @param $module
* The name of the module (without the .module extension).
* @param $hook
* The name of the hook to invoke.
* @param ...
* Arguments to pass to the hook implementation.
* @return
* The return value of the hook implementation.
*/
function module_invoke() {
$args = func_get_args();
$module = $args[0];
$hook = $args[1];
unset($args[0], $args[1]);
$function = $module .'_'. $hook;
if (module_hook($module, $hook)) {
return call_user_func_array($function, $args);
}
}
/**
* Invoke a hook in all enabled modules that implement it.
*
* @param $hook
* The name of the hook to invoke.
* @param ...
* Arguments to pass to the hook.
* @return
* An array of return values of the hook implementations. If modules return
* arrays from their implementations, those are merged into one array.
*/
function module_invoke_all() {
$args = func_get_args();
$hook = $args[0];
unset($args[0]);
$return = array();
foreach (module_implements($hook) as $module) {
$function = $module .'_'. $hook;
$result = call_user_func_array($function, $args);
if (isset($result) && is_array($result)) {
$return = array_merge_recursive($return, $result);
}
else if (isset($result)) {
$return[] = $result;
}
}
return $return;
}
/**
* @} End of "defgroup hooks".
*/
/**
* Array of modules required by core.
*/
function drupal_required_modules() {
return array('block', 'filter', 'node', 'system', 'user');
}

439
includes/pager.inc Normal file
View file

@ -0,0 +1,439 @@
<?php
/**
* @file
* Functions to aid in presenting database results as a set of pages.
*/
/**
* Perform a paged database query.
*
* Use this function when doing select queries you wish to be able to page. The
* pager uses LIMIT-based queries to fetch only the records required to render a
* certain page. However, it has to learn the total number of records returned
* by the query to compute the number of pages (the number of records / records
* per page). This is done by inserting "COUNT(*)" in the original query. For
* example, the query "SELECT nid, type FROM node WHERE status = '1' ORDER BY
* sticky DESC, created DESC" would be rewritten to read "SELECT COUNT(*) FROM
* node WHERE status = '1' ORDER BY sticky DESC, created DESC". Rewriting the
* query is accomplished using a regular expression.
*
* Unfortunately, the rewrite rule does not always work as intended for queries
* that already have a "COUNT(*)" or a "GROUP BY" clause, and possibly for
* other complex queries. In those cases, you can optionally pass a query that
* will be used to count the records.
*
* For example, if you want to page the query "SELECT COUNT(*), TYPE FROM node
* GROUP BY TYPE", pager_query() would invoke the incorrect query "SELECT
* COUNT(*) FROM node GROUP BY TYPE". So instead, you should pass "SELECT
* COUNT(DISTINCT(TYPE)) FROM node" as the optional $count_query parameter.
*
* @param $query
* The SQL query that needs paging.
* @param $limit
* The number of query results to display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $count_query
* An SQL query used to count matching records.
* @param ...
* A variable number of arguments which are substituted into the query (and
* the count query) using printf() syntax. Instead of a variable number of
* query arguments, you may also pass a single array containing the query
* arguments.
* @return
* A database query result resource, or FALSE if the query was not executed
* correctly.
*
* @ingroup database
*/
function pager_query($query, $limit = 10, $element = 0, $count_query = NULL) {
global $pager_page_array, $pager_total, $pager_total_items;
$page = isset($_GET['page']) ? $_GET['page'] : '';
// Substitute in query arguments.
$args = func_get_args();
$args = array_slice($args, 4);
// Alternative syntax for '...'
if (isset($args[0]) && is_array($args[0])) {
$args = $args[0];
}
// Construct a count query if none was given.
if (!isset($count_query)) {
$count_query = preg_replace(array('/SELECT.*?FROM /As', '/ORDER BY .*/'), array('SELECT COUNT(*) FROM ', ''), $query);
}
// Convert comma-separated $page to an array, used by other functions.
$pager_page_array = explode(',', $page);
// We calculate the total of pages as ceil(items / limit).
$pager_total_items[$element] = db_result(db_query($count_query, $args));
$pager_total[$element] = ceil($pager_total_items[$element] / $limit);
$pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
return db_query_range($query, $args, $pager_page_array[$element] * $limit, $limit);
}
/**
* Compose a query string to append to pager requests.
*
* @return
* A query string that consists of all components of the current page request
* except for those pertaining to paging.
*/
function pager_get_querystring() {
static $string = NULL;
if (!isset($string)) {
$string = drupal_query_string_encode($_REQUEST, array_merge(array('q', 'page', 'pass'), array_keys($_COOKIE)));
}
return $string;
}
/**
* Returns HTML for a query pager.
*
* Menu callbacks that display paged query results should call theme('pager') to
* retrieve a pager control so that users can view other results.
* Format a list of nearby pages with additional query results.
*
* @param $tags
* An array of labels for the controls in the pager.
* @param $limit
* The number of query results to display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $parameters
* An associative array of query string parameters to append to the pager links.
* @param $quantity
* The number of pages in the list.
* @return
* An HTML string that generates the query pager.
*
* @ingroup themeable
*/
function theme_pager($tags = array(), $limit = 10, $element = 0, $parameters = array(), $quantity = 9) {
global $pager_page_array, $pager_total;
// Calculate various markers within this pager piece:
// Middle is used to "center" pages around the current page.
$pager_middle = ceil($quantity / 2);
// current is the page we are currently paged to
$pager_current = $pager_page_array[$element] + 1;
// first is the first page listed by this pager piece (re quantity)
$pager_first = $pager_current - $pager_middle + 1;
// last is the last page listed by this pager piece (re quantity)
$pager_last = $pager_current + $quantity - $pager_middle;
// max is the maximum page number
$pager_max = $pager_total[$element];
// End of marker calculations.
// Prepare for generation loop.
$i = $pager_first;
if ($pager_last > $pager_max) {
// Adjust "center" if at end of query.
$i = $i + ($pager_max - $pager_last);
$pager_last = $pager_max;
}
if ($i <= 0) {
// Adjust "center" if at start of query.
$pager_last = $pager_last + (1 - $i);
$i = 1;
}
// End of generation loop preparation.
$li_first = theme('pager_first', (isset($tags[0]) ? $tags[0] : t('« first')), $limit, $element, $parameters);
$li_previous = theme('pager_previous', (isset($tags[1]) ? $tags[1] : t(' previous')), $limit, $element, 1, $parameters);
$li_next = theme('pager_next', (isset($tags[3]) ? $tags[3] : t('next ')), $limit, $element, 1, $parameters);
$li_last = theme('pager_last', (isset($tags[4]) ? $tags[4] : t('last »')), $limit, $element, $parameters);
if ($pager_total[$element] > 1) {
if ($li_first) {
$items[] = array(
'class' => 'pager-first',
'data' => $li_first,
);
}
if ($li_previous) {
$items[] = array(
'class' => 'pager-previous',
'data' => $li_previous,
);
}
// When there is more than one page, create the pager list.
if ($i != $pager_max) {
if ($i > 1) {
$items[] = array(
'class' => 'pager-ellipsis',
'data' => '…',
);
}
// Now generate the actual pager piece.
for (; $i <= $pager_last && $i <= $pager_max; $i++) {
if ($i < $pager_current) {
$items[] = array(
'class' => 'pager-item',
'data' => theme('pager_previous', $i, $limit, $element, ($pager_current - $i), $parameters),
);
}
if ($i == $pager_current) {
$items[] = array(
'class' => 'pager-current',
'data' => $i,
);
}
if ($i > $pager_current) {
$items[] = array(
'class' => 'pager-item',
'data' => theme('pager_next', $i, $limit, $element, ($i - $pager_current), $parameters),
);
}
}
if ($i < $pager_max) {
$items[] = array(
'class' => 'pager-ellipsis',
'data' => '…',
);
}
}
// End generation.
if ($li_next) {
$items[] = array(
'class' => 'pager-next',
'data' => $li_next,
);
}
if ($li_last) {
$items[] = array(
'class' => 'pager-last',
'data' => $li_last,
);
}
return theme('item_list', $items, NULL, 'ul', array('class' => 'pager'));
}
}
/**
* @defgroup pagerpieces Pager pieces
* @{
* Theme functions for customizing pager elements.
*
* Note that you should NOT modify this file to customize your pager.
*/
/**
* Returns HTML for a "first page" link.
*
* @param $text
* The name (or image) of the link.
* @param $limit
* The number of query results to display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $parameters
* An associative array of query string parameters to append to the pager links.
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_first($text, $limit, $element = 0, $parameters = array()) {
global $pager_page_array;
$output = '';
// If we are anywhere but the first page
if ($pager_page_array[$element] > 0) {
$output = theme('pager_link', $text, pager_load_array(0, $element, $pager_page_array), $element, $parameters);
}
return $output;
}
/**
* Returns HTML for a "previous page" link.
*
* @param $text
* The name (or image) of the link.
* @param $limit
* The number of query results to display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $interval
* The number of pages to move backward when the link is clicked.
* @param $parameters
* An associative array of query string parameters to append to the pager links.
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_previous($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
global $pager_page_array;
$output = '';
// If we are anywhere but the first page
if ($pager_page_array[$element] > 0) {
$page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array);
// If the previous page is the first page, mark the link as such.
if ($page_new[$element] == 0) {
$output = theme('pager_first', $text, $limit, $element, $parameters);
}
// The previous page is not the first page.
else {
$output = theme('pager_link', $text, $page_new, $element, $parameters);
}
}
return $output;
}
/**
* Returns HTML for a "next page" link.
*
* @param $text
* The name (or image) of the link.
* @param $limit
* The number of query results to display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $interval
* The number of pages to move forward when the link is clicked.
* @param $parameters
* An associative array of query string parameters to append to the pager links.
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_next($text, $limit, $element = 0, $interval = 1, $parameters = array()) {
global $pager_page_array, $pager_total;
$output = '';
// If we are anywhere but the last page
if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
$page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array);
// If the next page is the last page, mark the link as such.
if ($page_new[$element] == ($pager_total[$element] - 1)) {
$output = theme('pager_last', $text, $limit, $element, $parameters);
}
// The next page is not the last page.
else {
$output = theme('pager_link', $text, $page_new, $element, $parameters);
}
}
return $output;
}
/**
* Returns HTML for a "last page" link.
*
* @param $text
* The name (or image) of the link.
* @param $limit
* The number of query results to display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $parameters
* An associative array of query string parameters to append to the pager links.
* @return
* An HTML string that generates this piece of the query pager.
*
* @ingroup themeable
*/
function theme_pager_last($text, $limit, $element = 0, $parameters = array()) {
global $pager_page_array, $pager_total;
$output = '';
// If we are anywhere but the last page
if ($pager_page_array[$element] < ($pager_total[$element] - 1)) {
$output = theme('pager_link', $text, pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), $element, $parameters);
}
return $output;
}
/**
* Returns HTML for a link to a specific query result page.
*
* @param $text
* The link text. Also used to figure out the title attribute of the link,
* if it is not provided in $attributes['title']; in this case, $text must
* be one of the standard pager link text strings that would be generated by
* the pager theme functions, such as a number or t('« first').
* @param $page_new
* The first result to display on the linked page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
* @param $parameters
* An associative array of query string parameters to append to the pager link.
* @param $attributes
* An associative array of HTML attributes to apply to the pager link.
* @return
* An HTML string that generates the link.
*
* @ingroup themeable
*/
function theme_pager_link($text, $page_new, $element, $parameters = array(), $attributes = array()) {
$page = isset($_GET['page']) ? $_GET['page'] : '';
if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) {
$parameters['page'] = $new_page;
}
$query = array();
if (count($parameters)) {
$query[] = drupal_query_string_encode($parameters, array());
}
$querystring = pager_get_querystring();
if ($querystring != '') {
$query[] = $querystring;
}
// Set each pager link title
if (!isset($attributes['title'])) {
static $titles = NULL;
if (!isset($titles)) {
$titles = array(
t('« first') => t('Go to first page'),
t(' previous') => t('Go to previous page'),
t('next ') => t('Go to next page'),
t('last »') => t('Go to last page'),
);
}
if (isset($titles[$text])) {
$attributes['title'] = $titles[$text];
}
else if (is_numeric($text)) {
$attributes['title'] = t('Go to page @number', array('@number' => $text));
}
}
return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => count($query) ? implode('&', $query) : NULL));
}
/**
* @} End of "Pager pieces".
*/
/**
* Helper function
*
* Copies $old_array to $new_array and sets $new_array[$element] = $value
* Fills in $new_array[0 .. $element - 1] = 0
*/
function pager_load_array($value, $element, $old_array) {
$new_array = $old_array;
// Look for empty elements.
for ($i = 0; $i < $element; $i++) {
if (!$new_array[$i]) {
// Load found empty element with 0.
$new_array[$i] = 0;
}
}
// Update the changed element.
$new_array[$element] = (int)$value;
return $new_array;
}

251
includes/path.inc Normal file
View file

@ -0,0 +1,251 @@
<?php
/**
* @file
* Functions to handle paths in Drupal, including path aliasing.
*
* These functions are not loaded for cached pages, but modules that need
* to use them in hook_init() or hook exit() can make them available, by
* executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);".
*/
/**
* Initialize the $_GET['q'] variable to the proper normal path.
*/
function drupal_init_path() {
if (!empty($_GET['q'])) {
$_GET['q'] = drupal_get_normal_path(trim($_GET['q'], '/'));
}
else {
$_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node'));
}
}
/**
* Given an alias, return its Drupal system URL if one exists. Given a Drupal
* system URL return one of its aliases if such a one exists. Otherwise,
* return FALSE.
*
* @param $action
* One of the following values:
* - wipe: delete the alias cache.
* - alias: return an alias for a given Drupal system path (if one exists).
* - source: return the Drupal system URL for a path alias (if one exists).
* @param $path
* The path to investigate for corresponding aliases or system URLs.
* @param $path_language
* Optional language code to search the path with. Defaults to the page language.
* If there's no path defined for that language it will search paths without
* language.
*
* @return
* Either a Drupal system path, an aliased path, or FALSE if no path was
* found.
*/
function drupal_lookup_path($action, $path = '', $path_language = '') {
global $language;
// $map is an array with language keys, holding arrays of Drupal paths to alias relations
static $map = array(), $no_src = array(), $count;
$path_language = $path_language ? $path_language : $language->language;
// Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
if (!isset($count)) {
$count = db_result(db_query('SELECT COUNT(pid) FROM {url_alias}'));
}
if ($action == 'wipe') {
$map = array();
$no_src = array();
$count = NULL;
}
elseif ($count > 0 && $path != '') {
if ($action == 'alias') {
if (isset($map[$path_language][$path])) {
return $map[$path_language][$path];
}
// Get the most fitting result falling back with alias without language
$alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s' AND language IN('%s', '') ORDER BY language DESC, pid DESC", $path, $path_language));
$map[$path_language][$path] = $alias;
return $alias;
}
// Check $no_src for this $path in case we've already determined that there
// isn't a path that has this alias
elseif ($action == 'source' && !isset($no_src[$path_language][$path])) {
// Look for the value $path within the cached $map
$src = FALSE;
if (!isset($map[$path_language]) || !($src = array_search($path, $map[$path_language]))) {
// Get the most fitting result falling back with alias without language
if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s' AND language IN('%s', '') ORDER BY language DESC, pid DESC", $path, $path_language))) {
$map[$path_language][$src] = $path;
}
else {
// We can't record anything into $map because we do not have a valid
// index and there is no need because we have not learned anything
// about any Drupal path. Thus cache to $no_src.
$no_src[$path_language][$path] = TRUE;
}
}
return $src;
}
}
return FALSE;
}
/**
* Given an internal Drupal path, return the alias set by the administrator.
*
* @param $path
* An internal Drupal path.
* @param $path_language
* An optional language code to look up the path in.
*
* @return
* An aliased path if one was found, or the original path if no alias was
* found.
*/
function drupal_get_path_alias($path, $path_language = '') {
$result = $path;
if ($alias = drupal_lookup_path('alias', $path, $path_language)) {
$result = $alias;
}
return $result;
}
/**
* Given a path alias, return the internal path it represents.
*
* @param $path
* A Drupal path alias.
* @param $path_language
* An optional language code to look up the path in.
*
* @return
* The internal path represented by the alias, or the original alias if no
* internal path was found.
*/
function drupal_get_normal_path($path, $path_language = '') {
$result = $path;
if ($src = drupal_lookup_path('source', $path, $path_language)) {
$result = $src;
}
if (function_exists('custom_url_rewrite_inbound')) {
// Modules may alter the inbound request path by reference.
custom_url_rewrite_inbound($result, $path, $path_language);
}
return $result;
}
/**
* Return a component of the current Drupal path.
*
* When viewing a page at the path "admin/content/types", for example, arg(0)
* would return "admin", arg(1) would return "content", and arg(2) would return
* "types".
*
* Avoid use of this function where possible, as resulting code is hard to read.
* Instead, attempt to use named arguments in menu callback functions. See the
* explanation in menu.inc for how to construct callbacks that take arguments.
*
* @param $index
* The index of the component, where each component is separated by a '/'
* (forward-slash), and where the first component has an index of 0 (zero).
* @param $path
* A path to break into components. Defaults to the path of the current page.
*
* @return
* The component specified by $index, or NULL if the specified component was
* not found. If called without arguments, it returns an array containing all
* the components of the current path.
*/
function arg($index = NULL, $path = NULL) {
static $arguments;
if (!isset($path)) {
$path = $_GET['q'];
}
if (!isset($arguments[$path])) {
$arguments[$path] = explode('/', $path);
}
if (!isset($index)) {
return $arguments[$path];
}
if (isset($arguments[$path][$index])) {
return $arguments[$path][$index];
}
}
/**
* Get the title of the current page, for display on the page and in the title bar.
*
* @return
* The current page's title.
*/
function drupal_get_title() {
$title = drupal_set_title();
// during a bootstrap, menu.inc is not included and thus we cannot provide a title
if (!isset($title) && function_exists('menu_get_active_title')) {
$title = check_plain(menu_get_active_title());
}
return $title;
}
/**
* Set the title of the current page, for display on the page and in the title bar.
*
* @param $title
* Optional string value to assign to the page title; or if set to NULL
* (default), leaves the current title unchanged.
*
* @return
* The updated title of the current page.
*/
function drupal_set_title($title = NULL) {
static $stored_title;
if (isset($title)) {
$stored_title = $title;
}
return $stored_title;
}
/**
* Check if the current page is the front page.
*
* @return
* Boolean value: TRUE if the current page is the front page; FALSE if otherwise.
*/
function drupal_is_front_page() {
static $is_front_page;
if (!isset($is_front_page)) {
// As drupal_init_path updates $_GET['q'] with the 'site_frontpage' path,
// we can check it against the 'site_frontpage' variable.
$is_front_page = ($_GET['q'] == drupal_get_normal_path(variable_get('site_frontpage', 'node')));
}
return $is_front_page;
}
/**
* Check if a path matches any pattern in a set of patterns.
*
* @param $path
* The path to match.
* @param $patterns
* String containing a set of patterns separated by \n, \r or \r\n.
*
* @return
* 1 if there is a match, 0 if there is not a match.
*/
function drupal_match_path($path, $patterns) {
static $regexps;
if (!isset($regexps[$patterns])) {
$regexps[$patterns] = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($patterns, '/')) .')$/';
}
return preg_match($regexps[$patterns], $path);
}

209
includes/session.inc Normal file
View file

@ -0,0 +1,209 @@
<?php
/**
* @file
* User session handling functions.
*/
function sess_open($save_path, $session_name) {
return TRUE;
}
function sess_close() {
return TRUE;
}
/**
* Reads an entire session from the database (internal use only).
*
* Also initializes the $user object for the user associated with the session.
* This function is registered with session_set_save_handler() to support
* database-backed sessions. It is called on every page load when PHP sets
* up the $_SESSION superglobal.
*
* This function is an internal function and must not be called directly.
* Doing so may result in logging out the current user, corrupting session data
* or other unexpected behavior. Session data must always be accessed via the
* $_SESSION superglobal.
*
* @param $key
* The session ID of the session to retrieve.
*
* @return
* The user's session, or an empty string if no session exists.
*/
function sess_read($key) {
global $user;
// Write and Close handlers are called after destructing objects since PHP 5.0.5
// Thus destructors can use sessions but session handler can't use objects.
// So we are moving session closure before destructing objects.
register_shutdown_function('session_write_close');
// Handle the case of first time visitors and clients that don't store cookies (eg. web crawlers).
if (empty($key) || !isset($_COOKIE[session_name()])) {
$user = drupal_anonymous_user();
return '';
}
// Otherwise, if the session is still active, we have a record of the client's session in the database.
$user = db_fetch_object(db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = '%s'", $key));
// We found the client's session record and they are an authenticated,
// active user.
if ($user && $user->uid > 0 && $user->status == 1) {
// This is done to unserialize the data member of $user
$user = drupal_unpack($user);
// Add roles element to $user
$user->roles = array();
$user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user';
$result = db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = %d", $user->uid);
while ($role = db_fetch_object($result)) {
$user->roles[$role->rid] = $role->name;
}
}
// We didn't find the client's record (session has expired), or they are
// blocked, or they are an anonymous user.
else {
$session = isset($user->session) ? $user->session : '';
$user = drupal_anonymous_user($session);
}
return $user->session;
}
/**
* Writes an entire session to the database (internal use only).
*
* This function is registered with session_set_save_handler() to support
* database-backed sessions.
*
* This function is an internal function and must not be called directly.
* Doing so may result in corrupted session data or other unexpected behavior.
* Session data must always be accessed via the $_SESSION superglobal.
*
* @param $key
* The session ID of the session to write to.
* @param $value
* Session data to write as a serialized string.
*
* @return
* Always returns TRUE.
*/
function sess_write($key, $value) {
global $user;
// If saving of session data is disabled or if the client doesn't have a session,
// and one isn't being created ($value), do nothing. This keeps crawlers out of
// the session table. This reduces memory and server load, and gives more useful
// statistics. We can't eliminate anonymous session table rows without breaking
// the throttle module and the "Who's Online" block.
if (!session_save_session() || ($user->uid == 0 && empty($_COOKIE[session_name()]) && empty($value))) {
return TRUE;
}
db_query("UPDATE {sessions} SET uid = %d, cache = %d, hostname = '%s', session = '%s', timestamp = %d WHERE sid = '%s'", $user->uid, isset($user->cache) ? $user->cache : '', ip_address(), $value, time(), $key);
if (db_affected_rows()) {
// Last access time is updated no more frequently than once every 180 seconds.
// This reduces contention in the users table.
if ($user->uid && time() - $user->access > variable_get('session_write_interval', 180)) {
db_query("UPDATE {users} SET access = %d WHERE uid = %d", time(), $user->uid);
}
}
else {
// If this query fails, another parallel request probably got here first.
// In that case, any session data generated in this request is discarded.
@db_query("INSERT INTO {sessions} (sid, uid, cache, hostname, session, timestamp) VALUES ('%s', %d, %d, '%s', '%s', %d)", $key, $user->uid, isset($user->cache) ? $user->cache : '', ip_address(), $value, time());
}
return TRUE;
}
/**
* Called when an anonymous user becomes authenticated or vice-versa.
*/
function sess_regenerate() {
$old_session_id = session_id();
// We code around http://bugs.php.net/bug.php?id=32802 by destroying
// the session cookie by setting expiration in the past (a negative
// value). This issue only arises in PHP versions before 4.4.0,
// regardless of the Drupal configuration.
// TODO: remove this when we require at least PHP 4.4.0
if (isset($_COOKIE[session_name()])) {
setcookie(session_name(), '', time() - 42000, '/');
}
session_regenerate_id();
db_query("UPDATE {sessions} SET sid = '%s' WHERE sid = '%s'", session_id(), $old_session_id);
}
/**
* Counts how many users have sessions. Can count either anonymous sessions or authenticated sessions.
*
* @param int $timestamp
* A Unix timestamp representing a point of time in the past.
* The default is 0, which counts all existing sessions.
* @param boolean $anonymous
* TRUE counts only anonymous users.
* FALSE counts only authenticated users.
* @return int
* The number of users with sessions.
*/
function sess_count($timestamp = 0, $anonymous = true) {
$query = $anonymous ? ' AND uid = 0' : ' AND uid > 0';
return db_result(db_query('SELECT COUNT(sid) AS count FROM {sessions} WHERE timestamp >= %d'. $query, $timestamp));
}
/**
* Called by PHP session handling with the PHP session ID to end a user's session.
*
* @param string $sid
* the session id
*/
function sess_destroy_sid($sid) {
db_query("DELETE FROM {sessions} WHERE sid = '%s'", $sid);
}
/**
* End a specific user's session
*
* @param string $uid
* the user id
*/
function sess_destroy_uid($uid) {
db_query('DELETE FROM {sessions} WHERE uid = %d', $uid);
}
function sess_gc($lifetime) {
// Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough
// value. For example, if you want user sessions to stay in your database
// for three weeks before deleting them, you need to set gc_maxlifetime
// to '1814400'. At that value, only after a user doesn't log in after
// three weeks (1814400 seconds) will his/her session be removed.
db_query("DELETE FROM {sessions} WHERE timestamp < %d", time() - $lifetime);
return TRUE;
}
/**
* Determine whether to save session data of the current request.
*
* This function allows the caller to temporarily disable writing of session data,
* should the request end while performing potentially dangerous operations, such as
* manipulating the global $user object. See http://drupal.org/node/218104 for usage
*
* @param $status
* Disables writing of session data when FALSE, (re-)enables writing when TRUE.
* @return
* FALSE if writing session data has been disabled. Otherwise, TRUE.
*/
function session_save_session($status = NULL) {
static $save_session = TRUE;
if (isset($status)) {
$save_session = $status;
}
return ($save_session);
}

199
includes/tablesort.inc Normal file
View file

@ -0,0 +1,199 @@
<?php
/**
* @file
* Functions to aid in the creation of sortable tables.
*
* All tables created with a call to theme('table') have the option of having
* column headers that the user can click on to sort the table by that column.
*/
/**
* Initialize the table sort context.
*/
function tablesort_init($header) {
$ts = tablesort_get_order($header);
$ts['sort'] = tablesort_get_sort($header);
$ts['query_string'] = tablesort_get_querystring();
return $ts;
}
/**
* Create an SQL sort clause.
*
* This function produces the ORDER BY clause to insert in your SQL queries,
* assuring that the returned database table rows match the sort order chosen
* by the user.
*
* @param $header
* An array of column headers in the format described in theme_table().
* @param $before
* An SQL string to insert after ORDER BY and before the table sorting code.
* Useful for sorting by important attributes like "sticky" first.
* @return
* An SQL string to append to the end of a query.
*
* @ingroup database
*/
function tablesort_sql($header, $before = '') {
$ts = tablesort_init($header);
if ($ts['sql']) {
// Based on code from db_escape_table(), but this can also contain a dot.
$field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']);
// Sort order can only be ASC or DESC.
$sort = drupal_strtoupper($ts['sort']);
$sort = in_array($sort, array('ASC', 'DESC')) ? $sort : '';
return " ORDER BY $before $field $sort";
}
}
/**
* Format a column header.
*
* If the cell in question is the column header for the current sort criterion,
* it gets special formatting. All possible sort criteria become links.
*
* @param $cell
* The cell to format.
* @param $header
* An array of column headers in the format described in theme_table().
* @param $ts
* The current table sort context as returned from tablesort_init().
* @return
* A properly formatted cell, ready for _theme_table_cell().
*/
function tablesort_header($cell, $header, $ts) {
// Special formatting for the currently sorted column header.
if (is_array($cell) && isset($cell['field'])) {
$title = t('sort by @s', array('@s' => $cell['data']));
if ($cell['data'] == $ts['name']) {
$ts['sort'] = (($ts['sort'] == 'asc') ? 'desc' : 'asc');
if (isset($cell['class'])) {
$cell['class'] .= ' active';
}
else {
$cell['class'] = 'active';
}
$image = theme('tablesort_indicator', $ts['sort']);
}
else {
// If the user clicks a different header, we want to sort ascending initially.
$ts['sort'] = 'asc';
$image = '';
}
if (!empty($ts['query_string'])) {
$ts['query_string'] = '&'. $ts['query_string'];
}
$cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => 'sort='. $ts['sort'] .'&order='. urlencode($cell['data']) . $ts['query_string'], 'html' => TRUE));
unset($cell['field'], $cell['sort']);
}
return $cell;
}
/**
* Format a table cell.
*
* Adds a class attribute to all cells in the currently active column.
*
* @param $cell
* The cell to format.
* @param $header
* An array of column headers in the format described in theme_table().
* @param $ts
* The current table sort context as returned from tablesort_init().
* @param $i
* The index of the cell's table column.
* @return
* A properly formatted cell, ready for _theme_table_cell().
*/
function tablesort_cell($cell, $header, $ts, $i) {
if (isset($header[$i]['data']) && $header[$i]['data'] == $ts['name'] && !empty($header[$i]['field'])) {
if (is_array($cell)) {
if (isset($cell['class'])) {
$cell['class'] .= ' active';
}
else {
$cell['class'] = 'active';
}
}
else {
$cell = array('data' => $cell, 'class' => 'active');
}
}
return $cell;
}
/**
* Compose a query string to append to table sorting requests.
*
* @return
* A query string that consists of all components of the current page request
* except for those pertaining to table sorting.
*/
function tablesort_get_querystring() {
return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order', 'pass'), array_keys($_COOKIE)));
}
/**
* Determine the current sort criterion.
*
* @param $headers
* An array of column headers in the format described in theme_table().
* @return
* An associative array describing the criterion, containing the keys:
* - "name": The localized title of the table column.
* - "sql": The name of the database field to sort on.
*/
function tablesort_get_order($headers) {
$order = isset($_GET['order']) ? $_GET['order'] : '';
foreach ($headers as $header) {
if (isset($header['data']) && $order == $header['data']) {
return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
}
if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
$default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
}
}
if (isset($default)) {
return $default;
}
else {
// The first column specified is initial 'order by' field unless otherwise specified
if (is_array($headers[0])) {
$headers[0] += array('data' => NULL, 'field' => NULL);
return array('name' => $headers[0]['data'], 'sql' => $headers[0]['field']);
}
else {
return array('name' => $headers[0]);
}
}
}
/**
* Determine the current sort direction.
*
* @param $headers
* An array of column headers in the format described in theme_table().
* @return
* The current sort direction ("asc" or "desc").
*/
function tablesort_get_sort($headers) {
if (isset($_GET['sort'])) {
return ($_GET['sort'] == 'desc') ? 'desc' : 'asc';
}
// User has not specified a sort. Use default if specified; otherwise use "asc".
else {
foreach ($headers as $header) {
if (is_array($header) && array_key_exists('sort', $header)) {
return $header['sort'];
}
}
}
return 'asc';
}

2056
includes/theme.inc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,290 @@
<?php
/**
* @file
* Theming for maintenance pages.
*/
/**
* Sets up the theming system for site installs, updates and when the site is
* in off-line mode. It also applies when the database is unavailable.
*
* Minnelli is always used for the initial install and update operations. In
* other cases, "settings.php" must have a "maintenance_theme" key set for the
* $conf variable in order to change the maintenance theme.
*/
function _drupal_maintenance_theme() {
global $theme, $theme_key;
// If $theme is already set, assume the others are set too, and do nothing.
if (isset($theme)) {
return;
}
require_once './includes/path.inc';
require_once './includes/theme.inc';
require_once './includes/common.inc';
require_once './includes/unicode.inc';
require_once './includes/file.inc';
require_once './includes/module.inc';
require_once './includes/database.inc';
unicode_check();
// Install and update pages are treated differently to prevent theming overrides.
if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) {
$theme = 'minnelli';
}
else {
if (!db_is_active()) {
// Because we are operating in a crippled environment, we need to
// bootstrap just enough to allow hook invocations to work.
$module_list['system']['filename'] = 'modules/system/system.module';
$module_list['filter']['filename'] = 'modules/filter/filter.module';
module_list(TRUE, FALSE, FALSE, $module_list);
drupal_load('module', 'system');
drupal_load('module', 'filter');
}
$theme = variable_get('maintenance_theme', 'minnelli');
}
$themes = list_themes();
// Store the identifier for retrieving theme settings with.
$theme_key = $theme;
// Find all our ancestor themes and put them in an array.
$base_theme = array();
$ancestor = $theme;
while ($ancestor && isset($themes[$ancestor]->base_theme)) {
$base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme];
$ancestor = $themes[$ancestor]->base_theme;
}
_init_theme($themes[$theme], array_reverse($base_theme), '_theme_load_offline_registry');
// These are usually added from system_init() -except maintenance.css.
// When the database is inactive it's not called so we add it here.
drupal_add_css(drupal_get_path('module', 'system') .'/defaults.css', 'module');
drupal_add_css(drupal_get_path('module', 'system') .'/system.css', 'module');
drupal_add_css(drupal_get_path('module', 'system') .'/system-menus.css', 'module');
drupal_add_css(drupal_get_path('module', 'system') .'/maintenance.css', 'module');
}
/**
* This builds the registry when the site needs to bypass any database calls.
*/
function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
$registry = _theme_build_registry($theme, $base_theme, $theme_engine);
_theme_set_registry($registry);
}
/**
* Return a themed list of maintenance tasks to perform.
*
* @ingroup themeable
*/
function theme_task_list($items, $active = NULL) {
$done = isset($items[$active]) || $active == NULL;
$output = '<ol class="task-list">';
foreach ($items as $k => $item) {
if ($active == $k) {
$class = 'active';
$done = false;
}
else {
$class = $done ? 'done' : '';
}
$output .= '<li class="'. $class .'">'. $item .'</li>';
}
$output .= '</ol>';
return $output;
}
/**
* Generate a themed installation page.
*
* Note: this function is not themeable.
*
* @param $content
* The page content to show.
*/
function theme_install_page($content) {
drupal_set_header('Content-Type: text/html; charset=utf-8');
// Assign content.
$variables['content'] = $content;
// Delay setting the message variable so it can be processed below.
$variables['show_messages'] = FALSE;
// The maintenance preprocess function is recycled here.
template_preprocess_maintenance_page($variables);
// Special handling of error messages
$messages = drupal_set_message();
if (isset($messages['error'])) {
$title = count($messages['error']) > 1 ? st('The following errors must be resolved before you can continue the installation process') : st('The following error must be resolved before you can continue the installation process');
$variables['messages'] .= '<h3>'. $title .':</h3>';
$variables['messages'] .= theme('status_messages', 'error');
$variables['content'] .= '<p>'. st('Please check the error messages and <a href="!url">try again</a>.', array('!url' => check_url(request_uri()))) .'</p>';
}
// Special handling of warning messages
if (isset($messages['warning'])) {
$title = count($messages['warning']) > 1 ? st('The following installation warnings should be carefully reviewed') : st('The following installation warning should be carefully reviewed');
$variables['messages'] .= '<h4>'. $title .':</h4>';
$variables['messages'] .= theme('status_messages', 'warning');
}
// Special handling of status messages
if (isset($messages['status'])) {
$title = count($messages['status']) > 1 ? st('The following installation warnings should be carefully reviewed, but in most cases may be safely ignored') : st('The following installation warning should be carefully reviewed, but in most cases may be safely ignored');
$variables['messages'] .= '<h4>'. $title .':</h4>';
$variables['messages'] .= theme('status_messages', 'status');
}
// This was called as a theme hook (not template), so we need to
// fix path_to_theme() for the template, to point at the actual
// theme rather than system module as owner of the hook.
global $theme_path;
$theme_path = 'themes/garland';
return theme_render_template('themes/garland/maintenance-page.tpl.php', $variables);
}
/**
* Generate a themed update page.
*
* Note: this function is not themeable.
*
* @param $content
* The page content to show.
* @param $show_messages
* Whether to output status and error messages.
* FALSE can be useful to postpone the messages to a subsequent page.
*/
function theme_update_page($content, $show_messages = TRUE) {
// Set required headers.
drupal_set_header('Content-Type: text/html; charset=utf-8');
// Assign content and show message flag.
$variables['content'] = $content;
$variables['show_messages'] = $show_messages;
// The maintenance preprocess function is recycled here.
template_preprocess_maintenance_page($variables);
// Special handling of warning messages.
$messages = drupal_set_message();
if (isset($messages['warning'])) {
$title = count($messages['warning']) > 1 ? 'The following update warnings should be carefully reviewed before continuing' : 'The following update warning should be carefully reviewed before continuing';
$variables['messages'] .= '<h4>'. $title .':</h4>';
$variables['messages'] .= theme('status_messages', 'warning');
}
// This was called as a theme hook (not template), so we need to
// fix path_to_theme() for the template, to point at the actual
// theme rather than system module as owner of the hook.
global $theme_path;
$theme_path = 'themes/garland';
return theme_render_template('themes/garland/maintenance-page.tpl.php', $variables);
}
/**
* The variables generated here is a mirror of template_preprocess_page().
* This preprocessor will run it's course when theme_maintenance_page() is
* invoked. It is also used in theme_install_page() and theme_update_page() to
* keep all the variables consistent.
*
* An alternate template file of "maintenance-page-offline.tpl.php" can be
* used when the database is offline to hide errors and completely replace the
* content.
*
* The $variables array contains the following arguments:
* - $content
* - $show_blocks
*
* @see maintenance-page.tpl.php
*/
function template_preprocess_maintenance_page(&$variables) {
// Add favicon
if (theme_get_setting('toggle_favicon')) {
drupal_set_html_head('<link rel="shortcut icon" href="'. check_url(theme_get_setting('favicon')) .'" type="image/x-icon" />');
}
global $theme;
// Retrieve the theme data to list all available regions.
$theme_data = _system_theme_data();
$regions = $theme_data[$theme]->info['regions'];
// Get all region content set with drupal_set_content().
foreach (array_keys($regions) as $region) {
// Assign region to a region variable.
$region_content = drupal_get_content($region);
isset($variables[$region]) ? $variables[$region] .= $region_content : $variables[$region] = $region_content;
}
// Setup layout variable.
$variables['layout'] = 'none';
if (!empty($variables['left'])) {
$variables['layout'] = 'left';
}
if (!empty($variables['right'])) {
$variables['layout'] = ($variables['layout'] == 'left') ? 'both' : 'right';
}
// Construct page title
if (drupal_get_title()) {
$head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal'));
}
else {
$head_title = array(variable_get('site_name', 'Drupal'));
if (variable_get('site_slogan', '')) {
$head_title[] = variable_get('site_slogan', '');
}
}
$variables['head_title'] = implode(' | ', $head_title);
$variables['base_path'] = base_path();
$variables['breadcrumb'] = '';
$variables['feed_icons'] = '';
$variables['footer_message'] = filter_xss_admin(variable_get('site_footer', FALSE));
$variables['head'] = drupal_get_html_head();
$variables['help'] = '';
$variables['language'] = $GLOBALS['language'];
$variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
$variables['logo'] = theme_get_setting('logo');
$variables['messages'] = $variables['show_messages'] ? theme('status_messages') : '';
$variables['mission'] = '';
$variables['primary_links'] = array();
$variables['secondary_links'] = array();
$variables['search_box'] = '';
$variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : '');
$variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : '');
$variables['css'] = drupal_add_css();
$variables['styles'] = drupal_get_css();
$variables['scripts'] = drupal_get_js();
$variables['tabs'] = '';
$variables['title'] = drupal_get_title();
$variables['closure'] = '';
// Compile a list of classes that are going to be applied to the body element.
$body_classes = array();
$body_classes[] = 'in-maintenance';
if (isset($variables['db_is_active']) && !$variables['db_is_active']) {
$body_classes[] = 'db-offline';
}
if ($variables['layout'] == 'both') {
$body_classes[] = 'two-sidebars';
}
elseif ($variables['layout'] == 'none') {
$body_classes[] = 'no-sidebars';
}
else {
$body_classes[] = 'one-sidebar sidebar-'. $variables['layout'];
}
$variables['body_classes'] = implode(' ', $body_classes);
// Dead databases will show error messages so supplying this template will
// allow themers to override the page and the content completely.
if (isset($variables['db_is_active']) && !$variables['db_is_active']) {
$variables['template_file'] = 'maintenance-page-offline';
}
}

View file

@ -0,0 +1,265 @@
<?php
/**
* @file
* (X)HTML entities, as defined in HTML 4.01.
*
* @see http://www.w3.org/TR/html401/sgml/entities.html
*/
$html_entities = array(
'&Aacute;' => 'Á',
'&aacute;' => 'á',
'&Acirc;' => 'Â',
'&acirc;' => 'â',
'&acute;' => '´',
'&AElig;' => 'Æ',
'&aelig;' => 'æ',
'&Agrave;' => 'À',
'&agrave;' => 'à',
'&alefsym;' => 'ℵ',
'&Alpha;' => 'Α',
'&alpha;' => 'α',
'&amp;' => '&',
'&and;' => '∧',
'&ang;' => '∠',
'&Aring;' => 'Å',
'&aring;' => 'å',
'&asymp;' => '≈',
'&Atilde;' => 'Ã',
'&atilde;' => 'ã',
'&Auml;' => 'Ä',
'&auml;' => 'ä',
'&bdquo;' => '„',
'&Beta;' => 'Β',
'&beta;' => 'β',
'&brvbar;' => '¦',
'&bull;' => '•',
'&cap;' => '∩',
'&Ccedil;' => 'Ç',
'&ccedil;' => 'ç',
'&cedil;' => '¸',
'&cent;' => '¢',
'&Chi;' => 'Χ',
'&chi;' => 'χ',
'&circ;' => 'ˆ',
'&clubs;' => '♣',
'&cong;' => '≅',
'&copy;' => '©',
'&crarr;' => '↵',
'&cup;' => '',
'&curren;' => '¤',
'&dagger;' => '†',
'&Dagger;' => '‡',
'&darr;' => '↓',
'&dArr;' => '⇓',
'&deg;' => '°',
'&Delta;' => 'Δ',
'&delta;' => 'δ',
'&diams;' => '♦',
'&divide;' => '÷',
'&Eacute;' => 'É',
'&eacute;' => 'é',
'&Ecirc;' => 'Ê',
'&ecirc;' => 'ê',
'&Egrave;' => 'È',
'&egrave;' => 'è',
'&empty;' => '∅',
'&emsp;' => '',
'&ensp;' => '',
'&Epsilon;' => 'Ε',
'&epsilon;' => 'ε',
'&equiv;' => '≡',
'&Eta;' => 'Η',
'&eta;' => 'η',
'&ETH;' => 'Ð',
'&eth;' => 'ð',
'&Euml;' => 'Ë',
'&euml;' => 'ë',
'&euro;' => '€',
'&exist;' => '∃',
'&fnof;' => 'ƒ',
'&forall;' => '∀',
'&frac12;' => '½',
'&frac14;' => '¼',
'&frac34;' => '¾',
'&frasl;' => '',
'&Gamma;' => 'Γ',
'&gamma;' => 'γ',
'&ge;' => '≥',
'&harr;' => '↔',
'&hArr;' => '⇔',
'&hearts;' => '♥',
'&hellip;' => '…',
'&Iacute;' => 'Í',
'&iacute;' => 'í',
'&Icirc;' => 'Î',
'&icirc;' => 'î',
'&iexcl;' => '¡',
'&Igrave;' => 'Ì',
'&igrave;' => 'ì',
'&image;' => '',
'&infin;' => '∞',
'&int;' => '∫',
'&Iota;' => 'Ι',
'&iota;' => 'ι',
'&iquest;' => '¿',
'&isin;' => '∈',
'&Iuml;' => 'Ï',
'&iuml;' => 'ï',
'&Kappa;' => 'Κ',
'&kappa;' => 'κ',
'&Lambda;' => 'Λ',
'&lambda;' => 'λ',
'&lang;' => '〈',
'&laquo;' => '«',
'&larr;' => '←',
'&lArr;' => '⇐',
'&lceil;' => '⌈',
'&ldquo;' => '“',
'&le;' => '≤',
'&lfloor;' => '⌊',
'&lowast;' => '',
'&loz;' => '◊',
'&lrm;' => '',
'&lsaquo;' => '',
'&lsquo;' => '',
'&macr;' => '¯',
'&mdash;' => '—',
'&micro;' => 'µ',
'&middot;' => '·',
'&minus;' => '',
'&Mu;' => 'Μ',
'&mu;' => 'μ',
'&nabla;' => '∇',
'&nbsp;' => ' ',
'&ndash;' => '',
'&ne;' => '≠',
'&ni;' => '∋',
'&not;' => '¬',
'&notin;' => '∉',
'&nsub;' => '⊄',
'&Ntilde;' => 'Ñ',
'&ntilde;' => 'ñ',
'&Nu;' => 'Ν',
'&nu;' => 'ν',
'&Oacute;' => 'Ó',
'&oacute;' => 'ó',
'&Ocirc;' => 'Ô',
'&ocirc;' => 'ô',
'&OElig;' => 'Œ',
'&oelig;' => 'œ',
'&Ograve;' => 'Ò',
'&ograve;' => 'ò',
'&oline;' => '‾',
'&Omega;' => 'Ω',
'&omega;' => 'ω',
'&Omicron;' => 'Ο',
'&omicron;' => 'ο',
'&oplus;' => '⊕',
'&or;' => '',
'&ordf;' => 'ª',
'&ordm;' => 'º',
'&Oslash;' => 'Ø',
'&oslash;' => 'ø',
'&Otilde;' => 'Õ',
'&otilde;' => 'õ',
'&otimes;' => '⊗',
'&Ouml;' => 'Ö',
'&ouml;' => 'ö',
'&para;' => '¶',
'&part;' => '∂',
'&permil;' => '‰',
'&perp;' => '⊥',
'&Phi;' => 'Φ',
'&phi;' => 'φ',
'&Pi;' => 'Π',
'&pi;' => 'π',
'&piv;' => 'ϖ',
'&plusmn;' => '±',
'&pound;' => '£',
'&prime;' => '',
'&Prime;' => '″',
'&prod;' => '∏',
'&prop;' => '∝',
'&Psi;' => 'Ψ',
'&psi;' => 'ψ',
'&radic;' => '√',
'&rang;' => '〉',
'&raquo;' => '»',
'&rarr;' => '→',
'&rArr;' => '⇒',
'&rceil;' => '⌉',
'&rdquo;' => '”',
'&real;' => '',
'&reg;' => '®',
'&rfloor;' => '⌋',
'&Rho;' => 'Ρ',
'&rho;' => 'ρ',
'&rlm;' => '',
'&rsaquo;' => '',
'&rsquo;' => '',
'&sbquo;' => '',
'&Scaron;' => 'Š',
'&scaron;' => 'š',
'&sdot;' => '⋅',
'&sect;' => '§',
'&shy;' => '­',
'&Sigma;' => 'Σ',
'&sigma;' => 'σ',
'&sigmaf;' => 'ς',
'&sim;' => '',
'&spades;' => '♠',
'&sub;' => '⊂',
'&sube;' => '⊆',
'&sum;' => '∑',
'&sup1;' => '¹',
'&sup2;' => '²',
'&sup3;' => '³',
'&sup;' => '⊃',
'&supe;' => '⊇',
'&szlig;' => 'ß',
'&Tau;' => 'Τ',
'&tau;' => 'τ',
'&there4;' => '∴',
'&Theta;' => 'Θ',
'&theta;' => 'θ',
'&thetasym;' => 'ϑ',
'&thinsp;' => '',
'&THORN;' => 'Þ',
'&thorn;' => 'þ',
'&tilde;' => '˜',
'&times;' => '×',
'&trade;' => '™',
'&Uacute;' => 'Ú',
'&uacute;' => 'ú',
'&uarr;' => '↑',
'&uArr;' => '⇑',
'&Ucirc;' => 'Û',
'&ucirc;' => 'û',
'&Ugrave;' => 'Ù',
'&ugrave;' => 'ù',
'&uml;' => '¨',
'&upsih;' => 'ϒ',
'&Upsilon;' => 'Υ',
'&upsilon;' => 'υ',
'&Uuml;' => 'Ü',
'&uuml;' => 'ü',
'&weierp;' => '℘',
'&Xi;' => 'Ξ',
'&xi;' => 'ξ',
'&Yacute;' => 'Ý',
'&yacute;' => 'ý',
'&yen;' => '¥',
'&yuml;' => 'ÿ',
'&Yuml;' => 'Ÿ',
'&Zeta;' => 'Ζ',
'&zeta;' => 'ζ',
'&zwj;' => '',
'&zwnj;' => '',
'&gt;' => '>',
'&lt;' => '<',
'&quot;' => '"',
// Add apostrophe (XML).
'&apos;' => "'",
);

547
includes/unicode.inc Normal file
View file

@ -0,0 +1,547 @@
<?php
/**
* Indicates an error during check for PHP unicode support.
*/
define('UNICODE_ERROR', -1);
/**
* Indicates that standard PHP (emulated) unicode support is being used.
*/
define('UNICODE_SINGLEBYTE', 0);
/**
* Indicates that full unicode support with the PHP mbstring extension is being
* used.
*/
define('UNICODE_MULTIBYTE', 1);
/**
* Wrapper around _unicode_check().
*/
function unicode_check() {
list($GLOBALS['multibyte']) = _unicode_check();
}
/**
* Perform checks about Unicode support in PHP, and set the right settings if
* needed.
*
* Because Drupal needs to be able to handle text in various encodings, we do
* not support mbstring function overloading. HTTP input/output conversion must
* be disabled for similar reasons.
*
* @param $errors
* Whether to report any fatal errors with form_set_error().
*/
function _unicode_check() {
// Ensure translations don't break at install time
$t = get_t();
// Set the standard C locale to ensure consistent, ASCII-only string handling.
setlocale(LC_CTYPE, 'C');
// Check for outdated PCRE library
// Note: we check if U+E2 is in the range U+E0 - U+E1. This test returns TRUE on old PCRE versions.
if (preg_match('/[à-á]/u', 'â')) {
return array(UNICODE_ERROR, $t('The PCRE library in your PHP installation is outdated. This will cause problems when handling Unicode text. If you are running PHP 4.3.3 or higher, make sure you are using the PCRE library supplied by PHP. Please refer to the <a href="@url">PHP PCRE documentation</a> for more information.', array('@url' => 'http://www.php.net/pcre')));
}
// Check for mbstring extension
if (!function_exists('mb_strlen')) {
return array(UNICODE_SINGLEBYTE, $t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="@url">PHP mbstring extension</a> for improved Unicode support.', array('@url' => 'http://www.php.net/mbstring')));
}
// Check mbstring configuration
if (ini_get('mbstring.func_overload') != 0) {
return array(UNICODE_ERROR, $t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini <em>mbstring.func_overload</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
}
if (ini_get('mbstring.encoding_translation') != 0) {
return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
}
if (ini_get('mbstring.http_input') != 'pass') {
return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
}
if (ini_get('mbstring.http_output') != 'pass') {
return array(UNICODE_ERROR, $t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
}
// Set appropriate configuration
mb_internal_encoding('utf-8');
mb_language('uni');
return array(UNICODE_MULTIBYTE, '');
}
/**
* Return Unicode library status and errors.
*/
function unicode_requirements() {
// Ensure translations don't break at install time
$t = get_t();
$libraries = array(
UNICODE_SINGLEBYTE => $t('Standard PHP'),
UNICODE_MULTIBYTE => $t('PHP Mbstring Extension'),
UNICODE_ERROR => $t('Error'),
);
$severities = array(
UNICODE_SINGLEBYTE => REQUIREMENT_WARNING,
UNICODE_MULTIBYTE => REQUIREMENT_OK,
UNICODE_ERROR => REQUIREMENT_ERROR,
);
list($library, $description) = _unicode_check();
$requirements['unicode'] = array(
'title' => $t('Unicode library'),
'value' => $libraries[$library],
);
if ($description) {
$requirements['unicode']['description'] = $description;
}
$requirements['unicode']['severity'] = $severities[$library];
return $requirements;
}
/**
* Prepare a new XML parser.
*
* This is a wrapper around xml_parser_create() which extracts the encoding from
* the XML data first and sets the output encoding to UTF-8. This function should
* be used instead of xml_parser_create(), because PHP 4's XML parser doesn't
* check the input encoding itself. "Starting from PHP 5, the input encoding is
* automatically detected, so that the encoding parameter specifies only the
* output encoding."
*
* This is also where unsupported encodings will be converted. Callers should
* take this into account: $data might have been changed after the call.
*
* @param &$data
* The XML data which will be parsed later.
* @return
* An XML parser object.
*/
function drupal_xml_parser_create(&$data) {
// Default XML encoding is UTF-8
$encoding = 'utf-8';
$bom = FALSE;
// Check for UTF-8 byte order mark (PHP5's XML parser doesn't handle it).
if (!strncmp($data, "\xEF\xBB\xBF", 3)) {
$bom = TRUE;
$data = substr($data, 3);
}
// Check for an encoding declaration in the XML prolog if no BOM was found.
if (!$bom && @ereg('^<\?xml[^>]+encoding="([^"]+)"', $data, $match)) {
$encoding = $match[1];
}
// Unsupported encodings are converted here into UTF-8.
$php_supported = array('utf-8', 'iso-8859-1', 'us-ascii');
if (!in_array(strtolower($encoding), $php_supported)) {
$out = drupal_convert_to_utf8($data, $encoding);
if ($out !== FALSE) {
$encoding = 'utf-8';
$data = @ereg_replace('^(<\?xml[^>]+encoding)="([^"]+)"', '\\1="utf-8"', $out);
}
else {
watchdog('php', 'Could not convert XML encoding %s to UTF-8.', array('%s' => $encoding), WATCHDOG_WARNING);
return 0;
}
}
$xml_parser = xml_parser_create($encoding);
xml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, 'utf-8');
return $xml_parser;
}
/**
* Convert data to UTF-8
*
* Requires the iconv, GNU recode or mbstring PHP extension.
*
* @param $data
* The data to be converted.
* @param $encoding
* The encoding that the data is in
* @return
* Converted data or FALSE.
*/
function drupal_convert_to_utf8($data, $encoding) {
if (function_exists('iconv')) {
$out = @iconv($encoding, 'utf-8', $data);
}
else if (function_exists('mb_convert_encoding')) {
$out = @mb_convert_encoding($data, 'utf-8', $encoding);
}
else if (function_exists('recode_string')) {
$out = @recode_string($encoding .'..utf-8', $data);
}
else {
watchdog('php', 'Unsupported encoding %s. Please install iconv, GNU recode or mbstring for PHP.', array('%s' => $encoding), WATCHDOG_ERROR);
return FALSE;
}
return $out;
}
/**
* Truncate a UTF-8-encoded string safely to a number of bytes.
*
* If the end position is in the middle of a UTF-8 sequence, it scans backwards
* until the beginning of the byte sequence.
*
* Use this function whenever you want to chop off a string at an unsure
* location. On the other hand, if you're sure that you're splitting on a
* character boundary (e.g. after using strpos() or similar), you can safely use
* substr() instead.
*
* @param $string
* The string to truncate.
* @param $len
* An upper limit on the returned string length.
* @return
* The truncated string.
*/
function drupal_truncate_bytes($string, $len) {
if (strlen($string) <= $len) {
return $string;
}
if ((ord($string[$len]) < 0x80) || (ord($string[$len]) >= 0xC0)) {
return substr($string, 0, $len);
}
while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0) {};
return substr($string, 0, $len);
}
/**
* Truncate a UTF-8-encoded string safely to a number of characters.
*
* @param $string
* The string to truncate.
* @param $len
* An upper limit on the returned string length.
* @param $wordsafe
* Flag to truncate at last space within the upper limit. Defaults to FALSE.
* @param $dots
* Flag to add trailing dots. Defaults to FALSE.
* @return
* The truncated string.
*/
function truncate_utf8($string, $len, $wordsafe = FALSE, $dots = FALSE) {
if (drupal_strlen($string) <= $len) {
return $string;
}
if ($dots) {
$len -= 4;
}
if ($wordsafe) {
$string = drupal_substr($string, 0, $len + 1); // leave one more character
if ($last_space = strrpos($string, ' ')) { // space exists AND is not on position 0
$string = substr($string, 0, $last_space);
}
else {
$string = drupal_substr($string, 0, $len);
}
}
else {
$string = drupal_substr($string, 0, $len);
}
if ($dots) {
$string .= ' ...';
}
return $string;
}
/**
* Encodes MIME/HTTP header values that contain non-ASCII, UTF-8 encoded
* characters.
*
* For example, mime_header_encode('tést.txt') returns "=?UTF-8?B?dMOpc3QudHh0?=".
*
* See http://www.rfc-editor.org/rfc/rfc2047.txt for more information.
*
* Notes:
* - Only encode strings that contain non-ASCII characters.
* - We progressively cut-off a chunk with truncate_utf8(). This is to ensure
* each chunk starts and ends on a character boundary.
* - Using \n as the chunk separator may cause problems on some systems and may
* have to be changed to \r\n or \r.
*/
function mime_header_encode($string) {
if (preg_match('/[^\x20-\x7E]/', $string)) {
$chunk_size = 47; // floor((75 - strlen("=?UTF-8?B??=")) * 0.75);
$len = strlen($string);
$output = '';
while ($len > 0) {
$chunk = drupal_truncate_bytes($string, $chunk_size);
$output .= ' =?UTF-8?B?'. base64_encode($chunk) ."?=\n";
$c = strlen($chunk);
$string = substr($string, $c);
$len -= $c;
}
return trim($output);
}
return $string;
}
/**
* Complement to mime_header_encode
*/
function mime_header_decode($header) {
// First step: encoded chunks followed by other encoded chunks (need to collapse whitespace)
$header = preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=\s+(?==\?)/', '_mime_header_decode', $header);
// Second step: remaining chunks (do not collapse whitespace)
return preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=/', '_mime_header_decode', $header);
}
/**
* Helper function to mime_header_decode
*/
function _mime_header_decode($matches) {
// Regexp groups:
// 1: Character set name
// 2: Escaping method (Q or B)
// 3: Encoded data
$data = ($matches[2] == 'B') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3]));
if (strtolower($matches[1]) != 'utf-8') {
$data = drupal_convert_to_utf8($data, $matches[1]);
}
return $data;
}
/**
* Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes.
*
* Double-escaped entities will only be decoded once ("&amp;lt;" becomes "&lt;",
* not "<"). Be careful when using this function, as decode_entities can revert
* previous sanitization efforts (&lt;script&gt; will become <script>).
*
* @param $text
* The text to decode entities in.
* @param $exclude
* An array of characters which should not be decoded. For example,
* array('<', '&', '"'). This affects both named and numerical entities.
*
* @return
* The input $text, with all HTML entities decoded once.
*/
function decode_entities($text, $exclude = array()) {
static $html_entities;
if (!isset($html_entities)) {
include_once './includes/unicode.entities.inc';
}
// Flip the exclude list so that we can do quick lookups later.
$exclude = array_flip($exclude);
// Use a regexp to select all entities in one pass, to avoid decoding
// double-escaped entities twice. The PREG_REPLACE_EVAL modifier 'e' is
// being used to allow for a callback (see
// http://php.net/manual/en/reference.pcre.pattern.modifiers).
return preg_replace('/&(#x?)?([A-Za-z0-9]+);/e', '_decode_entities("$1", "$2", "$0", $html_entities, $exclude)', $text);
}
/**
* Helper function for decode_entities
*/
function _decode_entities($prefix, $codepoint, $original, &$html_entities, &$exclude) {
// Named entity
if (!$prefix) {
// A named entity not in the exclude list.
if (isset($html_entities[$original]) && !isset($exclude[$html_entities[$original]])) {
return $html_entities[$original];
}
else {
return $original;
}
}
// Hexadecimal numerical entity
if ($prefix == '#x') {
$codepoint = base_convert($codepoint, 16, 10);
}
// Decimal numerical entity (strip leading zeros to avoid PHP octal notation)
else {
$codepoint = preg_replace('/^0+/', '', $codepoint);
}
// Encode codepoint as UTF-8 bytes
if ($codepoint < 0x80) {
$str = chr($codepoint);
}
else if ($codepoint < 0x800) {
$str = chr(0xC0 | ($codepoint >> 6))
. chr(0x80 | ($codepoint & 0x3F));
}
else if ($codepoint < 0x10000) {
$str = chr(0xE0 | ( $codepoint >> 12))
. chr(0x80 | (($codepoint >> 6) & 0x3F))
. chr(0x80 | ( $codepoint & 0x3F));
}
else if ($codepoint < 0x200000) {
$str = chr(0xF0 | ( $codepoint >> 18))
. chr(0x80 | (($codepoint >> 12) & 0x3F))
. chr(0x80 | (($codepoint >> 6) & 0x3F))
. chr(0x80 | ( $codepoint & 0x3F));
}
// Check for excluded characters
if (isset($exclude[$str])) {
return $original;
}
else {
return $str;
}
}
/**
* Count the amount of characters in a UTF-8 string. This is less than or
* equal to the byte count.
*/
function drupal_strlen($text) {
global $multibyte;
if ($multibyte == UNICODE_MULTIBYTE) {
return mb_strlen($text);
}
else {
// Do not count UTF-8 continuation bytes.
return strlen(preg_replace("/[\x80-\xBF]/", '', $text));
}
}
/**
* Uppercase a UTF-8 string.
*/
function drupal_strtoupper($text) {
global $multibyte;
if ($multibyte == UNICODE_MULTIBYTE) {
return mb_strtoupper($text);
}
else {
// Use C-locale for ASCII-only uppercase
$text = strtoupper($text);
// Case flip Latin-1 accented letters
$text = preg_replace_callback('/\xC3[\xA0-\xB6\xB8-\xBE]/', '_unicode_caseflip', $text);
return $text;
}
}
/**
* Lowercase a UTF-8 string.
*/
function drupal_strtolower($text) {
global $multibyte;
if ($multibyte == UNICODE_MULTIBYTE) {
return mb_strtolower($text);
}
else {
// Use C-locale for ASCII-only lowercase
$text = strtolower($text);
// Case flip Latin-1 accented letters
$text = preg_replace_callback('/\xC3[\x80-\x96\x98-\x9E]/', '_unicode_caseflip', $text);
return $text;
}
}
/**
* Helper function for case conversion of Latin-1.
* Used for flipping U+C0-U+DE to U+E0-U+FD and back.
*/
function _unicode_caseflip($matches) {
return $matches[0][0] . chr(ord($matches[0][1]) ^ 32);
}
/**
* Capitalize the first letter of a UTF-8 string.
*/
function drupal_ucfirst($text) {
// Note: no mbstring equivalent!
return drupal_strtoupper(drupal_substr($text, 0, 1)) . drupal_substr($text, 1);
}
/**
* Cut off a piece of a string based on character indices and counts. Follows
* the same behavior as PHP's own substr() function.
*
* Note that for cutting off a string at a known character/substring
* location, the usage of PHP's normal strpos/substr is safe and
* much faster.
*/
function drupal_substr($text, $start, $length = NULL) {
global $multibyte;
if ($multibyte == UNICODE_MULTIBYTE) {
return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length);
}
else {
$strlen = strlen($text);
// Find the starting byte offset
$bytes = 0;
if ($start > 0) {
// Count all the continuation bytes from the start until we have found
// $start characters
$bytes = -1; $chars = -1;
while ($bytes < $strlen && $chars < $start) {
$bytes++;
$c = ord($text[$bytes]);
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
}
}
}
else if ($start < 0) {
// Count all the continuation bytes from the end until we have found
// abs($start) characters
$start = abs($start);
$bytes = $strlen; $chars = 0;
while ($bytes > 0 && $chars < $start) {
$bytes--;
$c = ord($text[$bytes]);
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
}
}
}
$istart = $bytes;
// Find the ending byte offset
if ($length === NULL) {
$bytes = $strlen - 1;
}
else if ($length > 0) {
// Count all the continuation bytes from the starting index until we have
// found $length + 1 characters. Then backtrack one byte.
$bytes = $istart; $chars = 0;
while ($bytes < $strlen && $chars < $length) {
$bytes++;
$c = ord($text[$bytes]);
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
}
}
$bytes--;
}
else if ($length < 0) {
// Count all the continuation bytes from the end until we have found
// abs($length) characters
$length = abs($length);
$bytes = $strlen - 1; $chars = 0;
while ($bytes >= 0 && $chars < $length) {
$c = ord($text[$bytes]);
if ($c < 0x80 || $c >= 0xC0) {
$chars++;
}
$bytes--;
}
}
$iend = $bytes;
return substr($text, $istart, max(0, $iend - $istart + 1));
}
}

515
includes/xmlrpc.inc Normal file
View file

@ -0,0 +1,515 @@
<?php
/**
* @file
* Drupal XML-RPC library. Based on the IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002-2005
* Version 1.7 (beta) - Simon Willison, 23rd May 2005
* Site: http://scripts.incutio.com/xmlrpc/
* Manual: http://scripts.incutio.com/xmlrpc/manual.php
* This version is made available under the GNU GPL License
*/
/**
* Recursively turn a data structure into objects with 'data' and 'type' attributes.
*
* @param $data
* The data structure.
* @param $type
* Optional type assign to $data.
* @return
* Object.
*/
function xmlrpc_value($data, $type = FALSE) {
$xmlrpc_value = new stdClass();
$xmlrpc_value->data = $data;
if (!$type) {
$type = xmlrpc_value_calculate_type($xmlrpc_value);
}
$xmlrpc_value->type = $type;
if ($type == 'struct') {
// Turn all the values in the array into new xmlrpc_values
foreach ($xmlrpc_value->data as $key => $value) {
$xmlrpc_value->data[$key] = xmlrpc_value($value);
}
}
if ($type == 'array') {
for ($i = 0, $j = count($xmlrpc_value->data); $i < $j; $i++) {
$xmlrpc_value->data[$i] = xmlrpc_value($xmlrpc_value->data[$i]);
}
}
return $xmlrpc_value;
}
/**
* Map PHP type to XML-RPC type.
*
* @param $xmlrpc_value
* Variable whose type should be mapped.
* @return
* XML-RPC type as string.
* @see
* http://www.xmlrpc.com/spec#scalars
*/
function xmlrpc_value_calculate_type(&$xmlrpc_value) {
// http://www.php.net/gettype: Never use gettype() to test for a certain type [...] Instead, use the is_* functions.
if (is_bool($xmlrpc_value->data)) {
return 'boolean';
}
if (is_double($xmlrpc_value->data)) {
return 'double';
}
if (is_int($xmlrpc_value->data)) {
return 'int';
}
if (is_array($xmlrpc_value->data)) {
// empty or integer-indexed arrays are 'array', string-indexed arrays 'struct'
return empty($xmlrpc_value->data) || range(0, count($xmlrpc_value->data) - 1) === array_keys($xmlrpc_value->data) ? 'array' : 'struct';
}
if (is_object($xmlrpc_value->data)) {
if ($xmlrpc_value->data->is_date) {
return 'date';
}
if ($xmlrpc_value->data->is_base64) {
return 'base64';
}
$xmlrpc_value->data = get_object_vars($xmlrpc_value->data);
return 'struct';
}
// default
return 'string';
}
/**
* Generate XML representing the given value.
*
* @param $xmlrpc_value
* @return
* XML representation of value.
*/
function xmlrpc_value_get_xml($xmlrpc_value) {
switch ($xmlrpc_value->type) {
case 'boolean':
return '<boolean>'. (($xmlrpc_value->data) ? '1' : '0') .'</boolean>';
break;
case 'int':
return '<int>'. $xmlrpc_value->data .'</int>';
break;
case 'double':
return '<double>'. $xmlrpc_value->data .'</double>';
break;
case 'string':
// Note: we don't escape apostrophes because of the many blogging clients
// that don't support numerical entities (and XML in general) properly.
return '<string>'. htmlspecialchars($xmlrpc_value->data) .'</string>';
break;
case 'array':
$return = '<array><data>'."\n";
foreach ($xmlrpc_value->data as $item) {
$return .= ' <value>'. xmlrpc_value_get_xml($item) ."</value>\n";
}
$return .= '</data></array>';
return $return;
break;
case 'struct':
$return = '<struct>'."\n";
foreach ($xmlrpc_value->data as $name => $value) {
$return .= " <member><name>". check_plain($name) ."</name><value>";
$return .= xmlrpc_value_get_xml($value) ."</value></member>\n";
}
$return .= '</struct>';
return $return;
break;
case 'date':
return xmlrpc_date_get_xml($xmlrpc_value->data);
break;
case 'base64':
return xmlrpc_base64_get_xml($xmlrpc_value->data);
break;
}
return FALSE;
}
/**
* Construct an object representing an XML-RPC message.
*
* @param $message
* String containing XML as defined at http://www.xmlrpc.com/spec
* @return
* Object
*/
function xmlrpc_message($message) {
$xmlrpc_message = new stdClass();
$xmlrpc_message->array_structs = array(); // The stack used to keep track of the current array/struct
$xmlrpc_message->array_structs_types = array(); // The stack used to keep track of if things are structs or array
$xmlrpc_message->current_struct_name = array(); // A stack as well
$xmlrpc_message->message = $message;
return $xmlrpc_message;
}
/**
* Parse an XML-RPC message. If parsing fails, the faultCode and faultString
* will be added to the message object.
*
* @param $xmlrpc_message
* Object generated by xmlrpc_message()
* @return
* TRUE if parsing succeeded; FALSE otherwise
*/
function xmlrpc_message_parse(&$xmlrpc_message) {
$xmlrpc_message->_parser = xml_parser_create();
// Set XML parser to take the case of tags into account.
xml_parser_set_option($xmlrpc_message->_parser, XML_OPTION_CASE_FOLDING, FALSE);
// Set XML parser callback functions
xml_set_element_handler($xmlrpc_message->_parser, 'xmlrpc_message_tag_open', 'xmlrpc_message_tag_close');
xml_set_character_data_handler($xmlrpc_message->_parser, 'xmlrpc_message_cdata');
xmlrpc_message_set($xmlrpc_message);
// Strip XML declaration.
$header = preg_replace('/<\?xml.*?\?'.'>/s', '', substr($xmlrpc_message->message, 0, 100), 1);
$xml = trim(substr_replace($xmlrpc_message->message, $header, 0, 100));
if ($xml == '') {
return FALSE;
}
// Strip DTD.
$header = preg_replace('/^<!DOCTYPE[^>]*+>/i', '', substr($xml, 0, 200), 1);
$xml = trim(substr_replace($xml, $header, 0, 200));
if ($xml == '') {
return FALSE;
}
// Confirm the XML now starts with a valid root tag. A root tag can end in [> \t\r\n]
$root_tag = substr($xml, 0, strcspn(substr($xml, 0, 20), "> \t\r\n"));
// Reject a second DTD.
if (strtoupper($root_tag) == '<!DOCTYPE') {
return FALSE;
}
if (!in_array($root_tag, array('<methodCall', '<methodResponse', '<fault'))) {
return FALSE;
}
// Skip parsing if there is an unreasonably large number of tags.
// substr_count() has much better performance (compared to preg_match_all())
// for large payloads but is less accurate, so we check for twice the desired
// number of allowed tags (to take into account opening/closing tags as well
// as false positives).
if (substr_count($xml, '<') > 2 * variable_get('xmlrpc_message_maximum_tag_count', 30000)) {
return FALSE;
}
if (!xml_parse($xmlrpc_message->_parser, $xml)) {
return FALSE;
}
xml_parser_free($xmlrpc_message->_parser);
// Grab the error messages, if any
$xmlrpc_message = xmlrpc_message_get();
if (!isset($xmlrpc_message->messagetype)) {
return FALSE;
}
elseif ($xmlrpc_message->messagetype == 'fault') {
$xmlrpc_message->fault_code = $xmlrpc_message->params[0]['faultCode'];
$xmlrpc_message->fault_string = $xmlrpc_message->params[0]['faultString'];
}
return TRUE;
}
/**
* Store a copy of the $xmlrpc_message object temporarily.
*
* @param $value
* Object
* @return
* The most recently stored $xmlrpc_message
*/
function xmlrpc_message_set($value = NULL) {
static $xmlrpc_message;
if ($value) {
$xmlrpc_message = $value;
}
return $xmlrpc_message;
}
function xmlrpc_message_get() {
return xmlrpc_message_set();
}
function xmlrpc_message_tag_open($parser, $tag, $attr) {
$xmlrpc_message = xmlrpc_message_get();
$xmlrpc_message->current_tag_contents = '';
$xmlrpc_message->last_open = $tag;
switch ($tag) {
case 'methodCall':
case 'methodResponse':
case 'fault':
$xmlrpc_message->messagetype = $tag;
break;
// Deal with stacks of arrays and structs
case 'data':
$xmlrpc_message->array_structs_types[] = 'array';
$xmlrpc_message->array_structs[] = array();
break;
case 'struct':
$xmlrpc_message->array_structs_types[] = 'struct';
$xmlrpc_message->array_structs[] = array();
break;
}
xmlrpc_message_set($xmlrpc_message);
}
function xmlrpc_message_cdata($parser, $cdata) {
$xmlrpc_message = xmlrpc_message_get();
$xmlrpc_message->current_tag_contents .= $cdata;
xmlrpc_message_set($xmlrpc_message);
}
function xmlrpc_message_tag_close($parser, $tag) {
$xmlrpc_message = xmlrpc_message_get();
$value_flag = FALSE;
switch ($tag) {
case 'int':
case 'i4':
$value = (int)trim($xmlrpc_message->current_tag_contents);
$value_flag = TRUE;
break;
case 'double':
$value = (double)trim($xmlrpc_message->current_tag_contents);
$value_flag = TRUE;
break;
case 'string':
$value = $xmlrpc_message->current_tag_contents;
$value_flag = TRUE;
break;
case 'dateTime.iso8601':
$value = xmlrpc_date(trim($xmlrpc_message->current_tag_contents));
// $value = $iso->getTimestamp();
$value_flag = TRUE;
break;
case 'value':
// If no type is indicated, the type is string
// We take special care for empty values
if (trim($xmlrpc_message->current_tag_contents) != '' || (isset($xmlrpc_message->last_open) && ($xmlrpc_message->last_open == 'value'))) {
$value = (string)$xmlrpc_message->current_tag_contents;
$value_flag = TRUE;
}
unset($xmlrpc_message->last_open);
break;
case 'boolean':
$value = (boolean)trim($xmlrpc_message->current_tag_contents);
$value_flag = TRUE;
break;
case 'base64':
$value = base64_decode(trim($xmlrpc_message->current_tag_contents));
$value_flag = TRUE;
break;
// Deal with stacks of arrays and structs
case 'data':
case 'struct':
$value = array_pop($xmlrpc_message->array_structs );
array_pop($xmlrpc_message->array_structs_types);
$value_flag = TRUE;
break;
case 'member':
array_pop($xmlrpc_message->current_struct_name);
break;
case 'name':
$xmlrpc_message->current_struct_name[] = trim($xmlrpc_message->current_tag_contents);
break;
case 'methodName':
$xmlrpc_message->methodname = trim($xmlrpc_message->current_tag_contents);
break;
}
if ($value_flag) {
if (count($xmlrpc_message->array_structs ) > 0) {
// Add value to struct or array
if ($xmlrpc_message->array_structs_types[count($xmlrpc_message->array_structs_types)-1] == 'struct') {
// Add to struct
$xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][$xmlrpc_message->current_struct_name[count($xmlrpc_message->current_struct_name)-1]] = $value;
}
else {
// Add to array
$xmlrpc_message->array_structs [count($xmlrpc_message->array_structs )-1][] = $value;
}
}
else {
// Just add as a parameter
$xmlrpc_message->params[] = $value;
}
}
if (!in_array($tag, array("data", "struct", "member"))) {
$xmlrpc_message->current_tag_contents = '';
}
xmlrpc_message_set($xmlrpc_message);
}
/**
* Construct an object representing an XML-RPC request
*
* @param $method
* The name of the method to be called
* @param $args
* An array of parameters to send with the method.
* @return
* Object
*/
function xmlrpc_request($method, $args) {
$xmlrpc_request = new stdClass();
$xmlrpc_request->method = $method;
$xmlrpc_request->args = $args;
$xmlrpc_request->xml = <<<EOD
<?xml version="1.0"?>
<methodCall>
<methodName>{$xmlrpc_request->method}</methodName>
<params>
EOD;
foreach ($xmlrpc_request->args as $arg) {
$xmlrpc_request->xml .= '<param><value>';
$v = xmlrpc_value($arg);
$xmlrpc_request->xml .= xmlrpc_value_get_xml($v);
$xmlrpc_request->xml .= "</value></param>\n";
}
$xmlrpc_request->xml .= '</params></methodCall>';
return $xmlrpc_request;
}
function xmlrpc_error($code = NULL, $message = NULL, $reset = FALSE) {
static $xmlrpc_error;
if (isset($code)) {
$xmlrpc_error = new stdClass();
$xmlrpc_error->is_error = TRUE;
$xmlrpc_error->code = $code;
$xmlrpc_error->message = $message;
}
elseif ($reset) {
$xmlrpc_error = NULL;
}
return $xmlrpc_error;
}
function xmlrpc_error_get_xml($xmlrpc_error) {
return <<<EOD
<methodResponse>
<fault>
<value>
<struct>
<member>
<name>faultCode</name>
<value><int>{$xmlrpc_error->code}</int></value>
</member>
<member>
<name>faultString</name>
<value><string>{$xmlrpc_error->message}</string></value>
</member>
</struct>
</value>
</fault>
</methodResponse>
EOD;
}
function xmlrpc_date($time) {
$xmlrpc_date = new stdClass();
$xmlrpc_date->is_date = TRUE;
// $time can be a PHP timestamp or an ISO one
if (is_numeric($time)) {
$xmlrpc_date->year = gmdate('Y', $time);
$xmlrpc_date->month = gmdate('m', $time);
$xmlrpc_date->day = gmdate('d', $time);
$xmlrpc_date->hour = gmdate('H', $time);
$xmlrpc_date->minute = gmdate('i', $time);
$xmlrpc_date->second = gmdate('s', $time);
$xmlrpc_date->iso8601 = gmdate('Ymd\TH:i:s', $time);
}
else {
$xmlrpc_date->iso8601 = $time;
$time = str_replace(array('-', ':'), '', $time);
$xmlrpc_date->year = substr($time, 0, 4);
$xmlrpc_date->month = substr($time, 4, 2);
$xmlrpc_date->day = substr($time, 6, 2);
$xmlrpc_date->hour = substr($time, 9, 2);
$xmlrpc_date->minute = substr($time, 11, 2);
$xmlrpc_date->second = substr($time, 13, 2);
}
return $xmlrpc_date;
}
function xmlrpc_date_get_xml($xmlrpc_date) {
return '<dateTime.iso8601>'. $xmlrpc_date->year . $xmlrpc_date->month . $xmlrpc_date->day .'T'. $xmlrpc_date->hour .':'. $xmlrpc_date->minute .':'. $xmlrpc_date->second .'</dateTime.iso8601>';
}
function xmlrpc_base64($data) {
$xmlrpc_base64 = new stdClass();
$xmlrpc_base64->is_base64 = TRUE;
$xmlrpc_base64->data = $data;
return $xmlrpc_base64;
}
function xmlrpc_base64_get_xml($xmlrpc_base64) {
return '<base64>'. base64_encode($xmlrpc_base64->data) .'</base64>';
}
/**
* Execute an XML remote procedural call. This is private function; call xmlrpc()
* in common.inc instead of this function.
*
* @return
* A $xmlrpc_message object if the call succeeded; FALSE if the call failed
*/
function _xmlrpc() {
$args = func_get_args();
$url = array_shift($args);
xmlrpc_clear_error();
if (is_array($args[0])) {
$method = 'system.multicall';
$multicall_args = array();
foreach ($args[0] as $call) {
$multicall_args[] = array('methodName' => array_shift($call), 'params' => $call);
}
$args = array($multicall_args);
}
else {
$method = array_shift($args);
}
$xmlrpc_request = xmlrpc_request($method, $args);
$result = drupal_http_request($url, array("Content-Type" => "text/xml"), 'POST', $xmlrpc_request->xml);
if ($result->code != 200) {
xmlrpc_error($result->code, $result->error);
return FALSE;
}
$message = xmlrpc_message($result->data);
// Now parse what we've got back
if (!xmlrpc_message_parse($message)) {
// XML error
xmlrpc_error(-32700, t('Parse error. Not well formed'));
return FALSE;
}
// Is the message a fault?
if ($message->messagetype == 'fault') {
xmlrpc_error($message->fault_code, $message->fault_string);
return FALSE;
}
// Message must be OK
return $message->params[0];
}
/**
* Returns the last XML-RPC client error number
*/
function xmlrpc_errno() {
$error = xmlrpc_error();
return ($error != NULL ? $error->code : NULL);
}
/**
* Returns the last XML-RPC client error message
*/
function xmlrpc_error_msg() {
$error = xmlrpc_error();
return ($error != NULL ? $error->message : NULL);
}
/**
* Clears any previous error.
*/
function xmlrpc_clear_error() {
xmlrpc_error(NULL, NULL, TRUE);
}

323
includes/xmlrpcs.inc Normal file
View file

@ -0,0 +1,323 @@
<?php
/**
* The main entry point for XML-RPC requests.
*
* @param $callbacks
* Array of external XML-RPC method names with the callbacks they map to.
*/
function xmlrpc_server($callbacks) {
$xmlrpc_server = new stdClass();
// Define built-in XML-RPC method names
$defaults = array(
'system.multicall' => 'xmlrpc_server_multicall',
array(
'system.methodSignature',
'xmlrpc_server_method_signature',
array('array', 'string'),
'Returns an array describing the return type and required parameters of a method.'
),
array(
'system.getCapabilities',
'xmlrpc_server_get_capabilities',
array('struct'),
'Returns a struct describing the XML-RPC specifications supported by this server.'
),
array(
'system.listMethods',
'xmlrpc_server_list_methods',
array('array'),
'Returns an array of available methods on this server.'),
array(
'system.methodHelp',
'xmlrpc_server_method_help',
array('string', 'string'),
'Returns a documentation string for the specified method.')
);
// We build an array of all method names by combining the built-ins
// with those defined by modules implementing the _xmlrpc hook.
// Built-in methods are overridable.
foreach (array_merge($defaults, (array)$callbacks) as $key => $callback) {
// we could check for is_array($callback)
if (is_int($key)) {
$method = $callback[0];
$xmlrpc_server->callbacks[$method] = $callback[1];
$xmlrpc_server->signatures[$method] = $callback[2];
$xmlrpc_server->help[$method] = $callback[3];
}
else {
$xmlrpc_server->callbacks[$key] = $callback;
$xmlrpc_server->signatures[$key] = '';
$xmlrpc_server->help[$key] = '';
}
}
$data = file_get_contents('php://input');
if (!$data) {
die('XML-RPC server accepts POST requests only.');
}
$xmlrpc_server->message = xmlrpc_message($data);
if (!xmlrpc_message_parse($xmlrpc_server->message)) {
xmlrpc_server_error(-32700, t('Parse error. Request not well formed.'));
}
if ($xmlrpc_server->message->messagetype != 'methodCall') {
xmlrpc_server_error(-32600, t('Server error. Invalid XML-RPC. Request must be a methodCall.'));
}
if (!isset($xmlrpc_server->message->params)) {
$xmlrpc_server->message->params = array();
}
xmlrpc_server_set($xmlrpc_server);
$result = xmlrpc_server_call($xmlrpc_server, $xmlrpc_server->message->methodname, $xmlrpc_server->message->params);
if (is_object($result) && !empty($result->is_error)) {
xmlrpc_server_error($result);
}
// Encode the result
$r = xmlrpc_value($result);
// Create the XML
$xml = '
<methodResponse>
<params>
<param>
<value>'.
xmlrpc_value_get_xml($r)
.'</value>
</param>
</params>
</methodResponse>
';
// Send it
xmlrpc_server_output($xml);
}
/**
* Throw an XML-RPC error.
*
* @param $error
* an error object OR integer error code
* @param $message
* description of error, used only if integer error code was passed
*/
function xmlrpc_server_error($error, $message = FALSE) {
if ($message && !is_object($error)) {
$error = xmlrpc_error($error, $message);
}
xmlrpc_server_output(xmlrpc_error_get_xml($error));
}
function xmlrpc_server_output($xml) {
$xml = '<?xml version="1.0"?>'."\n". $xml;
header('Connection: close');
header('Content-Length: '. strlen($xml));
header('Content-Type: text/xml');
header('Date: '. date('r'));
echo $xml;
exit;
}
/**
* Store a copy of the request temporarily.
*
* @param $xmlrpc_server
* Request object created by xmlrpc_server().
*/
function xmlrpc_server_set($xmlrpc_server = NULL) {
static $server;
if (!isset($server)) {
$server = $xmlrpc_server;
}
return $server;
}
// Retrieve the stored request.
function xmlrpc_server_get() {
return xmlrpc_server_set();
}
/**
* Dispatch the request and any parameters to the appropriate handler.
*
* @param $xmlrpc_server
* @param $methodname
* The external XML-RPC method name, e.g. 'system.methodHelp'
* @param $args
* Array containing any parameters that were sent along with the request.
*/
function xmlrpc_server_call($xmlrpc_server, $methodname, $args) {
// Make sure parameters are in an array
if ($args && !is_array($args)) {
$args = array($args);
}
// Has this method been mapped to a Drupal function by us or by modules?
if (!isset($xmlrpc_server->callbacks[$methodname])) {
return xmlrpc_error(-32601, t('Server error. Requested method @methodname not specified.', array("@methodname" => $xmlrpc_server->message->methodname)));
}
$method = $xmlrpc_server->callbacks[$methodname];
$signature = $xmlrpc_server->signatures[$methodname];
// If the method has a signature, validate the request against the signature
if (is_array($signature)) {
$ok = TRUE;
$return_type = array_shift($signature);
// Check the number of arguments
if (count($args) != count($signature)) {
return xmlrpc_error(-32602, t('Server error. Wrong number of method parameters.'));
}
// Check the argument types
foreach ($signature as $key => $type) {
$arg = $args[$key];
switch ($type) {
case 'int':
case 'i4':
if (is_array($arg) || !is_int($arg)) {
$ok = FALSE;
}
break;
case 'base64':
case 'string':
if (!is_string($arg)) {
$ok = FALSE;
}
break;
case 'boolean':
if ($arg !== FALSE && $arg !== TRUE) {
$ok = FALSE;
}
break;
case 'float':
case 'double':
if (!is_float($arg)) {
$ok = FALSE;
}
break;
case 'date':
case 'dateTime.iso8601':
if (!$arg->is_date) {
$ok = FALSE;
}
break;
}
if (!$ok) {
return xmlrpc_error(-32602, t('Server error. Invalid method parameters.'));
}
}
}
if (!function_exists($method)) {
return xmlrpc_error(-32601, t('Server error. Requested function @method does not exist.', array("@method" => $method)));
}
// Call the mapped function
return call_user_func_array($method, $args);
}
function xmlrpc_server_multicall($methodcalls) {
// See http://www.xmlrpc.com/discuss/msgReader$1208
// To avoid multicall expansion attacks, limit the number of duplicate method
// calls allowed with a default of 1. Set to -1 for unlimited.
$duplicate_method_limit = variable_get('xmlrpc_multicall_duplicate_method_limit', 1);
$method_count = array();
$return = array();
$xmlrpc_server = xmlrpc_server_get();
foreach ($methodcalls as $call) {
$ok = TRUE;
if (!isset($call['methodName']) || !isset($call['params'])) {
$result = xmlrpc_error(3, t('Invalid syntax for system.multicall.'));
$ok = FALSE;
}
$method = $call['methodName'];
$method_count[$method] = isset($method_count[$method]) ? $method_count[$method] + 1 : 1;
$params = $call['params'];
if ($method == 'system.multicall') {
$result = xmlrpc_error(-32600, t('Recursive calls to system.multicall are forbidden.'));
}
elseif ($duplicate_method_limit > 0 && $method_count[$method] > $duplicate_method_limit) {
$result = xmlrpc_error(-156579, t('Too many duplicate method calls in system.multicall.'));
}
elseif ($ok) {
$result = xmlrpc_server_call($xmlrpc_server, $method, $params);
}
if (is_object($result) && !empty($result->is_error)) {
$return[] = array(
'faultCode' => $result->code,
'faultString' => $result->message
);
}
else {
$return[] = $result;
}
}
return $return;
}
/**
* XML-RPC method system.listMethods maps to this function.
*/
function xmlrpc_server_list_methods() {
$xmlrpc_server = xmlrpc_server_get();
return array_keys($xmlrpc_server->callbacks);
}
/**
* XML-RPC method system.getCapabilities maps to this function.
* See http://groups.yahoo.com/group/xml-rpc/message/2897
*/
function xmlrpc_server_get_capabilities() {
return array(
'xmlrpc' => array(
'specUrl' => 'http://www.xmlrpc.com/spec',
'specVersion' => 1
),
'faults_interop' => array(
'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
'specVersion' => 20010516
),
'system.multicall' => array(
'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',
'specVersion' => 1
),
'introspection' => array(
'specUrl' => 'http://scripts.incutio.com/xmlrpc/introspection.html',
'specVersion' => 1
)
);
}
/**
* XML-RPC method system.methodSignature maps to this function.
*
* @param $methodname
* Name of method for which we return a method signature.
* @return array
* An array of types representing the method signature of the
* function that the methodname maps to. The methodSignature of
* this function is 'array', 'string' because it takes an array
* and returns a string.
*/
function xmlrpc_server_method_signature($methodname) {
$xmlrpc_server = xmlrpc_server_get();
if (!isset($xmlrpc_server->callbacks[$methodname])) {
return xmlrpc_error(-32601, t('Server error. Requested method @methodname not specified.', array("@methodname" => $methodname)));
}
if (!is_array($xmlrpc_server->signatures[$methodname])) {
return xmlrpc_error(-32601, t('Server error. Requested method @methodname signature not specified.', array("@methodname" => $methodname)));
}
// We array of types
$return = array();
foreach ($xmlrpc_server->signatures[$methodname] as $type) {
$return[] = $type;
}
return $return;
}
/**
* XML-RPC method system.methodHelp maps to this function.
*
* @param $method
* Name of method for which we return a help string.
*/
function xmlrpc_server_method_help($method) {
$xmlrpc_server = xmlrpc_server_get();
return $xmlrpc_server->help[$method];
}

38
index.php Normal file
View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* The PHP page that serves all page requests on a Drupal installation.
*
* The routines here dispatch control to the appropriate handler, which then
* prints the appropriate page.
*
* All Drupal code is released under the GNU General Public License.
* See COPYRIGHT.txt and LICENSE.txt.
*/
require_once './includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
$return = menu_execute_active_handler();
// Menu status constants are integers; page content is a string.
if (is_int($return)) {
switch ($return) {
case MENU_NOT_FOUND:
drupal_not_found();
break;
case MENU_ACCESS_DENIED:
drupal_access_denied();
break;
case MENU_SITE_OFFLINE:
drupal_site_offline();
break;
}
}
elseif (isset($return)) {
// Print any value (including an empty string) except NULL or undefined:
print theme('page', $return);
}
drupal_page_footer();

1195
install.php Normal file

File diff suppressed because it is too large Load diff

266
misc/ahah.js Normal file
View file

@ -0,0 +1,266 @@
/**
* Provides AJAX-like page updating via AHAH (Asynchronous HTML and HTTP).
*
* AHAH is a method of making a request via Javascript while viewing an HTML
* page. The request returns a small chunk of HTML, which is then directly
* injected into the page.
*
* Drupal uses this file to enhance form elements with #ahah[path] and
* #ahah[wrapper] properties. If set, this file will automatically be included
* to provide AHAH capabilities.
*/
/**
* Attaches the ahah behavior to each ahah form element.
*/
Drupal.behaviors.ahah = function(context) {
for (var base in Drupal.settings.ahah) {
if (!$('#'+ base + '.ahah-processed').size()) {
var element_settings = Drupal.settings.ahah[base];
$(element_settings.selector).each(function() {
element_settings.element = this;
var ahah = new Drupal.ahah(base, element_settings);
});
$('#'+ base).addClass('ahah-processed');
}
}
};
/**
* AHAH object.
*/
Drupal.ahah = function(base, element_settings) {
// Set the properties for this object.
this.element = element_settings.element;
this.selector = element_settings.selector;
this.event = element_settings.event;
this.keypress = element_settings.keypress;
this.url = element_settings.url;
this.wrapper = '#'+ element_settings.wrapper;
this.effect = element_settings.effect;
this.method = element_settings.method;
this.progress = element_settings.progress;
this.button = element_settings.button || { };
this.immutable = element_settings.immutable;
this.buildId = null;
if (this.effect == 'none') {
this.showEffect = 'show';
this.hideEffect = 'hide';
this.showSpeed = '';
}
else if (this.effect == 'fade') {
this.showEffect = 'fadeIn';
this.hideEffect = 'fadeOut';
this.showSpeed = 'slow';
}
else {
this.showEffect = this.effect + 'Toggle';
this.hideEffect = this.effect + 'Toggle';
this.showSpeed = 'slow';
}
// Record the form action and target, needed for iFrame file uploads.
var form = $(this.element).parents('form');
this.form_action = form.attr('action');
this.form_target = form.attr('target');
this.form_encattr = form.attr('encattr');
// Set the options for the ajaxSubmit function.
// The 'this' variable will not persist inside of the options object.
var ahah = this;
var options = {
url: ahah.url,
data: ahah.button,
beforeSubmit: function(form_values, element_settings, options) {
return ahah.beforeSubmit(form_values, element_settings, options);
},
beforeSend: function(request, options) {
return ahah.beforeSend(request, options);
},
success: function(response, status) {
// Sanity check for browser support (object expected).
// When using iFrame uploads, responses must be returned as a string.
if (typeof(response) == 'string') {
response = Drupal.parseJson(response);
}
return ahah.success(response, status);
},
complete: function(response, status) {
ahah.complete(response, status);
if (status == 'error' || status == 'parsererror') {
return ahah.error(response, ahah.url);
}
},
dataType: 'json',
type: 'POST'
};
// Bind the ajaxSubmit function to the element event.
$(element_settings.element).bind(element_settings.event, function() {
$(element_settings.element).parents('form').ajaxSubmit(options);
return false;
});
// If necessary, enable keyboard submission so that AHAH behaviors
// can be triggered through keyboard input as well as e.g. a mousedown
// action.
if (element_settings.keypress) {
$(element_settings.element).keypress(function(event) {
// Detect enter key.
if (event.keyCode == 13) {
$(element_settings.element).trigger(element_settings.event);
return false;
}
});
}
};
/**
* Handler for the form redirection submission.
*/
Drupal.ahah.prototype.beforeSubmit = function (form_values, element, options) {
// Disable the element that received the change.
$(this.element).addClass('progress-disabled').attr('disabled', true);
// Insert progressbar or throbber.
if (this.progress.type == 'bar') {
var progressBar = new Drupal.progressBar('ahah-progress-' + this.element.id, eval(this.progress.update_callback), this.progress.method, eval(this.progress.error_callback));
if (this.progress.message) {
progressBar.setProgress(-1, this.progress.message);
}
if (this.progress.url) {
progressBar.startMonitoring(this.progress.url, this.progress.interval || 1500);
}
this.progress.element = $(progressBar.element).addClass('ahah-progress ahah-progress-bar');
this.progress.object = progressBar;
$(this.element).after(this.progress.element);
}
else if (this.progress.type == 'throbber') {
this.progress.element = $('<div class="ahah-progress ahah-progress-throbber"><div class="throbber">&nbsp;</div></div>');
if (this.progress.message) {
$('.throbber', this.progress.element).after('<div class="message">' + this.progress.message + '</div>')
}
$(this.element).after(this.progress.element);
}
// Record the build-id.
if (this.immutable) {
var ahah = this;
$.each(form_values, function () {
if (this.name == 'form_build_id') {
ahah.buildId = this.value;
return false;
}
});
}
};
/**
* Modify the request object before it is sent.
*/
Drupal.ahah.prototype.beforeSend = function (request, options) {
if (this.immutable) {
request.setRequestHeader('X-Drupal-Accept-Build-Id', '1');
}
}
/**
* Handler for the form redirection completion.
*/
Drupal.ahah.prototype.success = function (response, status) {
var wrapper = $(this.wrapper);
var form = $(this.element).parents('form');
// Manually insert HTML into the jQuery object, using $() directly crashes
// Safari with long string lengths. http://dev.jquery.com/ticket/1152
var new_content = $('<div></div>').html(response.data);
// Restore the previous action and target to the form.
form.attr('action', this.form_action);
this.form_target ? form.attr('target', this.form_target) : form.removeAttr('target');
this.form_encattr ? form.attr('target', this.form_encattr) : form.removeAttr('encattr');
// Remove the progress element.
if (this.progress.element) {
$(this.progress.element).remove();
}
if (this.progress.object) {
this.progress.object.stopMonitoring();
}
$(this.element).removeClass('progress-disabled').attr('disabled', false);
// Add the new content to the page.
Drupal.freezeHeight();
if (this.method == 'replace') {
wrapper.empty().append(new_content);
}
else {
wrapper[this.method](new_content);
}
// Immediately hide the new content if we're using any effects.
if (this.showEffect != 'show') {
new_content.hide();
}
// Determine what effect use and what content will receive the effect, then
// show the new content. For browser compatibility, Safari is excluded from
// using effects on table rows.
if (($.browser.safari && $("tr.ahah-new-content", new_content).size() > 0)) {
new_content.show();
}
else if ($('.ahah-new-content', new_content).size() > 0) {
$('.ahah-new-content', new_content).hide();
new_content.show();
$(".ahah-new-content", new_content)[this.showEffect](this.showSpeed);
}
else if (this.showEffect != 'show') {
new_content[this.showEffect](this.showSpeed);
}
// Attach all javascript behaviors to the new content, if it was successfully
// added to the page, this if statement allows #ahah[wrapper] to be optional.
if (new_content.parents('html').length > 0) {
Drupal.attachBehaviors(new_content);
}
Drupal.unfreezeHeight();
};
/**
* Handler for the form redirection error.
*/
Drupal.ahah.prototype.error = function (response, uri) {
alert(Drupal.ahahError(response, uri));
// Resore the previous action and target to the form.
$(this.element).parent('form').attr( { action: this.form_action, target: this.form_target} );
// Remove the progress element.
if (this.progress.element) {
$(this.progress.element).remove();
}
if (this.progress.object) {
this.progress.object.stopMonitoring();
}
// Undo hide.
$(this.wrapper).show();
// Re-enable the element.
$(this.element).removeClass('progess-disabled').attr('disabled', false);
};
/**
* Handler called when the request finishes, whether in failure or success.
*/
Drupal.ahah.prototype.complete = function (response, status) {
// Update form build id if necessary.
if (this.immutable) {
var newBuildId = response.getResponseHeader('X-Drupal-Build-Id');
if (this.buildId && newBuildId && this.buildId != newBuildId) {
var $element = $('input[name="form_build_id"][value="' + this.buildId + '"]');
$element.val(newBuildId);
$element.attr('id', newBuildId);
}
this.buildId = null;
}
}

BIN
misc/arrow-asc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

BIN
misc/arrow-desc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

307
misc/autocomplete.js Normal file
View file

@ -0,0 +1,307 @@
/**
* Attaches the autocomplete behavior to all required fields
*/
Drupal.behaviors.autocomplete = function (context) {
var acdb = [];
$('input.autocomplete:not(.autocomplete-processed)', context).each(function () {
var uri = this.value;
if (!acdb[uri]) {
acdb[uri] = new Drupal.ACDB(uri);
}
var input = $('#' + this.id.substr(0, this.id.length - 13))
.attr('autocomplete', 'OFF')[0];
$(input.form).submit(Drupal.autocompleteSubmit);
new Drupal.jsAC(input, acdb[uri]);
$(this).addClass('autocomplete-processed');
});
};
/**
* Prevents the form from submitting if the suggestions popup is open
* and closes the suggestions popup when doing so.
*/
Drupal.autocompleteSubmit = function () {
return $('#autocomplete').each(function () {
this.owner.hidePopup();
}).size() == 0;
};
/**
* An AutoComplete object
*/
Drupal.jsAC = function (input, db) {
var ac = this;
this.input = input;
this.db = db;
$(this.input)
.keydown(function (event) { return ac.onkeydown(this, event); })
.keyup(function (event) { ac.onkeyup(this, event); })
.blur(function () { ac.hidePopup(); ac.db.cancel(); });
};
/**
* Handler for the "keydown" event
*/
Drupal.jsAC.prototype.onkeydown = function (input, e) {
if (!e) {
e = window.event;
}
switch (e.keyCode) {
case 40: // down arrow
this.selectDown();
return false;
case 38: // up arrow
this.selectUp();
return false;
default: // all other keys
return true;
}
};
/**
* Handler for the "keyup" event
*/
Drupal.jsAC.prototype.onkeyup = function (input, e) {
if (!e) {
e = window.event;
}
switch (e.keyCode) {
case 16: // shift
case 17: // ctrl
case 18: // alt
case 20: // caps lock
case 33: // page up
case 34: // page down
case 35: // end
case 36: // home
case 37: // left arrow
case 38: // up arrow
case 39: // right arrow
case 40: // down arrow
return true;
case 9: // tab
case 13: // enter
case 27: // esc
this.hidePopup(e.keyCode);
return true;
default: // all other keys
if (input.value.length > 0)
this.populatePopup();
else
this.hidePopup(e.keyCode);
return true;
}
};
/**
* Puts the currently highlighted suggestion into the autocomplete field
*/
Drupal.jsAC.prototype.select = function (node) {
this.input.value = node.autocompleteValue;
};
/**
* Highlights the next suggestion
*/
Drupal.jsAC.prototype.selectDown = function () {
if (this.selected && this.selected.nextSibling) {
this.highlight(this.selected.nextSibling);
}
else {
var lis = $('li', this.popup);
if (lis.size() > 0) {
this.highlight(lis.get(0));
}
}
};
/**
* Highlights the previous suggestion
*/
Drupal.jsAC.prototype.selectUp = function () {
if (this.selected && this.selected.previousSibling) {
this.highlight(this.selected.previousSibling);
}
};
/**
* Highlights a suggestion
*/
Drupal.jsAC.prototype.highlight = function (node) {
if (this.selected) {
$(this.selected).removeClass('selected');
}
$(node).addClass('selected');
this.selected = node;
};
/**
* Unhighlights a suggestion
*/
Drupal.jsAC.prototype.unhighlight = function (node) {
$(node).removeClass('selected');
this.selected = false;
};
/**
* Hides the autocomplete suggestions
*/
Drupal.jsAC.prototype.hidePopup = function (keycode) {
// Select item if the right key or mousebutton was pressed
if (this.selected && ((keycode && keycode != 46 && keycode != 8 && keycode != 27) || !keycode)) {
this.input.value = this.selected.autocompleteValue;
}
// Hide popup
var popup = this.popup;
if (popup) {
this.popup = null;
$(popup).fadeOut('fast', function() { $(popup).remove(); });
}
this.selected = false;
};
/**
* Positions the suggestions popup and starts a search
*/
Drupal.jsAC.prototype.populatePopup = function () {
// Show popup
if (this.popup) {
$(this.popup).remove();
}
this.selected = false;
this.popup = document.createElement('div');
this.popup.id = 'autocomplete';
this.popup.owner = this;
$(this.popup).css({
marginTop: this.input.offsetHeight +'px',
width: (this.input.offsetWidth - 4) +'px',
display: 'none'
});
$(this.input).before(this.popup);
// Do search
this.db.owner = this;
this.db.search(this.input.value);
};
/**
* Fills the suggestion popup with any matches received
*/
Drupal.jsAC.prototype.found = function (matches) {
// If no value in the textfield, do not show the popup.
if (!this.input.value.length) {
return false;
}
// Prepare matches
var ul = document.createElement('ul');
var ac = this;
for (key in matches) {
var li = document.createElement('li');
$(li)
.html('<div>'+ matches[key] +'</div>')
.mousedown(function () { ac.select(this); })
.mouseover(function () { ac.highlight(this); })
.mouseout(function () { ac.unhighlight(this); });
li.autocompleteValue = key;
$(ul).append(li);
}
// Show popup with matches, if any
if (this.popup) {
if (ul.childNodes.length > 0) {
$(this.popup).empty().append(ul).show();
}
else {
$(this.popup).css({visibility: 'hidden'});
this.hidePopup();
}
}
};
Drupal.jsAC.prototype.setStatus = function (status) {
switch (status) {
case 'begin':
$(this.input).addClass('throbbing');
break;
case 'cancel':
case 'error':
case 'found':
$(this.input).removeClass('throbbing');
break;
}
};
/**
* An AutoComplete DataBase object
*/
Drupal.ACDB = function (uri) {
this.uri = uri;
this.delay = 300;
this.cache = {};
};
/**
* Performs a cached and delayed search
*/
Drupal.ACDB.prototype.search = function (searchString) {
var db = this;
this.searchString = searchString;
// See if this string needs to be searched for anyway. The pattern ../ is
// stripped since it may be misinterpreted by the browser.
searchString = searchString.replace(/^\s+|\.{2,}\/|\s+$/g, '');
// Skip empty search strings, or search strings ending with a comma, since
// that is the separator between search terms.
if (searchString.length <= 0 ||
searchString.charAt(searchString.length - 1) == ',') {
return;
}
// See if this key has been searched for before
if (this.cache[searchString]) {
return this.owner.found(this.cache[searchString]);
}
// Initiate delayed search
if (this.timer) {
clearTimeout(this.timer);
}
this.timer = setTimeout(function() {
db.owner.setStatus('begin');
// Ajax GET request for autocompletion
$.ajax({
type: "GET",
url: db.uri +'/'+ Drupal.encodeURIComponent(searchString),
dataType: 'json',
success: function (matches) {
if (typeof matches['status'] == 'undefined' || matches['status'] != 0) {
db.cache[searchString] = matches;
// Verify if these are still the matches the user wants to see
if (db.searchString == searchString) {
db.owner.found(matches);
}
db.owner.setStatus('found');
}
},
error: function (xmlhttp) {
alert(Drupal.ahahError(xmlhttp, db.uri));
}
});
}, this.delay);
};
/**
* Cancels the current autocomplete request
*/
Drupal.ACDB.prototype.cancel = function() {
if (this.owner) this.owner.setStatus('cancel');
if (this.timer) clearTimeout(this.timer);
this.searchString = '';
};

37
misc/batch.js Normal file
View file

@ -0,0 +1,37 @@
/**
* Attaches the batch behavior to progress bars.
*/
Drupal.behaviors.batch = function (context) {
// This behavior attaches by ID, so is only valid once on a page.
if ($('#progress.batch-processed').size()) {
return;
}
$('#progress', context).addClass('batch-processed').each(function () {
var holder = this;
var uri = Drupal.settings.batch.uri;
var initMessage = Drupal.settings.batch.initMessage;
var errorMessage = Drupal.settings.batch.errorMessage;
// Success: redirect to the summary.
var updateCallback = function (progress, status, pb) {
if (progress == 100) {
pb.stopMonitoring();
window.location = uri+'&op=finished';
}
};
var errorCallback = function (pb) {
var div = document.createElement('p');
div.className = 'error';
$(div).html(errorMessage);
$(holder).prepend(div);
$('#wait').hide();
};
var progress = new Drupal.progressBar('updateprogress', updateCallback, "POST", errorCallback);
progress.setProgress(-1, initMessage);
$(holder).append(progress.element);
progress.startMonitoring(uri+'&op=do', 10);
});
};

BIN
misc/blog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 B

76
misc/collapse.js Normal file
View file

@ -0,0 +1,76 @@
/**
* Toggle the visibility of a fieldset using smooth animations
*/
Drupal.toggleFieldset = function(fieldset) {
if ($(fieldset).is('.collapsed')) {
// Action div containers are processed separately because of a IE bug
// that alters the default submit button behavior.
var content = $('> div:not(.action)', fieldset);
$(fieldset).removeClass('collapsed');
content.hide();
content.slideDown( {
duration: 'fast',
easing: 'linear',
complete: function() {
Drupal.collapseScrollIntoView(this.parentNode);
this.parentNode.animating = false;
$('div.action', fieldset).show();
},
step: function() {
// Scroll the fieldset into view
Drupal.collapseScrollIntoView(this.parentNode);
}
});
}
else {
$('div.action', fieldset).hide();
var content = $('> div:not(.action)', fieldset).slideUp('fast', function() {
$(this.parentNode).addClass('collapsed');
this.parentNode.animating = false;
});
}
};
/**
* Scroll a given fieldset into view as much as possible.
*/
Drupal.collapseScrollIntoView = function (node) {
var h = self.innerHeight || document.documentElement.clientHeight || $('body')[0].clientHeight || 0;
var offset = self.pageYOffset || document.documentElement.scrollTop || $('body')[0].scrollTop || 0;
var posY = $(node).offset().top;
var fudge = 55;
if (posY + node.offsetHeight + fudge > h + offset) {
if (node.offsetHeight > h) {
window.scrollTo(0, posY);
} else {
window.scrollTo(0, posY + node.offsetHeight - h + fudge);
}
}
};
Drupal.behaviors.collapse = function (context) {
$('fieldset.collapsible > legend:not(.collapse-processed)', context).each(function() {
var fieldset = $(this.parentNode);
// Expand if there are errors inside
if ($('input.error, textarea.error, select.error', fieldset).size() > 0) {
fieldset.removeClass('collapsed');
}
// Turn the legend into a clickable link and wrap the contents of the fieldset
// in a div for easier animation
var text = this.innerHTML;
$(this).empty().append($('<a href="#">'+ text +'</a>').click(function() {
var fieldset = $(this).parents('fieldset:first')[0];
// Don't animate multiple times
if (!fieldset.animating) {
fieldset.animating = true;
Drupal.toggleFieldset(fieldset);
}
return false;
}))
.after($('<div class="fieldset-wrapper"></div>')
.append(fieldset.children(':not(legend):not(.action)')))
.addClass('collapse-processed');
});
};

BIN
misc/draggable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

319
misc/drupal.js Normal file
View file

@ -0,0 +1,319 @@
/**
* Override jQuery.fn.init to guard against XSS attacks.
*
* See http://bugs.jquery.com/ticket/9521
*/
(function () {
var jquery_init = jQuery.fn.init;
jQuery.fn.init = function (selector, context, rootjQuery) {
// If the string contains a "#" before a "<", treat it as invalid HTML.
if (selector && typeof selector === 'string') {
var hash_position = selector.indexOf('#');
if (hash_position >= 0) {
var bracket_position = selector.indexOf('<');
if (bracket_position > hash_position) {
throw 'Syntax error, unrecognized expression: ' + selector;
}
}
}
return jquery_init.call(this, selector, context, rootjQuery);
};
jQuery.fn.init.prototype = jquery_init.prototype;
})();
var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'themes': {}, 'locale': {} };
/**
* Set the variable that indicates if JavaScript behaviors should be applied
*/
Drupal.jsEnabled = document.getElementsByTagName && document.createElement && document.createTextNode && document.documentElement && document.getElementById;
/**
* Attach all registered behaviors to a page element.
*
* Behaviors are event-triggered actions that attach to page elements, enhancing
* default non-Javascript UIs. Behaviors are registered in the Drupal.behaviors
* object as follows:
* @code
* Drupal.behaviors.behaviorName = function () {
* ...
* };
* @endcode
*
* Drupal.attachBehaviors is added below to the jQuery ready event and so
* runs on initial page load. Developers implementing AHAH/AJAX in their
* solutions should also call this function after new page content has been
* loaded, feeding in an element to be processed, in order to attach all
* behaviors to the new content.
*
* Behaviors should use a class in the form behaviorName-processed to ensure
* the behavior is attached only once to a given element. (Doing so enables
* the reprocessing of given elements, which may be needed on occasion despite
* the ability to limit behavior attachment to a particular element.)
*
* @param context
* An element to attach behaviors to. If none is given, the document element
* is used.
*/
Drupal.attachBehaviors = function(context) {
context = context || document;
if (Drupal.jsEnabled) {
// Execute all of them.
jQuery.each(Drupal.behaviors, function() {
this(context);
});
}
};
/**
* Encode special characters in a plain-text string for display as HTML.
*/
Drupal.checkPlain = function(str) {
str = String(str);
var replace = { '&': '&amp;', '"': '&quot;', '<': '&lt;', '>': '&gt;' };
for (var character in replace) {
var regex = new RegExp(character, 'g');
str = str.replace(regex, replace[character]);
}
return str;
};
/**
* Translate strings to the page language or a given language.
*
* See the documentation of the server-side t() function for further details.
*
* @param str
* A string containing the English string to translate.
* @param args
* An object of replacements pairs to make after translation. Incidences
* of any key in this array are replaced with the corresponding value.
* Based on the first character of the key, the value is escaped and/or themed:
* - !variable: inserted as is
* - @variable: escape plain text to HTML (Drupal.checkPlain)
* - %variable: escape text and theme as a placeholder for user-submitted
* content (checkPlain + Drupal.theme('placeholder'))
* @return
* The translated string.
*/
Drupal.t = function(str, args) {
// Fetch the localized version of the string.
if (Drupal.locale.strings && Drupal.locale.strings[str]) {
str = Drupal.locale.strings[str];
}
if (args) {
// Transform arguments before inserting them
for (var key in args) {
switch (key.charAt(0)) {
// Escaped only
case '@':
args[key] = Drupal.checkPlain(args[key]);
break;
// Pass-through
case '!':
break;
// Escaped and placeholder
case '%':
default:
args[key] = Drupal.theme('placeholder', args[key]);
break;
}
str = str.replace(key, args[key]);
}
}
return str;
};
/**
* Format a string containing a count of items.
*
* This function ensures that the string is pluralized correctly. Since Drupal.t() is
* called by this function, make sure not to pass already-localized strings to it.
*
* See the documentation of the server-side format_plural() function for further details.
*
* @param count
* The item count to display.
* @param singular
* The string for the singular case. Please make sure it is clear this is
* singular, to ease translation (e.g. use "1 new comment" instead of "1 new").
* Do not use @count in the singular string.
* @param plural
* The string for the plural case. Please make sure it is clear this is plural,
* to ease translation. Use @count in place of the item count, as in "@count
* new comments".
* @param args
* An object of replacements pairs to make after translation. Incidences
* of any key in this array are replaced with the corresponding value.
* Based on the first character of the key, the value is escaped and/or themed:
* - !variable: inserted as is
* - @variable: escape plain text to HTML (Drupal.checkPlain)
* - %variable: escape text and theme as a placeholder for user-submitted
* content (checkPlain + Drupal.theme('placeholder'))
* Note that you do not need to include @count in this array.
* This replacement is done automatically for the plural case.
* @return
* A translated string.
*/
Drupal.formatPlural = function(count, singular, plural, args) {
var args = args || {};
args['@count'] = count;
// Determine the index of the plural form.
var index = Drupal.locale.pluralFormula ? Drupal.locale.pluralFormula(args['@count']) : ((args['@count'] == 1) ? 0 : 1);
if (index == 0) {
return Drupal.t(singular, args);
}
else if (index == 1) {
return Drupal.t(plural, args);
}
else {
args['@count['+ index +']'] = args['@count'];
delete args['@count'];
return Drupal.t(plural.replace('@count', '@count['+ index +']'), args);
}
};
/**
* Generate the themed representation of a Drupal object.
*
* All requests for themed output must go through this function. It examines
* the request and routes it to the appropriate theme function. If the current
* theme does not provide an override function, the generic theme function is
* called.
*
* For example, to retrieve the HTML that is output by theme_placeholder(text),
* call Drupal.theme('placeholder', text).
*
* @param func
* The name of the theme function to call.
* @param ...
* Additional arguments to pass along to the theme function.
* @return
* Any data the theme function returns. This could be a plain HTML string,
* but also a complex object.
*/
Drupal.theme = function(func) {
for (var i = 1, args = []; i < arguments.length; i++) {
args.push(arguments[i]);
}
return (Drupal.theme[func] || Drupal.theme.prototype[func]).apply(this, args);
};
/**
* Parse a JSON response.
*
* The result is either the JSON object, or an object with 'status' 0 and 'data' an error message.
*/
Drupal.parseJson = function (data) {
if ((data.substring(0, 1) != '{') && (data.substring(0, 1) != '[')) {
return { status: 0, data: data.length ? data : Drupal.t('Unspecified error') };
}
return eval('(' + data + ');');
};
/**
* Freeze the current body height (as minimum height). Used to prevent
* unnecessary upwards scrolling when doing DOM manipulations.
*/
Drupal.freezeHeight = function () {
Drupal.unfreezeHeight();
var div = document.createElement('div');
$(div).css({
position: 'absolute',
top: '0px',
left: '0px',
width: '1px',
height: $('body').css('height')
}).attr('id', 'freeze-height');
$('body').append(div);
};
/**
* Unfreeze the body height
*/
Drupal.unfreezeHeight = function () {
$('#freeze-height').remove();
};
/**
* Wrapper around encodeURIComponent() which avoids Apache quirks (equivalent of
* drupal_urlencode() in PHP). This function should only be used on paths, not
* on query string arguments.
*/
Drupal.encodeURIComponent = function (item, uri) {
uri = uri || location.href;
item = encodeURIComponent(item).replace(/%2F/g, '/');
return (uri.indexOf('?q=') != -1) ? item : item.replace(/%26/g, '%2526').replace(/%23/g, '%2523').replace(/\/\//g, '/%252F');
};
/**
* Get the text selection in a textarea.
*/
Drupal.getSelection = function (element) {
if (typeof(element.selectionStart) != 'number' && document.selection) {
// The current selection
var range1 = document.selection.createRange();
var range2 = range1.duplicate();
// Select all text.
range2.moveToElementText(element);
// Now move 'dummy' end point to end point of original range.
range2.setEndPoint('EndToEnd', range1);
// Now we can calculate start and end points.
var start = range2.text.length - range1.text.length;
var end = start + range1.text.length;
return { 'start': start, 'end': end };
}
return { 'start': element.selectionStart, 'end': element.selectionEnd };
};
/**
* Build an error message from ahah response.
*/
Drupal.ahahError = function(xmlhttp, uri) {
if (xmlhttp.status == 200) {
if (jQuery.trim($(xmlhttp.responseText).text())) {
var message = Drupal.t("An error occurred. \n@uri\n@text", {'@uri': uri, '@text': xmlhttp.responseText });
}
else {
var message = Drupal.t("An error occurred. \n@uri\n(no information available).", {'@uri': uri, '@text': xmlhttp.responseText });
}
}
else {
var message = Drupal.t("An HTTP error @status occurred. \n@uri", {'@uri': uri, '@status': xmlhttp.status });
}
return message;
}
// Global Killswitch on the <html> element
if (Drupal.jsEnabled) {
// Global Killswitch on the <html> element
$(document.documentElement).addClass('js');
// 'js enabled' cookie
document.cookie = 'has_js=1; path=/';
// Attach all behaviors.
$(document).ready(function() {
Drupal.attachBehaviors(this);
});
}
/**
* The default themes.
*/
Drupal.theme.prototype = {
/**
* Formats text for emphasized display in a placeholder inside a sentence.
*
* @param str
* The text to format (plain-text).
* @return
* The formatted text (html).
*/
placeholder: function(str) {
return '<em>' + Drupal.checkPlain(str) + '</em>';
}
};

BIN
misc/druplicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,34 @@
.farbtastic {
position: relative;
}
.farbtastic * {
position: absolute;
cursor: crosshair;
}
.farbtastic, .farbtastic .wheel {
width: 195px;
height: 195px;
}
.farbtastic .color, .farbtastic .overlay {
top: 47px;
left: 47px;
width: 101px;
height: 101px;
}
.farbtastic .wheel {
background: url(wheel.png) no-repeat;
width: 195px;
height: 195px;
}
.farbtastic .overlay {
background: url(mask.png) no-repeat;
}
.farbtastic .marker {
width: 17px;
height: 17px;
margin: -8px 0 0 -8px;
overflow: hidden;
background: url(marker.png) no-repeat;
}

View file

@ -0,0 +1,314 @@
// Farbtastic 1.2
jQuery.fn.farbtastic = function (callback) {
$.farbtastic(this, callback);
return this;
};
jQuery.farbtastic = function (container, callback) {
var container = $(container).get(0);
return container.farbtastic || (container.farbtastic = new jQuery._farbtastic(container, callback));
};
jQuery._farbtastic = function (container, callback) {
// Store farbtastic object
var fb = this;
// Insert markup
$(container).html('<div class="farbtastic"><div class="color"></div><div class="wheel"></div><div class="overlay"></div><div class="h-marker marker"></div><div class="sl-marker marker"></div></div>');
var e = $('.farbtastic', container);
fb.wheel = $('.wheel', container).get(0);
// Dimensions
fb.radius = 84;
fb.square = 100;
fb.width = 194;
// Fix background PNGs in IE6
if (navigator.appVersion.match(/MSIE [0-6]\./)) {
$('*', e).each(function () {
if (this.currentStyle.backgroundImage != 'none') {
var image = this.currentStyle.backgroundImage;
image = this.currentStyle.backgroundImage.substring(5, image.length - 2);
$(this).css({
'backgroundImage': 'none',
'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
});
}
});
}
/**
* Link to the given element(s) or callback.
*/
fb.linkTo = function (callback) {
// Unbind previous nodes
if (typeof fb.callback == 'object') {
$(fb.callback).unbind('keyup', fb.updateValue);
}
// Reset color
fb.color = null;
// Bind callback or elements
if (typeof callback == 'function') {
fb.callback = callback;
}
else if (typeof callback == 'object' || typeof callback == 'string') {
fb.callback = $(callback);
fb.callback.bind('keyup', fb.updateValue);
if (fb.callback.get(0).value) {
fb.setColor(fb.callback.get(0).value);
}
}
return this;
};
fb.updateValue = function (event) {
if (this.value && this.value != fb.color) {
fb.setColor(this.value);
}
};
/**
* Change color with HTML syntax #123456
*/
fb.setColor = function (color) {
var unpack = fb.unpack(color);
if (fb.color != color && unpack) {
fb.color = color;
fb.rgb = unpack;
fb.hsl = fb.RGBToHSL(fb.rgb);
fb.updateDisplay();
}
return this;
};
/**
* Change color with HSL triplet [0..1, 0..1, 0..1]
*/
fb.setHSL = function (hsl) {
fb.hsl = hsl;
fb.rgb = fb.HSLToRGB(hsl);
fb.color = fb.pack(fb.rgb);
fb.updateDisplay();
return this;
};
/////////////////////////////////////////////////////
/**
* Retrieve the coordinates of the given event relative to the center
* of the widget.
*/
fb.widgetCoords = function (event) {
var x, y;
var el = event.target || event.srcElement;
var reference = fb.wheel;
// If the offset from the relative element is undefined calculate it.
if ( typeof event.offsetX == 'undefined' && typeof event.offsetY == 'undefined' ) {
var offset = $(event.target).offset(false);
event.offsetX = event.pageX - offset.left;
event.offsetY = event.pageY - offset.top;
}
// Use offset coordinates and find common offsetParent
var pos = { x: event.offsetX, y: event.offsetY };
// Send the coordinates upwards through the offsetParent chain.
var e = el;
while (e) {
e.mouseX = pos.x;
e.mouseY = pos.y;
pos.x += e.offsetLeft;
pos.y += e.offsetTop;
e = e.offsetParent;
}
// Look for the coordinates starting from the wheel widget.
var e = reference;
var offset = { x: 0, y: 0 };
while (e) {
if (typeof e.mouseX != 'undefined') {
x = e.mouseX - offset.x;
y = e.mouseY - offset.y;
break;
}
offset.x += e.offsetLeft;
offset.y += e.offsetTop;
e = e.offsetParent;
}
// Reset stored coordinates
e = el;
while (e) {
e.mouseX = undefined;
e.mouseY = undefined;
e = e.offsetParent;
}
// Subtract distance to middle
return { x: x - fb.width / 2, y: y - fb.width / 2 };
};
/**
* Mousedown handler
*/
fb.mousedown = function (event) {
// Capture mouse
if (!document.dragging) {
$(document).bind('mousemove', fb.mousemove).bind('mouseup', fb.mouseup);
document.dragging = true;
}
// Check which area is being dragged
var pos = fb.widgetCoords(event);
fb.circleDrag = Math.max(Math.abs(pos.x), Math.abs(pos.y)) * 2 > fb.square;
// Process
fb.mousemove(event);
return false;
};
/**
* Mousemove handler
*/
fb.mousemove = function (event) {
// Get coordinates relative to color picker center
var pos = fb.widgetCoords(event);
// Set new HSL parameters
if (fb.circleDrag) {
var hue = Math.atan2(pos.x, -pos.y) / 6.28;
if (hue < 0) hue += 1;
fb.setHSL([hue, fb.hsl[1], fb.hsl[2]]);
}
else {
var sat = Math.max(0, Math.min(1, -(pos.x / fb.square) + .5));
var lum = Math.max(0, Math.min(1, -(pos.y / fb.square) + .5));
fb.setHSL([fb.hsl[0], sat, lum]);
}
return false;
};
/**
* Mouseup handler
*/
fb.mouseup = function () {
// Uncapture mouse
$(document).unbind('mousemove', fb.mousemove);
$(document).unbind('mouseup', fb.mouseup);
document.dragging = false;
};
/**
* Update the markers and styles
*/
fb.updateDisplay = function () {
// Markers
var angle = fb.hsl[0] * 6.28;
$('.h-marker', e).css({
left: Math.round(Math.sin(angle) * fb.radius + fb.width / 2) + 'px',
top: Math.round(-Math.cos(angle) * fb.radius + fb.width / 2) + 'px'
});
$('.sl-marker', e).css({
left: Math.round(fb.square * (.5 - fb.hsl[1]) + fb.width / 2) + 'px',
top: Math.round(fb.square * (.5 - fb.hsl[2]) + fb.width / 2) + 'px'
});
// Saturation/Luminance gradient
$('.color', e).css('backgroundColor', fb.pack(fb.HSLToRGB([fb.hsl[0], 1, 0.5])));
// Linked elements or callback
if (typeof fb.callback == 'object') {
// Set background/foreground color
$(fb.callback).css({
backgroundColor: fb.color,
color: fb.hsl[2] > 0.5 ? '#000' : '#fff'
});
// Change linked value
$(fb.callback).each(function() {
if (this.value && this.value != fb.color) {
this.value = fb.color;
}
});
}
else if (typeof fb.callback == 'function') {
fb.callback.call(fb, fb.color);
}
};
/* Various color utility functions */
fb.pack = function (rgb) {
var r = Math.round(rgb[0] * 255);
var g = Math.round(rgb[1] * 255);
var b = Math.round(rgb[2] * 255);
return '#' + (r < 16 ? '0' : '') + r.toString(16) +
(g < 16 ? '0' : '') + g.toString(16) +
(b < 16 ? '0' : '') + b.toString(16);
};
fb.unpack = function (color) {
if (color.length == 7) {
return [parseInt('0x' + color.substring(1, 3)) / 255,
parseInt('0x' + color.substring(3, 5)) / 255,
parseInt('0x' + color.substring(5, 7)) / 255];
}
else if (color.length == 4) {
return [parseInt('0x' + color.substring(1, 2)) / 15,
parseInt('0x' + color.substring(2, 3)) / 15,
parseInt('0x' + color.substring(3, 4)) / 15];
}
};
fb.HSLToRGB = function (hsl) {
var m1, m2, r, g, b;
var h = hsl[0], s = hsl[1], l = hsl[2];
m2 = (l <= 0.5) ? l * (s + 1) : l + s - l*s;
m1 = l * 2 - m2;
return [this.hueToRGB(m1, m2, h+0.33333),
this.hueToRGB(m1, m2, h),
this.hueToRGB(m1, m2, h-0.33333)];
};
fb.hueToRGB = function (m1, m2, h) {
h = (h < 0) ? h + 1 : ((h > 1) ? h - 1 : h);
if (h * 6 < 1) return m1 + (m2 - m1) * h * 6;
if (h * 2 < 1) return m2;
if (h * 3 < 2) return m1 + (m2 - m1) * (0.66666 - h) * 6;
return m1;
};
fb.RGBToHSL = function (rgb) {
var min, max, delta, h, s, l;
var r = rgb[0], g = rgb[1], b = rgb[2];
min = Math.min(r, Math.min(g, b));
max = Math.max(r, Math.max(g, b));
delta = max - min;
l = (min + max) / 2;
s = 0;
if (l > 0 && l < 1) {
s = delta / (l < 0.5 ? (2 * l) : (2 - 2 * l));
}
h = 0;
if (delta > 0) {
if (max == r && max != g) h += (g - b) / delta;
if (max == g && max != b) h += (2 + (b - r) / delta);
if (max == b && max != r) h += (4 + (r - g) / delta);
h /= 6;
}
return [h, s, l];
};
// Install mousedown handler (the others are set on the document on-demand)
$('*', e).mousedown(fb.mousedown);
// Init color
fb.setColor('#000000');
// Set linked elements/callback
if (callback) {
fb.linkTo(callback);
}
};

BIN
misc/farbtastic/marker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

BIN
misc/farbtastic/mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
misc/farbtastic/wheel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
misc/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
misc/feed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 764 B

9
misc/form.js Normal file
View file

@ -0,0 +1,9 @@
Drupal.behaviors.multiselectSelector = function() {
// Automatically selects the right radio button in a multiselect control.
$('.multiselect select:not(.multiselectSelector-processed)')
.addClass('multiselectSelector-processed').change(function() {
$('.multiselect input:radio[value="'+ this.id.substr(5) +'"]')
.attr('checked', true);
});
};

BIN
misc/forum-closed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 B

BIN
misc/forum-default.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 B

BIN
misc/forum-hot-new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 B

BIN
misc/forum-hot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

BIN
misc/forum-new.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

BIN
misc/forum-sticky.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

BIN
misc/grippie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

12
misc/jquery.form.js Normal file

File diff suppressed because one or more lines are too long

12
misc/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
misc/menu-collapsed-rtl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 B

BIN
misc/menu-collapsed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

BIN
misc/menu-expanded.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 B

BIN
misc/menu-leaf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

BIN
misc/powered-blue-80x15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,011 B

BIN
misc/powered-blue-88x31.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
misc/powered-gray-80x15.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

BIN
misc/powered-gray-88x31.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

7
misc/print-rtl.css Normal file
View file

@ -0,0 +1,7 @@
body {
direction: rtl;
}
th {
text-align: right;
}

25
misc/print.css Normal file
View file

@ -0,0 +1,25 @@
body {
margin: 1em;
background-color: #fff;
}
th {
text-align: left; /* LTR */
color: #006;
border-bottom: 1px solid #ccc;
}
tr.odd {
background-color: #ddd;
}
tr.even {
background-color: #fff;
}
td {
padding: 5px;
}
#menu {
visibility: hidden;
}
#main {
margin: 1em;
}

BIN
misc/progress.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

106
misc/progress.js Normal file
View file

@ -0,0 +1,106 @@
/**
* A progressbar object. Initialized with the given id. Must be inserted into
* the DOM afterwards through progressBar.element.
*
* method is the function which will perform the HTTP request to get the
* progress bar state. Either "GET" or "POST".
*
* e.g. pb = new progressBar('myProgressBar');
* some_element.appendChild(pb.element);
*/
Drupal.progressBar = function (id, updateCallback, method, errorCallback) {
var pb = this;
this.id = id;
this.method = method || "GET";
this.updateCallback = updateCallback;
this.errorCallback = errorCallback;
this.element = document.createElement('div');
this.element.id = id;
this.element.className = 'progress';
$(this.element).html('<div class="bar"><div class="filled"></div></div>'+
'<div class="percentage"></div>'+
'<div class="message">&nbsp;</div>');
};
/**
* Set the percentage and status message for the progressbar.
*/
Drupal.progressBar.prototype.setProgress = function (percentage, message) {
if (percentage >= 0 && percentage <= 100) {
$('div.filled', this.element).css('width', percentage +'%');
$('div.percentage', this.element).html(percentage +'%');
}
$('div.message', this.element).html(message);
if (this.updateCallback) {
this.updateCallback(percentage, message, this);
}
};
/**
* Start monitoring progress via Ajax.
*/
Drupal.progressBar.prototype.startMonitoring = function (uri, delay) {
this.delay = delay;
this.uri = uri;
this.sendPing();
};
/**
* Stop monitoring progress via Ajax.
*/
Drupal.progressBar.prototype.stopMonitoring = function () {
clearTimeout(this.timer);
// This allows monitoring to be stopped from within the callback
this.uri = null;
};
/**
* Request progress data from server.
*/
Drupal.progressBar.prototype.sendPing = function () {
if (this.timer) {
clearTimeout(this.timer);
}
if (this.uri) {
var pb = this;
// When doing a post request, you need non-null data. Otherwise a
// HTTP 411 or HTTP 406 (with Apache mod_security) error may result.
$.ajax({
type: this.method,
url: this.uri,
data: '',
dataType: 'json',
success: function (progress) {
// Display errors
if (progress.status == 0) {
pb.displayError(progress.data);
return;
}
// Update display
pb.setProgress(progress.percentage, progress.message);
// Schedule next timer
pb.timer = setTimeout(function() { pb.sendPing(); }, pb.delay);
},
error: function (xmlhttp) {
pb.displayError(Drupal.ahahError(xmlhttp, pb.uri));
}
});
}
};
/**
* Display errors on the page.
*/
Drupal.progressBar.prototype.displayError = function (string) {
var error = document.createElement('div');
error.className = 'error';
error.innerHTML = string;
$(this.element).before(error).hide();
if (this.errorCallback) {
this.errorCallback(this);
}
};

1099
misc/tabledrag.js Normal file

File diff suppressed because it is too large Load diff

116
misc/tableheader.js Normal file
View file

@ -0,0 +1,116 @@
Drupal.tableHeaderDoScroll = function() {
if (typeof(Drupal.tableHeaderOnScroll)=='function') {
Drupal.tableHeaderOnScroll();
}
};
Drupal.behaviors.tableHeader = function (context) {
// This breaks in anything less than IE 7. Prevent it from running.
if (jQuery.browser.msie && parseInt(jQuery.browser.version, 10) < 7) {
return;
}
// Keep track of all cloned table headers.
var headers = [];
$('table.sticky-enabled thead:not(.tableHeader-processed)', context).each(function () {
// Clone thead so it inherits original jQuery properties.
var headerClone = $(this).clone(true).insertBefore(this.parentNode).wrap('<table class="sticky-header"></table>').parent().css({
position: 'fixed',
top: '0px'
});
headerClone = $(headerClone)[0];
headers.push(headerClone);
// Store parent table.
var table = $(this).parent('table')[0];
headerClone.table = table;
// Finish initialzing header positioning.
tracker(headerClone);
$(table).addClass('sticky-table');
$(this).addClass('tableHeader-processed');
});
// Define the anchor holding var.
var prevAnchor = '';
// Track positioning and visibility.
function tracker(e) {
// Save positioning data.
var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
if (e.viewHeight != viewHeight) {
e.viewHeight = viewHeight;
e.vPosition = $(e.table).offset().top - 4;
e.hPosition = $(e.table).offset().left;
e.vLength = e.table.clientHeight - 100;
// Resize header and its cell widths.
var parentCell = $('th', e.table);
$('th', e).each(function(index) {
var cellWidth = parentCell.eq(index).css('width');
// Exception for IE7.
if (cellWidth == 'auto') {
cellWidth = parentCell.get(index).clientWidth +'px';
}
$(this).css('width', cellWidth);
});
$(e).css('width', $(e.table).css('width'));
}
// Track horizontal positioning relative to the viewport and set visibility.
var hScroll = document.documentElement.scrollLeft || document.body.scrollLeft;
var vOffset = (document.documentElement.scrollTop || document.body.scrollTop) - e.vPosition;
var visState = (vOffset > 0 && vOffset < e.vLength) ? 'visible' : 'hidden';
$(e).css({left: -hScroll + e.hPosition +'px', visibility: visState});
// Check the previous anchor to see if we need to scroll to make room for the header.
// Get the height of the header table and scroll up that amount.
if (prevAnchor != location.hash) {
if (location.hash != '') {
var offset = $(document).find('td' + location.hash).offset();
if (offset) {
var top = offset.top;
var scrollLocation = top - $(e).height();
$('body, html').scrollTop(scrollLocation);
}
}
prevAnchor = location.hash;
}
}
// Only attach to scrollbars once, even if Drupal.attachBehaviors is called
// multiple times.
if (!$('body').hasClass('tableHeader-processed')) {
$('body').addClass('tableHeader-processed');
$(window).scroll(Drupal.tableHeaderDoScroll);
$(document.documentElement).scroll(Drupal.tableHeaderDoScroll);
}
// Track scrolling.
Drupal.tableHeaderOnScroll = function() {
$(headers).each(function () {
tracker(this);
});
};
// Track resizing.
var time = null;
var resize = function () {
// Ensure minimum time between adjustments.
if (time) {
return;
}
time = setTimeout(function () {
$('table.sticky-header').each(function () {
// Force cell width calculation.
this.viewHeight = 0;
tracker(this);
});
// Reset timer
time = null;
}, 250);
};
$(window).resize(resize);
};

86
misc/tableselect.js Normal file
View file

@ -0,0 +1,86 @@
Drupal.behaviors.tableSelect = function (context) {
$('form table:has(th.select-all):not(.tableSelect-processed)', context).each(Drupal.tableSelect);
};
Drupal.tableSelect = function() {
// Do not add a "Select all" checkbox if there are no rows with checkboxes in the table
if ($('td input:checkbox', this).size() == 0) {
return;
}
// Keep track of the table, which checkbox is checked and alias the settings.
var table = this, checkboxes, lastChecked;
var strings = { 'selectAll': Drupal.t('Select all rows in this table'), 'selectNone': Drupal.t('Deselect all rows in this table') };
var updateSelectAll = function(state) {
$('th.select-all input:checkbox', table).each(function() {
$(this).attr('title', state ? strings.selectNone : strings.selectAll);
this.checked = state;
});
};
// Find all <th> with class select-all, and insert the check all checkbox.
$('th.select-all', table).prepend($('<input type="checkbox" class="form-checkbox" />').attr('title', strings.selectAll)).click(function(event) {
if ($(event.target).is('input:checkbox')) {
// Loop through all checkboxes and set their state to the select all checkbox' state.
checkboxes.each(function() {
this.checked = event.target.checked;
// Either add or remove the selected class based on the state of the check all checkbox.
$(this).parents('tr:first')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
});
// Update the title and the state of the check all box.
updateSelectAll(event.target.checked);
}
});
// For each of the checkboxes within the table.
checkboxes = $('td input:checkbox', table).click(function(e) {
// Either add or remove the selected class based on the state of the check all checkbox.
$(this).parents('tr:first')[ this.checked ? 'addClass' : 'removeClass' ]('selected');
// If this is a shift click, we need to highlight everything in the range.
// Also make sure that we are actually checking checkboxes over a range and
// that a checkbox has been checked or unchecked before.
if (e.shiftKey && lastChecked && lastChecked != e.target) {
// We use the checkbox's parent TR to do our range searching.
Drupal.tableSelectRange($(e.target).parents('tr')[0], $(lastChecked).parents('tr')[0], e.target.checked);
}
// If all checkboxes are checked, make sure the select-all one is checked too, otherwise keep unchecked.
updateSelectAll((checkboxes.length == $(checkboxes).filter(':checked').length));
// Keep track of the last checked checkbox.
lastChecked = e.target;
});
$(this).addClass('tableSelect-processed');
};
Drupal.tableSelectRange = function(from, to, state) {
// We determine the looping mode based on the order of from and to.
var mode = from.rowIndex > to.rowIndex ? 'previousSibling' : 'nextSibling';
// Traverse through the sibling nodes.
for (var i = from[mode]; i; i = i[mode]) {
// Make sure that we're only dealing with elements.
if (i.nodeType != 1) {
continue;
}
// Either add or remove the selected class based on the state of the target checkbox.
$(i)[ state ? 'addClass' : 'removeClass' ]('selected');
$('input:checkbox', i).each(function() {
this.checked = state;
});
if (to.nodeType) {
// If we are at the end of the range, stop.
if (i == to) {
break;
}
}
// A faster alternative to doing $(i).filter(to).length.
else if (jQuery.filter(to, [i]).r.length) {
break;
}
}
};

95
misc/teaser.js Normal file
View file

@ -0,0 +1,95 @@
/**
* Auto-attach for teaser behavior.
*
* Note: depends on resizable textareas.
*/
Drupal.behaviors.teaser = function(context) {
// This breaks in Konqueror. Prevent it from running.
if (/KDE/.test(navigator.vendor)) {
return;
}
$('textarea.teaser:not(.teaser-processed)', context).each(function() {
var teaser = $(this).addClass('teaser-processed');
// Move teaser textarea before body, and remove its form-item wrapper.
var body = $('#'+ Drupal.settings.teaser[this.id]);
var checkbox = $('#'+ Drupal.settings.teaserCheckbox[this.id]).parent();
var checked = $(checkbox).children('input').attr('checked') ? true : false;
var parent = teaser[0].parentNode;
$(body).before(teaser);
$(parent).remove();
function trim(text) {
return text.replace(/^\s+/g, '').replace(/\s+$/g, '');
}
// Join the teaser back to the body.
function join_teaser() {
if (teaser.val()) {
body.val(trim(teaser.val()) +'\r\n\r\n'+ trim(body.val()));
}
// Empty, hide and disable teaser.
teaser[0].value = '';
$(teaser).attr('disabled', 'disabled');
$(teaser).parent().slideUp('fast');
// Change label.
$(this).val(Drupal.t('Split summary at cursor'));
// Hide separate teaser checkbox.
$(checkbox).hide();
// Force a hidden checkbox to be checked (to ensure that the body is
// correctly processed on form submit when teaser/body are in joined
// state), and remember the current checked status.
checked = $(checkbox).children('input').attr('checked') ? true : false;
$(checkbox).children('input').attr('checked', true);
}
// Split the teaser from the body.
function split_teaser() {
body[0].focus();
var selection = Drupal.getSelection(body[0]);
var split = selection.start;
var text = body.val();
// Note: using val() fails sometimes. jQuery bug?
teaser[0].value = trim(text.slice(0, split));
body[0].value = trim(text.slice(split));
// Reveal and enable teaser
$(teaser).attr('disabled', '');
$(teaser).parent().slideDown('fast');
// Change label
$(this).val(Drupal.t('Join summary'));
// Show separate teaser checkbox, restore checked value.
$(checkbox).show().children('input').attr('checked', checked);
}
// Add split/join button.
var button = $('<div class="teaser-button-wrapper"><input type="button" class="teaser-button" /></div>');
var include = $('#'+ this.id.substring(0, this.id.length - 2) +'include');
$(include).parent().parent().before(button);
// Extract the teaser from the body, if set. Otherwise, stay in joined mode.
var text = body.val().split('<!--break-->');
if (text.length >= 2) {
teaser[0].value = trim(text.shift());
body[0].value = trim(text.join('<!--break-->'));
$(teaser).attr('disabled', '');
$('input', button).val(Drupal.t('Join summary')).toggle(join_teaser, split_teaser);
}
else {
$('input', button).val(Drupal.t('Split summary at cursor')).toggle(split_teaser, join_teaser);
$(checkbox).hide().children('input').attr('checked', true);
}
// Make sure that textarea.js has done its magic to ensure proper visibility state.
if (Drupal.behaviors.textarea && teaser.is(('.form-textarea:not(.textarea-processed)'))) {
Drupal.behaviors.textarea(teaser.parentNode);
}
// Set initial visibility
if ($(teaser).is(':disabled')) {
$(teaser).parent().hide();
}
});
};

35
misc/textarea.js Normal file
View file

@ -0,0 +1,35 @@
Drupal.behaviors.textarea = function(context) {
$('textarea.resizable:not(.textarea-processed)', context).each(function() {
// Avoid non-processed teasers.
if ($(this).is(('textarea.teaser:not(.teaser-processed)'))) {
return false;
}
var textarea = $(this).addClass('textarea-processed'), staticOffset = null;
// When wrapping the text area, work around an IE margin bug. See:
// http://jaspan.com/ie-inherited-margin-bug-form-elements-and-haslayout
$(this).wrap('<div class="resizable-textarea"><span></span></div>')
.parent().append($('<div class="grippie"></div>').mousedown(startDrag));
var grippie = $('div.grippie', $(this).parent())[0];
grippie.style.marginRight = (grippie.offsetWidth - $(this)[0].offsetWidth) +'px';
function startDrag(e) {
staticOffset = textarea.height() - e.pageY;
textarea.css('opacity', 0.25);
$(document).mousemove(performDrag).mouseup(endDrag);
return false;
}
function performDrag(e) {
textarea.height(Math.max(32, staticOffset + e.pageY) + 'px');
return false;
}
function endDrag(e) {
$(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
textarea.css('opacity', 1);
}
});
};

BIN
misc/throbber.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
misc/tree-bottom.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

BIN
misc/tree.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 979 B

Some files were not shown because too many files have changed in this diff Show more