Compare commits

..
1 Commits
Author SHA1 Message Date
bachy 9a18131bcf first import 2.1
Signed-off-by: bachy <git@g-u-i.net>
2012-10-27 15:02:05 +02:00
1107 changed files with 7570 additions and 310860 deletions
-13
View File
@@ -1,13 +0,0 @@
# Ignore configuration files that may contain sensitive information.
sites/*/settings*.php
# Ignore paths that contain user-generated content.
sites/*
#sites/*/files
#sites/*/private
_base_d7.tmproj
/_materio.sublime-project
/_materio.sublime-workspace
/_solr.sublime-project
/_solr.sublime-workspace
-236
View File
@@ -1,236 +0,0 @@
#
# 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)$|^(\..*|Entries.*|Repository|Root|Tag|Template)$">
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
# Set the default handler.
DirectoryIndex index.php index.html index.htm
# Override PHP settings that cannot be changed at runtime. See
# sites/default/default.settings.php and drupal_initialize_variables() in
# includes/bootstrap.inc for settings that can be changed at runtime.
# PHP 5, Apache 1 and 2.
<IfModule mod_php5.c>
php_flag magic_quotes_gpc off
php_flag magic_quotes_sybase off
php_flag register_globals off
php_flag session.auto_start off
php_value mbstring.http_input pass
php_value mbstring.http_output pass
php_flag mbstring.encoding_translation off
</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
# Block access to "hidden" directories whose names begin with a period. This
# includes directories used by version control systems such as Subversion or
# Git to store control files. Files whose names begin with a period, as well
# as the control files used by CVS, are protected by the FilesMatch directive
# above.
#
# NOTE: This only works when mod_rewrite is loaded. Without mod_rewrite, it is
# not possible to block access to entire directories from .htaccess, because
# <DirectoryMatch> is not allowed here.
#
# If you do not have mod_rewrite installed, you should remove these
# directories from your webroot or otherwise protect them from being
# downloaded.
RewriteRule "(^|/)\." - [F]
# custom
RewriteCond %{HTTP_HOST} ^basebeta\.materio\.com [OR]
RewriteCond %{HTTP_HOST} ^materio\.com [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.fr [OR]
RewriteCond %{HTTP_HOST} ^materio\.fr [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.net [OR]
RewriteCond %{HTTP_HOST} ^materio\.net [OR]
RewriteCond %{HTTP_HOST} ^www\.materio\.eu [OR]
RewriteCond %{HTTP_HOST} ^materio\.eu [NC]
RewriteRule ^ http://www.materio.com%{REQUEST_URI} [L,R=301]
# 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/...)
# uncomment the following:
# RewriteCond %{HTTP_HOST} !^www\. [NC]
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [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 the following:
# RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
# RewriteRule ^ http://%1%{REQUEST_URI} [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 /
# Pass all requests not referring directly to files in the filesystem to
# index.php. Clean URLs are handled in drupal_environment_initialize().
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^ index.php [L]
# Rules to correctly serve gzip compressed CSS and JS files.
# Requires both mod_rewrite and mod_headers to be enabled.
<IfModule mod_headers.c>
# Serve gzip compressed CSS files if they exist and the client accepts gzip.
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.css $1\.css\.gz [QSA]
# Serve gzip compressed JS files if they exist and the client accepts gzip.
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.js $1\.js\.gz [QSA]
# Serve correct content types, and prevent mod_deflate double gzip.
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1]
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1]
<FilesMatch "(\.js\.gz|\.css\.gz)$">
# Serve correct encoding type.
Header set Content-Encoding gzip
# Force proxies to cache gzipped & non-gzipped css/js files separately.
Header append Vary Accept-Encoding
</FilesMatch>
</IfModule>
</IfModule>
# FROM Html5 BoilerPlate 4.0
# ----------------------------------------------------------------------
# Proper MIME type for all files
# ----------------------------------------------------------------------
# JavaScript
# Normalize to standard type (it's sniffed in IE anyways)
# tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript js jsonp
AddType application/json json
# Audio
AddType audio/ogg oga ogg
AddType audio/mp4 m4a f4a f4b
# Video
AddType video/ogg ogv
AddType video/mp4 mp4 m4v f4v f4p
AddType video/webm webm
AddType video/x-flv flv
# SVG
# Required for svg webfonts on iPad
# twitter.com/FontSquirrel/status/14855840545
AddType image/svg+xml svg svgz
AddEncoding gzip svgz
# Webfonts
AddType application/vnd.ms-fontobject eot
AddType application/x-font-ttf ttf ttc
AddType font/opentype otf
AddType application/x-font-woff woff
# Assorted types
AddType image/x-icon ico
AddType image/webp webp
AddType text/cache-manifest appcache manifest
AddType text/x-component htc
AddType application/xml rss atom xml rdf
AddType application/x-chrome-extension crx
AddType application/x-opera-extension oex
AddType application/x-xpinstall xpi
AddType application/octet-stream safariextz
AddType application/x-web-app-manifest+json webapp
AddType text/x-vcard vcf
AddType application/x-shockwave-flash swf
AddType text/vtt vtt
# ----------------------------------------------------------------------
# UTF-8 encoding
# ----------------------------------------------------------------------
# Use UTF-8 encoding for anything served text/plain or text/html
AddDefaultCharset utf-8
# Force UTF-8 for a number of file formats
AddCharset utf-8 .atom .css .js .json .rss .vtt .xml
# ----------------------------------------------------------------------
# A little more security
# ----------------------------------------------------------------------
# Block access to "hidden" directories or files whose names begin with a
# period. This includes directories used by version control systems such as
# Subversion or Git.
<IfModule mod_rewrite.c>
RewriteCond %{SCRIPT_FILENAME} -d [OR]
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule "(^|/)\." - [F]
</IfModule>
# Block access to backup and source files. These files may be left by some
# text/html editors and pose a great security danger, when anyone can access
# them.
<FilesMatch "(\.(bak|config|sql|fla|psd|ini|log|sh|inc|swp|dist)|~)$">
Order allow,deny
Deny from all
Satisfy All
</FilesMatch>
# Increase cookie security
<IfModule php5_module>
php_value session.cookie_httponly true
</IfModule>
@@ -1,155 +0,0 @@
diff --git a/modules/user/user.module b/modules/user/user.module
index 47ac642..c204f6d 100644
--- a/modules/user/user.module
+++ b/modules/user/user.module
@@ -2286,14 +2286,26 @@ function user_external_login_register($name, $module) {
*
* @param object $account
* An object containing the user account.
+ * @param array $options
+ * (optional) A keyed array of settings. Supported options are:
+ * - langcode: A language code to be used when generating locale-sensitive
+ * urls. If langcode is NULL the users preferred language is used.
*
* @return
* A unique URL that provides a one-time log in for the user, from which
* they can change their password.
*/
-function user_pass_reset_url($account) {
+function user_pass_reset_url($account, $options = array()) {
$timestamp = REQUEST_TIME;
- return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
+ $url_options = array('absolute' => TRUE);
+ if (isset($options['langcode'])) {
+ $languages = language_list();
+ $url_options['language'] = $languages[$options['langcode']];
+ }
+ else {
+ $url_options['language'] = user_preferred_language($account);
+ }
+ return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), $url_options);
}
/**
@@ -2305,6 +2317,10 @@ function user_pass_reset_url($account) {
* - uid: The user uid number.
* - pass: The hashed user password string.
* - login: The user login name.
+ * @param array $options
+ * (optional) A keyed array of settings. Supported options are:
+ * - langcode: A language code to be used when generating locale-sensitive
+ * urls. If langcode is NULL the users preferred language is used.
*
* @return
* A unique URL that may be used to confirm the cancellation of the user
@@ -2313,9 +2329,17 @@ function user_pass_reset_url($account) {
* @see user_mail_tokens()
* @see user_cancel_confirm()
*/
-function user_cancel_url($account) {
+function user_cancel_url($account, $options = array()) {
$timestamp = REQUEST_TIME;
- return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE));
+ $url_options = array('absolute' => TRUE);
+ if (isset($options['langcode'])) {
+ $languages = language_list();
+ $url_options['language'] = $languages[$options['langcode']];
+ }
+ else {
+ $url_options['language'] = user_preferred_language($account);
+ }
+ return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), $url_options);
}
/**
@@ -2752,7 +2776,7 @@ Your account on [site:name] has been canceled.
if ($replace) {
// We do not sanitize the token replacement, since the output of this
// replacement is intended for an e-mail message, not a web browser.
- return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+ return token_replace($text, $variables, array('langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
}
return $text;
@@ -2779,8 +2803,8 @@ Your account on [site:name] has been canceled.
*/
function user_mail_tokens(&$replacements, $data, $options) {
if (isset($data['user'])) {
- $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user']);
- $replacements['[user:cancel-url]'] = user_cancel_url($data['user']);
+ $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options);
+ $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
}
}
diff --git a/modules/user/user.test b/modules/user/user.test
index b53db07..0417642 100644
--- a/modules/user/user.test
+++ b/modules/user/user.test
@@ -2012,6 +2012,26 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase {
);
}
+ public function setUp() {
+ parent::setUp('locale');
+
+ $account = $this->drupalCreateUser(array('access administration pages', 'administer languages'));
+ $this->drupalLogin($account);
+
+ // Add language.
+ $edit = array('langcode' => 'de');
+ $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+
+ // Enable URL language detection and selection.
+ $edit = array('language[enabled][locale-url]' => 1);
+ $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+
+ // Reset static caching.
+ drupal_static_reset('language_list');
+ drupal_static_reset('locale_url_outbound_alter');
+ drupal_static_reset('locale_language_url_rewrite_url');
+ }
+
/**
* Creates a user, then tests the tokens generated from it.
*/
@@ -2062,6 +2082,39 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase {
$output = token_replace($input, array('user' => $account), array('language' => $language, 'sanitize' => FALSE));
$this->assertEqual($output, $expected, t('Unsanitized user token %token replaced.', array('%token' => $input)));
}
+
+ $languages = language_list();
+
+ // Generate login and cancel link.
+ $tests = array();
+ $tests['[user:one-time-login-url]'] = user_pass_reset_url($account);
+ $tests['[user:cancel-url]'] = user_cancel_url($account);
+
+ // Generate tokens with interface language.
+ $link = url('user', array('absolute' => TRUE));
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('user' => $account), array('langcode' => $language->language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+ $this->assertTrue(strpos($output, $link) === 0, 'Generated URL is in interface language.');
+ }
+
+ // Generate tokens with the user's preferred language.
+ $edit['language'] = 'de';
+ $account = user_save($account, $edit);
+ $link = url('user', array('language' => $languages[$account->language], 'absolute' => TRUE));
+ foreach ($tests as $input => $expected) {
+ $output = token_replace($input, array('user' => $account), array('callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+ $this->assertTrue(strpos($output, $link) === 0, "Generated URL is in the user's preferred language.");
+ }
+
+ // Generate tokens with one specific language.
+ $link = url('user', array('language' => $languages['de'], 'absolute' => TRUE));
+ foreach ($tests as $input => $expected) {
+ foreach (array($user1, $user2) as $account) {
+ $output = token_replace($input, array('user' => $account), array('langcode' => 'de', 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+ $this->assertTrue(strpos($output, $link) === 0, "Generated URL in in the requested language.");
+ }
+ }
+
}
}
+323 -1640
View File
File diff suppressed because it is too large Load Diff
-44
View File
@@ -1,44 +0,0 @@
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; 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 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) 2010 Matt Farina
jQuery - Copyright (c) 2010 John Resig
jQuery BBQ - Copyright (c) 2010 "Cowboy" Ben Alman
jQuery Cookie - Copyright (c) 2006 Klaus Hartl
jQuery Form - Copyright (c) 2010 Mike Alsup
jQuery Once - Copyright (c) 2009 Konstantin Kfer
jQuery UI - Copyright (c) 2010 by the original authors
(http://jqueryui.com/about)
Sizzle.js - Copyright (c) 2010 The Dojo Foundation (http://sizzlejs.com/)
PHP
ArchiveTar - Copyright (c) 1997 - 2008 Vincent Blavet
-42
View File
@@ -1,42 +0,0 @@
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 log in 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 the following command:
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER
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
If the InnoDB storage engine is available, it will be used for all database
tables. InnoDB provides features over MyISAM such as transaction support,
row-level locks, and consistent non-locking reads.
-44
View File
@@ -1,44 +0,0 @@
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 want to create a 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-createrole --no-createdb username
If there are no errors, then the command was successful.
2. CREATE DRUPAL DATABASE
This step is only necessary if you don't already have a database set up
(e.g., by your host) or want to create a new database for use with Drupal
only. The following command creates a new database named 'databasename',
which is owned by the previously created 'username':
createdb --encoding=UTF8 --owner=username databasename
If there are no errors, then the command was successful.
3. CREATE SCHEMA OR SCHEMAS (Optional advanced step)
Drupal will run across different schemas within your database if you so wish.
By default, Drupal runs inside the 'public' schema but you can use $db_prefix
inside settings.php to define a schema for Drupal to run inside of, or
specify tables that are shared inside of a separate schema. Drupal will not
create schemas for you. In fact, the user that Drupal runs as should not be
allowed to do this. You'll need to execute the SQL below as a superuser,
replace 'username' with the username that Drupal uses to connect to
PostgreSQL, and replace 'schema_name' with a schema name you wish to use,
such as 'shared':
CREATE SCHEMA schema_name AUTHORIZATION username;
Do this for as many schemas as you need. See default.settings.php for
instructions on how to set which tables use which schemas.
-31
View File
@@ -1,31 +0,0 @@
SQLITE REQUIREMENTS
-------------------
To use SQLite with your Drupal installation, the following requirements must be
met: Server has PHP 5.2 or later with PDO, and the PDO SQLite driver must be
enabled.
SQLITE DATABASE CREATION
------------------------
The Drupal installer will create the SQLite database for you. The only
requirement is that the installer must have write permissions to the directory
where the database file resides. This directory (not just the database file) also
has to remain writeable by the web server going forward for SQLite to continue to
be able to operate.
On the "Database configuration" form in the "Database file" field, you must
supply the exact path to where you wish your database file to reside. It is
strongly suggested that you choose a path that is outside of the webroot, yet
ensure that the directory is writeable by the web server.
If you must place your database file in your webroot, you could try using the
following in your "Database file" field:
sites/default/files/.ht.sqlite
Note: The .ht in the name will tell Apache to prevent the database from being
downloaded. Please check that the file is, indeed, protected by your webserver.
If not, please consult the documentation of your webserver on how to protect a
file from downloading.
-398
View File
@@ -1,398 +0,0 @@
CONTENTS OF THIS FILE
---------------------
* Requirements and notes
* Optional server requirements
* Installation
* Building and customizing your site
* Multisite configuration
* More information
REQUIREMENTS AND NOTES
----------------------
Drupal requires:
- A web server. Apache (version 2.0 or greater) is recommended.
- PHP 5.2.4 (or greater) (http://www.php.net/).
- One of the following databases:
- MySQL 5.0.15 (or greater) (http://www.mysql.com/).
- MariaDB 5.1.44 (or greater) (http://mariadb.org/). MariaDB is a fully
compatible drop-in replacement for MySQL.
- PostgreSQL 8.3 (or greater) (http://www.postgresql.org/).
- SQLite 3.4.2 (or greater) (http://www.sqlite.org/).
For more detailed information about Drupal requirements, including a list of
PHP extensions and configurations that are required, see "System requirements"
(http://drupal.org/requirements) in the Drupal.org online documentation.
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.org online documentation.
Note that all directories mentioned in this document are always relative to the
directory of your Drupal installation, and commands are meant to be run from
this directory (except for the initial commands that create that directory).
OPTIONAL SERVER REQUIREMENTS
----------------------------
- If you want 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 "Clean URLs with IIS"
(http://drupal.org/node/3854) in the Drupal.org online documentation.
- If you plan to use XML-based services such as RSS aggregation, you will need
PHP's XML extension. This extension is enabled by default on most PHP
installations.
- To serve gzip compressed CSS and JS files on an Apache web server, you will
need the mod_headers module and the ability to use local .htaccess files.
- Some Drupal functionality (e.g., checking whether Drupal and contributed
modules need updates, RSS aggregation, etc.) require that the web server be
able to go out to the web and download information. If you want to use this
functionality, you need to verify that your hosting provider or server
configuration allows the web server to initiate outbound connections. Most web
hosting setups allow this.
INSTALLATION
------------
1. Download and extract Drupal.
You can obtain the latest Drupal release from http://drupal.org -- the files
are available in .tar.gz and .zip formats and can be extracted using most
compression tools.
To download and extract the files, on a typical Unix/Linux command line, use
the following commands (assuming you want version x.y of Drupal in .tar.gz
format):
wget http://drupal.org/files/projects/drupal-x.y.tar.gz
tar -zxvf drupal-x.y.tar.gz
This will create a new directory drupal-x.y/ containing all Drupal files and
directories. Then, to move the contents of that directory into a directory
within your web server's document root or your public HTML directory,
continue with this command:
mv drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation
2. Optionally, download a translation.
By default, Drupal is installed in English, and further languages may be
installed later. If you prefer to install Drupal in another language
initially:
- Download a translation file for the correct Drupal version and language
from the translation server: http://localize.drupal.org/translate/downloads
- Place the file into your installation profile's translations directory.
For instance, if you are using the Standard installation profile,
move the .po file into the directory:
profiles/standard/translations/
For detailed instructions, visit http://drupal.org/localize
3. Create the Drupal database.
Because Drupal stores all site information in a database, you must create
this database in order to install Drupal, and grant Drupal certain database
privileges (such as the ability to create tables). For details, consult
INSTALL.mysql.txt, INSTALL.pgsql.txt, or INSTALL.sqlite.txt. You may also
need to consult your web hosting provider for instructions specific to your
web host.
Take note of the username, password, database name, and hostname as you
create the database. You will enter this information during the install.
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, add the
site maintenance account (the first user, also known as user/1), and provide
basic web site settings.
During installation, several files and directories need to be created, which
the install script will try to do automatically. However, on some hosting
environments, manual steps are required, and the install script will tell
you that it cannot proceed until you fix certain issues. This is normal and
does not indicate a problem with your server.
The most common steps you may need to perform are:
a. Missing files directory.
The install script will attempt to create a file storage directory in
the default location at sites/default/files (the location of the files
directory may be changed after Drupal is installed).
If auto-creation fails, you can make it work by changing permissions on
the sites/default directory so that the web server can create the files
directory within it for you. (If you are creating a multisite
installation, substitute the correct sites directory for sites/default;
see the Multisite Configuration section of this file, below.)
For example, on a Unix/Linux command line, you can grant everyone
(including the web server) permission to write to the sites/default
directory with this command:
chmod a+w sites/default
Be sure to set the permissions back after the installation is finished!
Sample command:
chmod go-w sites/default
Alternatively, instead of allowing the web server to create the files
directory for you as described above, you can create it yourself. Sample
commands from a Unix/Linux command line:
mkdir sites/default/files
chmod a+w sites/default/files
b. Missing settings file.
Drupal will try to automatically create a settings.php configuration file,
which is normally in the directory sites/default (to avoid problems when
upgrading, Drupal is not packaged with this file). If auto-creation fails,
you will need to create this file yourself, using the file
sites/default/default.settings.php as a template.
For example, on a Unix/Linux command line, you can make a copy of the
default.settings.php file with the command:
cp sites/default/default.settings.php sites/default/settings.php
Next, grant write privileges to the file to everyone (including the web
server) with the command:
chmod a+w sites/default/settings.php
Be sure to set the permissions back after the installation is finished!
Sample command:
chmod go-w sites/default/settings.php
c. Write permissions after install.
The install script will attempt to write-protect the settings.php file and
the sites/default directory after saving your configuration. If this
fails, you will be notified, and you can do it manually. Sample commands
from a Unix/Linux command line:
chmod go-w sites/default/settings.php
chmod go-w sites/default
5. Verify that the site is working.
When the install script finishes, you will be logged in with the site
maintenance account on a "Welcome" page. If the default Drupal theme is not
displaying properly and links on the page result in "Page Not Found" errors,
you may be experiencing problems with clean URLs. Visit
http://drupal.org/getting-started/clean-urls to troubleshoot.
6. Change file system storage settings (optional).
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, you can modify the file system path 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).
- Your site runs on a number of web servers behind a load balancer or reverse
proxy (modify the file system path on each server to point to a shared file
repository).
- You want to restrict access to uploaded files.
To modify the file system path:
a. Ensure that the new location for the path exists and is writable by the
web server. For example, to create a new directory named uploads and grant
write permissions, use the following commands on a Unix/Linux command
line:
mkdir uploads
chmod a+w uploads
b. Navigate to Administration > Configuration > Media > File system, and
enter the desired path. Note that if you want to use private file storage,
you need to first enter the path for private files and save the
configuration, and then change the "Default download method" setting and
save again.
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.
7. Revoke documentation file permissions (optional).
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, from a Unix/Linux command line you can use
the following command:
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
Drupal.org online documentation.
8. Set up independent "cron" maintenance jobs.
Many Drupal modules have tasks that must be run periodically, including the
Search module (building and updating the index used for keyword searching),
the Aggregator module (retrieving feeds from other sites), and the System
module (performing routine maintenance and pruning of database tables). These
tasks are known as "cron maintenance tasks", named after the Unix/Linux
"cron" utility.
When you install Drupal, its built-in cron feature is enabled, which
automatically runs the cron tasks periodically, triggered by people visiting
pages of your site. You can configure the built-in cron feature by navigating
to Administration > Configuration > System > Cron.
It is also possible to run the cron tasks independent of site visits; this is
recommended for most sites. To do this, you will need to set up an automated
process to visit the page cron.php on your site, which executes the cron
tasks.
The URL of the cron.php page requires a "cron key" to protect against
unauthorized access. Your site's cron key is automatically generated during
installation and is specific to your site. The full URL of the page, with the
cron key, is available in the "Cron maintenance tasks" section of the Status
report page at Administration > Reports > Status report.
As an example for how to set up this automated process, you can use the
crontab utility on Unix/Linux systems. The following crontab line uses the
wget command to visit the cron.php page, and runs each hour, on the hour:
0 * * * * wget -O - -q -t 1 http://example.com/cron.php?cron_key=YOURKEY
Replace the text "http://example.com/cron.php?cron_key=YOURKEY" in the
example with the full URL displayed under "Cron maintenance tasks" on the
"Status report" page.
More information about cron maintenance tasks is available at
http://drupal.org/cron, and sample cron shell scripts can be found in the
scripts/ directory. (Note that these scripts must be customized like the
above example, to add your site-specific cron key and domain name.)
BUILDING AND CUSTOMIZING YOUR SITE
----------------------------------
A new installation of Drupal defaults to a very basic configuration. To extend
your site, you use "modules" and "themes". A module is a plugin that adds
functionality to Drupal, while a theme changes the look of your site. The core
of Drupal provides several optional modules and themes, and you can download
more at http://drupal.org/project/modules and http://drupal.org/project/themes
Do not mix downloaded or custom modules and themes with Drupal's core modules
and themes. Drupal's modules and themes are located in the top-level modules and
themes directories, while the modules and themes you add to Drupal are normally
placed in the sites/all/modules and sites/all/themes directories. If you run a
multisite installation, you can also place modules and themes in the
site-specific directories -- see the Multisite Configuration section, below.
Never edit Drupal's core modules and themes; instead, use the hooks available in
the Drupal API. To modify the behavior of Drupal, develop a module as described
at http://drupal.org/developing/modules. To modify the look of Drupal, create a
subtheme as described at http://drupal.org/node/225125, or a completely new
theme as described at http://drupal.org/documentation/theme
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 http://drupal.org/getting-started/6/install/multi-site
For more information on configuring Drupal's file system path in a multisite
configuration, see step 6 above.
MORE INFORMATION
----------------
- See the Drupal.org online documentation:
http://drupal.org/documentation
- For a list of security announcements, see the "Security advisories" 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, visit
http://drupal.org and click on Community and Support in the top or bottom
navigation.
+228 -293
View File
@@ -1,339 +1,274 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
GNU GENERAL PUBLIC LICENSE
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.
Version 2, June 1991
Preamble
Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave,
Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute
verbatim copies of this license document, but changing it is not allowed.
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.
Preamble
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.
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 Library General Public License
instead.) You can apply it to your programs, too.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
The precise terms and conditions for copying, distribution and
modification follow.
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.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
The precise terms and conditions for copying, distribution and modification
follow.
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".
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND
MODIFICATION
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.
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".
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.
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.
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.
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.
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:
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.
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
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:
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.
a) You must cause the modified files to carry prominent notices stating that
you changed the files and the date of any change.
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.)
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.
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.
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.)
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.
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.
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:
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,
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,
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.)
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
END OF TERMS AND CONDITIONS
-303
View File
@@ -1,303 +0,0 @@
Drupal core is built and maintained by the Drupal project community. Everyone is
encouraged to submit issues and changes (patches) to improve Drupal, and to
contribute in other ways -- see http://drupal.org/contribute to find out how.
Branch maintainers
------------------
The Drupal Core branch maintainers oversee the development of Drupal as a whole.
The branch maintainers for Drupal 7 are:
- Dries Buytaert 'dries' http://drupal.org/user/1
- Angela Byron 'webchick' http://drupal.org/user/24967
- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
Component maintainers
---------------------
The Drupal Core component maintainers oversee the development of Drupal
subsystems. See http://drupal.org/contribute/core-maintainers for more
information on their responsibilities, and to find out how to become a component
maintainer. Current component maintainers for Drupal 7:
Ajax system
- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
- Earl Miles 'merlinofchaos' http://drupal.org/user/26979
Base system
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
Batch system
- Yves Chedemois 'yched' http://drupal.org/user/39567
Cache system
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
Cron system
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Derek Wright 'dww' http://drupal.org/user/46549
Database system
- Larry Garfield 'Crell' http://drupal.org/user/26398
- MySQL driver
- Larry Garfield 'Crell' http://drupal.org/user/26398
- David Strauss 'David Strauss' http://drupal.org/user/93254
- PostgreSQL driver
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Josh Waihi 'fiasco' http://drupal.org/user/188162
- Sqlite driver
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Károly Négyesi 'chx' http://drupal.org/user/9446
Database update system
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Ashok Modi 'BTMash' http://drupal.org/user/60422
Entity system
- Wolfgang Ziegler 'fago' http://drupal.org/user/16747
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
- Franz Heinzmann 'Frando' http://drupal.org/user/21850
File system
- Andrew Morton 'drewish' http://drupal.org/user/34869
- Aaron Winborn 'aaron' http://drupal.org/user/33420
Form system
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
- Wolfgang Ziegler 'fago' http://drupal.org/user/16747
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
- Franz Heinzmann 'Frando' http://drupal.org/user/21850
Image system
- Andrew Morton 'drewish' http://drupal.org/user/34869
- Nathan Haug 'quicksketch' http://drupal.org/user/35821
Install system
- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
JavaScript
- Théodore Biadala 'nod_' http://drupal.org/user/598310
- Steve De Jonghe 'seutje' http://drupal.org/user/264148
- Jesse Renée Beach 'jessebeach' http://drupal.org/user/748566
Language system
- Francesco Placella 'plach' http://drupal.org/user/183211
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
Lock system
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
Mail system
- ?
Markup
- Jacine Luisi 'Jacine' http://drupal.org/user/88931
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
Menu system
- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
- Károly Négyesi 'chx' http://drupal.org/user/9446
Path system
- Dave Reid 'davereid' http://drupal.org/user/53892
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
Render system
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
- Franz Heinzmann 'Frando' http://drupal.org/user/21850
Theme system
- Earl Miles 'merlinofchaos' http://drupal.org/user/26979
- Alex Bronstein 'effulgentsia' http://drupal.org/user/78040
- Joon Park 'dvessel' http://drupal.org/user/56782
- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
Token system
- Dave Reid 'davereid' http://drupal.org/user/53892
XML-RPC system
- Frederic G. Marand 'fgm' http://drupal.org/user/27985
Topic coordinators
------------------
Accessibility
- Everett Zufelt 'Everett Zufelt' http://drupal.org/user/406552
- Brandon Bowersox-Johnson 'bowersox' http://drupal.org/user/186415
Documentation
- Jennifer Hodgdon 'jhodgdon' http://drupal.org/user/155601
Security
- Greg Knaddison 'greggles' http://drupal.org/user/36762
Translations
- Gerhard Killesreiter 'killes' http://drupal.org/user/83
User experience and usability
- Roy Scholten 'yoroy' http://drupal.org/user/41502
- Bojhan Somers 'Bojhan' http://drupal.org/user/87969
Node Access
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
- Ken Rickard 'agentrickard' http://drupal.org/user/20975
- Jess Myrbo 'xjm' http://drupal.org/user/65776
Module maintainers
------------------
Aggregator module
- ?
Block module
- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
Blog module
- ?
Book module
- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
Color module
- ?
Comment module
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
Contact module
- Dave Reid 'davereid' http://drupal.org/user/53892
Contextual module
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
Dashboard module
- ?
Database logging module
- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063
Field module
- Yves Chedemois 'yched' http://drupal.org/user/39567
- Barry Jaspan 'bjaspan' http://drupal.org/user/46413
Field UI module
- Yves Chedemois 'yched' http://drupal.org/user/39567
File module
- Aaron Winborn 'aaron' http://drupal.org/user/33420
Filter module
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
Forum module
- Lee Rowlands 'larowlan' http://drupal.org/user/395439
Help module
- ?
Image module
- Nathan Haug 'quicksketch' http://drupal.org/user/35821
Locale module
- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166
Menu module
- ?
Node module
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
- David Strauss 'David Strauss' http://drupal.org/user/93254
OpenID module
- Vojtech Kusy 'wojtha' http://drupal.org/user/56154
- Christian Schmidt 'c960657' http://drupal.org/user/216078
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
Overlay module
- Katherine Senzee 'ksenzee' http://drupal.org/user/139855
Path module
- Dave Reid 'davereid' http://drupal.org/user/53892
PHP module
- ?
Poll module
- Andrei Mateescu 'amateescu' http://drupal.org/user/729614
Profile module
- ?
RDF module
- Stéphane Corlosquet 'scor' http://drupal.org/user/52142
Search module
- Doug Green 'douggreen' http://drupal.org/user/29191
Shortcut module
- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
Simpletest module
- Jimmy Berry 'boombatower' http://drupal.org/user/214218
- Károly Négyesi 'chx' http://drupal.org/user/9446
Statistics module
- Tim Millwood 'timmillwood' http://drupal.org/user/227849
Syslog module
- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063
System module
- ?
Taxonomy module
- Jess Myrbo 'xjm' http://drupal.org/user/65776
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
- Benjamin Doherty 'bangpound' http://drupal.org/user/100456
Toolbar module
- ?
Tracker module
- David Strauss 'David Strauss' http://drupal.org/user/93254
Translation module
- Francesco Placella 'plach' http://drupal.org/user/183211
Trigger module
- ?
Update module
- Derek Wright 'dww' http://drupal.org/user/46549
User module
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
- David Strauss 'David Strauss' http://drupal.org/user/93254
Theme maintainers
-----------------
Bartik theme
- Jen Simmons 'jensimmons' http://drupal.org/user/140882
- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393
Garland theme
- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
Seven theme
- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393
Stark theme
- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
+33 -103
View File
@@ -1,123 +1,53 @@
CONTENTS OF THIS FILE
---------------------
-- SUMMARY --
* About Drupal
* Configuration and features
* Installation profiles
* Appearance
* Developing for Drupal
Wysiwyg API allows to users of your site to use WYSIWYG/rich-text, and other
client-side editors for editing contents. This module depends on third-party
editor libraries, most often based on JavaScript.
ABOUT DRUPAL
------------
For a full description of the module, visit the project page:
http://drupal.org/project/wysiwyg
To submit bug reports and feature suggestions, or to track changes:
http://drupal.org/project/issues/wysiwyg
Drupal is an open source content management platform supporting a variety of
websites ranging from personal weblogs to large community-driven websites. For
more information, see the Drupal website at http://drupal.org/, and join the
Drupal community at http://drupal.org/community.
Legal information about Drupal:
* Know your rights when using Drupal:
See LICENSE.txt in the same directory as this document.
* Learn about the Drupal trademark and logo policy:
http://drupal.com/trademark
-- REQUIREMENTS --
CONFIGURATION AND FEATURES
--------------------------
* None.
Drupal core (what you get when you download and extract a drupal-x.y.tar.gz or
drupal-x.y.zip file from http://drupal.org/project/drupal) has what you need to
get started with your website. It includes several modules (extensions that add
functionality) for common website features, such as managing content, user
accounts, image uploading, and search. Core comes with many options that allow
site-specific configuration. In addition to the core modules, there are
thousands of contributed modules (for functionality not included with Drupal
core) available for download.
More about configuration:
* Install, upgrade, and maintain Drupal:
See INSTALL.txt and UPGRADE.txt in the same directory as this document.
* Learn about how to use Drupal to create your site:
http://drupal.org/documentation
* Download contributed modules to sites/all/modules to extend Drupal's
functionality:
http://drupal.org/project/modules
* See also: "Developing for Drupal" for writing your own modules, below.
-- INSTALLATION --
INSTALLATION PROFILES
---------------------
* Install as usual, see http://drupal.org/node/70151 for further information.
Installation profiles define additional steps (such as enabling modules,
defining content types, etc.) that run after the base installation provided
by core when Drupal is first installed. There are two basic installation
profiles provided with Drupal core.
* Go to Administration » Configuration » Content authoring » Wysiwyg,
and follow the displayed installation instructions to download and install one
of the supported editors.
Installation profiles from the Drupal community modify the installation process
to provide a website for a specific use case, such as a CMS for media
publishers, a web-based project tracking tool, or a full-fledged CRM for
non-profit organizations raising money and accepting donations. They can be
distributed as bare installation profiles or as "distributions". Distributions
include Drupal core, the installation profile, and all other required
extensions, such as contributed and custom modules, themes, and third-party
libraries. Bare installation profiles require you to download Drupal Core and
the required extensions separately; place the downloaded profile in the
/profiles directory before you start the installation process. Note that the
contents of this directory may be overwritten during updates of Drupal core;
it is advised to keep code backups or use a version control system.
Additionally, modules and themes may be placed inside subdirectories in a
specific installation profile such as profiles/your_site_profile/modules and
profiles/your_site_profile/themes respectively to restrict their usage to only
sites that were installed with that specific profile.
-- CONFIGURATION --
More about installation profiles and distributions:
* Read about the difference between installation profiles and distributions:
http://drupal.org/node/1089736
* Download contributed installation profiles and distributions:
http://drupal.org/project/distributions
* Develop your own installation profile or distribution:
http://drupal.org/developing/distributions
* Go to Administration » Configuration » Content authoring » Text formats, and
APPEARANCE
----------
- either configure the Full HTML format, assign it to trusted roles, and
disable "HTML filter", "Line break converter", and (optionally) "URL filter".
In Drupal, the appearance of your site is set by the theme (themes are
extensions that set fonts, colors, and layout). Drupal core comes with several
themes. More themes are available for download, and you can also create your own
custom theme.
- or add a new text format, assign it to trusted roles, and ensure that above
mentioned input filters are disabled.
More about themes:
* Download contributed themes to sites/all/themes to modify Drupal's
appearance:
http://drupal.org/project/themes
* Develop your own theme:
http://drupal.org/documentation/theme
* Setup editor profiles in Administration » Configuration » Content authoring
» Wysiwyg.
DEVELOPING FOR DRUPAL
---------------------
Drupal contains an extensive API that allows you to add to and modify the
functionality of your site. The API consists of "hooks", which allow modules to
react to system events and customize Drupal's behavior, and functions that
standardize common operations such as database queries and form generation. The
flexible hook architecture means that you should never need to directly modify
the files that come with Drupal core to achieve the functionality you want;
instead, functionality modifications take the form of modules.
-- CONTACT --
When you need new functionality for your Drupal site, search for existing
contributed modules. If you find a module that matches except for a bug or an
additional needed feature, change the module and contribute your improvements
back to the project in the form of a "patch". Create new custom modules only
when nothing existing comes close to what you need.
Current maintainers:
* Daniel F. Kudwien (sun) - http://drupal.org/user/54136
* Henrik Danielsson (TwoD) - http://drupal.org/user/244227
This project has been sponsored by:
* UNLEASHED MIND
Specialized in consulting and planning of Drupal powered sites, UNLEASHED
MIND offers installation, development, theming, customization, and hosting
to get you started. Visit http://www.unleashedmind.com for more information.
More about developing:
* Search for existing contributed modules:
http://drupal.org/project/modules
* Contribute a patch:
http://drupal.org/patch/submit
* Develop your own module:
http://drupal.org/developing/modules
* Follow best practices:
http://drupal.org/best-practices
* Refer to the API documentation:
http://api.drupal.org/api/drupal/7
-236
View File
@@ -1,236 +0,0 @@
INTRODUCTION
------------
This document describes how to:
* Update your Drupal site from one minor 7.x version to another minor 7.x
version; for example, from 7.8 to 7.9, or from 7.6 to 7.10.
* Upgrade your Drupal site's major version from 6.x to 7.x.
First steps and definitions:
* If you are upgrading to Drupal version x.y, then x is known as the major
version number, and y is known as the minor version number. The download
file will be named drupal-x.y.tar.gz (or drupal-x.y.zip).
* All directories mentioned in this document are relative to the directory of
your Drupal installation.
* Make a full backup of all files, directories, and your database(s) before
starting, and save it outside your Drupal installation directory.
Instructions may be found at http://drupal.org/upgrade/backing-up-the-db
* It is wise to try an update or upgrade on a test copy of your site before
applying it to your live site. Even minor updates can cause your site's
behavior to change.
* Each new release of Drupal has release notes, which explain the changes made
since the previous version and any special instructions needed to update or
upgrade to the new version. You can find a link to the release notes for the
version you are upgrading or updating to on the Drupal project page
(http://drupal.org/project/drupal).
UPGRADE PROBLEMS
----------------
If you encounter errors during this process,
* Note any error messages you see.
* Restore your site to its previous state, using the file and database backups
you created before you started the upgrade process. Do not attempt to do
further upgrades on a site that had update problems.
* Consult one of the support options listed on http://drupal.org/support
More in-depth information on upgrading can be found at http://drupal.org/upgrade
MINOR VERSION UPDATES
---------------------
To update from one minor 7.x version of Drupal to any later 7.x version, after
following the instructions in the INTRODUCTION section at the top of this file:
1. Log in as a user with the permission "Administer software updates".
2. Go to Administration > Configuration > Development > Maintenance mode.
Enable the "Put site into maintenance mode" checkbox and save the
configuration.
3. Remove all old core files and directories, except for the 'sites' directory
and any custom files you added elsewhere.
If you made modifications to files like .htaccess or robots.txt, you will
need to re-apply them from your backup, after the new files are in place.
Sometimes an update includes changes to default.settings.php (this will be
noted in the release notes). If that's the case, follow these steps:
- Make a backup copy of your settings.php file, with a different file name.
- Make a copy of the new default.settings.php file, and name the copy
settings.php (overwriting your previous settings.php file).
- Copy the custom and site-specific entries from the backup you made into the
new settings.php file. You will definitely need the lines giving the
database information, and you will also want to copy in any other
customizations you have added.
4. Download the latest Drupal 7.x release from http://drupal.org to a
directory outside of your web root. Extract the archive and copy the files
into your Drupal directory.
On a typical Unix/Linux command line, use the following commands to download
and extract:
wget http://drupal.org/files/projects/drupal-x.y.tar.gz
tar -zxvf drupal-x.y.tar.gz
This creates a new directory drupal-x.y/ containing all Drupal files and
directories. Copy the files into your Drupal installation directory:
cp -R drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation
If you do not have command line access to your server, download the archive
from http://drupal.org using your web browser, extract it, and then use an
FTP client to upload the files to your web root.
5. Re-apply any modifications to files such as .htaccess or robots.txt.
6. Run update.php by visiting http://www.example.com/update.php (replace
www.example.com with your domain name). This will update the core database
tables.
If you are unable to access update.php do the following:
- Open settings.php with a text editor.
- Find the line that says:
$update_free_access = FALSE;
- Change it into:
$update_free_access = TRUE;
- Once the upgrade is done, $update_free_access must be reverted to FALSE.
7. Go to Administration > Reports > Status report. Verify that everything is
working as expected.
8. Ensure that $update_free_access is FALSE in settings.php.
9. Go to Administration > Configuration > Development > Maintenance mode.
Disable the "Put site into maintenance mode" checkbox and save the
configuration.
MAJOR VERSION UPGRADE
---------------------
To upgrade from a previous major version of Drupal to Drupal 7.x, after
following the instructions in the INTRODUCTION section at the top of this file:
1. Check on the Drupal 7 status of your contributed and custom modules and
themes. See http://drupal.org/node/948216 for information on upgrading
contributed modules and themes. See http://drupal.org/node/895314 for a list
of modules that have been moved into core for Drupal 7, and instructions on
how to update them. See http://drupal.org/update/modules for information on
how to update your custom modules, and http://drupal.org/update/theme for
custom themes.
You may decide at this point that you cannot upgrade your site, because
needed modules or themes are not ready for Drupal 7.
2. Update to the latest available version of Drupal 6.x (if your current version
is Drupal 5.x, you have to upgrade to 6.x first). If you need to update,
download Drupal 6.x and follow the instructions in its UPGRADE.txt. This
document only applies for upgrades from 6.x to 7.x.
3. In addition to updating to the latest available version of Drupal 6.x core,
you must also upgrade all of your contributed modules for Drupal to their
latest Drupal 6.x versions.
4. Log in as user ID 1 (the site maintenance user).
5. Go to Administer > Site configuration > Site maintenance. Select
"Off-line" and save the configuration.
6. Go to Administer > Site building > Themes. Enable "Garland" and select it as
the default theme.
7. Go to Administer > Site building > Modules. Disable all modules that are not
listed under "Core - required" or "Core - optional". It is possible that some
modules cannot be disabled, because others depend on them. Repeat this step
until all non-core modules are disabled.
If you know that you will not re-enable some modules for Drupal 7.x and you
no longer need their data, then you can uninstall them under the Uninstall
tab after disabling them.
8. On the command line or in your FTP client, remove the file
sites/default/default.settings.php
9. Remove all old core files and directories, except for the 'sites' directory
and any custom files you added elsewhere.
If you made modifications to files like .htaccess or robots.txt, you will
need to re-apply them from your backup, after the new files are in place.
10. If you uninstalled any modules, remove them from the sites/all/modules and
other sites/*/modules directories. Leave other modules in place, even though
they are incompatible with Drupal 7.x.
11. Download the latest Drupal 7.x release from http://drupal.org to a
directory outside of your web root. Extract the archive and copy the files
into your Drupal directory.
On a typical Unix/Linux command line, use the following commands to download
and extract:
wget http://drupal.org/files/projects/drupal-x.y.tar.gz
tar -zxvf drupal-x.y.tar.gz
This creates a new directory drupal-x.y/ containing all Drupal files and
directories. Copy the files into your Drupal installation directory:
cp -R drupal-x.y/* drupal-x.y/.htaccess /path/to/your/installation
If you do not have command line access to your server, download the archive
from http://drupal.org using your web browser, extract it, and then use an
FTP client to upload the files to your web root.
12. Re-apply any modifications to files such as .htaccess or robots.txt.
13. Make your settings.php file writeable, so that the update process can
convert it to the format of Drupal 7.x. settings.php is usually located in
sites/default/settings.php
14. Run update.php by visiting http://www.example.com/update.php (replace
www.example.com with your domain name). This will update the core database
tables.
If you are unable to access update.php do the following:
- Open settings.php with a text editor.
- Find the line that says:
$update_free_access = FALSE;
- Change it into:
$update_free_access = TRUE;
- Once the upgrade is done, $update_free_access must be reverted to FALSE.
15. Backup your database after the core upgrade has run.
16. Replace and update your non-core modules and themes, following the
procedures at http://drupal.org/node/948216
17. Go to Administration > Reports > Status report. Verify that everything is
working as expected.
18. Ensure that $update_free_access is FALSE in settings.php.
19. Go to Administration > Configuration > Development > Maintenance mode.
Disable the "Put site into maintenance mode" checkbox and save the
configuration.
To get started with Drupal 7 administration, visit
http://drupal.org/getting-started/7/admin
-30
View File
@@ -1,30 +0,0 @@
diff --git a/misc/autocomplete.js b/misc/autocomplete.js
index 8f7ac60..5679081 100644
--- a/misc/autocomplete.js
+++ b/misc/autocomplete.js
@@ -114,6 +114,7 @@ Drupal.jsAC.prototype.onkeyup = function (input, e) {
*/
Drupal.jsAC.prototype.select = function (node) {
this.input.value = $(node).data('autocompleteValue');
+ $(this.input).trigger('autocompleteSelect', [node]);
};
/**
@@ -167,7 +168,7 @@ Drupal.jsAC.prototype.unhighlight = function (node) {
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).data('autocompleteValue');
+ this.select(this.selected);
}
// Hide popup.
var popup = this.popup;
@@ -220,7 +221,7 @@ Drupal.jsAC.prototype.found = function (matches) {
for (key in matches) {
$('<li></li>')
.html($('<div></div>').html(matches[key]))
- .mousedown(function () { ac.select(this); })
+ .mousedown(function () { ac.hidePopup(this); })
.mouseover(function () { ac.highlight(this); })
.mouseout(function () { ac.unhighlight(this); })
.data('autocompleteValue', key)
-173
View File
@@ -1,173 +0,0 @@
<?php
/**
* @file
* Administrative script for running authorized file operations.
*
* Using this script, the site owner (the user actually owning the files on
* the webserver) can authorize certain file-related operations to proceed
* with elevated privileges, for example to deploy and upgrade modules or
* themes. Users should not visit this page directly, but instead use an
* administrative user interface which knows how to redirect the user to this
* script as part of a multistep process. This script actually performs the
* selected operations without loading all of Drupal, to be able to more
* gracefully recover from errors. Access to the script is controlled by a
* global killswitch in settings.php ('allow_authorize_operations') and via
* the 'administer software updates' permission.
*
* There are helper functions for setting up an operation to run via this
* system in modules/system/system.module. For more information, see:
* @link authorize Authorized operation helper functions @endlink
*/
/**
* Root directory of Drupal installation.
*/
define('DRUPAL_ROOT', getcwd());
/**
* Global flag to identify update.php and authorize.php runs, and so
* avoid various unwanted operations, such as hook_init() and
* hook_exit() invokes, css/js preprocessing and translation, and
* solve some theming issues. This flag is checked on several places
* in Drupal code (not just authorize.php).
*/
define('MAINTENANCE_MODE', 'update');
/**
* Renders a 403 access denied page for authorize.php.
*/
function authorize_access_denied_page() {
drupal_add_http_header('Status', '403 Forbidden');
watchdog('access denied', 'authorize.php', NULL, WATCHDOG_WARNING);
drupal_set_title('Access denied');
return t('You are not allowed to access this page.');
}
/**
* Determines if the current user is allowed to run authorize.php.
*
* The killswitch in settings.php overrides all else, otherwise, the user must
* have access to the 'administer software updates' permission.
*
* @return
* TRUE if the current user can run authorize.php, otherwise FALSE.
*/
function authorize_access_allowed() {
return variable_get('allow_authorize_operations', TRUE) && user_access('administer software updates');
}
// *** Real work of the script begins here. ***
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
require_once DRUPAL_ROOT . '/includes/common.inc';
require_once DRUPAL_ROOT . '/includes/file.inc';
require_once DRUPAL_ROOT . '/includes/module.inc';
require_once DRUPAL_ROOT . '/includes/ajax.inc';
// We prepare only a minimal bootstrap. This includes the database and
// variables, however, so we have access to the class autoloader registry.
drupal_bootstrap(DRUPAL_BOOTSTRAP_SESSION);
// This must go after drupal_bootstrap(), which unsets globals!
global $conf;
// We have to enable the user and system modules, even to check access and
// display errors via the maintenance theme.
$module_list['system']['filename'] = 'modules/system/system.module';
$module_list['user']['filename'] = 'modules/user/user.module';
module_list(TRUE, FALSE, FALSE, $module_list);
drupal_load('module', 'system');
drupal_load('module', 'user');
// We also want to have the language system available, but we do *NOT* want to
// actually call drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE), since that would
// also force us through the DRUPAL_BOOTSTRAP_PAGE_HEADER phase, which loads
// all the modules, and that's exactly what we're trying to avoid.
drupal_language_initialize();
// Initialize the maintenance theme for this administrative script.
drupal_maintenance_theme();
$output = '';
$show_messages = TRUE;
if (authorize_access_allowed()) {
// Load both the Form API and Batch API.
require_once DRUPAL_ROOT . '/includes/form.inc';
require_once DRUPAL_ROOT . '/includes/batch.inc';
// Load the code that drives the authorize process.
require_once DRUPAL_ROOT . '/includes/authorize.inc';
// For the sake of Batch API and a few other low-level functions, we need to
// initialize the URL path into $_GET['q']. However, we do not want to raise
// our bootstrap level, nor do we want to call drupal_initialize_path(),
// since that is assuming that modules are loaded and invoking hooks.
// However, all we really care is if we're in the middle of a batch, in which
// case $_GET['q'] will already be set, we just initialize it to an empty
// string if it's not already defined.
if (!isset($_GET['q'])) {
$_GET['q'] = '';
}
if (isset($_SESSION['authorize_operation']['page_title'])) {
drupal_set_title($_SESSION['authorize_operation']['page_title']);
}
else {
drupal_set_title(t('Authorize file system changes'));
}
// See if we've run the operation and need to display a report.
if (isset($_SESSION['authorize_results']) && $results = $_SESSION['authorize_results']) {
// Clear the session out.
unset($_SESSION['authorize_results']);
unset($_SESSION['authorize_operation']);
unset($_SESSION['authorize_filetransfer_info']);
if (!empty($results['page_title'])) {
drupal_set_title($results['page_title']);
}
if (!empty($results['page_message'])) {
drupal_set_message($results['page_message']['message'], $results['page_message']['type']);
}
$output = theme('authorize_report', array('messages' => $results['messages']));
$links = array();
if (is_array($results['tasks'])) {
$links += $results['tasks'];
}
else {
$links = array_merge($links, array(
l(t('Administration pages'), 'admin'),
l(t('Front page'), '<front>'),
));
}
$output .= theme('item_list', array('items' => $links, 'title' => t('Next steps')));
}
// If a batch is running, let it run.
elseif (isset($_GET['batch'])) {
$output = _batch_page();
}
else {
if (empty($_SESSION['authorize_operation']) || empty($_SESSION['authorize_filetransfer_info'])) {
$output = t('It appears you have reached this page in error.');
}
elseif (!$batch = batch_get()) {
// We have a batch to process, show the filetransfer form.
$elements = drupal_get_form('authorize_filetransfer_form');
$output = drupal_render($elements);
}
}
// We defer the display of messages until all operations are done.
$show_messages = !(($batch = batch_get()) && isset($batch['running']));
}
else {
$output = authorize_access_denied_page();
}
if (!empty($output)) {
print theme('update_page', array('content' => $output, 'show_messages' => $show_messages));
}
-26
View File
@@ -1,26 +0,0 @@
<?php
/**
* @file
* Handles incoming requests to fire off regularly-scheduled tasks (cron jobs).
*/
/**
* Root directory of Drupal installation.
*/
define('DRUPAL_ROOT', getcwd());
include_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
if (!isset($_GET['cron_key']) || variable_get('cron_key', 'drupal') != $_GET['cron_key']) {
watchdog('cron', 'Cron could not run because an invalid key was used.', array(), WATCHDOG_NOTICE);
drupal_access_denied();
}
elseif (variable_get('maintenance_mode', 0)) {
watchdog('cron', 'Cron could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE);
drupal_access_denied();
}
else {
drupal_cron_run();
}
+333
View File
@@ -0,0 +1,333 @@
<?php
/**
* @file
* Editor integration functions for CKEditor.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_ckeditor_editor() {
$editor['ckeditor'] = array(
'title' => 'CKEditor',
'vendor url' => 'http://ckeditor.com',
'download url' => 'http://ckeditor.com/download',
'libraries' => array(
'' => array(
'title' => 'Default',
'files' => array(
'ckeditor.js' => array('preprocess' => FALSE),
),
),
'src' => array(
'title' => 'Source',
'files' => array(
'ckeditor_source.js' => array('preprocess' => FALSE),
),
),
),
'version callback' => 'wysiwyg_ckeditor_version',
'themes callback' => 'wysiwyg_ckeditor_themes',
'settings callback' => 'wysiwyg_ckeditor_settings',
'plugin callback' => 'wysiwyg_ckeditor_plugins',
'plugin settings callback' => 'wysiwyg_ckeditor_plugin_settings',
'proxy plugin' => array(
'drupal' => array(
'load' => TRUE,
'proxy' => TRUE,
),
),
'proxy plugin settings callback' => 'wysiwyg_ckeditor_proxy_plugin_settings',
'versions' => array(
'3.0.0.3665' => array(
'js files' => array('ckeditor-3.0.js'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_ckeditor_version($editor) {
$library = $editor['library path'] . '/ckeditor.js';
if (!file_exists($library)) {
return;
}
$library = fopen($library, 'r');
$max_lines = 8;
while ($max_lines && $line = fgets($library, 500)) {
// version:'CKEditor 3.0 SVN',revision:'3665'
// version:'3.0 RC',revision:'3753'
// version:'3.0.1',revision:'4391'
if (preg_match('@version:\'(?:CKEditor )?([\d\.]+)(?:.+revision:\'([\d]+))?@', $line, $version)) {
fclose($library);
// Version numbers need to have three parts since 3.0.1.
$version[1] = preg_replace('/^(\d+)\.(\d+)$/', '${1}.${2}.0', $version[1]);
return $version[1] . '.' . $version[2];
}
$max_lines--;
}
fclose($library);
}
/**
* Determine available editor themes or check/reset a given one.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $profile
* A wysiwyg editor profile.
*
* @return
* An array of theme names. The first returned name should be the default
* theme name.
*/
function wysiwyg_ckeditor_themes($editor, $profile) {
// @todo Skins are not themes but this will do for now.
$path = $editor['library path'] . '/skins/';
if (file_exists($path) && ($dir_handle = opendir($path))) {
$themes = array();
while ($file = readdir($dir_handle)) {
if (is_dir($path . $file) && substr($file, 0, 1) != '.' && $file != 'CVS') {
$themes[] = $file;
}
}
closedir($dir_handle);
natcasesort($themes);
$themes = array_values($themes);
return !empty($themes) ? $themes : array('default');
}
else {
return array('default');
}
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_ckeditor_settings($editor, $config, $theme) {
$settings = array(
'baseHref' => $GLOBALS['base_url'] . '/',
'width' => '100%',
// For better compatibility with smaller textareas.
'resize_minWidth' => 450,
'height' => 420,
// @todo Do not use skins as themes and add separate skin handling.
'theme' => 'default',
'skin' => !empty($theme) ? $theme : 'kama',
// By default, CKEditor converts most characters into HTML entities. Since
// it does not support a custom definition, but Drupal supports Unicode, we
// disable at least the additional character sets. CKEditor always converts
// XML default characters '&', '<', '>'.
// @todo Check whether completely disabling ProcessHTMLEntities is an option.
'entities_latin' => FALSE,
'entities_greek' => FALSE,
);
// Add HTML block format settings; common block formats are already predefined
// by CKEditor.
if (isset($config['block_formats'])) {
$block_formats = explode(',', drupal_strtolower($config['block_formats']));
$predefined_formats = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'address', 'div');
foreach (array_diff($block_formats, $predefined_formats) as $tag) {
$tag = trim($tag);
$settings["format_$tag"] = array('element' => $tag);
}
$settings['format_tags'] = implode(';', $block_formats);
}
if (isset($config['apply_source_formatting'])) {
$settings['apply_source_formatting'] = $config['apply_source_formatting'];
}
if (isset($config['css_setting'])) {
// Versions below 3.0.1 could only handle one stylesheet.
if (version_compare($editor['installed version'], '3.0.1.4391', '<')) {
if ($config['css_setting'] == 'theme') {
$settings['contentsCss'] = reset(wysiwyg_get_css());
}
elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['contentsCss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
}
}
else {
if ($config['css_setting'] == 'theme') {
$settings['contentsCss'] = wysiwyg_get_css();
}
elseif ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['contentsCss'] = explode(',', strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme())));
}
}
}
if (isset($config['language'])) {
$settings['language'] = $config['language'];
}
if (isset($config['resizing'])) {
// CKEditor tests "!== false", so ensure it is a Boolean.
$settings['resize_enabled'] = (bool) $config['resizing'];
}
if (isset($config['toolbar_loc'])) {
$settings['toolbarLocation'] = $config['toolbar_loc'];
}
$settings['toolbar'] = array();
if (!empty($config['buttons'])) {
$extra_plugins = array();
$plugins = wysiwyg_get_plugins($editor['name']);
foreach ($config['buttons'] as $plugin => $buttons) {
foreach ($buttons as $button => $enabled) {
// Iterate separately over buttons and extensions properties.
foreach (array('buttons', 'extensions') as $type) {
// Skip unavailable plugins.
if (!isset($plugins[$plugin][$type][$button])) {
continue;
}
// Add buttons.
if ($type == 'buttons') {
$settings['toolbar'][] = $button;
}
// Add external Drupal plugins to the list of extensions.
if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) {
$extra_plugins[] = $button;
}
// Add external plugins to the list of extensions.
elseif ($type == 'buttons' && empty($plugins[$plugin]['internal'])) {
$extra_plugins[] = $plugin;
}
// Add internal buttons that also need to be loaded as extension.
elseif ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
$extra_plugins[] = $plugin;
}
// Add plain extensions.
elseif ($type == 'extensions' && !empty($plugins[$plugin]['load'])) {
$extra_plugins[] = $plugin;
}
// Allow plugins to add or override global configuration settings.
if (!empty($plugins[$plugin]['options'])) {
$settings = array_merge($settings, $plugins[$plugin]['options']);
}
}
}
}
if (!empty($extra_plugins)) {
$settings['extraPlugins'] = implode(',', $extra_plugins);
}
}
// For now, all buttons are placed into one row.
$settings['toolbar'] = array($settings['toolbar']);
return $settings;
}
/**
* Build a JS settings array of native external plugins that need to be loaded separately.
*/
function wysiwyg_ckeditor_plugin_settings($editor, $profile, $plugins) {
$settings = array();
foreach ($plugins as $name => $plugin) {
// Register all plugins that need to be loaded.
if (!empty($plugin['load'])) {
$settings[$name] = array();
// Add path for native external plugins.
if (empty($plugin['internal']) && isset($plugin['path'])) {
$settings[$name]['path'] = base_path() . $plugin['path'] . '/';
}
// Force native internal plugins to use the standard path.
else {
$settings[$name]['path'] = base_path() . $editor['library path'] . '/plugins/' . $name . '/';
}
// CKEditor defaults to 'plugin.js' on its own when filename is not set.
if (!empty($plugin['filename'])) {
$settings[$name]['fileName'] = $plugin['filename'];
}
}
}
return $settings;
}
/**
* Build a JS settings array for Drupal plugins loaded via the proxy plugin.
*/
function wysiwyg_ckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
$settings = array();
foreach ($plugins as $name => $plugin) {
// Populate required plugin settings.
$settings[$name] = $plugin['dialog settings'] + array(
'title' => $plugin['title'],
'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
'iconTitle' => $plugin['icon title'],
// @todo These should only be set if the plugin defined them.
'css' => base_path() . $plugin['css path'] . '/' . $plugin['css file'],
);
}
return $settings;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_ckeditor_plugins($editor) {
$plugins = array(
'default' => array(
'buttons' => array(
'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'),
'Strike' => t('Strike-through'),
'JustifyLeft' => t('Align left'), 'JustifyCenter' => t('Align center'), 'JustifyRight' => t('Align right'), 'JustifyBlock' => t('Justify'),
'BulletedList' => t('Bullet list'), 'NumberedList' => t('Numbered list'),
'Outdent' => t('Outdent'), 'Indent' => t('Indent'),
'Undo' => t('Undo'), 'Redo' => t('Redo'),
'Link' => t('Link'), 'Unlink' => t('Unlink'), 'Anchor' => t('Anchor'),
'Image' => t('Image'),
'TextColor' => t('Forecolor'), 'BGColor' => t('Backcolor'),
'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'),
'Blockquote' => t('Blockquote'), 'Source' => t('Source code'),
'HorizontalRule' => t('Horizontal rule'),
'Cut' => t('Cut'), 'Copy' => t('Copy'), 'Paste' => t('Paste'),
'PasteText' => t('Paste Text'), 'PasteFromWord' => t('Paste from Word'),
'ShowBlocks' => t('Show blocks'),
'RemoveFormat' => t('Remove format'),
'SpecialChar' => t('Character map'),
'Format' => t('HTML block format'), 'Font' => t('Font'), 'FontSize' => t('Font size'), 'Styles' => t('Font style'),
'Table' => t('Table'),
'SelectAll' => t('Select all'), 'Find' => t('Search'), 'Replace' => t('Replace'),
'Flash' => t('Flash'), 'Smiley' => t('Smiley'),
'CreateDiv' => t('Div container'),
'Iframe' => t('iFrame'),
'Maximize' => t('Maximize'),
'SpellChecker' => t('Check spelling'), 'Scayt' => t('Check spelling as you type'),
'About' => t('About'),
),
'internal' => TRUE,
),
);
if (version_compare($editor['installed version'], '3.1.0.4885', '<')) {
unset($plugins['default']['buttons']['CreateDiv']);
}
if (version_compare($editor['installed version'], '3.5.0.6260', '<')) {
unset($plugins['default']['buttons']['Iframe']);
}
return $plugins;
}
+11
View File
@@ -0,0 +1,11 @@
/**
* openWYSIWYG.
*/
table.tableTextareaEditor, table.tableTextareaEditor table {
margin: 0;
border-collapse: separate;
}
table.tableTextareaEditor td {
padding: 0;
}
+27
View File
@@ -0,0 +1,27 @@
/**
* TinyMCE 2.x
*/
table.mceEditor {
clear: left;
}
/**
* Align all buttons and separators in a single row, so they wrap into multiple
* rows if required.
*/
.mceToolbarTop a, .mceToolbarBottom a {
float: left;
}
.mceSeparatorLine {
float: left;
margin-top: 3px;
}
.mceSelectList {
float: left;
margin-bottom: 1px;
}
/* Place table plugin buttons into new row */
#mce_editor_0_table, #mce_editor_1_table {
clear: left;
}
+24
View File
@@ -0,0 +1,24 @@
/**
* TinyMCE 3.x
*/
table.mceLayout {
clear: left;
}
/**
* Align all buttons and separators in a single row, so they wrap into multiple
* rows if required.
*/
.mceToolbar td {
display: inline;
}
.mceToolbar a,
.mceSeparator {
float: left;
}
.mceListBox,
.mceSplitButton {
float: left;
margin-bottom: 1px;
}
+292
View File
@@ -0,0 +1,292 @@
<?php
/**
* @file
* Editor integration functions for FCKeditor.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_fckeditor_editor() {
$editor['fckeditor'] = array(
'title' => 'FCKeditor',
'vendor url' => 'http://www.fckeditor.net',
'download url' => 'http://www.fckeditor.net/download',
'libraries' => array(
'' => array(
'title' => 'Default',
'files' => array('fckeditor.js'),
),
),
'version callback' => 'wysiwyg_fckeditor_version',
'themes callback' => 'wysiwyg_fckeditor_themes',
'settings callback' => 'wysiwyg_fckeditor_settings',
'plugin callback' => 'wysiwyg_fckeditor_plugins',
'plugin settings callback' => 'wysiwyg_fckeditor_plugin_settings',
'proxy plugin' => array(
'drupal' => array(
'load' => TRUE,
'proxy' => TRUE,
),
),
'proxy plugin settings callback' => 'wysiwyg_fckeditor_proxy_plugin_settings',
'versions' => array(
'2.6' => array(
'js files' => array('fckeditor-2.6.js'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_fckeditor_version($editor) {
$library = $editor['library path'] . '/fckeditor.js';
if (!file_exists($library)) {
return;
}
$library = fopen($library, 'r');
$max_lines = 100;
while ($max_lines && $line = fgets($library, 60)) {
if (preg_match('@^FCKeditor.prototype.Version\s*= \'([\d\.]+)@', $line, $version)) {
fclose($library);
return $version[1];
}
$max_lines--;
}
fclose($library);
}
/**
* Determine available editor themes or check/reset a given one.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $profile
* A wysiwyg editor profile.
*
* @return
* An array of theme names. The first returned name should be the default
* theme name.
*/
function wysiwyg_fckeditor_themes($editor, $profile) {
return array('default', 'office2003', 'silver');
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_fckeditor_settings($editor, $config, $theme) {
$settings = array(
'EditorPath' => base_path() . $editor['library path'] . '/',
'SkinPath' => base_path() . $editor['library path'] . '/editor/skins/' . $theme . '/',
'CustomConfigurationsPath' => base_path() . drupal_get_path('module', 'wysiwyg') . '/editors/js/fckeditor.config.js',
'Width' => '100%',
'Height' => 420,
'LinkBrowser' => FALSE,
'LinkUpload' => FALSE,
'ImageBrowser' => FALSE,
'ImageUpload' => FALSE,
'FlashBrowser' => FALSE,
'FlashUpload' => FALSE,
// By default, FCKeditor converts most characters into HTML entities. Since
// it does not support a custom definition, but Drupal supports Unicode, we
// disable at least the additional character sets. FCKeditor always converts
// XML default characters '&', '<', '>'.
// @todo Check whether completely disabling ProcessHTMLEntities is an option.
'IncludeLatinEntities' => FALSE,
'IncludeGreekEntities' => FALSE,
);
if (isset($config['block_formats'])) {
$settings['FontFormats'] = strtr($config['block_formats'], array(',' => ';'));
}
if (isset($config['apply_source_formatting'])) {
$settings['FormatOutput'] = $settings['FormatSource'] = $config['apply_source_formatting'];
}
if (isset($config['paste_auto_cleanup_on_paste'])) {
$settings['AutoDetectPasteFromWord'] = $config['paste_auto_cleanup_on_paste'];
}
if (isset($config['css_setting'])) {
if ($config['css_setting'] == 'theme') {
$settings['EditorAreaCSS'] = implode(',', wysiwyg_get_css());
}
else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['EditorAreaCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
}
}
// Use our custom toolbar set.
$settings['ToolbarSet'] = 'Wysiwyg';
// Populate our custom toolbar set for fckeditor.config.js.
$settings['buttons'] = array();
if (!empty($config['buttons'])) {
$plugins = wysiwyg_get_plugins($editor['name']);
foreach ($config['buttons'] as $plugin => $buttons) {
foreach ($buttons as $button => $enabled) {
// Iterate separately over buttons and extensions properties.
foreach (array('buttons', 'extensions') as $type) {
// Skip unavailable plugins.
if (!isset($plugins[$plugin][$type][$button])) {
continue;
}
// Add buttons.
if ($type == 'buttons') {
$settings['buttons'][] = $button;
}
// Allow plugins to add or override global configuration settings.
if (!empty($plugins[$plugin]['options'])) {
$settings = array_merge($settings, $plugins[$plugin]['options']);
}
}
}
}
}
// For now, all buttons are placed into one row.
$settings['buttons'] = array($settings['buttons']);
return $settings;
}
/**
* Build a JS settings array of native external plugins that need to be loaded separately.
*/
function wysiwyg_fckeditor_plugin_settings($editor, $profile, $plugins) {
$settings = array();
foreach ($plugins as $name => $plugin) {
// Register all plugins that need to be loaded.
if (!empty($plugin['load'])) {
$settings[$name] = array();
// Add path for native external plugins; internal ones do not need a path.
if (empty($plugin['internal']) && isset($plugin['path'])) {
// All native FCKeditor plugins use the filename fckplugin.js.
$settings[$name]['path'] = base_path() . $plugin['path'] . '/';
}
if (!empty($plugin['languages'])) {
$settings[$name]['languages'] = $plugin['languages'];
}
}
}
return $settings;
}
/**
* Build a JS settings array for Drupal plugins loaded via the proxy plugin.
*/
function wysiwyg_fckeditor_proxy_plugin_settings($editor, $profile, $plugins) {
$settings = array();
foreach ($plugins as $name => $plugin) {
// Populate required plugin settings.
$settings[$name] = $plugin['dialog settings'] + array(
'title' => $plugin['title'],
'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
'iconTitle' => $plugin['icon title'],
// @todo These should only be set if the plugin defined them.
'css' => base_path() . $plugin['css path'] . '/' . $plugin['css file'],
);
}
return $settings;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_fckeditor_plugins($editor) {
$plugins = array(
'default' => array(
'buttons' => array(
'Bold' => t('Bold'), 'Italic' => t('Italic'), 'Underline' => t('Underline'),
'StrikeThrough' => t('Strike-through'),
'JustifyLeft' => t('Align left'), 'JustifyCenter' => t('Align center'), 'JustifyRight' => t('Align right'), 'JustifyFull' => t('Justify'),
'UnorderedList' => t('Bullet list'), 'OrderedList' => t('Numbered list'),
'Outdent' => t('Outdent'), 'Indent' => t('Indent'),
'Undo' => t('Undo'), 'Redo' => t('Redo'),
'Link' => t('Link'), 'Unlink' => t('Unlink'), 'Anchor' => t('Anchor'),
'Image' => t('Image'),
'TextColor' => t('Forecolor'), 'BGColor' => t('Backcolor'),
'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'),
'Blockquote' => t('Blockquote'), 'Source' => t('Source code'),
'Rule' => t('Horizontal rule'),
'Cut' => t('Cut'), 'Copy' => t('Copy'), 'Paste' => t('Paste'),
'PasteText' => t('Paste Text'), 'PasteWord' => t('Paste from Word'),
'ShowBlocks' => t('Show blocks'),
'RemoveFormat' => t('Remove format'),
'SpecialChar' => t('Character map'),
'About' => t('About'),
'FontFormat' => t('HTML block format'), 'FontName' => t('Font'), 'FontSize' => t('Font size'), 'Style' => t('Font style'),
'Table' => t('Table'),
'Find' => t('Search'), 'Replace' => t('Replace'), 'SelectAll' => t('Select all'),
'CreateDiv' => t('Create DIV container'),
'Flash' => t('Flash'), 'Smiley' => t('Smiley'),
'FitWindow' => t('FitWindow'),
'SpellCheck' => t('Check spelling'),
),
'internal' => TRUE,
),
'autogrow' => array(
'path' => $editor['library path'] . '/editor/plugins',
'extensions' => array(
'autogrow' => t('Autogrow'),
),
'options' => array(
'AutoGrowMax' => 800,
),
'internal' => TRUE,
'load' => TRUE,
),
'bbcode' => array(
'path' => $editor['library path'] . '/editor/plugins',
'extensions' => array(
'bbcode' => t('BBCode'),
),
'internal' => TRUE,
'load' => TRUE,
),
'dragresizetable' => array(
'path' => $editor['library path'] . '/editor/plugins',
'extensions' => array(
'dragresizetable' => t('Table drag/resize'),
),
'internal' => TRUE,
'load' => TRUE,
),
'tablecommands' => array(
'path' => $editor['library path'] . '/editor/plugins',
'buttons' => array(
'TableCellProp' => t('Table: Cell properties'),
'TableInsertRowAfter' => t('Table: Insert row after'),
'TableInsertColumnAfter' => t('Table: Insert column after'),
'TableInsertCellAfter' => t('Table: Insert cell after'),
'TableDeleteRows' => t('Table: Delete rows'),
'TableDeleteColumns' => t('Table: Delete columns'),
'TableDeleteCells' => t('Table: Delete cells'),
'TableMergeCells' => t('Table: Merge cells'),
'TableHorizontalSplitCell' => t('Table: Horizontal split cell'),
),
'internal' => TRUE,
'load' => TRUE,
),
);
return $plugins;
}
+217
View File
@@ -0,0 +1,217 @@
(function($) {
Drupal.wysiwyg.editor.init.ckeditor = function(settings) {
// Plugins must only be loaded once. Only the settings from the first format
// will be used but they're identical anyway.
var registeredPlugins = {};
for (var format in settings) {
if (Drupal.settings.wysiwyg.plugins[format]) {
// Register native external plugins.
// Array syntax required; 'native' is a predefined token in JavaScript.
for (var pluginName in Drupal.settings.wysiwyg.plugins[format]['native']) {
if (!registeredPlugins[pluginName]) {
var plugin = Drupal.settings.wysiwyg.plugins[format]['native'][pluginName];
CKEDITOR.plugins.addExternal(pluginName, plugin.path, plugin.fileName);
registeredPlugins[pluginName] = true;
}
}
// Register Drupal plugins.
for (var pluginName in Drupal.settings.wysiwyg.plugins[format].drupal) {
if (!registeredPlugins[pluginName]) {
Drupal.wysiwyg.editor.instance.ckeditor.addPlugin(pluginName, Drupal.settings.wysiwyg.plugins[format].drupal[pluginName], Drupal.settings.wysiwyg.plugins.drupal[pluginName]);
registeredPlugins[pluginName] = true;
}
}
}
}
};
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.ckeditor = function(context, params, settings) {
// Apply editor instance settings.
CKEDITOR.config.customConfig = '';
settings.on = {
instanceReady: function(ev) {
var editor = ev.editor;
// Get a list of block, list and table tags from CKEditor's XHTML DTD.
// @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Output_Formatting.
var dtd = CKEDITOR.dtd;
var tags = CKEDITOR.tools.extend({}, dtd.$block, dtd.$listItem, dtd.$tableContent);
// Set source formatting rules for each listed tag except <pre>.
// Linebreaks can be inserted before or after opening and closing tags.
if (settings.apply_source_formatting) {
// Mimic FCKeditor output, by breaking lines between tags.
for (var tag in tags) {
if (tag == 'pre') {
continue;
}
this.dataProcessor.writer.setRules(tag, {
indent: true,
breakBeforeOpen: true,
breakAfterOpen: false,
breakBeforeClose: false,
breakAfterClose: true
});
}
}
else {
// CKEditor adds default formatting to <br>, so we want to remove that
// here too.
tags.br = 1;
// No indents or linebreaks;
for (var tag in tags) {
if (tag == 'pre') {
continue;
}
this.dataProcessor.writer.setRules(tag, {
indent: false,
breakBeforeOpen: false,
breakAfterOpen: false,
breakBeforeClose: false,
breakAfterClose: false
});
}
}
},
pluginsLoaded: function(ev) {
// Override the conversion methods to let Drupal plugins modify the data.
var editor = ev.editor;
if (editor.dataProcessor && Drupal.settings.wysiwyg.plugins[params.format]) {
editor.dataProcessor.toHtml = CKEDITOR.tools.override(editor.dataProcessor.toHtml, function(originalToHtml) {
// Convert raw data for display in WYSIWYG mode.
return function(data, fixForBody) {
for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
data = Drupal.wysiwyg.plugins[plugin].attach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], editor.name);
data = Drupal.wysiwyg.instances[params.field].prepareContent(data);
}
}
return originalToHtml.call(this, data, fixForBody);
};
});
editor.dataProcessor.toDataFormat = CKEDITOR.tools.override(editor.dataProcessor.toDataFormat, function(originalToDataFormat) {
// Convert WYSIWYG mode content to raw data.
return function(data, fixForBody) {
data = originalToDataFormat.call(this, data, fixForBody);
for (var plugin in Drupal.settings.wysiwyg.plugins[params.format].drupal) {
if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
data = Drupal.wysiwyg.plugins[plugin].detach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], editor.name);
}
}
return data;
};
});
}
},
selectionChange: function (event) {
var pluginSettings = Drupal.settings.wysiwyg.plugins[params.format];
if (pluginSettings && pluginSettings.drupal) {
$.each(pluginSettings.drupal, function (name) {
var plugin = Drupal.wysiwyg.plugins[name];
if ($.isFunction(plugin.isNode)) {
var node = event.data.selection.getSelectedElement();
var state = plugin.isNode(node ? node.$ : null) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF;
event.editor.getCommand(name).setState(state);
}
});
}
},
focus: function(ev) {
Drupal.wysiwyg.activeId = ev.editor.name;
}
};
// Attach editor.
CKEDITOR.replace(params.field, settings);
};
/**
* Detach a single or all editors.
*
* @todo 3.x: editor.prototype.getInstances() should always return an array
* containing all instances or the passed in params.field instance, but
* always return an array to simplify all detach functions.
*/
Drupal.wysiwyg.editor.detach.ckeditor = function(context, params) {
if (typeof params != 'undefined') {
var instance = CKEDITOR.instances[params.field];
if (instance) {
instance.destroy();
}
}
else {
for (var instanceName in CKEDITOR.instances) {
CKEDITOR.instances[instanceName].destroy();
}
}
};
Drupal.wysiwyg.editor.instance.ckeditor = {
addPlugin: function(pluginName, settings, pluginSettings) {
CKEDITOR.plugins.add(pluginName, {
// Wrap Drupal plugin in a proxy pluygin.
init: function(editor) {
if (settings.css) {
editor.on('mode', function(ev) {
if (ev.editor.mode == 'wysiwyg') {
// Inject CSS files directly into the editing area head tag.
$('head', $('#cke_contents_' + ev.editor.name + ' iframe').eq(0).contents()).append('<link rel="stylesheet" href="' + settings.css + '" type="text/css" >');
}
});
}
if (typeof Drupal.wysiwyg.plugins[pluginName].invoke == 'function') {
var pluginCommand = {
exec: function (editor) {
var data = { format: 'html', node: null, content: '' };
var selection = editor.getSelection();
if (selection) {
data.node = selection.getSelectedElement();
if (data.node) {
data.node = data.node.$;
}
if (selection.getType() == CKEDITOR.SELECTION_TEXT) {
if (CKEDITOR.env.ie) {
data.content = selection.getNative().createRange().text;
}
else {
data.content = selection.getNative().toString();
}
}
else if (data.node) {
// content is supposed to contain the "outerHTML".
data.content = data.node.parentNode.innerHTML;
}
}
Drupal.wysiwyg.plugins[pluginName].invoke(data, pluginSettings, editor.name);
}
};
editor.addCommand(pluginName, pluginCommand);
}
editor.ui.addButton(pluginName, {
label: settings.iconTitle,
command: pluginName,
icon: settings.icon
});
// @todo Add button state handling.
}
});
},
prepareContent: function(content) {
// @todo Don't know if we need this yet.
return content;
},
insert: function(content) {
content = this.prepareContent(content);
CKEDITOR.instances[this.field].insertHtml(content);
}
};
})(jQuery);
+181
View File
@@ -0,0 +1,181 @@
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.fckeditor = function(context, params, settings) {
var FCKinstance = new FCKeditor(params.field, settings.Width, settings.Height, settings.ToolbarSet);
// Apply editor instance settings.
FCKinstance.BasePath = settings.EditorPath;
FCKinstance.Config.wysiwygFormat = params.format;
FCKinstance.Config.CustomConfigurationsPath = settings.CustomConfigurationsPath;
// Load Drupal plugins and apply format specific settings.
// @see fckeditor.config.js
// @see Drupal.wysiwyg.editor.instance.fckeditor.init()
// Attach editor.
FCKinstance.ReplaceTextarea();
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.fckeditor = function(context, params) {
var instances = [];
if (typeof params != 'undefined' && typeof FCKeditorAPI != 'undefined') {
var instance = FCKeditorAPI.GetInstance(params.field);
if (instance) {
instances[params.field] = instance;
}
}
else {
instances = FCKeditorAPI.__Instances;
}
for (var instanceName in instances) {
var instance = instances[instanceName];
instance.UpdateLinkedField();
// Since we already detach the editor and update the textarea, the submit
// event handler needs to be removed to prevent data loss (in IE).
// FCKeditor uses 2 nested iFrames; instance.EditingArea.Window is the
// deepest. Its parent is the iFrame containing the editor.
var instanceScope = instance.EditingArea.Window.parent;
instanceScope.FCKTools.RemoveEventListener(instance.GetParentForm(), 'submit', instance.UpdateLinkedField);
// Run cleanups before forcing an unload of the iFrames or IE crashes.
// This also deletes the instance from the FCKeditorAPI.__Instances array.
instanceScope.FCKTools.RemoveEventListener(instanceScope, 'unload', instanceScope.FCKeditorAPI_Cleanup);
instanceScope.FCKTools.RemoveEventListener(instanceScope, 'beforeunload', instanceScope.FCKeditorAPI_ConfirmCleanup);
if (jQuery.isFunction(instanceScope.FCKIECleanup_Cleanup)) {
instanceScope.FCKIECleanup_Cleanup();
}
instanceScope.FCKeditorAPI_ConfirmCleanup();
instanceScope.FCKeditorAPI_Cleanup();
// Remove the editor elements.
$('#' + instanceName + '___Config').remove();
$('#' + instanceName + '___Frame').remove();
$('#' + instanceName).show();
}
};
Drupal.wysiwyg.editor.instance.fckeditor = {
init: function(instance) {
// Track which editor instance is active.
instance.FCK.Events.AttachEvent('OnFocus', function(editorInstance) {
Drupal.wysiwyg.activeId = editorInstance.Name;
});
// Create a custom data processor to wrap the default one and allow Drupal
// plugins modify the editor contents.
var wysiwygDataProcessor = function() {};
wysiwygDataProcessor.prototype = new instance.FCKDataProcessor();
// Attach: Convert text into HTML.
wysiwygDataProcessor.prototype.ConvertToHtml = function(data) {
// Called from SetData() with stripped comments/scripts, revert those
// manipulations and attach Drupal plugins.
var data = instance.FCKConfig.ProtectedSource.Revert(data);
if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat] && Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
for (var plugin in Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
data = Drupal.wysiwyg.plugins[plugin].attach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], instance.FCK.Name);
data = Drupal.wysiwyg.editor.instance.fckeditor.prepareContent(data);
}
}
}
// Re-protect the source and use the original data processor to convert it
// into XHTML.
data = instance.FCKConfig.ProtectedSource.Protect(data);
return instance.FCKDataProcessor.prototype.ConvertToHtml.call(this, data);
};
// Detach: Convert HTML into text.
wysiwygDataProcessor.prototype.ConvertToDataFormat = function(rootNode, excludeRoot, ignoreIfEmptyParagraph, format) {
// Called from GetData(), convert the content's DOM into a XHTML string
// using the original data processor and detach Drupal plugins.
var data = instance.FCKDataProcessor.prototype.ConvertToDataFormat.call(this, rootNode, excludeRoot, ignoreIfEmptyParagraph, format);
if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat] && Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
for (var plugin in Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal) {
if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
data = Drupal.wysiwyg.plugins[plugin].detach(data, Drupal.settings.wysiwyg.plugins.drupal[plugin], instance.FCK.Name);
}
}
}
return data;
};
instance.FCK.DataProcessor = new wysiwygDataProcessor();
},
addPlugin: function(plugin, settings, pluginSettings, instance) {
if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
return;
}
if (Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal[plugin].css) {
instance.FCKConfig.EditorAreaCSS += ',' + Drupal.settings.wysiwyg.plugins[instance.wysiwygFormat].drupal[plugin].css;
}
// @see fckcommands.js, fck_othercommands.js, fckpastewordcommand.js
instance.FCKCommands.RegisterCommand(plugin, {
// Invoke the plugin's button.
Execute: function () {
if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
var data = { format: 'html', node: instance.FCKSelection.GetParentElement() };
// @todo This is NOT the same as data.node.
data.content = data.node.innerHTML;
Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instance.FCK.Name);
}
},
// isNode: Return whether the plugin button should be enabled for the
// current selection.
// @see FCKUnlinkCommand.prototype.GetState()
GetState: function () {
// Always disabled if not in WYSIWYG mode.
if (instance.FCK.EditMode != FCK_EDITMODE_WYSIWYG) {
return FCK_TRISTATE_DISABLED;
}
var state = instance.FCK.GetNamedCommandState(this.Name);
// FCKeditor sets the wrong state in WebKit browsers.
if (!$.support.queryCommandEnabled && state == FCK_TRISTATE_DISABLED) {
state = FCK_TRISTATE_OFF;
}
if (state == FCK_TRISTATE_OFF && instance.FCK.EditMode == FCK_EDITMODE_WYSIWYG) {
if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
var node = instance.FCKSelection.GetSelectedElement();
state = Drupal.wysiwyg.plugins[plugin].isNode(node) ? FCK_TRISTATE_ON : FCK_TRISTATE_OFF;
}
}
return state;
},
/**
* Return information about the plugin as a name/value array.
*/
Name: plugin
});
// Register the plugin button.
// Arguments: commandName, label, tooltip, style, sourceView, contextSensitive, icon.
instance.FCKToolbarItems.RegisterItem(plugin, new instance.FCKToolbarButton(plugin, settings.iconTitle, settings.iconTitle, null, false, true, settings.icon));
},
openDialog: function(dialog, params) {
// @todo Implement open dialog.
},
closeDialog: function(dialog) {
// @todo Implement close dialog.
},
prepareContent: function(content) {
// @todo Not needed for FCKeditor?
return content;
},
insert: function(content) {
var instance = FCKeditorAPI.GetInstance(this.field);
// @see FCK.InsertHtml(), FCK.InsertElement()
instance.InsertHtml(content);
}
};
})(jQuery);
+73
View File
@@ -0,0 +1,73 @@
Drupal = window.parent.Drupal;
/**
* Fetch and provide original editor settings as local variable.
*
* FCKeditor does not support to pass complex variable types to the editor.
* Instance settings passed to FCKinstance.Config are temporarily stored in
* FCKConfig.PageConfig.
*/
var wysiwygFormat = FCKConfig.PageConfig.wysiwygFormat;
var wysiwygSettings = Drupal.settings.wysiwyg.configs.fckeditor[wysiwygFormat];
var pluginSettings = (Drupal.settings.wysiwyg.plugins[wysiwygFormat] ? Drupal.settings.wysiwyg.plugins[wysiwygFormat] : { 'native': {}, 'drupal': {} });
/**
* Apply format-specific settings.
*/
for (var setting in wysiwygSettings) {
if (setting == 'buttons') {
// Apply custom Wysiwyg toolbar for this format.
// FCKConfig.ToolbarSets['Wysiwyg'] = wysiwygSettings.buttons;
// Temporarily stack buttons into multiple button groups and remove
// separators until #277954 is solved.
FCKConfig.ToolbarSets['Wysiwyg'] = [];
for (var i = 0; i < wysiwygSettings.buttons[0].length; i++) {
FCKConfig.ToolbarSets['Wysiwyg'].push([wysiwygSettings.buttons[0][i]]);
}
FCKTools.AppendStyleSheet(document, '#xToolbar .TB_Start { display:none; }');
// Set valid height of select element in silver and office2003 skins.
if (FCKConfig.SkinPath.match(/\/office2003\/$/)) {
FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 24px; } #xToolbar .TB_End { display: none; }');
}
else if (FCKConfig.SkinPath.match(/\/silver\/$/)) {
FCKTools.AppendStyleSheet(document, '#xToolbar .SC_FieldCaption { height: 27px; }');
}
}
else {
FCKConfig[setting] = wysiwygSettings[setting];
}
}
/**
* Initialize this editor instance.
*/
Drupal.wysiwyg.editor.instance.fckeditor.init(window);
/**
* Register native plugins for this input format.
*
* Parameters to Plugins.Add are:
* - Plugin name.
* - Languages the plugin is available in.
* - Location of the plugin folder; <plugin_name>/fckplugin.js is appended.
*/
for (var plugin in pluginSettings['native']) {
// Languages and path may be undefined for internal plugins.
FCKConfig.Plugins.Add(plugin, pluginSettings['native'][plugin].languages, pluginSettings['native'][plugin].path);
}
/**
* Register Drupal plugins for this input format.
*
* Parameters to addPlugin() are:
* - Plugin name.
* - Format specific plugin settings.
* - General plugin settings.
* - A reference to this window so the plugin setup can access FCKConfig.
*/
for (var plugin in pluginSettings.drupal) {
Drupal.wysiwyg.editor.instance.fckeditor.addPlugin(plugin, pluginSettings.drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin], window);
}
+25
View File
@@ -0,0 +1,25 @@
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.jwysiwyg = function(context, params, settings) {
// Attach editor.
$('#' + params.field).wysiwyg();
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.jwysiwyg = function(context, params) {
var $field = $('#' + params.field);
var editor = $field.data('wysiwyg');
if (typeof editor != 'undefined') {
editor.saveContent();
editor.element.remove();
}
$field.removeData('wysiwyg');
$field.show();
};
})(jQuery);
+29
View File
@@ -0,0 +1,29 @@
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.markitup = function(context, params, settings) {
$('#' + params.field, context).markItUp(settings);
// Adjust CSS for editor buttons.
$.each(settings.markupSet, function (button) {
$('.' + settings.nameSpace + ' .' + this.className + ' a')
.css({ backgroundImage: 'url(' + settings.root + 'sets/default/images/' + button + '.png' + ')' })
.parents('li').css({ backgroundImage: 'none' });
});
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.markitup = function(context, params) {
if (typeof params != 'undefined') {
$('#' + params.field, context).markItUpRemove();
}
else {
$('.markItUpEditor', context).markItUpRemove();
}
};
})(jQuery);
+95
View File
@@ -0,0 +1,95 @@
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.nicedit = function(context, params, settings) {
// Intercept and ignore submit handlers or they will revert changes made
// since the instance was removed. The handlers are anonymous and hidden out
// of scope in a closure so we can't unbind them. The same operations are
// performed when the instance is detached anyway.
var oldAddEvent = bkLib.addEvent;
bkLib.addEvent = function(obj, type, fn) {
if (type != 'submit') {
oldAddEvent(obj, type, fn);
}
}
// Attach editor.
var editor = new nicEditor(settings);
editor.panelInstance(params.field);
// The old addEvent() must be restored after creating a new instance, as
// plugins with dialogs use it to bind submit handlers to their forms.
bkLib.addEvent = oldAddEvent;
editor.addEvent('focus', function () {
Drupal.wysiwyg.activeId = params.field;
});
};
/**
* Detach a single or all editors.
*
* See Drupal.wysiwyg.editor.detach.none() for a full description of this hook.
*/
Drupal.wysiwyg.editor.detach.nicedit = function(context, params) {
if (typeof params != 'undefined') {
var instance = nicEditors.findEditor(params.field);
if (instance) {
instance.ne.removeInstance(params.field);
instance.ne.removePanel();
}
}
else {
for (var e in nicEditors.editors) {
// Save contents of all editors back into textareas.
var instances = nicEditors.editors[e].nicInstances;
for (var i = 0; i < instances.length; i++) {
instances[i].remove();
}
// Remove all editor instances.
nicEditors.editors[e].nicInstances = [];
}
}
};
/**
* Instance methods for nicEdit.
*/
Drupal.wysiwyg.editor.instance.nicedit = {
insert: function (content) {
var instance = nicEditors.findEditor(this.field);
var editingArea = instance.getElm();
var sel = instance.getSel();
// IE.
if (document.selection) {
editingArea.focus();
sel.createRange().text = content;
}
else {
// Convert selection to a range.
var range;
// W3C compatible.
if (sel.getRangeAt) {
range = sel.getRangeAt(0);
}
// Safari.
else {
range = editingArea.ownerDocument.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, userSeletion.focusOffset);
}
// The code below doesn't work in IE, but it never gets here.
var fragment = editingArea.ownerDocument.createDocumentFragment();
// Fragments don't support innerHTML.
var wrapper = editingArea.ownerDocument.createElement('div');
wrapper.innerHTML = content;
while (wrapper.firstChild) {
fragment.appendChild(wrapper.firstChild);
}
range.deleteContents();
// Only fragment children are inserted.
range.insertNode(fragment);
}
}
};
})(jQuery);
+71
View File
@@ -0,0 +1,71 @@
(function($) {
/**
* Attach this editor to a target element.
*
* @param context
* A DOM element, supplied by Drupal.attachBehaviors().
* @param params
* An object containing input format parameters. Default parameters are:
* - editor: The internal editor name.
* - theme: The name/key of the editor theme/profile to use.
* - field: The CSS id of the target element.
* @param settings
* An object containing editor settings for all enabled editor themes.
*/
Drupal.wysiwyg.editor.attach.none = function(context, params, settings) {
if (params.resizable) {
var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first');
$wrapper.addClass('resizable');
if (Drupal.behaviors.textarea.attach) {
Drupal.behaviors.textarea.attach();
}
}
};
/**
* Detach a single or all editors.
*
* @param context
* A DOM element, supplied by Drupal.attachBehaviors().
* @param params
* (optional) An object containing input format parameters. If defined,
* only the editor instance in params.field should be detached. Otherwise,
* all editors should be detached and saved, so they can be submitted in
* AJAX/AHAH applications.
*/
Drupal.wysiwyg.editor.detach.none = function(context, params) {
if (typeof params != 'undefined') {
var $wrapper = $('#' + params.field).parents('.form-textarea-wrapper:first');
$wrapper.removeOnce('textarea').removeClass('.resizable-textarea')
.find('.grippie').remove();
}
};
/**
* Instance methods for plain text areas.
*/
Drupal.wysiwyg.editor.instance.none = {
insert: function(content) {
var editor = document.getElementById(this.field);
// IE support.
if (document.selection) {
editor.focus();
var sel = document.selection.createRange();
sel.text = content;
}
// Mozilla/Firefox/Netscape 7+ support.
else if (editor.selectionStart || editor.selectionStart == '0') {
var startPos = editor.selectionStart;
var endPos = editor.selectionEnd;
editor.value = editor.value.substring(0, startPos) + content + editor.value.substring(endPos, editor.value.length);
}
// Fallback, just add to the end of the content.
else {
editor.value += content;
}
}
};
})(jQuery);
+68
View File
@@ -0,0 +1,68 @@
// Backup $ and reset it to jQuery.
Drupal.wysiwyg._openwysiwyg = $;
$ = jQuery;
// Wrap openWYSIWYG's methods to temporarily use its version of $.
jQuery.each(WYSIWYG, function (key, value) {
if (jQuery.isFunction(value)) {
WYSIWYG[key] = function () {
var old$ = $;
$ = Drupal.wysiwyg._openwysiwyg;
var result = value.apply(this, arguments);
$ = old$;
return result;
};
}
});
// Override editor functions.
WYSIWYG.getEditor = function (n) {
return Drupal.wysiwyg._openwysiwyg("wysiwyg" + n);
};
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.openwysiwyg = function(context, params, settings) {
// Initialize settings.
settings.ImagesDir = settings.path + 'images/';
settings.PopupsDir = settings.path + 'popups/';
settings.CSSFile = settings.path + 'styles/wysiwyg.css';
//settings.DropDowns = [];
var config = new WYSIWYG.Settings();
for (var setting in settings) {
config[setting] = settings[setting];
}
// Attach editor.
WYSIWYG.setSettings(params.field, config);
WYSIWYG_Core.includeCSS(WYSIWYG.config[params.field].CSSFile);
WYSIWYG._generate(params.field, config);
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.openwysiwyg = function(context, params) {
if (typeof params != 'undefined') {
var instance = WYSIWYG.config[params.field];
if (typeof instance != 'undefined') {
WYSIWYG.updateTextArea(params.field);
jQuery('#wysiwyg_div_' + params.field).remove();
delete instance;
}
jQuery('#' + params.field).show();
}
else {
jQuery.each(WYSIWYG.config, function(field) {
WYSIWYG.updateTextArea(field);
jQuery('#wysiwyg_div_' + field).remove();
delete this;
jQuery('#' + field).show();
});
}
};
})(jQuery);
+213
View File
@@ -0,0 +1,213 @@
(function($) {
/**
* Initialize editor instances.
*
* This function needs to be called before the page is fully loaded, as
* calling tinyMCE.init() after the page is loaded breaks IE6.
*
* @param editorSettings
* An object containing editor settings for each input format.
*/
Drupal.wysiwyg.editor.init.tinymce = function(settings) {
// If JS compression is enabled, TinyMCE is unable to autodetect its global
// settinge, hence we need to define them manually.
// @todo Move global library settings somewhere else.
tinyMCE.baseURL = settings.global.editorBasePath;
tinyMCE.srcMode = (settings.global.execMode == 'src' ? '_src' : '');
tinyMCE.gzipMode = (settings.global.execMode == 'gzip');
// Initialize editor configurations.
for (var format in settings) {
if (format == 'global') {
continue;
}
tinyMCE.init(settings[format]);
if (Drupal.settings.wysiwyg.plugins[format]) {
// Load native external plugins.
// Array syntax required; 'native' is a predefined token in JavaScript.
for (var plugin in Drupal.settings.wysiwyg.plugins[format]['native']) {
tinyMCE.loadPlugin(plugin, Drupal.settings.wysiwyg.plugins[format]['native'][plugin]);
}
// Load Drupal plugins.
for (var plugin in Drupal.settings.wysiwyg.plugins[format].drupal) {
Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins[format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]);
}
}
}
};
/**
* Attach this editor to a target element.
*
* See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook.
*/
Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) {
// Configure editor settings for this input format.
for (var setting in settings) {
tinyMCE.settings[setting] = settings[setting];
}
// Remove TinyMCE's internal mceItem class, which was incorrectly added to
// submitted content by Wysiwyg <2.1. TinyMCE only temporarily adds the class
// for placeholder elements. If preemptively set, the class prevents (native)
// editor plugins from gaining an active state, so we have to manually remove
// it prior to attaching the editor. This is done on the client-side instead
// of the server-side, as Wysiwyg has no way to figure out where content is
// stored, and the class only affects editing.
$field = $('#' + params.field);
$field.val($field.val().replace(/(<.+?\s+class=['"][\w\s]*?)\bmceItem\b([\w\s]*?['"].*?>)/ig, '$1$2'));
// Attach editor.
tinyMCE.execCommand('mceAddControl', true, params.field);
};
/**
* Detach a single or all editors.
*
* See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
*/
Drupal.wysiwyg.editor.detach.tinymce = function(context, params) {
if (typeof params != 'undefined') {
tinyMCE.removeMCEControl(tinyMCE.getEditorId(params.field));
$('#' + params.field).removeAttr('style');
}
// else if (tinyMCE.activeEditor) {
// tinyMCE.triggerSave();
// tinyMCE.activeEditor.remove();
// }
};
Drupal.wysiwyg.editor.instance.tinymce = {
addPlugin: function(plugin, settings, pluginSettings) {
if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
return;
}
tinyMCE.addPlugin(plugin, {
// Register an editor command for this plugin, invoked by the plugin's button.
execCommand: function(editor_id, element, command, user_interface, value) {
switch (command) {
case plugin:
if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
var ed = tinyMCE.getInstanceById(editor_id);
var data = { format: 'html', node: ed.getFocusElement(), content: ed.getFocusElement() };
Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, ed.formTargetElementId);
return true;
}
}
// Pass to next handler in chain.
return false;
},
// Register the plugin button.
getControlHTML: function(control_name) {
switch (control_name) {
case plugin:
return tinyMCE.getButtonHTML(control_name, settings.iconTitle, settings.icon, plugin);
}
return '';
},
// Load custom CSS for editor contents on startup.
initInstance: function(ed) {
if (settings.css) {
tinyMCE.importCSS(ed.getDoc(), settings.css);
}
},
cleanup: function(type, content) {
switch (type) {
case 'insert_to_editor':
// Attach: Replace plain text with HTML representations.
if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
content = Drupal.wysiwyg.plugins[plugin].attach(content, pluginSettings, tinyMCE.selectedInstance.editorId);
content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(content);
}
break;
case 'get_from_editor':
// Detach: Replace HTML representations with plain text.
if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
content = Drupal.wysiwyg.plugins[plugin].detach(content, pluginSettings, tinyMCE.selectedInstance.editorId);
}
break;
}
// Pass through to next handler in chain
return content;
},
// isNode: Return whether the plugin button should be enabled for the
// current selection.
handleNodeChange: function(editor_id, node, undo_index, undo_levels, visual_aid, any_selection) {
if (node === null) {
return;
}
if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
if (Drupal.wysiwyg.plugins[plugin].isNode(node)) {
tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonSelected');
return true;
}
}
tinyMCE.switchClass(editor_id + '_' + plugin, 'mceButtonNormal');
return true;
},
/**
* Return information about the plugin as a name/value array.
*/
getInfo: function() {
return {
longname: settings.title
};
}
});
},
openDialog: function(dialog, params) {
var editor = tinyMCE.getInstanceById(this.field);
tinyMCE.openWindow({
file: dialog.url + '/' + this.field,
width: dialog.width,
height: dialog.height,
inline: 1
}, params);
},
closeDialog: function(dialog) {
var editor = tinyMCE.getInstanceById(this.field);
tinyMCEPopup.close();
},
prepareContent: function(content) {
// Certain content elements need to have additional DOM properties applied
// to prevent this editor from highlighting an internal button in addition
// to the button of a Drupal plugin.
var specialProperties = {
img: { 'name': 'mce_drupal' }
};
var $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
jQuery.each(specialProperties, function(element, properties) {
$content.find(element).each(function() {
for (var property in properties) {
if (property == 'class') {
$(this).addClass(properties[property]);
}
else {
$(this).attr(property, properties[property]);
}
}
});
});
return $content.html();
},
insert: function(content) {
content = this.prepareContent(content);
var editor = tinyMCE.getInstanceById(this.field);
editor.execCommand('mceInsertContent', false, content);
editor.repaint();
}
};
})(jQuery);
+235
View File
@@ -0,0 +1,235 @@
(function($) {
/**
* Initialize editor instances.
*
* @todo Is the following note still valid for 3.x?
* This function needs to be called before the page is fully loaded, as
* calling tinyMCE.init() after the page is loaded breaks IE6.
*
* @param editorSettings
* An object containing editor settings for each input format.
*/
Drupal.wysiwyg.editor.init.tinymce = function(settings) {
// If JS compression is enabled, TinyMCE is unable to autodetect its global
// settinge, hence we need to define them manually.
// @todo Move global library settings somewhere else.
tinyMCE.baseURL = settings.global.editorBasePath;
tinyMCE.srcMode = (settings.global.execMode == 'src' ? '_src' : '');
tinyMCE.gzipMode = (settings.global.execMode == 'gzip');
// Initialize editor configurations.
for (var format in settings) {
if (format == 'global') {
continue;
};
tinyMCE.init(settings[format]);
if (Drupal.settings.wysiwyg.plugins[format]) {
// Load native external plugins.
// Array syntax required; 'native' is a predefined token in JavaScript.
for (var plugin in Drupal.settings.wysiwyg.plugins[format]['native']) {
tinymce.PluginManager.load(plugin, Drupal.settings.wysiwyg.plugins[format]['native'][plugin]);
}
// Load Drupal plugins.
for (var plugin in Drupal.settings.wysiwyg.plugins[format].drupal) {
Drupal.wysiwyg.editor.instance.tinymce.addPlugin(plugin, Drupal.settings.wysiwyg.plugins[format].drupal[plugin], Drupal.settings.wysiwyg.plugins.drupal[plugin]);
}
}
}
};
/**
* Attach this editor to a target element.
*
* See Drupal.wysiwyg.editor.attach.none() for a full desciption of this hook.
*/
Drupal.wysiwyg.editor.attach.tinymce = function(context, params, settings) {
// Configure editor settings for this input format.
var ed = new tinymce.Editor(params.field, settings);
// Reset active instance id on any event.
ed.onEvent.add(function(ed, e) {
Drupal.wysiwyg.activeId = ed.id;
});
// Make toolbar buttons wrappable (required for IE).
ed.onPostRender.add(function (ed) {
var $toolbar = $('<div class="wysiwygToolbar"></div>');
$('#' + ed.editorContainer + ' table.mceToolbar > tbody > tr > td').each(function () {
$('<div></div>').addClass(this.className).append($(this).children()).appendTo($toolbar);
});
$('#' + ed.editorContainer + ' table.mceLayout td.mceToolbar').append($toolbar);
$('#' + ed.editorContainer + ' table.mceToolbar').remove();
});
// Remove TinyMCE's internal mceItem class, which was incorrectly added to
// submitted content by Wysiwyg <2.1. TinyMCE only temporarily adds the class
// for placeholder elements. If preemptively set, the class prevents (native)
// editor plugins from gaining an active state, so we have to manually remove
// it prior to attaching the editor. This is done on the client-side instead
// of the server-side, as Wysiwyg has no way to figure out where content is
// stored, and the class only affects editing.
$field = $('#' + params.field);
$field.val($field.val().replace(/(<.+?\s+class=['"][\w\s]*?)\bmceItem\b([\w\s]*?['"].*?>)/ig, '$1$2'));
// Attach editor.
ed.render();
};
/**
* Detach a single or all editors.
*
* See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
*/
Drupal.wysiwyg.editor.detach.tinymce = function(context, params) {
if (typeof params != 'undefined') {
var instance = tinyMCE.get(params.field);
if (instance) {
instance.save();
instance.remove();
}
}
else {
// Save contents of all editors back into textareas.
tinyMCE.triggerSave();
// Remove all editor instances.
for (var instance in tinyMCE.editors) {
tinyMCE.editors[instance].remove();
}
}
};
Drupal.wysiwyg.editor.instance.tinymce = {
addPlugin: function(plugin, settings, pluginSettings) {
if (typeof Drupal.wysiwyg.plugins[plugin] != 'object') {
return;
}
tinymce.create('tinymce.plugins.' + plugin, {
/**
* Initialize the plugin, executed after the plugin has been created.
*
* @param ed
* The tinymce.Editor instance the plugin is initialized in.
* @param url
* The absolute URL of the plugin location.
*/
init: function(ed, url) {
// Register an editor command for this plugin, invoked by the plugin's button.
ed.addCommand(plugin, function() {
if (typeof Drupal.wysiwyg.plugins[plugin].invoke == 'function') {
var data = { format: 'html', node: ed.selection.getNode(), content: ed.selection.getContent() };
// TinyMCE creates a completely new instance for fullscreen mode.
var instanceId = ed.id == 'mce_fullscreen' ? ed.getParam('fullscreen_editor_id') : ed.id;
Drupal.wysiwyg.plugins[plugin].invoke(data, pluginSettings, instanceId);
}
});
// Register the plugin button.
ed.addButton(plugin, {
title : settings.iconTitle,
cmd : plugin,
image : settings.icon
});
// Load custom CSS for editor contents on startup.
ed.onInit.add(function() {
if (settings.css) {
ed.dom.loadCSS(settings.css);
}
});
// Attach: Replace plain text with HTML representations.
ed.onBeforeSetContent.add(function(ed, data) {
if (typeof Drupal.wysiwyg.plugins[plugin].attach == 'function') {
data.content = Drupal.wysiwyg.plugins[plugin].attach(data.content, pluginSettings, ed.id);
data.content = Drupal.wysiwyg.editor.instance.tinymce.prepareContent(data.content);
}
});
// Detach: Replace HTML representations with plain text.
ed.onGetContent.add(function(ed, data) {
if (typeof Drupal.wysiwyg.plugins[plugin].detach == 'function') {
data.content = Drupal.wysiwyg.plugins[plugin].detach(data.content, pluginSettings, ed.id);
}
});
// isNode: Return whether the plugin button should be enabled for the
// current selection.
ed.onNodeChange.add(function(ed, command, node) {
if (typeof Drupal.wysiwyg.plugins[plugin].isNode == 'function') {
command.setActive(plugin, Drupal.wysiwyg.plugins[plugin].isNode(node));
}
});
},
/**
* Return information about the plugin as a name/value array.
*/
getInfo: function() {
return {
longname: settings.title
};
}
});
// Register plugin.
tinymce.PluginManager.add(plugin, tinymce.plugins[plugin]);
},
openDialog: function(dialog, params) {
var instanceId = this.isFullscreen() ? 'mce_fullscreen' : this.field;
var editor = tinyMCE.get(instanceId);
editor.windowManager.open({
file: dialog.url + '/' + instanceId,
width: dialog.width,
height: dialog.height,
inline: 1
}, params);
},
closeDialog: function(dialog) {
var instanceId = this.isFullscreen() ? 'mce_fullscreen' : this.field;
var editor = tinyMCE.get(instanceId);
editor.windowManager.close(dialog);
},
prepareContent: function(content) {
// Certain content elements need to have additional DOM properties applied
// to prevent this editor from highlighting an internal button in addition
// to the button of a Drupal plugin.
var specialProperties = {
img: { 'class': 'mceItem' }
};
var $content = $('<div>' + content + '</div>'); // No .outerHTML() in jQuery :(
// Find all placeholder/replacement content of Drupal plugins.
$content.find('.drupal-content').each(function() {
// Recursively process DOM elements below this element to apply special
// properties.
var $drupalContent = $(this);
$.each(specialProperties, function(element, properties) {
$drupalContent.find(element).andSelf().each(function() {
for (var property in properties) {
if (property == 'class') {
$(this).addClass(properties[property]);
}
else {
$(this).attr(property, properties[property]);
}
}
});
});
});
return $content.html();
},
insert: function(content) {
content = this.prepareContent(content);
var instanceId = this.isFullscreen() ? 'mce_fullscreen' : this.field;
tinyMCE.execInstanceCommand(instanceId, 'mceInsertContent', false, content);
},
isFullscreen: function() {
// TinyMCE creates a completely new instance for fullscreen mode.
return tinyMCE.activeEditor.id == 'mce_fullscreen' && tinyMCE.activeEditor.getParam('fullscreen_editor_id') == this.field;
}
};
})(jQuery);
+133
View File
@@ -0,0 +1,133 @@
var wysiwygWhizzywig = { currentField: null, fields: {} };
var buttonPath = null;
/**
* Override Whizzywig's document.write() function.
*
* Whizzywig uses document.write() by default, which leads to a blank page when
* invoked in jQuery.ready(). Luckily, Whizzywig developers implemented a
* shorthand w() substitute function that we can override to redirect the output
* into the global wysiwygWhizzywig variable.
*
* @see o()
*/
var w = function (string) {
if (string) {
wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] += string;
}
return wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField];
};
/**
* Override Whizzywig's document.getElementById() function.
*
* Since we redirect the output of w() into a temporary string upon attaching
* an editor, we also have to override the o() shorthand substitute function
* for document.getElementById() to search in the document or our container.
* This override function also inserts the editor instance when Whizzywig
* tries to access its IFRAME, so it has access to the full/regular window
* object.
*
* @see w()
*/
var o = function (id) {
// Upon first access to "whizzy" + id, Whizzywig tries to access its IFRAME,
// so we need to insert the editor into the DOM.
if (id == 'whizzy' + wysiwygWhizzywig.currentField && wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]) {
jQuery('#' + wysiwygWhizzywig.currentField).after('<div id="' + wysiwygWhizzywig.currentField + '-whizzywig"></div>');
// Iframe's .contentWindow becomes null in Webkit if inserted via .after().
jQuery('#' + wysiwygWhizzywig.currentField + '-whizzywig').html(w());
// Prevent subsequent invocations from inserting the editor multiple times.
wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
}
// If id exists in the regular window.document, return it.
if (jQuery('#' + id).size()) {
return jQuery('#' + id).get(0);
}
// Otherwise return id from our container.
return jQuery('#' + id, w()).get(0);
};
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) {
// Previous versions used per-button images found in this location,
// now it is only used for custom buttons.
if (settings.buttonPath) {
window.buttonPath = settings.buttonPath;
}
// Assign the toolbar image path used for native buttons, if available.
if (settings.toolbarImagePath) {
btn._f = settings.toolbarImagePath;
}
// Fall back to text labels for all buttons.
else {
window.buttonPath = 'textbuttons';
}
// Create Whizzywig container.
wysiwygWhizzywig.currentField = params.field;
wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
// Whizzywig needs to have the width set 'inline'.
$field = $('#' + params.field);
var originalValues = Drupal.wysiwyg.instances[params.field];
originalValues.originalStyle = $field.attr('style');
$field.css('width', $field.width() + 'px');
// Attach editor.
makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all'));
// Whizzywig fails to detect and set initial textarea contents.
var instance = $('#whizzy' + params.field).get(0);
if (instance) {
instance.contentWindow.document.body.innerHTML = tidyD($field.val());
}
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) {
var detach = function (index) {
var id = whizzies[index];
var instance = $('#whizzy' + id).get(0);
if (!instance) {
return;
}
var editingArea = instance.contentWindow.document;
var $field = $('#' + id);
// Whizzywig shows the original textarea in source mode.
if ($field.css('display') == 'block') {
editingArea.body.innerHTML = $field.val();
}
// Save contents of editor back into textarea.
$field.val(tidyH(editingArea));
// Remove editor instance.
$('#' + id + '-whizzywig').remove();
whizzies.splice(index, 1);
// Restore original textarea styling.
var originalValues = Drupal.wysiwyg.instances[id];
$field.removeAttr('style');
$field.attr('style', originalValues.originalStyle);
};
if (typeof params != 'undefined') {
for (var i = 0; i < whizzies.length; i++) {
if (whizzies[i] == params.field) {
detach(i);
break;
}
}
}
else {
while (whizzies.length > 0) {
detach(0);
}
}
};
})(jQuery);
+85
View File
@@ -0,0 +1,85 @@
var buttonPath = null;
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) {
// Previous versions used per-button images found in this location,
// now it is only used for custom buttons.
if (settings.buttonPath) {
window.buttonPath = settings.buttonPath;
}
// Assign the toolbar image path used for native buttons, if available.
if (settings.toolbarImagePath) {
btn._f = settings.toolbarImagePath;
}
// Fall back to text labels for all buttons.
else {
window.buttonPath = 'textbuttons';
}
// Whizzywig needs to have the width set 'inline'.
$field = $('#' + params.field);
var originalValues = Drupal.wysiwyg.instances[params.field];
originalValues.originalStyle = $field.attr('style');
$field.css('width', $field.width() + 'px');
// Attach editor.
makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all'));
// Whizzywig fails to detect and set initial textarea contents.
var instance = $('#whizzy' + params.field).get(0);
if (instance) {
instance.contentWindow.document.body.innerHTML = tidyD($field.val());
}
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) {
var detach = function (index) {
var id = whizzies[index];
var instance = $('#whizzy' + id).get(0);
if (!instance) {
return;
}
var editingArea = instance.contentWindow.document;
var $field = $('#' + id);
// Whizzywig shows the original textarea in source mode.
if ($field.css('display') == 'block') {
editingArea.body.innerHTML = $field.val();
}
// Save contents of editor back into textarea.
$field.val(tidyH(editingArea));
// Move original textarea back to its previous location.
$container = $('#CONTAINER' + id);
$field.insertBefore($container);
// Remove editor instance.
$container.remove();
whizzies.splice(index, 1);
// Restore original textarea styling.
var originalValues = Drupal.wysiwyg.instances[id];
$field.removeAttr('style');
$field.attr('style', originalValues.originalStyle);
}
if (typeof params != 'undefined') {
for (var i = 0; i < whizzies.length; i++) {
if (whizzies[i] == params.field) {
detach(i);
break;
}
}
}
else {
while (whizzies.length > 0) {
detach(0);
}
}
};
})(jQuery);
+126
View File
@@ -0,0 +1,126 @@
var wysiwygWhizzywig = { currentField: null, fields: {} };
var buttonPath = null;
/**
* Override Whizzywig's document.write() function.
*
* Whizzywig uses document.write() by default, which leads to a blank page when
* invoked in jQuery.ready(). Luckily, Whizzywig developers implemented a
* shorthand w() substitute function that we can override to redirect the output
* into the global wysiwygWhizzywig variable.
*
* @see o()
*/
var w = function (string) {
if (string) {
wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] += string;
}
return wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField];
};
/**
* Override Whizzywig's document.getElementById() function.
*
* Since we redirect the output of w() into a temporary string upon attaching
* an editor, we also have to override the o() shorthand substitute function
* for document.getElementById() to search in the document or our container.
* This override function also inserts the editor instance when Whizzywig
* tries to access its IFRAME, so it has access to the full/regular window
* object.
*
* @see w()
*/
var o = function (id) {
// Upon first access to "whizzy" + id, Whizzywig tries to access its IFRAME,
// so we need to insert the editor into the DOM.
if (id == 'whizzy' + wysiwygWhizzywig.currentField && wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField]) {
jQuery('#' + wysiwygWhizzywig.currentField).after('<div id="' + wysiwygWhizzywig.currentField + '-whizzywig"></div>');
// Iframe's .contentWindow becomes null in Webkit if inserted via .after().
jQuery('#' + wysiwygWhizzywig.currentField + '-whizzywig').html(w());
// Prevent subsequent invocations from inserting the editor multiple times.
wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
}
// If id exists in the regular window.document, return it.
if (jQuery('#' + id).size()) {
return jQuery('#' + id).get(0);
}
// Otherwise return id from our container.
return jQuery('#' + id, w()).get(0);
};
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.whizzywig = function(context, params, settings) {
// Assign button images path, if available.
if (settings.buttonPath) {
window.buttonPath = settings.buttonPath;
}
// Create Whizzywig container.
wysiwygWhizzywig.currentField = params.field;
wysiwygWhizzywig.fields[wysiwygWhizzywig.currentField] = '';
// Whizzywig needs to have the width set 'inline'.
$field = $('#' + params.field);
var originalValues = Drupal.wysiwyg.instances[params.field];
originalValues.originalStyle = $field.attr('style');
$field.css('width', $field.width() + 'px');
// Attach editor.
makeWhizzyWig(params.field, (settings.buttons ? settings.buttons : 'all'));
// Whizzywig fails to detect and set initial textarea contents.
var instance = $('#whizzy' + params.field).get(0);
if (instance) {
instance.contentWindow.document.body.innerHTML = tidyD($field.val());
}
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.whizzywig = function(context, params) {
var detach = function (index) {
var id = whizzies[index];
var instance = $('#whizzy' + id).get(0);
if (!instance) {
return;
}
var body = instance.contentWindow.document.body;
var $field = $('#' + id);
// Whizzywig shows the original textarea in source mode.
if ($field.css('display') == 'block') {
body.innerHTML = $field.val();
}
body.innerHTML = tidyH(body.innerHTML);
// Save contents of editor back into textarea.
$field.val(window.get_xhtml ? get_xhtml(body) : body.innerHTML);
$field.val($field.val().replace(location.href + '#', '#'));
// Remove editor instance.
$('#' + id + '-whizzywig').remove();
whizzies.splice(index, 1);
// Restore original textarea styling.
var originalValues = Drupal.wysiwyg.instances[id];
$field.removeAttr('style');
$field.attr('style', originalValues.originalStyle);
};
if (typeof params != 'undefined') {
for (var i = 0; i < whizzies.length; i++) {
if (whizzies[i] == params.field) {
detach(i);
break;
}
}
}
else {
while (whizzies.length > 0) {
detach(0);
}
}
};
})(jQuery);
+56
View File
@@ -0,0 +1,56 @@
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.wymeditor = function (context, params, settings) {
// Prepend basePath to wymPath.
settings.wymPath = settings.basePath + settings.wymPath;
// Update activeId on focus.
settings.postInit = function (instance) {
$(instance._doc).focus(function () {
Drupal.wysiwyg.activeId = params.field;
});
};
// Attach editor.
$('#' + params.field).wymeditor(settings);
};
/**
* Detach a single or all editors.
*/
Drupal.wysiwyg.editor.detach.wymeditor = function (context, params) {
if (typeof params != 'undefined') {
var $field = $('#' + params.field);
var index = $field.data(WYMeditor.WYM_INDEX);
if (typeof index != 'undefined') {
var instance = WYMeditor.INSTANCES[index];
instance.update();
$(instance._box).remove();
$(instance._element).show();
delete instance;
}
$field.show();
}
else {
jQuery.each(WYMeditor.INSTANCES, function () {
this.update();
$(this._box).remove();
$(this._element).show();
delete this;
});
}
};
Drupal.wysiwyg.editor.instance.wymeditor = {
insert: function (content) {
var $field = $('#' + this.field);
var index = $field.data(WYMeditor.WYM_INDEX);
if (typeof index != 'undefined') {
var instance = WYMeditor.INSTANCES[index];
instance.insert(content);
}
}
};
})(jQuery);
+35
View File
@@ -0,0 +1,35 @@
(function($) {
/**
* Attach this editor to a target element.
*/
Drupal.wysiwyg.editor.attach.yui = function(context, params, settings) {
// Apply theme.
$('#' + params.field).parent().addClass('yui-skin-' + settings.theme);
// Attach editor.
var editor = new YAHOO.widget.Editor(params.field, settings);
editor.render();
};
/**
* Detach a single or all editors.
*
* See Drupal.wysiwyg.editor.detach.none() for a full desciption of this hook.
*/
Drupal.wysiwyg.editor.detach.yui = function(context, params) {
if (typeof params != 'undefined') {
var instance = YAHOO.widget.EditorInfo.getEditorById(params.field);
if (instance) {
instance.destroy();
}
}
else {
for (var e in YAHOO.widget.EditorInfo._instances) {
// Save contents of all editors back into textareas.
var instance = YAHOO.widget.EditorInfo._instances[e];
instance.destroy();
}
}
};
})(jQuery);
+62
View File
@@ -0,0 +1,62 @@
<?php
/**
* @file
* Editor integration functions for jWYSIWYG.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_jwysiwyg_editor() {
$editor['jwysiwyg'] = array(
'title' => 'jWYSIWYG',
'vendor url' => 'http://code.google.com/p/jwysiwyg/',
'download url' => 'http://code.google.com/p/jwysiwyg/downloads/list',
'libraries' => array(
'' => array(
'title' => 'Source',
'files' => array('jquery.wysiwyg.js'),
),
'pack' => array(
'title' => 'Packed',
'files' => array('jquery.wysiwyg.pack.js'),
),
),
'version callback' => 'wysiwyg_jwysiwyg_version',
// @todo Wrong property; add separate properties for editor requisites.
'css path' => wysiwyg_get_path('jwysiwyg'),
'versions' => array(
'0.5' => array(
'js files' => array('jwysiwyg.js'),
'css files' => array('jquery.wysiwyg.css'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_jwysiwyg_version($editor) {
$script = $editor['library path'] . '/jquery.wysiwyg.js';
if (!file_exists($script)) {
return;
}
$script = fopen($script, 'r');
fgets($script);
$line = fgets($script);
if (preg_match('@([0-9\.]+)$@', $line, $version)) {
fclose($script);
return $version[1];
}
fclose($script);
}
+189
View File
@@ -0,0 +1,189 @@
<?php
/**
* @file
* Editor integration functions for markItUp.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_markitup_editor() {
$editor['markitup'] = array(
'title' => 'markItUp',
'vendor url' => 'http://markitup.jaysalvat.com',
'download url' => 'http://markitup.jaysalvat.com/downloads',
'library path' => wysiwyg_get_path('markitup'),
'libraries' => array(
'' => array(
'title' => 'Source',
'files' => array('markitup/jquery.markitup.js'),
),
'pack' => array(
'title' => 'Packed',
'files' => array('markitup/jquery.markitup.pack.js'),
),
),
'version callback' => 'wysiwyg_markitup_version',
'themes callback' => 'wysiwyg_markitup_themes',
'settings callback' => 'wysiwyg_markitup_settings',
'plugin callback' => 'wysiwyg_markitup_plugins',
'versions' => array(
'1.1.5' => array(
'js files' => array('markitup.js'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_markitup_version($editor) {
// Changelog was in markitup/markitup/readme.txt <= 1.1.5.
$changelog = $editor['library path'] . '/markitup/readme.txt';
if (!file_exists($changelog)) {
// Changelog was moved up to markitup/CHANGELOG.md after 1.1.5.
$changelog = $editor['library path'] . '/CHANGELOG.md';
if (!file_exists($changelog)) {
return;
}
}
$changelog = fopen($changelog, 'r');
$line = fgets($changelog);
if (preg_match('@([0-9\.]+)@', $line, $version)) {
fclose($changelog);
return $version[1];
}
fclose($changelog);
}
/**
* Determine available editor themes or check/reset a given one.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $profile
* A wysiwyg editor profile.
*
* @return
* An array of theme names. The first returned name should be the default
* theme name.
*/
function wysiwyg_markitup_themes($editor, $profile) {
return array('simple', 'markitup');
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_markitup_settings($editor, $config, $theme) {
drupal_add_css($editor['library path'] . '/markitup/skins/' . $theme . '/style.css', array(
// Specify an alternate basename; otherwise, style.css would override a
// commonly used style.css file of the theme.
'basename' => 'markitup.' . $theme . '.style.css',
'group' => CSS_THEME,
));
$settings = array(
'root' => base_path() . $editor['library path'] . '/markitup/',
'nameSpace' => $theme,
'markupSet' => array(),
);
// Add configured buttons or all available.
$default_buttons = array(
'bold' => array(
'name' => t('Bold'),
'className' => 'markitup-bold',
'key' => 'B',
'openWith' => '(!(<strong>|!|<b>)!)',
'closeWith' => '(!(</strong>|!|</b>)!)',
),
'italic' => array(
'name' => t('Italic'),
'className' => 'markitup-italic',
'key' => 'I',
'openWith' => '(!(<em>|!|<i>)!)',
'closeWith' => '(!(</em>|!|</i>)!)',
),
'stroke' => array(
'name' => t('Strike-through'),
'className' => 'markitup-stroke',
'key' => 'S',
'openWith' => '<del>',
'closeWith' => '</del>',
),
'image' => array(
'name' => t('Image'),
'className' => 'markitup-image',
'key' => 'P',
'replaceWith' => '<img src="[![Source:!:http://]!]" alt="[![Alternative text]!]" />',
),
'link' => array(
'name' => t('Link'),
'className' => 'markitup-link',
'key' => 'K',
'openWith' => '<a href="[![Link:!:http://]!]"(!( title="[![Title]!]")!)>',
'closeWith' => '</a>',
'placeHolder' => 'Your text to link...',
),
// @todo
// 'cleanup' => array('name' => t('Clean-up'), 'className' => 'markitup-cleanup', 'replaceWith' => 'function(markitup) { return markitup.selection.replace(/<(.*?)>/g, "") }'),
'preview' => array(
'name' => t('Preview'),
'className' => 'markitup-preview',
'call' => 'preview',
),
);
$settings['markupSet'] = array();
if (!empty($config['buttons'])) {
foreach ($config['buttons'] as $plugin) {
foreach ($plugin as $button => $enabled) {
if (isset($default_buttons[$button])) {
$settings['markupSet'][$button] = $default_buttons[$button];
}
}
}
}
return $settings;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_markitup_plugins($editor) {
return array(
'default' => array(
'buttons' => array(
'bold' => t('Bold'), 'italic' => t('Italic'),
'stroke' => t('Strike-through'),
'link' => t('Link'),
'image' => t('Image'),
// 'cleanup' => t('Clean-up'),
'preview' => t('Preview'),
),
'internal' => TRUE,
),
);
}
+119
View File
@@ -0,0 +1,119 @@
<?php
/**
* @file
* Editor integration functions for NicEdit.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_nicedit_editor() {
$editor['nicedit'] = array(
'title' => 'NicEdit',
'vendor url' => 'http://nicedit.com',
'download url' => 'http://nicedit.com/download.php',
'libraries' => array(
'' => array(
'title' => 'Source',
'files' => array('nicEdit.js'),
),
),
'version callback' => 'wysiwyg_nicedit_version',
'settings callback' => 'wysiwyg_nicedit_settings',
'plugin callback' => 'wysiwyg_nicedit_plugins',
'versions' => array(
'0.9' => array(
'js files' => array('nicedit.js'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_nicedit_version($editor) {
// @see http://nicedit.com/forums/viewtopic.php?t=425
return '0.9';
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_nicedit_settings($editor, $config, $theme) {
$settings = array(
'iconsPath' => base_path() . $editor['library path'] . '/nicEditorIcons.gif',
);
// Add configured buttons or all available.
$settings['buttonList'] = array();
if (!empty($config['buttons'])) {
$buttons = array();
foreach ($config['buttons'] as $plugin) {
$buttons = array_merge($buttons, $plugin);
}
$settings['buttonList'] = array_keys($buttons);
}
// Add editor content stylesheet.
if (isset($config['css_setting'])) {
if ($config['css_setting'] == 'theme') {
$css = path_to_theme() . '/style.css';
if (file_exists($css)) {
$settings['externalCSS'] = base_path() . $css;
}
}
else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
}
}
return $settings;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_nicedit_plugins($editor) {
return array(
'default' => array(
'buttons' => array(
'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
'strikethrough' => t('Strike-through'),
'left' => t('Align left'), 'center' => t('Align center'), 'right' => t('Align right'),
'ul' => t('Bullet list'), 'ol' => t('Numbered list'),
'outdent' => t('Outdent'), 'indent' => t('Indent'),
'image' => t('Image'),
'forecolor' => t('Forecolor'), 'bgcolor' => t('Backcolor'),
'superscript' => t('Superscript'), 'subscript' => t('Subscript'),
'hr' => t('Horizontal rule'),
// @todo New challenge: Optional internal plugins packaged into editor
// library.
'link' => t('Link'), 'unlink' => t('Unlink'),
'fontFormat' => t('HTML block format'), 'fontFamily' => t('Font'), 'fontSize' => t('Font size'),
'xhtml' => t('Source code'),
),
'internal' => TRUE,
),
);
}
+173
View File
@@ -0,0 +1,173 @@
<?php
/**
* @file
* Editor integration functions for openWYSIWYG.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_openwysiwyg_editor() {
$editor['openwysiwyg'] = array(
'title' => 'openWYSIWYG',
'vendor url' => 'http://www.openwebware.com',
'download url' => 'http://www.openwebware.com/download.shtml',
'library path' => wysiwyg_get_path('openwysiwyg') . '/scripts',
'libraries' => array(
'src' => array(
'title' => 'Source',
'files' => array('wysiwyg.js'),
),
),
'version callback' => 'wysiwyg_openwysiwyg_version',
'themes callback' => 'wysiwyg_openwysiwyg_themes',
'settings callback' => 'wysiwyg_openwysiwyg_settings',
'plugin callback' => 'wysiwyg_openwysiwyg_plugins',
'versions' => array(
'1.4.7' => array(
'js files' => array('openwysiwyg.js'),
'css files' => array('openwysiwyg.css'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_openwysiwyg_version($editor) {
// 'library path' has '/scripts' appended already.
$changelog = $editor['editor path'] . '/changelog';
if (!file_exists($changelog)) {
return;
}
$changelog = fopen($changelog, 'r');
$line = fgets($changelog, 20);
if (preg_match('@v([\d\.]+)@', $line, $version)) {
fclose($changelog);
return $version[1];
}
fclose($changelog);
}
/**
* Determine available editor themes or check/reset a given one.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $profile
* A wysiwyg editor profile.
*
* @return
* An array of theme names. The first returned name should be the default
* theme name.
*/
function wysiwyg_openwysiwyg_themes($editor, $profile) {
return array('default');
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_openwysiwyg_settings($editor, $config, $theme) {
$settings = array(
'path' => base_path() . $editor['editor path'] . '/',
'Width' => '100%',
);
if (isset($config['path_loc']) && $config['path_loc'] == 'none') {
$settings['StatusBarEnabled'] = FALSE;
}
if (isset($config['css_setting'])) {
if ($config['css_setting'] == 'theme') {
$settings['CSSFile'] = reset(wysiwyg_get_css());
}
else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['CSSFile'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
}
}
$settings['Toolbar'] = array();
if (!empty($config['buttons'])) {
$plugins = wysiwyg_get_plugins($editor['name']);
foreach ($config['buttons'] as $plugin => $buttons) {
foreach ($buttons as $button => $enabled) {
foreach (array('buttons', 'extensions') as $type) {
// Skip unavailable plugins.
if (!isset($plugins[$plugin][$type][$button])) {
continue;
}
// Add buttons.
if ($type == 'buttons') {
$settings['Toolbar'][0][] = $button;
}
}
}
}
}
// @todo
// if (isset($config['block_formats'])) {
// $settings['DropDowns']['headings']['elements'] = explode(',', $config['block_formats']);
// }
return $settings;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_openwysiwyg_plugins($editor) {
$plugins = array(
'default' => array(
'buttons' => array(
'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
'strikethrough' => t('Strike-through'),
'justifyleft' => t('Align left'), 'justifycenter' => t('Align center'), 'justifyright' => t('Align right'), 'justifyfull' => t('Justify'),
'unorderedlist' => t('Bullet list'), 'orderedlist' => t('Numbered list'),
'outdent' => t('Outdent'), 'indent' => t('Indent'),
'undo' => t('Undo'), 'redo' => t('Redo'),
'createlink' => t('Link'),
'insertimage' => t('Image'),
'cleanup' => t('Clean-up'),
'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'),
'superscript' => t('Sup'), 'subscript' => t('Sub'),
'blockquote' => t('Blockquote'), 'viewSource' => t('Source code'),
'hr' => t('Horizontal rule'),
'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'),
'visualaid' => t('Visual aid'),
'removeformat' => t('Remove format'),
'charmap' => t('Character map'),
'headings' => t('HTML block format'), 'font' => t('Font'), 'fontsize' => t('Font size'),
'maximize' => t('Fullscreen'),
'preview' => t('Preview'),
'print' => t('Print'),
'inserttable' => t('Table'),
'help' => t('Help'),
),
'internal' => TRUE,
),
);
return $plugins;
}
+608
View File
@@ -0,0 +1,608 @@
<?php
/**
* @file
* Editor integration functions for TinyMCE.
*/
/**
* Plugin implementation of hook_editor().
*
* @todo wysiwyg_<editor>_alter() to add/inject optional libraries like gzip.
*/
function wysiwyg_tinymce_editor() {
$editor['tinymce'] = array(
'title' => 'TinyMCE',
'vendor url' => 'http://tinymce.moxiecode.com',
'download url' => 'http://tinymce.moxiecode.com/download.php',
'library path' => wysiwyg_get_path('tinymce') . '/jscripts/tiny_mce',
'libraries' => array(
'' => array(
'title' => 'Minified',
'files' => array('tiny_mce.js'),
),
'src' => array(
'title' => 'Source',
'files' => array('tiny_mce_src.js'),
),
),
'version callback' => 'wysiwyg_tinymce_version',
'themes callback' => 'wysiwyg_tinymce_themes',
'settings callback' => 'wysiwyg_tinymce_settings',
'plugin callback' => 'wysiwyg_tinymce_plugins',
'plugin settings callback' => 'wysiwyg_tinymce_plugin_settings',
'proxy plugin' => array(
'drupal' => array(
'load' => TRUE,
'proxy' => TRUE,
),
),
'proxy plugin settings callback' => 'wysiwyg_tinymce_proxy_plugin_settings',
'versions' => array(
'2.1' => array(
'js files' => array('tinymce-2.js'),
'css files' => array('tinymce-2.css'),
'download url' => 'http://sourceforge.net/project/showfiles.php?group_id=103281&package_id=111430&release_id=557383',
),
// @todo Starting from 3.3, tiny_mce.js may support JS aggregation.
'3.1' => array(
'js files' => array('tinymce-3.js'),
'css files' => array('tinymce-3.css'),
'libraries' => array(
'' => array(
'title' => 'Minified',
'files' => array(
'tiny_mce.js' => array('preprocess' => FALSE),
),
),
'jquery' => array(
'title' => 'jQuery',
'files' => array('tiny_mce_jquery.js'),
),
'src' => array(
'title' => 'Source',
'files' => array('tiny_mce_src.js'),
),
),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_tinymce_version($editor) {
$script = $editor['library path'] . '/tiny_mce.js';
if (!file_exists($script)) {
return;
}
$script = fopen($script, 'r');
// Version is contained in the first 200 chars.
$line = fgets($script, 200);
fclose($script);
// 2.x: this.majorVersion="2";this.minorVersion="1.3"
// 3.x: majorVersion:'3',minorVersion:'2.0.1'
if (preg_match('@majorVersion[=:]["\'](\d).+?minorVersion[=:]["\']([\d\.]+)@', $line, $version)) {
return $version[1] . '.' . $version[2];
}
}
/**
* Determine available editor themes or check/reset a given one.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $profile
* A wysiwyg editor profile.
*
* @return
* An array of theme names. The first returned name should be the default
* theme name.
*/
function wysiwyg_tinymce_themes($editor, $profile) {
/*
$themes = array();
$dir = $editor['library path'] . '/themes/';
if (is_dir($dir) && $dh = opendir($dir)) {
while (($file = readdir($dh)) !== FALSE) {
if (!in_array($file, array('.', '..', 'CVS', '.svn')) && is_dir($dir . $file)) {
$themes[$file] = $file;
}
}
closedir($dh);
asort($themes);
}
return $themes;
*/
return array('advanced', 'simple');
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_tinymce_settings($editor, $config, $theme) {
$settings = array(
'button_tile_map' => TRUE, // @todo Add a setting for this.
'document_base_url' => base_path(),
'mode' => 'none',
'plugins' => array(),
'theme' => $theme,
'width' => '100%',
// Strict loading mode must be enabled; otherwise TinyMCE would use
// document.write() in IE and Chrome.
'strict_loading_mode' => TRUE,
// TinyMCE's URL conversion magic breaks Drupal modules that use a special
// syntax for paths. This makes 'relative_urls' obsolete.
'convert_urls' => FALSE,
// The default entity_encoding ('named') converts too many characters in
// languages (like Greek). Since Drupal supports Unicode, we only convert
// HTML control characters and invisible characters. TinyMCE always converts
// XML default characters '&', '<', '>'.
'entities' => '160,nbsp,173,shy,8194,ensp,8195,emsp,8201,thinsp,8204,zwnj,8205,zwj,8206,lrm,8207,rlm',
);
if (isset($config['apply_source_formatting'])) {
$settings['apply_source_formatting'] = $config['apply_source_formatting'];
}
if (isset($config['convert_fonts_to_spans'])) {
$settings['convert_fonts_to_spans'] = $config['convert_fonts_to_spans'];
}
if (isset($config['language'])) {
$settings['language'] = $config['language'];
}
if (isset($config['paste_auto_cleanup_on_paste'])) {
$settings['paste_auto_cleanup_on_paste'] = $config['paste_auto_cleanup_on_paste'];
}
if (isset($config['preformatted'])) {
$settings['preformatted'] = $config['preformatted'];
}
if (isset($config['remove_linebreaks'])) {
$settings['remove_linebreaks'] = $config['remove_linebreaks'];
}
if (isset($config['verify_html'])) {
$settings['verify_html'] = (bool) $config['verify_html'];
}
if (!empty($config['css_classes'])) {
$settings['theme_advanced_styles'] = implode(';', array_filter(explode("\n", str_replace("\r", '', $config['css_classes']))));
}
if (isset($config['css_setting'])) {
if ($config['css_setting'] == 'theme') {
$settings['content_css'] = implode(',', wysiwyg_get_css());
}
else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['content_css'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
}
}
// Find the enabled buttons and the button row they belong on.
// Also map the plugin metadata for each button.
// @todo What follows is a pain; needs a rewrite.
// $settings['buttons'] are stacked into $settings['theme_advanced_buttons1']
// later.
$settings['buttons'] = array();
if (!empty($config['buttons']) && is_array($config['buttons'])) {
// Only array keys in $settings['extensions'] matter; added to
// $settings['plugins'] later.
$settings['extensions'] = array();
// $settings['extended_valid_elements'] are just stacked, unique'd later,
// and transformed into a comma-separated string in
// wysiwyg_add_editor_settings().
// @todo Needs a complete plugin API redesign using arrays for
// tag => attributes definitions and array_merge_recursive().
$settings['extended_valid_elements'] = array();
$plugins = wysiwyg_get_plugins($editor['name']);
foreach ($config['buttons'] as $plugin => $buttons) {
foreach ($buttons as $button => $enabled) {
// Iterate separately over buttons and extensions properties.
foreach (array('buttons', 'extensions') as $type) {
// Skip unavailable plugins.
if (!isset($plugins[$plugin][$type][$button])) {
continue;
}
// Add buttons.
if ($type == 'buttons') {
$settings['buttons'][] = $button;
}
// Add external Drupal plugins to the list of extensions.
if ($type == 'buttons' && !empty($plugins[$plugin]['proxy'])) {
$settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $button)] = 1;
}
// Add external plugins to the list of extensions.
else if ($type == 'buttons' && empty($plugins[$plugin]['internal'])) {
$settings['extensions'][_wysiwyg_tinymce_plugin_name('add', $plugin)] = 1;
}
// Add internal buttons that also need to be loaded as extension.
else if ($type == 'buttons' && !empty($plugins[$plugin]['load'])) {
$settings['extensions'][$plugin] = 1;
}
// Add plain extensions.
else if ($type == 'extensions' && !empty($plugins[$plugin]['load'])) {
$settings['extensions'][$plugin] = 1;
}
// Allow plugins to add valid HTML elements.
if (!empty($plugins[$plugin]['extended_valid_elements'])) {
$settings['extended_valid_elements'] = array_merge($settings['extended_valid_elements'], $plugins[$plugin]['extended_valid_elements']);
}
// Allow plugins to add or override global configuration settings.
if (!empty($plugins[$plugin]['options'])) {
$settings = array_merge($settings, $plugins[$plugin]['options']);
}
}
}
}
// Clean-up.
$settings['extended_valid_elements'] = array_unique($settings['extended_valid_elements']);
if ($settings['extensions']) {
$settings['plugins'] = array_keys($settings['extensions']);
}
unset($settings['extensions']);
}
// Add theme-specific settings.
switch ($theme) {
case 'advanced':
$settings += array(
'theme_advanced_resize_horizontal' => FALSE,
'theme_advanced_resizing_use_cookie' => FALSE,
'theme_advanced_path_location' => isset($config['path_loc']) ? $config['path_loc'] : 'bottom',
'theme_advanced_resizing' => isset($config['resizing']) ? $config['resizing'] : 1,
'theme_advanced_toolbar_location' => isset($config['toolbar_loc']) ? $config['toolbar_loc'] : 'top',
'theme_advanced_toolbar_align' => isset($config['toolbar_align']) ? $config['toolbar_align'] : 'left',
);
if (isset($config['block_formats'])) {
$settings['theme_advanced_blockformats'] = $config['block_formats'];
}
if (isset($settings['buttons'])) {
// These rows explicitly need to be set to be empty, otherwise TinyMCE
// loads its default buttons of the advanced theme for each row.
$settings += array(
'theme_advanced_buttons1' => array(),
'theme_advanced_buttons2' => array(),
'theme_advanced_buttons3' => array(),
);
// @todo Allow to sort/arrange editor buttons.
for ($i = 0; $i < count($settings['buttons']); $i++) {
$settings['theme_advanced_buttons1'][] = $settings['buttons'][$i];
}
}
break;
}
unset($settings['buttons']);
// Convert the config values into the form expected by TinyMCE.
$csv_settings = array('plugins', 'extended_valid_elements', 'theme_advanced_buttons1', 'theme_advanced_buttons2', 'theme_advanced_buttons3');
foreach ($csv_settings as $key) {
if (isset($settings[$key]) && is_array($settings[$key])) {
$settings[$key] = implode(',', $settings[$key]);
}
}
return $settings;
}
/**
* Build a JS settings array of native external plugins that need to be loaded separately.
*
* TinyMCE requires that external plugins (i.e. not residing in the editor's
* directory) are loaded (once) upon initializing the editor.
*/
function wysiwyg_tinymce_plugin_settings($editor, $profile, $plugins) {
$settings = array();
foreach ($plugins as $name => $plugin) {
if (!empty($plugin['load'])) {
// Add path for native external plugins; internal ones are loaded
// automatically.
if (empty($plugin['internal']) && isset($plugin['filename'])) {
$settings[$name] = base_path() . $plugin['path'] . '/' . $plugin['filename'];
}
}
}
return $settings;
}
/**
* Build a JS settings array for Drupal plugins loaded via the proxy plugin.
*/
function wysiwyg_tinymce_proxy_plugin_settings($editor, $profile, $plugins) {
$settings = array();
foreach ($plugins as $name => $plugin) {
// Populate required plugin settings.
$settings[$name] = $plugin['dialog settings'] + array(
'title' => $plugin['title'],
'icon' => base_path() . $plugin['icon path'] . '/' . $plugin['icon file'],
'iconTitle' => $plugin['icon title'],
);
if (isset($plugin['css file'])) {
$settings[$name]['css'] = base_path() . $plugin['css path'] . '/' . $plugin['css file'];
}
}
return $settings;
}
/**
* Add or remove leading hiven to/of external plugin names.
*
* TinyMCE requires that external plugins, which should not be loaded from
* its own plugin repository are prefixed with a hiven in the name.
*
* @param string $op
* Operation to perform, 'add' or 'remove' (hiven).
* @param string $name
* A plugin name.
*/
function _wysiwyg_tinymce_plugin_name($op, $name) {
if ($op == 'add') {
if (strpos($name, '-') !== 0) {
return '-' . $name;
}
return $name;
}
else if ($op == 'remove') {
if (strpos($name, '-') === 0) {
return substr($name, 1);
}
return $name;
}
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_tinymce_plugins($editor) {
$plugins = array(
'default' => array(
'path' => $editor['library path'] . '/themes/advanced',
'buttons' => array(
'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
'strikethrough' => t('Strike-through'),
'justifyleft' => t('Align left'), 'justifycenter' => t('Align center'), 'justifyright' => t('Align right'), 'justifyfull' => t('Justify'),
'bullist' => t('Bullet list'), 'numlist' => t('Numbered list'),
'outdent' => t('Outdent'), 'indent' => t('Indent'),
'undo' => t('Undo'), 'redo' => t('Redo'),
'link' => t('Link'), 'unlink' => t('Unlink'), 'anchor' => t('Anchor'),
'image' => t('Image'),
'cleanup' => t('Clean-up'),
'forecolor' => t('Forecolor'), 'backcolor' => t('Backcolor'),
'sup' => t('Superscript'), 'sub' => t('Subscript'),
'blockquote' => t('Blockquote'), 'code' => t('Source code'),
'hr' => t('Horizontal rule'),
'cut' => t('Cut'), 'copy' => t('Copy'), 'paste' => t('Paste'),
'visualaid' => t('Visual aid'),
'removeformat' => t('Remove format'),
'charmap' => t('Character map'),
'help' => t('Help'),
),
'internal' => TRUE,
),
'advhr' => array(
'path' => $editor['library path'] . '/plugins/advhr',
'buttons' => array('advhr' => t('Advanced horizontal rule')),
'extended_valid_elements' => array('hr[class|width|size|noshade]'),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advhr',
'internal' => TRUE,
'load' => TRUE,
),
'advimage' => array(
'path' => $editor['library path'] . '/plugins/advimage',
'extensions' => array('advimage' => t('Advanced image')),
'extended_valid_elements' => array('img[src|alt|title|align|width|height|usemap|hspace|vspace|border|style|class|onmouseover|onmouseout|id|name]'),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advimage',
'internal' => TRUE,
'load' => TRUE,
),
'advlink' => array(
'path' => $editor['library path'] . '/plugins/advlink',
'extensions' => array('advlink' => t('Advanced link')),
'extended_valid_elements' => array('a[name|href|target|title|class|onfocus|onblur|onclick|ondlbclick|onmousedown|onmouseup|onmouseover|onmouseout|onkeypress|onkeydown|onkeyup|id|style|rel]'),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlink',
'internal' => TRUE,
'load' => TRUE,
),
'autosave' => array(
'path' => $editor['library path'] . '/plugins/autosave',
'extensions' => array('autosave' => t('Auto save')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autosave',
'internal' => TRUE,
'load' => TRUE,
),
'contextmenu' => array(
'path' => $editor['library path'] . '/plugins/contextmenu',
'extensions' => array('contextmenu' => t('Context menu')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/contextmenu',
'internal' => TRUE,
'load' => TRUE,
),
'directionality' => array(
'path' => $editor['library path'] . '/plugins/directionality',
'buttons' => array('ltr' => t('Left-to-right'), 'rtl' => t('Right-to-left')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/directionality',
'internal' => TRUE,
'load' => TRUE,
),
'emotions' => array(
'path' => $editor['library path'] . '/plugins/emotions',
'buttons' => array('emotions' => t('Emotions')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/emotions',
'internal' => TRUE,
'load' => TRUE,
),
'font' => array(
'path' => $editor['library path'] . '/plugins/font',
'buttons' => array('formatselect' => t('HTML block format'), 'fontselect' => t('Font'), 'fontsizeselect' => t('Font size'), 'styleselect' => t('Font style')),
'extended_valid_elements' => array('font[face|size|color|style],span[class|align|style]'),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/font',
'internal' => TRUE,
),
'fullscreen' => array(
'path' => $editor['library path'] . '/plugins/fullscreen',
'buttons' => array('fullscreen' => t('Fullscreen')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/fullscreen',
'internal' => TRUE,
'load' => TRUE,
),
'inlinepopups' => array(
'path' => $editor['library path'] . '/plugins/inlinepopups',
'extensions' => array('inlinepopups' => t('Inline popups')),
'options' => array(
'dialog_type' => array('modal'),
),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/inlinepopups',
'internal' => TRUE,
'load' => TRUE,
),
'insertdatetime' => array(
'path' => $editor['library path'] . '/plugins/insertdatetime',
'buttons' => array('insertdate' => t('Insert date'), 'inserttime' => t('Insert time')),
'options' => array(
'plugin_insertdate_dateFormat' => '%Y-%m-%d',
'plugin_insertdate_timeFormat' => '%H:%M:%S',
),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/insertdatetime',
'internal' => TRUE,
'load' => TRUE,
),
'layer' => array(
'path' => $editor['library path'] . '/plugins/layer',
'buttons' => array('insertlayer' => t('Insert layer'), 'moveforward' => t('Move forward'), 'movebackward' => t('Move backward'), 'absolute' => t('Absolute')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/layer',
'internal' => TRUE,
'load' => TRUE,
),
'paste' => array(
'path' => $editor['library path'] . '/plugins/paste',
'buttons' => array('pastetext' => t('Paste text'), 'pasteword' => t('Paste from Word'), 'selectall' => t('Select all')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/paste',
'internal' => TRUE,
'load' => TRUE,
),
'preview' => array(
'path' => $editor['library path'] . '/plugins/preview',
'buttons' => array('preview' => t('Preview')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/preview',
'internal' => TRUE,
'load' => TRUE,
),
'print' => array(
'path' => $editor['library path'] . '/plugins/print',
'buttons' => array('print' => t('Print')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/print',
'internal' => TRUE,
'load' => TRUE,
),
'searchreplace' => array(
'path' => $editor['library path'] . '/plugins/searchreplace',
'buttons' => array('search' => t('Search'), 'replace' => t('Replace')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/searchreplace',
'internal' => TRUE,
'load' => TRUE,
),
'style' => array(
'path' => $editor['library path'] . '/plugins/style',
'buttons' => array('styleprops' => t('Style properties')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/style',
'internal' => TRUE,
'load' => TRUE,
),
'table' => array(
'path' => $editor['library path'] . '/plugins/table',
'buttons' => array('tablecontrols' => t('Table')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/table',
'internal' => TRUE,
'load' => TRUE,
),
);
if (version_compare($editor['installed version'], '3', '<')) {
$plugins['flash'] = array(
'path' => $editor['library path'] . '/plugins/flash',
'buttons' => array('flash' => t('Flash')),
'extended_valid_elements' => array('img[class|src|alt|title|hspace|vspace|width|height|align|onmouseover|onmouseout|name|obj|param|embed]'),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/flash',
'internal' => TRUE,
'load' => TRUE,
);
}
if (version_compare($editor['installed version'], '2.0.6', '>')) {
$plugins['media'] = array(
'path' => $editor['library path'] . '/plugins/media',
'buttons' => array('media' => t('Media')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/media',
'internal' => TRUE,
'load' => TRUE,
);
$plugins['xhtmlxtras'] = array(
'path' => $editor['library path'] . '/plugins/xhtmlxtras',
'buttons' => array('cite' => t('Citation'), 'del' => t('Deleted'), 'abbr' => t('Abbreviation'), 'acronym' => t('Acronym'), 'ins' => t('Inserted'), 'attribs' => t('HTML attributes')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/xhtmlxtras',
'internal' => TRUE,
'load' => TRUE,
);
}
if (version_compare($editor['installed version'], '3', '>')) {
$plugins['bbcode'] = array(
'path' => $editor['library path'] . '/plugins/bbcode',
'extensions' => array('bbcode' => t('BBCode')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/bbcode',
'internal' => TRUE,
'load' => TRUE,
);
if (version_compare($editor['installed version'], '3.3', '<')) {
$plugins['safari'] = array(
'path' => $editor['library path'] . '/plugins/safari',
'extensions' => array('safari' => t('Safari compatibility')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/safari',
'internal' => TRUE,
'load' => TRUE,
);
}
}
if (version_compare($editor['installed version'], '3.2.5', '>=')) {
$plugins['autoresize'] = array(
'path' => $editor['library path'] . '/plugins/autoresize',
'extensions' => array('autoresize' => t('Auto resize')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/autoresize',
'internal' => TRUE,
'load' => TRUE,
);
}
if (version_compare($editor['installed version'], '3.3', '>=')) {
$plugins['advlist'] = array(
'path' => $editor['library path'] . '/plugins/advlist',
'extensions' => array('advlist' => t('Advanced list')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/advlist',
'internal' => TRUE,
'load' => TRUE,
);
}
if (version_compare($editor['installed version'], '3.2.6', '>=')) {
$plugins['wordcount'] = array(
'path' => $editor['library path'] . '/plugins/wordcount',
'extensions' => array('wordcount' => t('Word count')),
'url' => 'http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/wordcount',
'internal' => TRUE,
'load' => TRUE,
);
}
return $plugins;
}
+147
View File
@@ -0,0 +1,147 @@
<?php
/**
* @file
* Editor integration functions for Whizzywig.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_whizzywig_editor() {
$editor['whizzywig'] = array(
'title' => 'Whizzywig',
'vendor url' => 'http://www.unverse.net',
'download url' => 'http://www.unverse.net/whizzywig-download.html',
'libraries' => array(
'' => array(
'title' => 'Default',
'files' => array('whizzywig.js', 'xhtml.js'),
),
),
'version callback' => 'wysiwyg_whizzywig_version',
'settings callback' => 'wysiwyg_whizzywig_settings',
'plugin callback' => 'wysiwyg_whizzywig_plugins',
'versions' => array(
'55' => array(
'js files' => array('whizzywig.js'),
),
'56' => array(
'js files' => array('whizzywig-56.js'),
),
'60' => array(
'js files' => array('whizzywig-60.js'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_whizzywig_version($editor) {
$script = $editor['library path'] . '/whizzywig.js';
if (!file_exists($script)) {
return;
}
$script = fopen($script, 'r');
$line = fgets($script, 43);
// 55: Whizzywig v55i
// 60: Whizzywig 60
if (preg_match('@Whizzywig v?([0-9]+)@', $line, $version)) {
fclose($script);
return $version[1];
}
fclose($script);
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_whizzywig_settings($editor, $config, $theme) {
$settings = array();
// Add path to button images, if available.
if (is_dir($editor['library path'] . '/btn')) {
$settings['buttonPath'] = base_path() . $editor['library path'] . '/btn/';
}
if (file_exists($editor['library path'] . '/WhizzywigToolbar.png')) {
$settings['toolbarImagePath'] = base_path() . $editor['library path'] . '/WhizzywigToolbar.png';
}
// Filename changed in version 60.
elseif (file_exists($editor['library path'] . '/icons.png')) {
$settings['toolbarImagePath'] = base_path() . $editor['library path'] . '/icons.png';
}
// Add configured buttons or all available.
$settings['buttons'] = array();
if (!empty($config['buttons'])) {
$buttons = array();
foreach ($config['buttons'] as $plugin) {
$buttons = array_merge($buttons, $plugin);
}
$settings['buttons'] = implode(' ', array_keys($buttons));
}
// Add editor content stylesheet.
if (isset($config['css_setting'])) {
if ($config['css_setting'] == 'theme') {
$css = path_to_theme() . '/style.css';
if (file_exists($css)) {
$settings['externalCSS'] = base_path() . $css;
}
}
else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['externalCSS'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
}
}
return $settings;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_whizzywig_plugins($editor) {
return array(
'default' => array(
'buttons' => array(
'formatblock' => t('HTML block format'), 'fontname' => t('Font'), 'fontsize' => t('Font size'),
'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
'left' => t('Align left'), 'center' => t('Align center'), 'right' => t('Align right'),
'bullet' => t('Bullet list'), 'number' => t('Numbered list'),
'outdent' => t('Outdent'), 'indent' => t('Indent'),
'undo' => t('Undo'), 'redo' => t('Redo'),
'image' => t('Image'),
'color' => t('Forecolor'), 'hilite' => t('Backcolor'),
'rule' => t('Horizontal rule'),
'link' => t('Link'),
'image' => t('Image'),
'table' => t('Table'),
'clean' => t('Clean-up'),
'html' => t('Source code'),
'spellcheck' => t('Spell check'),
),
'internal' => TRUE,
),
);
}
+233
View File
@@ -0,0 +1,233 @@
<?php
/**
* @file
* Editor integration functions for WYMeditor.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_wymeditor_editor() {
$editor['wymeditor'] = array(
'title' => 'WYMeditor',
'vendor url' => 'http://www.wymeditor.org/',
'download url' => 'http://www.wymeditor.org/download/',
'library path' => wysiwyg_get_path('wymeditor') . '/wymeditor',
'libraries' => array(
'min' => array(
'title' => 'Minified',
'files' => array('jquery.wymeditor.min.js'),
),
'pack' => array(
'title' => 'Packed',
'files' => array('jquery.wymeditor.pack.js'),
),
'src' => array(
'title' => 'Source',
'files' => array('jquery.wymeditor.js'),
),
),
'version callback' => 'wysiwyg_wymeditor_version',
'themes callback' => 'wysiwyg_wymeditor_themes',
'settings callback' => 'wysiwyg_wymeditor_settings',
'plugin callback' => 'wysiwyg_wymeditor_plugins',
'versions' => array(
'0.5-rc1' => array(
'js files' => array('wymeditor.js'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_wymeditor_version($editor) {
$script = $editor['library path'] . '/jquery.wymeditor.js';
if (!file_exists($script)) {
return;
}
$script = fopen($script, 'r');
fgets($script);
$line = fgets($script);
if (preg_match('@version\s+([0-9a-z\.-]+)@', $line, $version)) {
fclose($script);
return $version[1];
}
fclose($script);
}
/**
* Determine available editor themes or check/reset a given one.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $profile
* A wysiwyg editor profile.
*
* @return
* An array of theme names. The first returned name should be the default
* theme name.
*/
function wysiwyg_wymeditor_themes($editor, $profile) {
return array('compact', 'default', 'minimal', 'silver', 'twopanels');
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_wymeditor_settings($editor, $config, $theme) {
// @todo Setup $library in wysiwyg_load_editor() already.
$library = (isset($editor['library']) ? $editor['library'] : key($editor['libraries']));
$settings = array(
'basePath' => base_path() . $editor['library path'] . '/',
'wymPath' => $editor['libraries'][$library]['files'][0],
// @todo Does not work in Drupal; jQuery can live anywhere.
'jQueryPath' => base_path() . 'misc/jquery.js',
'updateSelector' => '.form-submit',
'skin' => $theme,
);
if (isset($config['language'])) {
$settings['lang'] = $config['language'];
}
// Add configured buttons.
$settings['toolsItems'] = array();
if (!empty($config['buttons'])) {
$buttoninfo = _wysiwyg_wymeditor_button_info();
$plugins = wysiwyg_get_plugins($editor['name']);
foreach ($config['buttons'] as $plugin => $buttons) {
foreach ($buttons as $button => $enabled) {
// Iterate separately over buttons and extensions properties.
foreach (array('buttons', 'extensions') as $type) {
// Skip unavailable plugins.
if (!isset($plugins[$plugin][$type][$button])) {
continue;
}
// Add buttons.
if ($type == 'buttons') {
// Merge meta-data for internal default buttons.
if (isset($buttoninfo[$button])) {
$buttoninfo[$button] += array('name' => $button);
$settings['toolsItems'][] = $buttoninfo[$button];
}
// For custom buttons, try to provide a valid button definition.
else {
$settings['toolsItems'][] = array(
'name' => $button,
'title' => $plugins[$plugin][$type][$button],
'css' => 'wym_tools_' . $button,
);
}
}
}
}
}
}
if (!empty($config['block_formats'])) {
$containers = array(
'p' => 'Paragraph',
'h1' => 'Heading_1',
'h2' => 'Heading_2',
'h3' => 'Heading_3',
'h4' => 'Heading_4',
'h5' => 'Heading_5',
'h6' => 'Heading_6',
'pre' => 'Preformatted',
'blockquote' => 'Blockquote',
'th' => 'Table_Header',
);
foreach (explode(',', $config['block_formats']) as $tag) {
if (isset($containers[$tag])) {
$settings['containersItems'][] = array(
'name' => strtoupper($tag),
'title' => $containers[$tag],
'css' => 'wym_containers_' . $tag,
);
}
}
}
if (isset($config['css_setting'])) {
if ($config['css_setting'] == 'theme') {
// WYMeditor only supports one CSS file currently.
$settings['stylesheet'] = reset(wysiwyg_get_css());
}
else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['stylesheet'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
}
}
return $settings;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_wymeditor_plugins($editor) {
$plugins = array(
'default' => array(
'buttons' => array(
'Bold' => t('Bold'), 'Italic' => t('Italic'),
'InsertOrderedList' => t('Bullet list'), 'InsertUnorderedList' => t('Numbered list'),
'Outdent' => t('Outdent'), 'Indent' => t('Indent'),
'Undo' => t('Undo'), 'Redo' => t('Redo'),
'CreateLink' => t('Link'), 'Unlink' => t('Unlink'),
'InsertImage' => t('Image'),
'Superscript' => t('Superscript'), 'Subscript' => t('Subscript'),
'ToggleHtml' => t('Source code'),
'Paste' => t('Paste'),
'InsertTable' => t('Table'),
'Preview' => t('Preview'),
),
'internal' => TRUE,
),
);
return $plugins;
}
/**
* Helper function to provide additional meta-data for internal default buttons.
*/
function _wysiwyg_wymeditor_button_info() {
return array(
'Bold' => array('title'=> 'Strong', 'css'=> 'wym_tools_strong'),
'Italic' => array('title'=> 'Emphasis', 'css'=> 'wym_tools_emphasis'),
'Superscript' => array('title'=> 'Superscript', 'css'=> 'wym_tools_superscript'),
'Subscript' => array('title'=> 'Subscript', 'css'=> 'wym_tools_subscript'),
'InsertOrderedList' => array('title'=> 'Ordered_List', 'css'=> 'wym_tools_ordered_list'),
'InsertUnorderedList' => array('title'=> 'Unordered_List', 'css'=> 'wym_tools_unordered_list'),
'Indent' => array('title'=> 'Indent', 'css'=> 'wym_tools_indent'),
'Outdent' => array('title'=> 'Outdent', 'css'=> 'wym_tools_outdent'),
'Undo' => array('title'=> 'Undo', 'css'=> 'wym_tools_undo'),
'Redo' => array('title'=> 'Redo', 'css'=> 'wym_tools_redo'),
'CreateLink' => array('title'=> 'Link', 'css'=> 'wym_tools_link'),
'Unlink' => array('title'=> 'Unlink', 'css'=> 'wym_tools_unlink'),
'InsertImage' => array('title'=> 'Image', 'css'=> 'wym_tools_image'),
'InsertTable' => array('title'=> 'Table', 'css'=> 'wym_tools_table'),
'Paste' => array('title'=> 'Paste_From_Word', 'css'=> 'wym_tools_paste'),
'ToggleHtml' => array('title'=> 'HTML', 'css'=> 'wym_tools_html'),
'Preview' => array('title'=> 'Preview', 'css'=> 'wym_tools_preview'),
);
}
+297
View File
@@ -0,0 +1,297 @@
<?php
/**
* @file
* Editor integration functions for YUI editor.
*/
/**
* Plugin implementation of hook_editor().
*/
function wysiwyg_yui_editor() {
$editor['yui'] = array(
'title' => 'YUI editor',
'vendor url' => 'http://developer.yahoo.com/yui/editor/',
'download url' => 'http://developer.yahoo.com/yui/download/',
'library path' => wysiwyg_get_path('yui') . '/build',
'libraries' => array(
'min' => array(
'title' => 'Minified',
'files' => array(
'yahoo-dom-event/yahoo-dom-event.js',
'animation/animation-min.js',
'element/element-min.js',
'container/container-min.js',
'menu/menu-min.js',
'button/button-min.js',
'editor/editor-min.js',
),
),
'src' => array(
'title' => 'Source',
'files' => array(
'yahoo-dom-event/yahoo-dom-event.js',
'animation/animation.js',
'element/element.js',
'container/container.js',
'menu/menu.js',
'button/button.js',
'editor/editor.js',
),
),
),
'version callback' => 'wysiwyg_yui_version',
'themes callback' => 'wysiwyg_yui_themes',
'load callback' => 'wysiwyg_yui_load',
'settings callback' => 'wysiwyg_yui_settings',
'plugin callback' => 'wysiwyg_yui_plugins',
'versions' => array(
'2.7.0' => array(
'js files' => array('yui.js'),
),
),
);
return $editor;
}
/**
* Detect editor version.
*
* @param $editor
* An array containing editor properties as returned from hook_editor().
*
* @return
* The installed editor version.
*/
function wysiwyg_yui_version($editor) {
$library = $editor['library path'] . '/editor/editor.js';
if (!file_exists($library)) {
return;
}
$library = fopen($library, 'r');
$max_lines = 10;
while ($max_lines && $line = fgets($library, 60)) {
if (preg_match('@version:\s([0-9\.]+)@', $line, $version)) {
fclose($library);
return $version[1];
}
$max_lines--;
}
fclose($library);
}
/**
* Determine available editor themes or check/reset a given one.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $profile
* A wysiwyg editor profile.
*
* @return
* An array of theme names. The first returned name should be the default
* theme name.
*/
function wysiwyg_yui_themes($editor, $profile) {
return array('sam');
}
/**
* Perform additional actions upon loading this editor.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $library
* The internal library name (array key) to use.
*/
function wysiwyg_yui_load($editor, $library) {
drupal_add_css($editor['library path'] . '/menu/assets/skins/sam/menu.css');
drupal_add_css($editor['library path'] . '/button/assets/skins/sam/button.css');
drupal_add_css($editor['library path'] . '/fonts/fonts-min.css');
drupal_add_css($editor['library path'] . '/container/assets/skins/sam/container.css');
drupal_add_css($editor['library path'] . '/editor/assets/skins/sam/editor.css');
}
/**
* Return runtime editor settings for a given wysiwyg profile.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $config
* An array containing wysiwyg editor profile settings.
* @param $theme
* The name of a theme/GUI/skin to use.
*
* @return
* A settings array to be populated in
* Drupal.settings.wysiwyg.configs.{editor}
*/
function wysiwyg_yui_settings($editor, $config, $theme) {
$settings = array(
'theme' => $theme,
'animate' => TRUE,
'handleSubmit' => TRUE,
'markup' => 'xhtml',
'ptags' => TRUE,
);
if (isset($config['path_loc']) && $config['path_loc'] != 'none') {
$settings['dompath'] = $config['path_loc'];
}
// Enable auto-height feature when editor should be resizable.
if (!empty($config['resizing'])) {
$settings['autoHeight'] = TRUE;
}
$settings += array(
'toolbar' => array(
'collapse' => FALSE,
'draggable' => TRUE,
'buttonType' => 'advanced',
'buttons' => array(),
),
);
if (!empty($config['buttons'])) {
$buttons = array();
foreach ($config['buttons'] as $plugin => $enabled_buttons) {
foreach ($enabled_buttons as $button => $enabled) {
$extra = array();
if ($button == 'heading') {
$extra = array('menu' => array(
array('text' => 'Normal', 'value' => 'none', 'checked' => TRUE),
));
if (!empty($config['block_formats'])) {
$headings = array(
'p' => array('text' => 'Paragraph', 'value' => 'p'),
'h1' => array('text' => 'Heading 1', 'value' => 'h1'),
'h2' => array('text' => 'Heading 2', 'value' => 'h2'),
'h3' => array('text' => 'Heading 3', 'value' => 'h3'),
'h4' => array('text' => 'Heading 4', 'value' => 'h4'),
'h5' => array('text' => 'Heading 5', 'value' => 'h5'),
'h6' => array('text' => 'Heading 6', 'value' => 'h6'),
);
foreach (explode(',', $config['block_formats']) as $tag) {
if (isset($headings[$tag])) {
$extra['menu'][] = $headings[$tag];
}
}
}
}
else if ($button == 'fontname') {
$extra = array('menu' => array(
array('text' => 'Arial', 'checked' => TRUE),
array('text' => 'Arial Black'),
array('text' => 'Comic Sans MS'),
array('text' => 'Courier New'),
array('text' => 'Lucida Console'),
array('text' => 'Tahoma'),
array('text' => 'Times New Roman'),
array('text' => 'Trebuchet MS'),
array('text' => 'Verdana'),
));
}
$buttons[] = wysiwyg_yui_button_setting($editor, $plugin, $button, $extra);
}
}
// Group buttons in a dummy group.
$buttons = array('group' => 'default', 'label' => '', 'buttons' => $buttons);
$settings['toolbar']['buttons'] = array($buttons);
}
if (isset($config['css_setting'])) {
if ($config['css_setting'] == 'theme') {
$settings['extracss'] = wysiwyg_get_css();
}
else if ($config['css_setting'] == 'self' && isset($config['css_path'])) {
$settings['extracss'] = strtr($config['css_path'], array('%b' => base_path(), '%t' => path_to_theme()));
$settings['extracss'] = explode(',', $settings['extracss']);
}
// YUI only supports inline CSS, so we need to use @import directives.
// Syntax: '@import "/base/path/to/theme/style.css"; '
if (!empty($settings['extracss'])) {
$settings['extracss'] = '@import "' . implode('"; @import "', $settings['extracss']) . '";';
}
}
return $settings;
}
/**
* Create the JavaScript structure for a YUI button.
*
* @param $editor
* A processed hook_editor() array of editor properties.
* @param $plugin
* The internal name of a plugin.
* @param $button
* The internal name of a button, defined by $plugin.
* @param $extra
* (optional) An array containing arbitrary other elements to add to the
* resulting button.
*/
function wysiwyg_yui_button_setting($editor, $plugin, $button, $extra = array()) {
static $plugins;
if (!isset($plugins)) {
// @todo Invoke all enabled plugins, not just internals.
$plugins = wysiwyg_yui_plugins($editor);
}
// Return a simple separator.
if ($button === 'separator') {
return array('type' => 'separator');
}
// Setup defaults.
$type = 'push';
$label = $plugins[$plugin]['buttons'][$button];
// Special handling for certain buttons.
if (in_array($button, array('heading', 'fontname'))) {
$type = 'select';
$label = $extra['menu'][0]['text'];
}
elseif (in_array($button, array('fontsize'))) {
$type = 'spin';
}
elseif (in_array($button, array('forecolor', 'backcolor'))) {
$type = 'color';
}
$button = array(
'type' => $type,
'label' => $label,
'value' => $button,
);
// Add arbitrary other elements, if defined.
if (!empty($extra)) {
$button = array_merge($button, $extra);
}
return $button;
}
/**
* Return internal plugins for this editor; semi-implementation of hook_wysiwyg_plugin().
*/
function wysiwyg_yui_plugins($editor) {
return array(
'default' => array(
'buttons' => array(
'bold' => t('Bold'), 'italic' => t('Italic'), 'underline' => t('Underline'),
'strikethrough' => t('Strike-through'),
'justifyleft' => t('Align left'), 'justifycenter' => t('Align center'), 'justifyright' => t('Align right'), 'justifyfull' => t('Justify'),
'insertunorderedlist' => t('Bullet list'), 'insertorderedlist' => t('Numbered list'),
'outdent' => t('Outdent'), 'indent' => t('Indent'),
'undo' => t('Undo'), 'redo' => t('Redo'),
'createlink' => t('Link'),
'insertimage' => t('Image'),
'forecolor' => t('Font Color'), 'backcolor' => t('Background Color'),
'superscript' => t('Sup'), 'subscript' => t('Sub'),
'hiddenelements' => t('Show/hide hidden elements'),
'removeformat' => t('Remove format'),
'heading' => t('HTML block format'), 'fontname' => t('Font'), 'fontsize' => t('Font size'),
),
'internal' => TRUE,
),
);
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

-388
View File
@@ -1,388 +0,0 @@
<?php
/**
* @file
* This is the actions engine for executing stored actions.
*/
/**
* @defgroup actions Actions
* @{
* Functions that perform an action on a certain system object.
*
* Action functions are declared by modules by implementing hook_action_info().
* Modules can cause action functions to run by calling actions_do(), and
* trigger.module provides a user interface that lets administrators define
* events that cause action functions to run.
*
* Each action function takes two to four arguments:
* - $entity: The object that the action acts on, such as a node, comment, or
* user.
* - $context: Array of additional information about what triggered the action.
* - $a1, $a2: Optional additional information, which can be passed into
* actions_do() and will be passed along to the action function.
*
* @}
*/
/**
* Performs a given list of actions by executing their callback functions.
*
* Given the IDs of actions to perform, this function finds out what the
* callback functions for the actions are by querying the database. Then
* it calls each callback using the function call $function($object, $context,
* $a1, $a2), passing the input arguments of this function (see below) to the
* action function.
*
* @param $action_ids
* The IDs of the actions to perform. Can be a single action ID or an array
* of IDs. IDs of configurable actions must be given as numeric action IDs;
* IDs of non-configurable actions may be given as action function names.
* @param $object
* The object that the action will act on: a node, user, or comment object.
* @param $context
* Associative array containing extra information about what triggered
* the action call, with $context['hook'] giving the name of the hook
* that resulted in this call to actions_do().
* @param $a1
* Passed along to the callback.
* @param $a2
* Passed along to the callback.
*
* @return
* An associative array containing the results of the functions that
* perform the actions, keyed on action ID.
*
* @ingroup actions
*/
function actions_do($action_ids, $object = NULL, $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();
$actions_result = array();
if (is_array($action_ids)) {
$conditions = array();
foreach ($action_ids as $action_id) {
if (is_numeric($action_id)) {
$conditions[] = $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 (!empty($conditions)) {
$query = db_select('actions');
$query->addField('actions', 'aid');
$query->addField('actions', 'type');
$query->addField('actions', 'callback');
$query->addField('actions', 'parameters');
$query->condition('aid', $conditions, 'IN');
$result = $query->execute();
foreach ($result as $action) {
$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) {
// Configurable actions need parameters.
if (is_numeric($action_id)) {
$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 {
$actions_result[$action_id] = $action_id($object, $context, $a1, $a2);
}
}
}
// Optimized execution of a single action.
else {
// If it's a configurable action, retrieve stored parameters.
if (is_numeric($action_ids)) {
$action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject();
$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 {
if (function_exists($action_ids)) {
$actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2);
}
else {
// Set to avoid undefined index error messages later.
$actions_result[$action_ids] = FALSE;
}
}
}
$stack--;
return $actions_result;
}
/**
* Discovers all available actions by invoking hook_action_info().
*
* This function contrasts with actions_get_all_actions(); see the
* documentation of actions_get_all_actions() for an explanation.
*
* @param $reset
* Reset the action info static cache.
*
* @return
* An associative array keyed on action function name, with the same format
* as the return value of hook_action_info(), containing all
* modules' hook_action_info() return values as modified by any
* hook_action_info_alter() implementations.
*
* @see hook_action_info()
*/
function actions_list($reset = FALSE) {
$actions = &drupal_static(__FUNCTION__);
if (!isset($actions) || $reset) {
$actions = module_invoke_all('action_info');
drupal_alter('action_info', $actions);
}
// See module_implements() for an explanation of this cast.
return (array) $actions;
}
/**
* Retrieves all action instances from the database.
*
* This function differs from the actions_list() function, 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/config/system/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 numeric action ID. Each value is an associative
* array with keys 'callback', 'label', 'type' and 'configurable'.
*/
function actions_get_all_actions() {
$actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC);
foreach ($actions as &$action) {
$action['configurable'] = (bool) $action['parameters'];
unset($action['parameters']);
unset($action['aid']);
}
return $actions;
}
/**
* Creates an associative array keyed by hashes of function names or IDs.
*
* 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 or action IDs as keys
* and associative arrays with keys 'label', 'type', etc. as values.
* This is usually the output of actions_list() or actions_get_all_actions().
*
* @return
* An associative array whose keys are hashes of the input array keys, and
* whose corresponding values are associative arrays with components
* 'callback', 'label', 'type', and 'configurable' from the input array.
*/
function actions_actions_map($actions) {
$actions_map = array();
foreach ($actions as $callback => $array) {
$key = drupal_hash_base64($callback);
$actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback;
$actions_map[$key]['label'] = $array['label'];
$actions_map[$key]['type'] = $array['type'];
$actions_map[$key]['configurable'] = $array['configurable'];
}
return $actions_map;
}
/**
* Returns an action array key (function or ID), given its hash.
*
* Faster than actions_actions_map() when you only need the function name or ID.
*
* @param $hash
* Hash of a function name or action ID array key. The array key
* is a key into the return value of actions_list() (array key is the action
* function name) or actions_get_all_actions() (array key is the action ID).
*
* @return
* The corresponding array key, or FALSE if no match is found.
*/
function actions_function_lookup($hash) {
// Check for a function name match.
$actions_list = actions_list();
foreach ($actions_list as $function => $array) {
if (drupal_hash_base64($function) == $hash) {
return $function;
}
}
$aid = FALSE;
// Must be a configurable action; check database.
$result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC);
foreach ($result as $row) {
if (drupal_hash_base64($row['aid']) == $hash) {
$aid = $row['aid'];
break;
}
}
return $aid;
}
/**
* Synchronizes actions that are provided by modules in hook_action_info().
*
* Actions provided by modules in hook_action_info() implementations are
* synchronized with actions that are stored in the actions database table.
* This is necessary so that actions that do not require configuration can
* receive action IDs.
*
* @param $delete_orphans
* If TRUE, any actions that exist in the database but are no longer
* found in the code (for example, because the module that provides them has
* been disabled) will be deleted.
*/
function actions_synchronize($delete_orphans = FALSE) {
$actions_in_code = actions_list(TRUE);
$actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC);
// 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 (isset($actions_in_db[$callback])) {
unset($actions_in_db[$callback]);
}
else {
// This is a new singleton that we don't have an aid for; assign one.
db_insert('actions')
->fields(array(
'aid' => $callback,
'type' => $array['type'],
'callback' => $callback,
'parameters' => '',
'label' => $array['label'],
))
->execute();
watchdog('actions', "Action '%action' added.", array('%action' => $array['label']));
}
}
}
// Any actions that we have left in $actions_in_db are orphaned.
if ($actions_in_db) {
$orphaned = array_keys($actions_in_db);
if ($delete_orphans) {
$actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll();
foreach ($actions as $action) {
actions_delete($action->aid);
watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => $action->label));
}
}
else {
$link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan');
$count = count($actions_in_db);
$orphans = implode(', ', $orphaned);
watchdog('actions', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_INFO);
}
}
}
/**
* Saves an action and its 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 $label
* A user-supplied label 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, $label, $aid = NULL) {
// aid is the callback for singleton actions so we need to keep a separate
// table for numeric aids.
if (!$aid) {
$aid = db_next_id();
}
db_merge('actions')
->key(array('aid' => $aid))
->fields(array(
'callback' => $function,
'type' => $type,
'parameters' => serialize($params),
'label' => $label,
))
->execute();
watchdog('actions', 'Action %action saved.', array('%action' => $label));
return $aid;
}
/**
* Retrieves a single action from the database.
*
* @param $aid
* The ID of the action to retrieve.
*
* @return
* The appropriate action row from the database as an object.
*/
function actions_load($aid) {
return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject();
}
/**
* Deletes a single action from the database.
*
* @param $aid
* The ID of the action to delete.
*/
function actions_delete($aid) {
db_delete('actions')
->condition('aid', $aid)
->execute();
module_invoke_all('actions_delete', $aid);
}
-1211
View File
File diff suppressed because it is too large Load Diff
-68
View File
@@ -1,68 +0,0 @@
<?php
/**
* @file
* Shared classes and interfaces for the archiver system.
*/
/**
* Defines the common interface for all Archiver classes.
*/
interface ArchiverInterface {
/**
* Constructs a new archiver instance.
*
* @param $file_path
* The full system path of the archive to manipulate. Only local files
* are supported. If the file does not yet exist, it will be created if
* appropriate.
*/
public function __construct($file_path);
/**
* Adds the specified file or directory to the archive.
*
* @param $file_path
* The full system path of the file or directory to add. Only local files
* and directories are supported.
*
* @return ArchiverInterface
* The called object.
*/
public function add($file_path);
/**
* Removes the specified file from the archive.
*
* @param $path
* The file name relative to the root of the archive to remove.
*
* @return ArchiverInterface
* The called object.
*/
public function remove($path);
/**
* Extracts multiple files in the archive to the specified path.
*
* @param $path
* A full system path of the directory to which to extract files.
* @param $files
* Optionally specify a list of files to be extracted. Files are
* relative to the root of the archive. If not specified, all files
* in the archive will be extracted.
*
* @return ArchiverInterface
* The called object.
*/
public function extract($path, array $files = array());
/**
* Lists all files in the archive.
*
* @return
* An array of file names relative to the root of the archive.
*/
public function listContents();
}
-334
View File
@@ -1,334 +0,0 @@
<?php
/**
* @file
* Helper functions and form handlers used for the authorize.php script.
*/
/**
* Form constructor for the file transfer authorization form.
*
* Allows the user to choose a FileTransfer type and supply credentials.
*
* @see authorize_filetransfer_form_validate()
* @see authorize_filetransfer_form_submit()
* @ingroup forms
*/
function authorize_filetransfer_form($form, &$form_state) {
global $base_url, $is_https;
$form = array();
// If possible, we want to post this form securely via HTTPS.
$form['#https'] = TRUE;
// CSS we depend on lives in modules/system/maintenance.css, which is loaded
// via the default maintenance theme.
$form['#attached']['js'][] = $base_url . '/misc/authorize.js';
// Get all the available ways to transfer files.
if (empty($_SESSION['authorize_filetransfer_info'])) {
drupal_set_message(t('Unable to continue, no available methods of file transfer'), 'error');
return array();
}
$available_backends = $_SESSION['authorize_filetransfer_info'];
if (!$is_https) {
$form['information']['https_warning'] = array(
'#prefix' => '<div class="messages error">',
'#markup' => t('WARNING: You are not using an encrypted connection, so your password will be sent in plain text. <a href="@https-link">Learn more</a>.', array('@https-link' => 'http://drupal.org/https-information')),
'#suffix' => '</div>',
);
}
// Decide on a default backend.
if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default'])) {
$authorize_filetransfer_default = $form_state['values']['connection_settings']['authorize_filetransfer_default'];
}
elseif ($authorize_filetransfer_default = variable_get('authorize_filetransfer_default', NULL));
else {
$authorize_filetransfer_default = key($available_backends);
}
$form['information']['main_header'] = array(
'#prefix' => '<h3>',
'#markup' => t('To continue, provide your server connection details'),
'#suffix' => '</h3>',
);
$form['connection_settings']['#tree'] = TRUE;
$form['connection_settings']['authorize_filetransfer_default'] = array(
'#type' => 'select',
'#title' => t('Connection method'),
'#default_value' => $authorize_filetransfer_default,
'#weight' => -10,
);
/*
* Here we create two submit buttons. For a JS enabled client, they will
* only ever see submit_process. However, if a client doesn't have JS
* enabled, they will see submit_connection on the first form (when picking
* what filetransfer type to use, and submit_process on the second one (which
* leads to the actual operation).
*/
$form['submit_connection'] = array(
'#prefix' => "<br style='clear:both'/>",
'#name' => 'enter_connection_settings',
'#type' => 'submit',
'#value' => t('Enter connection settings'),
'#weight' => 100,
);
$form['submit_process'] = array(
'#name' => 'process_updates',
'#type' => 'submit',
'#value' => t('Continue'),
'#weight' => 100,
'#attributes' => array('style' => 'display:none'),
);
// Build a container for each connection type.
foreach ($available_backends as $name => $backend) {
$form['connection_settings']['authorize_filetransfer_default']['#options'][$name] = $backend['title'];
$form['connection_settings'][$name] = array(
'#type' => 'container',
'#attributes' => array('class' => array("filetransfer-$name", 'filetransfer')),
);
// We can't use #prefix on the container itself since then the header won't
// be hidden and shown when the containers are being manipulated via JS.
$form['connection_settings'][$name]['header'] = array(
'#markup' => '<h4>' . t('@backend connection settings', array('@backend' => $backend['title'])) . '</h4>',
);
$form['connection_settings'][$name] += _authorize_filetransfer_connection_settings($name);
// Start non-JS code.
if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default']) && $form_state['values']['connection_settings']['authorize_filetransfer_default'] == $name) {
// If the user switches from JS to non-JS, Drupal (and Batch API) will
// barf. This is a known bug: http://drupal.org/node/229825.
setcookie('has_js', '', time() - 3600, '/');
unset($_COOKIE['has_js']);
// Change the submit button to the submit_process one.
$form['submit_process']['#attributes'] = array();
unset($form['submit_connection']);
// Activate the proper filetransfer settings form.
$form['connection_settings'][$name]['#attributes']['style'] = 'display:block';
// Disable the select box.
$form['connection_settings']['authorize_filetransfer_default']['#disabled'] = TRUE;
// Create a button for changing the type of connection.
$form['connection_settings']['change_connection_type'] = array(
'#name' => 'change_connection_type',
'#type' => 'submit',
'#value' => t('Change connection type'),
'#weight' => -5,
'#attributes' => array('class' => array('filetransfer-change-connection-type')),
);
}
// End non-JS code.
}
return $form;
}
/**
* Generates the Form API array for a given connection backend's settings.
*
* @param $backend
* The name of the backend (e.g. 'ftp', 'ssh', etc).
*
* @return
* Form API array of connection settings for the given backend.
*
* @see hook_filetransfer_backends()
*/
function _authorize_filetransfer_connection_settings($backend) {
$defaults = variable_get('authorize_filetransfer_connection_settings_' . $backend, array());
$form = array();
// Create an instance of the file transfer class to get its settings form.
$filetransfer = authorize_get_filetransfer($backend);
if ($filetransfer) {
$form = $filetransfer->getSettingsForm();
}
// Fill in the defaults based on the saved settings, if any.
_authorize_filetransfer_connection_settings_set_defaults($form, NULL, $defaults);
return $form;
}
/**
* Sets the default settings on a file transfer connection form recursively.
*
* The default settings for the file transfer connection forms are saved in
* the database. The settings are stored as a nested array in the case of a
* settings form that has fieldsets or otherwise uses a nested structure.
* Therefore, to properly add defaults, we need to walk through all the
* children form elements and process those defaults recursively.
*
* @param $element
* Reference to the Form API form element we're operating on.
* @param $key
* The key for our current form element, if any.
* @param array $defaults
* The default settings for the file transfer backend we're operating on.
*/
function _authorize_filetransfer_connection_settings_set_defaults(&$element, $key, array $defaults) {
// If we're operating on a form element which isn't a fieldset, and we have
// a default setting saved, stash it in #default_value.
if (!empty($key) && isset($defaults[$key]) && isset($element['#type']) && $element['#type'] != 'fieldset') {
$element['#default_value'] = $defaults[$key];
}
// Now, we walk through all the child elements, and recursively invoke
// ourself on each one. Since the $defaults settings array can be nested
// (because of #tree, any values inside fieldsets will be nested), if
// there's a subarray of settings for the form key we're currently
// processing, pass in that subarray to the recursive call. Otherwise, just
// pass on the whole $defaults array.
foreach (element_children($element) as $child_key) {
_authorize_filetransfer_connection_settings_set_defaults($element[$child_key], $child_key, ((isset($defaults[$key]) && is_array($defaults[$key])) ? $defaults[$key] : $defaults));
}
}
/**
* Form validation handler for authorize_filetransfer_form().
*
* @see authorize_filetransfer_form()
* @see authorize_filetransfer_submit()
*/
function authorize_filetransfer_form_validate($form, &$form_state) {
// Only validate the form if we have collected all of the user input and are
// ready to proceed with updating or installing.
if ($form_state['triggering_element']['#name'] != 'process_updates') {
return;
}
if (isset($form_state['values']['connection_settings'])) {
$backend = $form_state['values']['connection_settings']['authorize_filetransfer_default'];
$filetransfer = authorize_get_filetransfer($backend, $form_state['values']['connection_settings'][$backend]);
try {
if (!$filetransfer) {
throw new Exception(t('Error, this type of connection protocol (%backend) does not exist.', array('%backend' => $backend)));
}
$filetransfer->connect();
}
catch (Exception $e) {
// The format of this error message is similar to that used on the
// database connection form in the installer.
form_set_error('connection_settings', t('Failed to connect to the server. The server reports the following message: !message For more help installing or updating code on your server, see the <a href="@handbook_url">handbook</a>.', array(
'!message' => '<p class="error">' . $e->getMessage() . '</p>',
'@handbook_url' => 'http://drupal.org/documentation/install/modules-themes',
)));
}
}
}
/**
* Form submission handler for authorize_filetransfer_form().
*
* @see authorize_filetransfer_form()
* @see authorize_filetransfer_validate()
*/
function authorize_filetransfer_form_submit($form, &$form_state) {
global $base_url;
switch ($form_state['triggering_element']['#name']) {
case 'process_updates':
// Save the connection settings to the DB.
$filetransfer_backend = $form_state['values']['connection_settings']['authorize_filetransfer_default'];
// If the database is available then try to save our settings. We have
// to make sure it is available since this code could potentially (will
// likely) be called during the installation process, before the
// database is set up.
try {
$connection_settings = array();
foreach ($form_state['values']['connection_settings'][$filetransfer_backend] as $key => $value) {
// We do *not* want to store passwords in the database, unless the
// backend explicitly says so via the magic #filetransfer_save form
// property. Otherwise, we store everything that's not explicitly
// marked with #filetransfer_save set to FALSE.
if (!isset($form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save'])) {
if ($form['connection_settings'][$filetransfer_backend][$key]['#type'] != 'password') {
$connection_settings[$key] = $value;
}
}
// The attribute is defined, so only save if set to TRUE.
elseif ($form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save']) {
$connection_settings[$key] = $value;
}
}
// Set this one as the default authorize method.
variable_set('authorize_filetransfer_default', $filetransfer_backend);
// Save the connection settings minus the password.
variable_set('authorize_filetransfer_connection_settings_' . $filetransfer_backend, $connection_settings);
$filetransfer = authorize_get_filetransfer($filetransfer_backend, $form_state['values']['connection_settings'][$filetransfer_backend]);
// Now run the operation.
authorize_run_operation($filetransfer);
}
catch (Exception $e) {
// If there is no database available, we don't care and just skip
// this part entirely.
}
break;
case 'enter_connection_settings':
$form_state['rebuild'] = TRUE;
break;
case 'change_connection_type':
$form_state['rebuild'] = TRUE;
unset($form_state['values']['connection_settings']['authorize_filetransfer_default']);
break;
}
}
/**
* Runs the operation specified in $_SESSION['authorize_operation'].
*
* @param $filetransfer
* The FileTransfer object to use for running the operation.
*/
function authorize_run_operation($filetransfer) {
$operation = $_SESSION['authorize_operation'];
unset($_SESSION['authorize_operation']);
if (!empty($operation['page_title'])) {
drupal_set_title($operation['page_title']);
}
require_once DRUPAL_ROOT . '/' . $operation['file'];
call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments']));
}
/**
* Gets a FileTransfer class for a specific transfer method and settings.
*
* @param $backend
* The FileTransfer backend to get the class for.
* @param $settings
* Array of settings for the FileTransfer.
*
* @return
* An instantiated FileTransfer object for the requested method and settings,
* or FALSE if there was an error finding or instantiating it.
*/
function authorize_get_filetransfer($backend, $settings = array()) {
$filetransfer = FALSE;
if (!empty($_SESSION['authorize_filetransfer_info'][$backend])) {
$backend_info = $_SESSION['authorize_filetransfer_info'][$backend];
if (!empty($backend_info['file'])) {
$file = $backend_info['file path'] . '/' . $backend_info['file'];
require_once $file;
}
if (class_exists($backend_info['class'])) {
// PHP 5.2 doesn't support $class::factory() syntax, so we have to
// use call_user_func_array() until we can require PHP 5.3.
$filetransfer = call_user_func_array(array($backend_info['class'], 'factory'), array(DRUPAL_ROOT, $settings));
}
}
return $filetransfer;
}
-539
View File
@@ -1,539 +0,0 @@
<?php
/**
* @file
* Batch processing API for processes to run in multiple HTTP requests.
*
* Note that batches are usually invoked by form submissions, which is
* why the core interaction functions of the batch processing API live in
* form.inc.
*
* @see form.inc
* @see batch_set()
* @see batch_process()
* @see batch_get()
*/
/**
* Loads a batch from the database.
*
* @param $id
* The ID of the batch to load. When a progressive batch is being processed,
* the relevant ID is found in $_REQUEST['id'].
*
* @return
* An array representing the batch, or FALSE if no batch was found.
*/
function batch_load($id) {
$batch = db_query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", array(
':bid' => $id,
':token' => drupal_get_token($id),
))->fetchField();
if ($batch) {
return unserialize($batch);
}
return FALSE;
}
/**
* Renders the batch processing page based on the current state of the batch.
*
* @see _batch_shutdown()
*/
function _batch_page() {
$batch = &batch_get();
if (!isset($_REQUEST['id'])) {
return FALSE;
}
// Retrieve the current state of the batch.
if (!$batch) {
$batch = batch_load($_REQUEST['id']);
if (!$batch) {
drupal_set_message(t('No active batch.'), 'error');
drupal_goto();
}
}
// Register database update for the end of processing.
drupal_register_shutdown_function('_batch_shutdown');
// Add batch-specific CSS.
foreach ($batch['sets'] as $batch_set) {
if (isset($batch_set['css'])) {
foreach ($batch_set['css'] as $css) {
drupal_add_css($css);
}
}
}
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
$output = NULL;
switch ($op) {
case 'start':
$output = _batch_start();
break;
case 'do':
// JavaScript-based progress page callback.
_batch_do();
break;
case 'do_nojs':
// Non-JavaScript-based progress page.
$output = _batch_progress_page_nojs();
break;
case 'finished':
$output = _batch_finished();
break;
}
return $output;
}
/**
* Initializes the batch processing.
*
* JavaScript-enabled clients are identified by the 'has_js' cookie set in
* drupal.js. If no JavaScript-enabled page has been visited during the current
* user's browser session, the non-JavaScript version is returned.
*/
function _batch_start() {
if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
return _batch_progress_page_js();
}
else {
return _batch_progress_page_nojs();
}
}
/**
* Outputs a batch processing page with JavaScript support.
*
* This initializes the batch and error messages. Note that in JavaScript-based
* processing, the batch processing page is displayed only once and updated via
* AHAH requests, so only the first batch set gets to define the page title.
* Titles specified by subsequent batch sets are not displayed.
*
* @see batch_set()
* @see _batch_do()
*/
function _batch_progress_page_js() {
$batch = batch_get();
$current_set = _batch_current_set();
drupal_set_title($current_set['title'], PASS_THROUGH);
// Merge required query parameters for batch processing into those provided by
// batch_set() or hook_batch_alter().
$batch['url_options']['query']['id'] = $batch['id'];
$js_setting = array(
'batch' => array(
'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'],
'initMessage' => $current_set['init_message'],
'uri' => url($batch['url'], $batch['url_options']),
),
);
drupal_add_js($js_setting, 'setting');
drupal_add_library('system', 'drupal.batch');
return '<div id="progress"></div>';
}
/**
* Does one execution pass with JavaScript and returns progress to the browser.
*
* @see _batch_progress_page_js()
* @see _batch_process()
*/
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_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message));
}
/**
* Outputs a batch processing page without JavaScript support.
*
* @see _batch_process()
*/
function _batch_progress_page_nojs() {
$batch = &batch_get();
$current_set = _batch_current_set();
drupal_set_title($current_set['title'], PASS_THROUGH);
$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. a nonexistent
// 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'];
$fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => 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();
}
// Merge required query parameters for batch processing into those provided by
// batch_set() or hook_batch_alter().
$batch['url_options']['query']['id'] = $batch['id'];
$batch['url_options']['query']['op'] = $new_op;
$url = url($batch['url'], $batch['url_options']);
$element = array(
'#tag' => 'meta',
'#attributes' => array(
'http-equiv' => 'Refresh',
'content' => '0; URL=' . $url,
),
);
drupal_add_html_head($element, 'batch_progress_meta_refresh');
return theme('progress_bar', array('percent' => $percentage, 'message' => $message));
}
/**
* Processes sets in a batch.
*
* If the batch was marked for progressive execution (default), this executes as
* many operations in batch sets until an execution time of 1 second has been
* exceeded. It will continue with the next operation of the same batch set in
* the next request.
*
* @return
* An array containing a completion value (in percent) and a status message.
*/
function _batch_process() {
$batch = &batch_get();
$current_set = &_batch_current_set();
// Indicate that this batch set needs to be initialized.
$set_changed = TRUE;
// If this batch was marked for progressive execution (e.g. forms submitted by
// drupal_form_submit()), initialize a timer to determine whether we need to
// proceed with the same batch phase when a processing time of 1 second has
// been exceeded.
if ($batch['progressive']) {
timer_start('batch_processing');
}
if (empty($current_set['start'])) {
$current_set['start'] = microtime(TRUE);
}
$queue = _batch_queue($current_set);
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 DRUPAL_ROOT . '/' . $current_set['file'];
}
$task_message = '';
// Assume a single pass operation and set the completion level to 1 by
// default.
$finished = 1;
if ($item = $queue->claimItem()) {
list($function, $args) = $item->data;
// Build the 'context' array and execute the function call.
$batch_context = array(
'sandbox' => &$current_set['sandbox'],
'results' => &$current_set['results'],
'finished' => &$finished,
'message' => &$task_message,
);
call_user_func_array($function, array_merge($args, array(&$batch_context)));
if ($finished >= 1) {
// Make sure this step is not counted twice when computing $current.
$finished = 0;
// Remove the processed operation and clear the sandbox.
$queue->deleteItem($item);
$current_set['count']--;
$current_set['sandbox'] = array();
}
}
// When all operations in the current batch set are completed, browse
// through the remaining sets, marking them 'successfully processed'
// along the way, until we find a set that contains operations.
// _batch_next_set() executes form submit handlers stored in 'control'
// sets (see form_execute_handlers()), which can in turn add new sets to
// the batch.
$set_changed = FALSE;
$old_set = $current_set;
while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) {
$current_set = &_batch_current_set();
$current_set['start'] = microtime(TRUE);
$set_changed = TRUE;
}
// At this point, either $current_set contains operations that need to be
// processed or all sets have been completed.
$queue = _batch_queue($current_set);
// If we are in progressive mode, break processing after 1 second.
if ($batch['progressive'] && timer_read('batch_processing') > 1000) {
// Record elapsed wall clock time.
$current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2);
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) set.
if ($set_changed && isset($current_set['queue'])) {
// Processing will continue with a fresh batch set.
$remaining = $current_set['count'];
$total = $current_set['total'];
$progress_message = $current_set['init_message'];
$task_message = '';
}
else {
// Processing will continue with the current batch set.
$remaining = $old_set['count'];
$total = $old_set['total'];
$progress_message = $old_set['progress_message'];
}
// Total progress is the number of operations that have fully run plus the
// completion level of the current operation.
$current = $total - $remaining + $finished;
$percentage = _batch_api_percentage($total, $current);
$elapsed = isset($current_set['elapsed']) ? $current_set['elapsed'] : 0;
$values = array(
'@remaining' => $remaining,
'@total' => $total,
'@current' => floor($current),
'@percentage' => $percentage,
'@elapsed' => format_interval($elapsed / 1000),
// If possible, estimate remaining processing time.
'@estimate' => ($current > 0) ? format_interval(($elapsed * ($total - $current) / $current) / 1000) : '-',
);
$message = strtr($progress_message, $values);
if (!empty($message)) {
$message .= '<br />';
}
if (!empty($task_message)) {
$message .= $task_message;
}
return array($percentage, $message);
}
else {
// If we are not in progressive mode, the entire batch has been processed.
return _batch_finished();
}
}
/**
* Formats the percent completion for a batch set.
*
* @param $total
* The total number of operations.
* @param $current
* The number of the current operation. This may be a floating point number
* rather than an integer in the case of a multi-step operation that is not
* yet complete; in that case, the fractional part of $current represents the
* fraction of the operation that has been completed.
*
* @return
* The properly formatted percentage, as a string. We output percentages
* using the correct number of decimal places so that we never print "100%"
* until we are finished, but we also never print more decimal places than
* are meaningful.
*
* @see _batch_process()
*/
function _batch_api_percentage($total, $current) {
if (!$total || $total == $current) {
// If $total doesn't evaluate as true or is equal to the current set, then
// we're finished, and we can return "100".
$percentage = "100";
}
else {
// We add a new digit at 200, 2000, etc. (since, for example, 199/200
// would round up to 100% if we didn't).
$decimal_places = max(0, floor(log10($total / 2.0)) - 1);
do {
// Calculate the percentage to the specified number of decimal places.
$percentage = sprintf('%01.' . $decimal_places . 'f', round($current / $total * 100, $decimal_places));
// When $current is an integer, the above calculation will always be
// correct. However, if $current is a floating point number (in the case
// of a multi-step batch operation that is not yet complete), $percentage
// may be erroneously rounded up to 100%. To prevent that, we add one
// more decimal place and try again.
$decimal_places++;
} while ($percentage == '100');
}
return $percentage;
}
/**
* Returns the batch set being currently processed.
*/
function &_batch_current_set() {
$batch = &batch_get();
return $batch['sets'][$batch['current_set']];
}
/**
* Retrieves the next set in a batch.
*
* If there is a subsequent set in this batch, assign it as the new set to
* process and execute its form submit handler (if defined), which may add
* further sets to this batch.
*
* @return
* TRUE if a subsequent set was found in the batch.
*/
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 alterations by previous form submit handlers.
$function($batch['form_state']['complete form'], $batch['form_state']);
}
return TRUE;
}
}
/**
* Ends the batch processing.
*
* Call the 'finished' callback of each batch set to allow custom handling of
* the results and resolve page redirection.
*/
function _batch_finished() {
$batch = &batch_get();
// Execute the 'finished' callbacks for each batch set, if defined.
foreach ($batch['sets'] as $batch_set) {
if (isset($batch_set['finished'])) {
// Check if the set requires an additional file for function definitions.
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
}
if (function_exists($batch_set['finished'])) {
$queue = _batch_queue($batch_set);
$operations = $queue->getAllItems();
$batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
}
}
}
// Clean up the batch table and unset the static $batch variable.
if ($batch['progressive']) {
db_delete('batch')
->condition('bid', $batch['id'])
->execute();
foreach ($batch['sets'] as $batch_set) {
if ($queue = _batch_queue($batch_set)) {
$queue->deleteQueue();
}
}
}
$_batch = $batch;
$batch = NULL;
// Clean-up the session. Not needed for CLI updates.
if (isset($_SESSION)) {
unset($_SESSION['batches'][$batch['id']]);
if (empty($_SESSION['batches'])) {
unset($_SESSION['batches']);
}
}
// Redirect if needed.
if ($_batch['progressive']) {
// Revert the 'destination' that was saved in batch_process().
if (isset($_batch['destination'])) {
$_GET['destination'] = $_batch['destination'];
}
// Determine the target path to redirect to.
if (!isset($_batch['form_state']['redirect'])) {
if (isset($_batch['redirect'])) {
$_batch['form_state']['redirect'] = $_batch['redirect'];
}
else {
$_batch['form_state']['redirect'] = $_batch['source_url'];
}
}
// Use drupal_redirect_form() to handle the redirection logic.
drupal_redirect_form($_batch['form_state']);
// If no redirection happened, redirect to the originating page. In case the
// form needs to be rebuilt, save the final $form_state for
// drupal_build_form().
if (!empty($_batch['form_state']['rebuild'])) {
$_SESSION['batch_form_state'] = $_batch['form_state'];
}
$function = $_batch['redirect_callback'];
if (function_exists($function)) {
$function($_batch['source_url'], array('query' => array('op' => 'finish', 'id' => $_batch['id'])));
}
}
}
/**
* Shutdown function: Stores the current batch data for the next request.
*
* @see _batch_page()
* @see drupal_register_shutdown_function()
*/
function _batch_shutdown() {
if ($batch = batch_get()) {
db_update('batch')
->fields(array('batch' => serialize($batch)))
->condition('bid', $batch['id'])
->execute();
}
}
-84
View File
@@ -1,84 +0,0 @@
<?php
/**
* @file
* Queue handlers used by the Batch API.
*
* These implementations:
* - Ensure FIFO ordering.
* - Allow an item to be repeatedly claimed until it is actually deleted (no
* notion of lease time or 'expire' date), to allow multipass operations.
*/
/**
* Defines a batch queue.
*
* Stale items from failed batches are cleaned from the {queue} table on cron
* using the 'created' date.
*/
class BatchQueue extends SystemQueue {
/**
* Overrides SystemQueue::claimItem().
*
* Unlike SystemQueue::claimItem(), this method provides a default lease
* time of 0 (no expiration) instead of 30. This allows the item to be
* claimed repeatedly until it is deleted.
*/
public function claimItem($lease_time = 0) {
$item = db_query_range('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject();
if ($item) {
$item->data = unserialize($item->data);
return $item;
}
return FALSE;
}
/**
* Retrieves all remaining items in the queue.
*
* This is specific to Batch API and is not part of the DrupalQueueInterface.
*/
public function getAllItems() {
$result = array();
$items = db_query('SELECT data FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchAll();
foreach ($items as $item) {
$result[] = unserialize($item->data);
}
return $result;
}
}
/**
* Defines a batch queue for non-progressive batches.
*/
class BatchMemoryQueue extends MemoryQueue {
/**
* Overrides MemoryQueue::claimItem().
*
* Unlike MemoryQueue::claimItem(), this method provides a default lease
* time of 0 (no expiration) instead of 30. This allows the item to be
* claimed repeatedly until it is deleted.
*/
public function claimItem($lease_time = 0) {
if (!empty($this->queue)) {
reset($this->queue);
return current($this->queue);
}
return FALSE;
}
/**
* Retrieves all remaining items in the queue.
*
* This is specific to Batch API and is not part of the DrupalQueueInterface.
*/
public function getAllItems() {
$result = array();
foreach ($this->queue as $item) {
$result[] = $item->data;
}
return $result;
}
}
File diff suppressed because it is too large Load Diff
-74
View File
@@ -1,74 +0,0 @@
<?php
/**
* @file
* Provides a stub cache implementation to be used during installation.
*/
/**
* Defines a stub cache implementation to be used during installation.
*
* The stub implementation is needed 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.
*/
class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterface {
/**
* Overrides DrupalDatabaseCache::get().
*/
function get($cid) {
return FALSE;
}
/**
* Overrides DrupalDatabaseCache::getMultiple().
*/
function getMultiple(&$cids) {
return array();
}
/**
* Overrides DrupalDatabaseCache::set().
*/
function set($cid, $data, $expire = CACHE_PERMANENT) {
}
/**
* Overrides DrupalDatabaseCache::clear().
*/
function clear($cid = NULL, $wildcard = FALSE) {
// If there is a database cache, attempt to clear it whenever possible. The
// reason for doing this is that the database cache can accumulate data
// during installation due to any full bootstraps that may occur at the
// same time (for example, Ajax requests triggered by the installer). If we
// didn't try to clear it whenever this function is called, the data in the
// cache would become stale; for example, the installer sometimes calls
// variable_set(), which updates the {variable} table and then clears the
// cache to make sure that the next page request picks up the new value.
// Not actually clearing the cache here therefore leads old variables to be
// loaded on the first page requests after installation, which can cause
// subtle bugs, some of which would not be fixed unless the site
// administrator cleared the cache manually.
try {
if (class_exists('Database')) {
parent::clear($cid, $wildcard);
}
}
// If the attempt at clearing the cache causes an error, that means that
// either the database connection is not set up yet or the relevant cache
// table in the database has not yet been created, so we can safely do
// nothing here.
catch (Exception $e) {
}
}
/**
* Overrides DrupalDatabaseCache::isEmpty().
*/
function isEmpty() {
return TRUE;
}
}
-558
View File
@@ -1,558 +0,0 @@
<?php
/**
* @file
* Functions and interfaces for cache handling.
*/
/**
* Gets the cache object for a cache bin.
*
* By default, this returns an instance of the DrupalDatabaseCache class.
* Classes implementing DrupalCacheInterface can register themselves both as a
* default implementation and for specific bins.
*
* @param $bin
* The cache bin for which the cache object should be returned.
* @return DrupalCacheInterface
* The cache object associated with the specified bin.
*
* @see DrupalCacheInterface
*/
function _cache_get_object($bin) {
// We do not use drupal_static() here because we do not want to change the
// storage of a cache bin mid-request.
static $cache_objects;
if (!isset($cache_objects[$bin])) {
$class = variable_get('cache_class_' . $bin);
if (!isset($class)) {
$class = variable_get('cache_default_class', 'DrupalDatabaseCache');
}
$cache_objects[$bin] = new $class($bin);
}
return $cache_objects[$bin];
}
/**
* Returns 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 $bin
* The cache bin to store the data in. Valid core values are 'cache_block',
* 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
* 'cache_menu', 'cache_page', 'cache_path', 'cache_update' or 'cache' for
* the default cache.
*
* @return
* The cache or FALSE on failure.
*
* @see cache_set()
*/
function cache_get($cid, $bin = 'cache') {
return _cache_get_object($bin)->get($cid);
}
/**
* Returns data from the persistent cache when given an array of cache IDs.
*
* @param $cids
* An array of cache IDs for the data to retrieve. This is passed by
* reference, and will have the IDs successfully returned from cache removed.
* @param $bin
* The cache bin where the data is stored.
*
* @return
* An array of the items successfully returned from cache indexed by cid.
*/
function cache_get_multiple(array &$cids, $bin = 'cache') {
return _cache_get_object($bin)->getMultiple($cids);
}
/**
* Stores data in the persistent cache.
*
* The persistent cache is split up into several cache bins. In the default
* cache implementation, each cache bin corresponds to a database table by the
* same name. Other implementations might want to store several bins in data
* structures that get flushed together. While it is not a problem for most
* cache bins if the entries in them are flushed before their expire time, some
* might break functionality or are extremely expensive to recalculate. These
* will be marked with a (*). The other bins expired automatically by core.
* Contributed modules can add additional bins and get them expired
* automatically by implementing hook_flush_caches().
*
* - cache: Generic cache storage bin (used for variables, theme registry,
* locale date, list of simpletest tests etc).
*
* - cache_block: Stores the content of various blocks.
*
* - cache field: Stores the field data belonging to a given object.
*
* - cache_filter: Stores filtered pieces of content.
*
* - cache_form(*): Stores multistep forms. Flushing this bin means that some
* forms displayed to users lose their state and the data already submitted
* to them.
*
* - cache_menu: Stores the structure of visible navigation menus per page.
*
* - cache_page: Stores generated pages for anonymous users. It is flushed
* very often, whenever a page changes, at least for every ode and comment
* submission. This is the only bin affected by the page cache setting on
* the administrator panel.
*
* - cache path: Stores the system paths that have an alias.
*
* - cache update(*): Stores available releases. The update server (for
* example, drupal.org) needs to produce the relevant XML for every project
* installed on the current site. As this is different for (almost) every
* site, it's very expensive to recalculate for the update server.
*
* The reasons for having several bins are as follows:
*
* - smaller bins mean smaller database tables and allow for faster selects and
* inserts
* - we try to put fast changing cache items and rather static ones into
* different bins. The effect is that only the fast changing bins will need a
* lot of writes to disk. The more static bins will also be better cacheable
* 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 $bin
* The cache bin to store the data in. Valid core values are 'cache_block',
* 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form',
* 'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default
* 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.
*
* @see cache_get()
*/
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
return _cache_get_object($bin)->set($cid, $data, $expire);
}
/**
* Expires data from the cache.
*
* If called without arguments, expirable entries will be cleared from the
* cache_page and cache_block bins.
*
* @param $cid
* If set, the cache ID to delete. Otherwise, all cache entries that can
* expire are deleted.
* @param $bin
* If set, the cache bin to delete from. Mandatory argument if $cid is set.
* @param $wildcard
* If 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 '*',
* the entire cache bin is emptied.
*/
function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
if (!isset($cid) && !isset($bin)) {
// Clear the block cache first, so stale data will
// not end up in the page cache.
if (module_exists('block')) {
cache_clear_all(NULL, 'cache_block');
}
cache_clear_all(NULL, 'cache_page');
return;
}
return _cache_get_object($bin)->clear($cid, $wildcard);
}
/**
* Checks if a cache bin is empty.
*
* A cache bin is considered empty if it does not contain any valid data for any
* cache ID.
*
* @param $bin
* The cache bin to check.
*
* @return
* TRUE if the cache bin specified is empty.
*/
function cache_is_empty($bin) {
return _cache_get_object($bin)->isEmpty();
}
/**
* Defines an interface for cache implementations.
*
* All cache implementations have to implement this interface.
* DrupalDatabaseCache provides the default implementation, which can be
* consulted as an example.
*
* To make Drupal use your implementation for a certain cache bin, you have to
* set a variable with the name of the cache bin as its key and the name of
* your class as its value. For example, if your implementation of
* DrupalCacheInterface was called MyCustomCache, the following line would make
* Drupal use it for the 'cache_page' bin:
* @code
* variable_set('cache_class_cache_page', 'MyCustomCache');
* @endcode
*
* Additionally, you can register your cache implementation to be used by
* default for all cache bins by setting the variable 'cache_default_class' to
* the name of your implementation of the DrupalCacheInterface, e.g.
* @code
* variable_set('cache_default_class', 'MyCustomCache');
* @endcode
*
* To implement a completely custom cache bin, use the same variable format:
* @code
* variable_set('cache_class_custom_bin', 'MyCustomCache');
* @endcode
* To access your custom cache bin, specify the name of the bin when storing
* or retrieving cached data:
* @code
* cache_set($cid, $data, 'custom_bin', $expire);
* cache_get($cid, 'custom_bin');
* @endcode
*
* @see _cache_get_object()
* @see DrupalDatabaseCache
*/
interface DrupalCacheInterface {
/**
* Constructs a new cache interface.
*
* @param $bin
* The cache bin for which the object is created.
*/
function __construct($bin);
/**
* Returns 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.
*
* @return
* The cache or FALSE on failure.
*/
function get($cid);
/**
* Returns data from the persistent cache when given an array of cache IDs.
*
* @param $cids
* An array of cache IDs for the data to retrieve. This is passed by
* reference, and will have the IDs successfully returned from cache
* removed.
*
* @return
* An array of the items successfully returned from cache indexed by cid.
*/
function getMultiple(&$cids);
/**
* Stores data in the persistent 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 $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.
*/
function set($cid, $data, $expire = CACHE_PERMANENT);
/**
* Expires data from the cache.
*
* If called without arguments, expirable entries will be cleared from the
* cache_page and cache_block bins.
*
* @param $cid
* If set, the cache ID to delete. Otherwise, all cache entries that can
* expire are deleted.
* @param $wildcard
* If set to TRUE, the $cid is treated as a substring
* to match rather than a complete ID. The match is a right hand
* match. If '*' is given as $cid, the bin $bin will be emptied.
*/
function clear($cid = NULL, $wildcard = FALSE);
/**
* Checks if a cache bin is empty.
*
* A cache bin is considered empty if it does not contain any valid data for
* any cache ID.
*
* @return
* TRUE if the cache bin specified is empty.
*/
function isEmpty();
}
/**
* Defines a default cache implementation.
*
* This is Drupal's default cache implementation. It uses the database to store
* cached data. Each cache bin corresponds to a database table by the same name.
*/
class DrupalDatabaseCache implements DrupalCacheInterface {
protected $bin;
/**
* Constructs a new DrupalDatabaseCache object.
*/
function __construct($bin) {
$this->bin = $bin;
}
/**
* Implements DrupalCacheInterface::get().
*/
function get($cid) {
$cids = array($cid);
$cache = $this->getMultiple($cids);
return reset($cache);
}
/**
* Implements DrupalCacheInterface::getMultiple().
*/
function getMultiple(&$cids) {
try {
// Garbage collection necessary when enforcing a minimum cache lifetime.
$this->garbageCollection($this->bin);
// When serving cached pages, the overhead of using db_select() was found
// to add around 30% overhead to the request. Since $this->bin is a
// variable, this means the call to db_query() here uses a concatenated
// string. This is highly discouraged under any other circumstances, and
// is used here only due to the performance overhead we would incur
// otherwise. When serving an uncached page, the overhead of using
// db_select() is a much smaller proportion of the request.
$result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
$cache = array();
foreach ($result as $item) {
$item = $this->prepareItem($item);
if ($item) {
$cache[$item->cid] = $item;
}
}
$cids = array_diff($cids, array_keys($cache));
return $cache;
}
catch (Exception $e) {
// If the database is never going to be available, cache requests should
// return FALSE in order to allow exception handling to occur.
return array();
}
}
/**
* Garbage collection for get() and getMultiple().
*
* @param $bin
* The bin being requested.
*/
protected function garbageCollection() {
$cache_lifetime = variable_get('cache_lifetime', 0);
// Clean-up the per-user cache expiration session data, so that the session
// handler can properly clean-up the session data for anonymous users.
if (isset($_SESSION['cache_expiration'])) {
$expire = REQUEST_TIME - $cache_lifetime;
foreach ($_SESSION['cache_expiration'] as $bin => $timestamp) {
if ($timestamp < $expire) {
unset($_SESSION['cache_expiration'][$bin]);
}
}
if (!$_SESSION['cache_expiration']) {
unset($_SESSION['cache_expiration']);
}
}
// Garbage collection of temporary items is only necessary when enforcing
// a minimum cache lifetime.
if (!$cache_lifetime) {
return;
}
// When cache lifetime is in force, avoid running garbage collection too
// often since this will remove temporary cache items indiscriminately.
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush && ($cache_flush + $cache_lifetime <= REQUEST_TIME)) {
// Reset the variable immediately to prevent a meltdown in heavy load situations.
variable_set('cache_flush_' . $this->bin, 0);
// Time to flush old cache data
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', $cache_flush, '<=')
->execute();
}
}
/**
* Prepares a cached item.
*
* Checks that items are either permanent or did not expire, and unserializes
* data as appropriate.
*
* @param $cache
* An item loaded from cache_get() or cache_get_multiple().
*
* @return
* The item with data unserialized as appropriate or FALSE if there is no
* valid item to load.
*/
protected function prepareItem($cache) {
global $user;
if (!isset($cache->data)) {
return FALSE;
}
// If the cached data is temporary and subject to a per-user minimum
// lifetime, compare the cache entry timestamp with the user session
// cache_expiration timestamp. If the cache entry is too old, ignore it.
if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && isset($_SESSION['cache_expiration'][$this->bin]) && $_SESSION['cache_expiration'][$this->bin] > $cache->created) {
// Ignore cache data that is too old and thus not valid for this user.
return FALSE;
}
// If the data is permanent or not subject to a minimum cache lifetime,
// unserialize and return the cached data.
if ($cache->serialized) {
$cache->data = unserialize($cache->data);
}
return $cache;
}
/**
* Implements DrupalCacheInterface::set().
*/
function set($cid, $data, $expire = CACHE_PERMANENT) {
$fields = array(
'serialized' => 0,
'created' => REQUEST_TIME,
'expire' => $expire,
);
if (!is_string($data)) {
$fields['data'] = serialize($data);
$fields['serialized'] = 1;
}
else {
$fields['data'] = $data;
$fields['serialized'] = 0;
}
try {
db_merge($this->bin)
->key(array('cid' => $cid))
->fields($fields)
->execute();
}
catch (Exception $e) {
// The database may not be available, so we'll ignore cache_set requests.
}
}
/**
* Implements DrupalCacheInterface::clear().
*/
function clear($cid = NULL, $wildcard = FALSE) {
global $user;
if (empty($cid)) {
if (variable_get('cache_lifetime', 0)) {
// We store the time in the current user's session. We then simulate
// that the cache was flushed for this user by not returning cached
// data that was cached before the timestamp.
$_SESSION['cache_expiration'][$this->bin] = REQUEST_TIME;
$cache_flush = variable_get('cache_flush_' . $this->bin, 0);
if ($cache_flush == 0) {
// This is the first request to clear the cache, start a timer.
variable_set('cache_flush_' . $this->bin, REQUEST_TIME);
}
elseif (REQUEST_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_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
variable_set('cache_flush_' . $this->bin, 0);
}
}
else {
// No minimum cache lifetime, flush all temporary cache entries now.
db_delete($this->bin)
->condition('expire', CACHE_PERMANENT, '<>')
->condition('expire', REQUEST_TIME, '<')
->execute();
}
}
else {
if ($wildcard) {
if ($cid == '*') {
db_truncate($this->bin)->execute();
}
else {
db_delete($this->bin)
->condition('cid', db_like($cid) . '%', 'LIKE')
->execute();
}
}
elseif (is_array($cid)) {
// Delete in chunks when a large array is passed.
do {
db_delete($this->bin)
->condition('cid', array_splice($cid, 0, 1000), 'IN')
->execute();
}
while (count($cid));
}
else {
db_delete($this->bin)
->condition('cid', $cid)
->execute();
}
}
}
/**
* Implements DrupalCacheInterface::isEmpty().
*/
function isEmpty() {
$this->garbageCollection();
$query = db_select($this->bin);
$query->addExpression('1');
$result = $query->range(0, 1)
->execute()
->fetchField();
return empty($result);
}
}
-8136
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-161
View File
@@ -1,161 +0,0 @@
<?php
/**
* @file
* Logging classes for the database layer.
*/
/**
* Database query logger.
*
* We log queries in a separate object rather than in the connection object
* because we want to be able to see all queries sent to a given database, not
* database target. If we logged the queries in each connection object we
* would not be able to track what queries went to which target.
*
* Every connection has one and only one logging object on it for all targets
* and logging keys.
*/
class DatabaseLog {
/**
* Cache of logged queries. This will only be used if the query logger is enabled.
*
* The structure for the logging array is as follows:
*
* array(
* $logging_key = array(
* array(query => '', args => array(), caller => '', target => '', time => 0),
* array(query => '', args => array(), caller => '', target => '', time => 0),
* ),
* );
*
* @var array
*/
protected $queryLog = array();
/**
* The connection key for which this object is logging.
*
* @var string
*/
protected $connectionKey = 'default';
/**
* Constructor.
*
* @param $key
* The database connection key for which to enable logging.
*/
public function __construct($key = 'default') {
$this->connectionKey = $key;
}
/**
* Begin logging queries to the specified connection and logging key.
*
* If the specified logging key is already running this method does nothing.
*
* @param $logging_key
* The identification key for this log request. By specifying different
* logging keys we are able to start and stop multiple logging runs
* simultaneously without them colliding.
*/
public function start($logging_key) {
if (empty($this->queryLog[$logging_key])) {
$this->clear($logging_key);
}
}
/**
* Retrieve the query log for the specified logging key so far.
*
* @param $logging_key
* The logging key to fetch.
* @return
* An indexed array of all query records for this logging key.
*/
public function get($logging_key) {
return $this->queryLog[$logging_key];
}
/**
* Empty the query log for the specified logging key.
*
* This method does not stop logging, it simply clears the log. To stop
* logging, use the end() method.
*
* @param $logging_key
* The logging key to empty.
*/
public function clear($logging_key) {
$this->queryLog[$logging_key] = array();
}
/**
* Stop logging for the specified logging key.
*
* @param $logging_key
* The logging key to stop.
*/
public function end($logging_key) {
unset($this->queryLog[$logging_key]);
}
/**
* Log a query to all active logging keys.
*
* @param $statement
* The prepared statement object to log.
* @param $args
* The arguments passed to the statement object.
* @param $time
* The time in milliseconds the query took to execute.
*/
public function log(DatabaseStatementInterface $statement, $args, $time) {
foreach (array_keys($this->queryLog) as $key) {
$this->queryLog[$key][] = array(
'query' => $statement->getQueryString(),
'args' => $args,
'target' => $statement->dbh->getTarget(),
'caller' => $this->findCaller(),
'time' => $time,
);
}
}
/**
* Determine the routine that called this query.
*
* We define "the routine that called this query" as the first entry in
* the call stack that is not inside includes/database and does have a file
* (which excludes call_user_func_array(), anonymous functions and similar).
* That makes the climbing logic very simple, and handles the variable stack
* depth caused by the query builders.
*
* @link http://www.php.net/debug_backtrace
* @return
* This method returns a stack trace entry similar to that generated by
* debug_backtrace(). However, it flattens the trace entry and the trace
* entry before it so that we get the function and args of the function that
* called into the database system, not the function and args of the
* database call itself.
*/
public function findCaller() {
$stack = debug_backtrace();
$stack_count = count($stack);
for ($i = 0; $i < $stack_count; ++$i) {
if (!empty($stack[$i]['file']) && strpos($stack[$i]['file'], 'includes' . DIRECTORY_SEPARATOR . 'database') === FALSE) {
$stack[$i] += array('args' => array());
return array(
'file' => $stack[$i]['file'],
'line' => $stack[$i]['line'],
'function' => $stack[$i + 1]['function'],
'class' => isset($stack[$i + 1]['class']) ? $stack[$i + 1]['class'] : NULL,
'type' => isset($stack[$i + 1]['type']) ? $stack[$i + 1]['type'] : NULL,
'args' => $stack[$i + 1]['args'],
);
}
}
}
}
-202
View File
@@ -1,202 +0,0 @@
<?php
/**
* @file
* Database interface code for MySQL database servers.
*/
/**
* @addtogroup database
* @{
*/
class DatabaseConnection_mysql extends DatabaseConnection {
/**
* Flag to indicate if we have registered the nextID cleanup function.
*
* @var boolean
*/
protected $shutdownRegistered = FALSE;
public function __construct(array $connection_options = array()) {
// This driver defaults to transaction support, except if explicitly passed FALSE.
$this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
// MySQL never supports transactional DDL.
$this->transactionalDDLSupport = FALSE;
$this->connectionOptions = $connection_options;
// The DSN should use either a socket or a host/port.
if (isset($connection_options['unix_socket'])) {
$dsn = 'mysql:unix_socket=' . $connection_options['unix_socket'];
}
else {
// Default to TCP connection on port 3306.
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
}
$dsn .= ';dbname=' . $connection_options['database'];
// Allow PDO options to be overridden.
$connection_options += array(
'pdo' => array(),
);
$connection_options['pdo'] += array(
// So we don't have to mess around with cursors and unbuffered queries by default.
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
// Because MySQL's prepared statements skip the query cache, because it's dumb.
PDO::ATTR_EMULATE_PREPARES => TRUE,
);
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
// 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($connection_options['collation'])) {
$this->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']);
}
else {
$this->exec('SET NAMES utf8');
}
// Set MySQL init_commands if not already defined. Default Drupal's MySQL
// behavior to conform more closely to SQL standards. This allows Drupal
// to run almost seamlessly on many different kinds of database systems.
// These settings force MySQL to behave the same as postgresql, or sqlite
// in regards to syntax interpretation and invalid data handling. See
// http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
// changed the meaning of TRADITIONAL we need to spell out the modes one by
// one.
$connection_options += array(
'init_commands' => array(),
);
$connection_options['init_commands'] += array(
'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
);
// Set connection options.
$this->exec(implode('; ', $connection_options['init_commands']));
}
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
}
public function queryTemporary($query, array $args = array(), array $options = array()) {
$tablename = $this->generateTemporaryTableName();
$this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY SELECT', $query), $args, $options);
return $tablename;
}
public function driver() {
return 'mysql';
}
public function databaseType() {
return 'mysql';
}
public function mapConditionOperator($operator) {
// We don't want to override any of the defaults.
return NULL;
}
public function nextId($existing_id = 0) {
$new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
// This should only happen after an import or similar event.
if ($existing_id >= $new_id) {
// If we INSERT a value manually into the sequences table, on the next
// INSERT, MySQL will generate a larger value. However, there is no way
// of knowing whether this value already exists in the table. MySQL
// provides an INSERT IGNORE which would work, but that can mask problems
// other than duplicate keys. Instead, we use INSERT ... ON DUPLICATE KEY
// UPDATE in such a way that the UPDATE does not do anything. This way,
// duplicate keys do not generate errors but everything else does.
$this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id));
$new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID));
}
if (!$this->shutdownRegistered) {
// Use register_shutdown_function() here to keep the database system
// independent of Drupal.
register_shutdown_function(array($this, 'nextIdDelete'));
$shutdownRegistered = TRUE;
}
return $new_id;
}
public function nextIdDelete() {
// While we want to clean up the table to keep it up from occupying too
// much storage and memory, we must keep the highest value in the table
// because InnoDB uses an in-memory auto-increment counter as long as the
// server runs. When the server is stopped and restarted, InnoDB
// reinitializes the counter for each table for the first INSERT to the
// table based solely on values from the table so deleting all values would
// be a problem in this case. Also, TRUNCATE resets the auto increment
// counter.
try {
$max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField();
// We know we are using MySQL here, no need for the slower db_delete().
$this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id));
}
// During testing, this function is called from shutdown with the
// simpletest prefix stored in $this->connection, and those tables are gone
// by the time shutdown is called so we need to ignore the database
// errors. There is no problem with completely ignoring errors here: if
// these queries fail, the sequence will work just fine, just use a bit
// more database storage and memory.
catch (PDOException $e) {
}
}
/**
* Overridden to work around issues to MySQL not supporting transactional DDL.
*/
protected function popCommittableTransactions() {
// Commit all the committable layers.
foreach (array_reverse($this->transactionLayers) as $name => $active) {
// Stop once we found an active transaction.
if ($active) {
break;
}
// If there are no more layers left then we should commit.
unset($this->transactionLayers[$name]);
if (empty($this->transactionLayers)) {
if (!PDO::commit()) {
throw new DatabaseTransactionCommitFailedException();
}
}
else {
// Attempt to release this savepoint in the standard way.
try {
$this->query('RELEASE SAVEPOINT ' . $name);
}
catch (PDOException $e) {
// However, in MySQL (InnoDB), savepoints are automatically committed
// when tables are altered or created (DDL transactions are not
// supported). This can cause exceptions due to trying to release
// savepoints which no longer exist.
//
// To avoid exceptions when no actual error has occurred, we silently
// succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
if ($e->errorInfo[1] == '1305') {
// If one SAVEPOINT was released automatically, then all were.
// Therefore, clean the transaction stack.
$this->transactionLayers = array();
// We also have to explain to PDO that the transaction stack has
// been cleaned-up.
PDO::commit();
}
else {
throw $e;
}
}
}
}
}
}
/**
* @} End of "addtogroup database".
*/
-33
View File
@@ -1,33 +0,0 @@
<?php
/**
* @file
* Installation code for MySQL embedded database engine.
*/
/**
* Specifies installation tasks for MySQL and equivalent databases.
*/
class DatabaseTasks_mysql extends DatabaseTasks {
/**
* The PDO driver name for MySQL and equivalent databases.
*
* @var string
*/
protected $pdoDriver = 'mysql';
/**
* Returns a human-readable name string for MySQL and equivalent databases.
*/
public function name() {
return st('MySQL, MariaDB, or equivalent');
}
/**
* Returns the minimum version for MySQL.
*/
public function minimumVersion() {
return '5.0.15';
}
}
-107
View File
@@ -1,107 +0,0 @@
<?php
/**
* @addtogroup database
* @{
*/
/**
* @file
* Query code for MySQL embedded database engine.
*/
class InsertQuery_mysql extends InsertQuery {
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
// If we're selecting from a SelectQuery, finish building the query and
// pass it back, as any remaining options are irrelevant.
if (empty($this->fromQuery)) {
$max_placeholder = 0;
$values = array();
foreach ($this->insertValues as $insert_values) {
foreach ($insert_values as $value) {
$values[':db_insert_placeholder_' . $max_placeholder++] = $value;
}
}
}
else {
$values = $this->fromQuery->getArguments();
}
$last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions);
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = array();
return $last_insert_id;
}
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
// If we're selecting from a SelectQuery, finish building the query and
// pass it back, as any remaining options are irrelevant.
if (!empty($this->fromQuery)) {
return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
}
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$max_placeholder = 0;
$values = array();
if (count($this->insertValues)) {
foreach ($this->insertValues as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$query .= implode(', ', $values);
return $query;
}
}
class TruncateQuery_mysql extends TruncateQuery {
public function __toString() {
// TRUNCATE is actually a DDL statement on MySQL, and DDL statements are
// not transactional, and result in an implicit COMMIT. When we are in a
// transaction, fallback to the slower, but transactional, DELETE.
if ($this->connection->inTransaction()) {
// Create a comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}';
}
else {
return parent::__toString();
}
}
}
/**
* @} End of "addtogroup database".
*/
-536
View File
@@ -1,536 +0,0 @@
<?php
/**
* @file
* Database schema code for MySQL database servers.
*/
/**
* @addtogroup schemaapi
* @{
*/
class DatabaseSchema_mysql extends DatabaseSchema {
/**
* Maximum length of a table comment in MySQL.
*/
const COMMENT_MAX_TABLE = 60;
/**
* Maximum length of a column comment in MySQL.
*/
const COMMENT_MAX_COLUMN = 255;
/**
* Get information about the table and database name from the prefix.
*
* @return
* A keyed array with information about the database, table name and prefix.
*/
protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
$info = array('prefix' => $this->connection->tablePrefix($table));
if ($add_prefix) {
$table = $info['prefix'] . $table;
}
if (($pos = strpos($table, '.')) !== FALSE) {
$info['database'] = substr($table, 0, $pos);
$info['table'] = substr($table, ++$pos);
}
else {
$db_info = Database::getConnectionInfo();
$info['database'] = $db_info['default']['database'];
$info['table'] = $table;
}
return $info;
}
/**
* Build a condition to match a table name against a standard information_schema.
*
* MySQL uses databases like schemas rather than catalogs so when we build
* a condition to query the information_schema.tables, we set the default
* database as the schema unless specified otherwise, and exclude table_catalog
* from the condition criteria.
*/
protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
$info = $this->connection->getConnectionOptions();
$table_info = $this->getPrefixInfo($table_name, $add_prefix);
$condition = new DatabaseCondition('AND');
$condition->condition('table_schema', $table_info['database']);
$condition->condition('table_name', $table_info['table'], $operator);
return $condition;
}
/**
* 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.
*/
protected function createTableSql($name, $table) {
$info = $this->connection->getConnectionOptions();
// Provide defaults if needed.
$table += array(
'mysql_engine' => 'InnoDB',
'mysql_character_set' => 'utf8',
);
$sql = "CREATE TABLE {" . $name . "} (\n";
// Add the SQL statement for each field.
foreach ($table['fields'] as $field_name => $field) {
$sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n";
}
// Process keys & indexes.
$keys = $this->createKeysSql($table);
if (count($keys)) {
$sql .= implode(", \n", $keys) . ", \n";
}
// Remove the last comma and space.
$sql = substr($sql, 0, -3) . "\n) ";
$sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set'];
// 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 DatabaseConnection_mysql
if (!empty($info['collation'])) {
$sql .= ' COLLATE ' . $info['collation'];
}
// Add table comment.
if (!empty($table['description'])) {
$sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
}
return array($sql);
}
/**
* 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.
*/
protected function createFieldSql($name, $spec) {
$sql = "`" . $name . "` " . $spec['mysql_type'];
if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT'))) {
if (isset($spec['length'])) {
$sql .= '(' . $spec['length'] . ')';
}
if (!empty($spec['binary'])) {
$sql .= ' BINARY';
}
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
}
if (!empty($spec['unsigned'])) {
$sql .= ' unsigned';
}
if (isset($spec['not null'])) {
if ($spec['not null']) {
$sql .= ' NOT NULL';
}
else {
$sql .= ' NULL';
}
}
if (!empty($spec['auto_increment'])) {
$sql .= ' auto_increment';
}
// $spec['default'] can be NULL, so we explicitly check for the key here.
if (array_key_exists('default', $spec)) {
if (is_string($spec['default'])) {
$spec['default'] = "'" . $spec['default'] . "'";
}
elseif (!isset($spec['default'])) {
$spec['default'] = 'NULL';
}
$sql .= ' DEFAULT ' . $spec['default'];
}
if (empty($spec['not null']) && !isset($spec['default'])) {
$sql .= ' DEFAULT NULL';
}
// Add column comment.
if (!empty($spec['description'])) {
$sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN);
}
return $sql;
}
/**
* Set database-engine specific properties for a field.
*
* @param $field
* A field description array, as specified in the schema documentation.
*/
protected function processField($field) {
if (!isset($field['size'])) {
$field['size'] = 'normal';
}
// Set the correct database-engine specific datatype.
// In case one is already provided, force it to uppercase.
if (isset($field['mysql_type'])) {
$field['mysql_type'] = drupal_strtoupper($field['mysql_type']);
}
else {
$map = $this->getFieldTypeMap();
$field['mysql_type'] = $map[$field['type'] . ':' . $field['size']];
}
if (isset($field['type']) && $field['type'] == 'serial') {
$field['auto_increment'] = TRUE;
}
return $field;
}
public function getFieldTypeMap() {
// 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 does not use drupal_static as its value never changes.
static $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',
);
return $map;
}
protected function createKeysSql($spec) {
$keys = array();
if (!empty($spec['primary key'])) {
$keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')';
}
if (!empty($spec['unique keys'])) {
foreach ($spec['unique keys'] as $key => $fields) {
$keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeysSqlHelper($fields) . ')';
}
}
if (!empty($spec['indexes'])) {
foreach ($spec['indexes'] as $index => $fields) {
$keys[] = 'INDEX `' . $index . '` (' . $this->createKeysSqlHelper($fields) . ')';
}
}
return $keys;
}
protected function createKeySql($fields) {
$return = array();
foreach ($fields as $field) {
if (is_array($field)) {
$return[] = '`' . $field[0] . '`(' . $field[1] . ')';
}
else {
$return[] = '`' . $field . '`';
}
}
return implode(', ', $return);
}
protected function createKeysSqlHelper($fields) {
$return = array();
foreach ($fields as $field) {
if (is_array($field)) {
$return[] = '`' . $field[0] . '`(' . $field[1] . ')';
}
else {
$return[] = '`' . $field . '`';
}
}
return implode(', ', $return);
}
public function renameTable($table, $new_name) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name)));
}
if ($this->tableExists($new_name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name)));
}
$info = $this->getPrefixInfo($new_name);
return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`');
}
public function dropTable($table) {
if (!$this->tableExists($table)) {
return FALSE;
}
$this->connection->query('DROP TABLE {' . $table . '}');
return TRUE;
}
public function addField($table, $field, $spec, $keys_new = array()) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table)));
}
if ($this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table)));
}
$fixnull = FALSE;
if (!empty($spec['not null']) && !isset($spec['default'])) {
$fixnull = TRUE;
$spec['not null'] = FALSE;
}
$query = 'ALTER TABLE {' . $table . '} ADD ';
$query .= $this->createFieldSql($field, $this->processField($spec));
if ($keys_sql = $this->createKeysSql($keys_new)) {
$query .= ', ADD ' . implode(', ADD ', $keys_sql);
}
$this->connection->query($query);
if (isset($spec['initial'])) {
$this->connection->update($table)
->fields(array($field => $spec['initial']))
->execute();
}
if ($fixnull) {
$spec['not null'] = TRUE;
$this->changeField($table, $field, $field, $spec);
}
}
public function dropField($table, $field) {
if (!$this->fieldExists($table, $field)) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`');
return TRUE;
}
public function fieldSetDefault($table, $field, $default) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
}
if (!isset($default)) {
$default = 'NULL';
}
else {
$default = is_string($default) ? "'$default'" : $default;
}
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $default);
}
public function fieldSetNoDefault($table, $field) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
}
public function indexExists($table, $name) {
// Returns one row for each column in the index. Result is string or FALSE.
// Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
$row = $this->connection->query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'")->fetchAssoc();
return isset($row['Key_name']);
}
public function addPrimaryKey($table, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table)));
}
if ($this->indexExists($table, 'PRIMARY')) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')');
}
public function dropPrimaryKey($table) {
if (!$this->indexExists($table, 'PRIMARY')) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY');
return TRUE;
}
public function addUniqueKey($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
}
if ($this->indexExists($table, $name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')');
}
public function dropUniqueKey($table, $name) {
if (!$this->indexExists($table, $name)) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`');
return TRUE;
}
public function addIndex($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
}
if ($this->indexExists($table, $name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')');
}
public function dropIndex($table, $name) {
if (!$this->indexExists($table, $name)) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`');
return TRUE;
}
public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field)));
}
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new)));
}
$sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec));
if ($keys_sql = $this->createKeysSql($keys_new)) {
$sql .= ', ADD ' . implode(', ADD ', $keys_sql);
}
$this->connection->query($sql);
}
public function prepareComment($comment, $length = NULL) {
// Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125
$comment = str_replace("'", '', $comment);
// Truncate comment to maximum comment length.
if (isset($length)) {
// Add table prefixes before truncating.
$comment = truncate_utf8($this->connection->prefixTables($comment), $length, TRUE, TRUE);
}
return $this->connection->quote($comment);
}
/**
* Retrieve a table or column comment.
*/
public function getComment($table, $column = NULL) {
$condition = $this->buildTableNameCondition($table);
if (isset($column)) {
$condition->condition('column_name', $column);
$condition->compile($this->connection, $this);
// Don't use {} around information_schema.columns table.
return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
}
$condition->compile($this->connection, $this);
// Don't use {} around information_schema.tables table.
$comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
// Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379
return preg_replace('/; InnoDB free:.*$/', '', $comment);
}
public function tableExists($table) {
// The information_schema table is very slow to query under MySQL 5.0.
// Instead, we try to select from the table in question. If it fails,
// the most likely reason is that it does not exist. That is dramatically
// faster than using information_schema.
// @link http://bugs.mysql.com/bug.php?id=19588
// @todo: This override should be removed once we require a version of MySQL
// that has that bug fixed.
try {
$this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1);
return TRUE;
}
catch (Exception $e) {
return FALSE;
}
}
public function fieldExists($table, $column) {
// The information_schema table is very slow to query under MySQL 5.0.
// Instead, we try to select from the table and field in question. If it
// fails, the most likely reason is that it does not exist. That is
// dramatically faster than using information_schema.
// @link http://bugs.mysql.com/bug.php?id=19588
// @todo: This override should be removed once we require a version of MySQL
// that has that bug fixed.
try {
$this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1);
return TRUE;
}
catch (Exception $e) {
return FALSE;
}
}
}
/**
* @} End of "addtogroup schemaapi".
*/
-223
View File
@@ -1,223 +0,0 @@
<?php
/**
* @file
* Database interface code for PostgreSQL database servers.
*/
/**
* @addtogroup database
* @{
*/
/**
* The name by which to obtain a lock for retrive the next insert id.
*/
define('POSTGRESQL_NEXTID_LOCK', 1000);
class DatabaseConnection_pgsql extends DatabaseConnection {
public function __construct(array $connection_options = array()) {
// This driver defaults to transaction support, except if explicitly passed FALSE.
$this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE);
// Transactional DDL is always available in PostgreSQL,
// but we'll only enable it if standard transactions are.
$this->transactionalDDLSupport = $this->transactionSupport;
// Default to TCP connection on port 5432.
if (empty($connection_options['port'])) {
$connection_options['port'] = 5432;
}
// PostgreSQL in trust mode doesn't require a password to be supplied.
if (empty($connection_options['password'])) {
$connection_options['password'] = NULL;
}
// If the password contains a backslash it is treated as an escape character
// http://bugs.php.net/bug.php?id=53217
// so backslashes in the password need to be doubled up.
// The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords
// will break on this doubling up when the bug is fixed, so check the version
//elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') {
else {
$connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']);
}
$this->connectionOptions = $connection_options;
$dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
// Allow PDO options to be overridden.
$connection_options += array(
'pdo' => array(),
);
$connection_options['pdo'] += array(
// Prepared statements are most effective for performance when queries
// are recycled (used several times). However, if they are not re-used,
// prepared statements become ineffecient. Since most of Drupal's
// prepared queries are not re-used, it should be faster to emulate
// the preparation than to actually ready statements for re-use. If in
// doubt, reset to FALSE and measure performance.
PDO::ATTR_EMULATE_PREPARES => TRUE,
// Convert numeric values to strings when fetching.
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
);
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
// Force PostgreSQL to use the UTF-8 character set by default.
$this->exec("SET NAMES 'UTF8'");
// Execute PostgreSQL init_commands.
if (isset($connection_options['init_commands'])) {
$this->exec(implode('; ', $connection_options['init_commands']));
}
}
public function prepareQuery($query) {
// mapConditionOperator converts LIKE operations to ILIKE for consistency
// with MySQL. However, Postgres does not support ILIKE on bytea (blobs)
// fields.
// To make the ILIKE operator work, we type-cast bytea fields into text.
// @todo This workaround only affects bytea fields, but the involved field
// types involved in the query are unknown, so there is no way to
// conditionally execute this for affected queries only.
return parent::prepareQuery(preg_replace('/ ([^ ]+) +(I*LIKE|NOT +I*LIKE) /i', ' ${1}::text ${2} ', $query));
}
public function query($query, array $args = array(), $options = array()) {
$options += $this->defaultOptions();
// The PDO PostgreSQL driver has a bug which
// doesn't type cast booleans correctly when
// parameters are bound using associative
// arrays.
// See http://bugs.php.net/bug.php?id=48383
foreach ($args as &$value) {
if (is_bool($value)) {
$value = (int) $value;
}
}
try {
if ($query instanceof DatabaseStatementInterface) {
$stmt = $query;
$stmt->execute(NULL, $options);
}
else {
$this->expandArguments($query, $args);
$stmt = $this->prepareQuery($query);
$stmt->execute($args, $options);
}
switch ($options['return']) {
case Database::RETURN_STATEMENT:
return $stmt;
case Database::RETURN_AFFECTED:
return $stmt->rowCount();
case Database::RETURN_INSERT_ID:
return $this->lastInsertId($options['sequence_name']);
case Database::RETURN_NULL:
return;
default:
throw new PDOException('Invalid return directive: ' . $options['return']);
}
}
catch (PDOException $e) {
if ($options['throw_exception']) {
// Add additional debug information.
if ($query instanceof DatabaseStatementInterface) {
$e->query_string = $stmt->getQueryString();
}
else {
$e->query_string = $query;
}
$e->args = $args;
throw $e;
}
return NULL;
}
}
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options);
}
public function queryTemporary($query, array $args = array(), array $options = array()) {
$tablename = $this->generateTemporaryTableName();
$this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options);
return $tablename;
}
public function driver() {
return 'pgsql';
}
public function databaseType() {
return 'pgsql';
}
public function mapConditionOperator($operator) {
static $specials;
// Function calls not allowed in static declarations, thus this method.
if (!isset($specials)) {
$specials = array(
// In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
// statements, we need to use ILIKE instead.
'LIKE' => array('operator' => 'ILIKE'),
'NOT LIKE' => array('operator' => 'NOT ILIKE'),
);
}
return isset($specials[$operator]) ? $specials[$operator] : NULL;
}
/**
* Retrive a the next id in a sequence.
*
* PostgreSQL has built in sequences. We'll use these instead of inserting
* and updating a sequences table.
*/
public function nextId($existing = 0) {
// Retrive the name of the sequence. This information cannot be cached
// because the prefix may change, for example, like it does in simpletests.
$sequence_name = $this->makeSequenceName('sequences', 'value');
// When PostgreSQL gets a value too small then it will lock the table,
// retry the INSERT and if it's still too small then alter the sequence.
$id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
if ($id > $existing) {
return $id;
}
// PostgreSQL advisory locks are simply locks to be used by an
// application such as Drupal. This will prevent other Drupal proccesses
// from altering the sequence while we are.
$this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")");
// While waiting to obtain the lock, the sequence may have been altered
// so lets try again to obtain an adequate value.
$id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
if ($id > $existing) {
$this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
return $id;
}
// Reset the sequence to a higher value than the existing id.
$this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1));
// Retrive the next id. We know this will be as high as we want it.
$id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField();
$this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")");
return $id;
}
}
/**
* @} End of "addtogroup database".
*/
-197
View File
@@ -1,197 +0,0 @@
<?php
/**
* @file
* Install functions for PostgreSQL embedded database engine.
*/
// PostgreSQL specific install functions
class DatabaseTasks_pgsql extends DatabaseTasks {
protected $pdoDriver = 'pgsql';
public function __construct() {
$this->tasks[] = array(
'function' => 'checkEncoding',
'arguments' => array(),
);
$this->tasks[] = array(
'function' => 'checkPHPVersion',
'arguments' => array(),
);
$this->tasks[] = array(
'function' => 'checkBinaryOutput',
'arguments' => array(),
);
$this->tasks[] = array(
'function' => 'initializeDatabase',
'arguments' => array(),
);
}
public function name() {
return st('PostgreSQL');
}
public function minimumVersion() {
return '8.3';
}
/**
* Check encoding is UTF8.
*/
protected function checkEncoding() {
try {
if (db_query('SHOW server_encoding')->fetchField() == 'UTF8') {
$this->pass(st('Database is encoded in UTF-8'));
}
else {
$replacements = array(
'%encoding' => 'UTF8',
'%driver' => $this->name(),
'!link' => '<a href="INSTALL.pgsql.txt">INSTALL.pgsql.txt</a>'
);
$text = 'The %driver database must use %encoding encoding to work with Drupal.';
$text .= 'Recreate the database with %encoding encoding. See !link for more details.';
$this->fail(st($text, $replacements));
}
}
catch (Exception $e) {
$this->fail(st('Drupal could not determine the encoding of the database was set to UTF-8'));
}
}
/**
* Check PHP version.
*
* There are two bugs in PDO_pgsql affecting Drupal:
*
* - in versions < 5.2.7, PDO_pgsql refuses to insert an empty string into
* a NOT NULL BLOB column. See: http://bugs.php.net/bug.php?id=46249
* - in versions < 5.2.11 and < 5.3.1 that prevents inserting integer values
* into numeric columns that exceed the PHP_INT_MAX value.
* See: http://bugs.php.net/bug.php?id=48924
*/
function checkPHPVersion() {
if (!version_compare(PHP_VERSION, '5.2.11', '>=') || (version_compare(PHP_VERSION, '5.3.0', '>=') && !version_compare(PHP_VERSION, '5.3.1', '>='))) {
$this->fail(st('The version of PHP you are using has known issues with PostgreSQL. You need to upgrade PHP to 5.2.11, 5.3.1 or greater.'));
};
}
/**
* Check Binary Output.
*
* Unserializing does not work on Postgresql 9 when bytea_output is 'hex'.
*/
function checkBinaryOutput() {
// PostgreSQL < 9 doesn't support bytea_output, so verify we are running
// at least PostgreSQL 9.
$database_connection = Database::getConnection();
if (version_compare($database_connection->version(), '9') >= 0) {
if (!$this->checkBinaryOutputSuccess()) {
// First try to alter the database. If it fails, raise an error telling
// the user to do it themselves.
$connection_options = $database_connection->getConnectionOptions();
// It is safe to include the database name directly here, because this
// code is only called when a connection to the database is already
// established, thus the database name is guaranteed to be a correct
// value.
$query = "ALTER DATABASE \"" . $connection_options['database'] . "\" SET bytea_output = 'escape';";
try {
db_query($query);
}
catch (Exception $e) {
// Ignore possible errors when the user doesn't have the necessary
// privileges to ALTER the database.
}
// Close the database connection so that the configuration parameter
// is applied to the current connection.
db_close();
// Recheck, if it fails, finally just rely on the end user to do the
// right thing.
if (!$this->checkBinaryOutputSuccess()) {
$replacements = array(
'%setting' => 'bytea_output',
'%current_value' => 'hex',
'%needed_value' => 'escape',
'!query' => "<code>" . $query . "</code>",
);
$this->fail(st("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: !query", $replacements));
}
}
}
}
/**
* Verify that a binary data roundtrip returns the original string.
*/
protected function checkBinaryOutputSuccess() {
$bytea_output = db_query("SELECT 'encoding'::bytea AS output")->fetchField();
return ($bytea_output == 'encoding');
}
/**
* Make PostgreSQL Drupal friendly.
*/
function initializeDatabase() {
// We create some functions using global names instead of prefixing them
// like we do with table names. This is so that we don't double up if more
// than one instance of Drupal is running on a single database. We therefore
// avoid trying to create them again in that case.
try {
// Create functions.
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
\'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
LANGUAGE \'sql\''
);
db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
\'SELECT greatest($1, greatest($2, $3));\'
LANGUAGE \'sql\''
);
// Don't use {} around pg_proc table.
if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
\'SELECT random();\'
LANGUAGE \'sql\''
);
}
db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
\'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\'
LANGUAGE \'sql\''
);
// Using || to concatenate in Drupal is not recommeneded because there are
// database drivers for Drupal that do not support the syntax, however
// they do support CONCAT(item1, item2) which we can replicate in
// PostgreSQL. PostgreSQL requires the function to be defined for each
// different argument variation the function can handle.
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS
\'SELECT CAST($1 AS text) || CAST($2 AS text);\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS
\'SELECT $1 || CAST($2 AS text);\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS
\'SELECT CAST($1 AS text) || $2;\'
LANGUAGE \'sql\'
');
db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS
\'SELECT $1 || $2;\'
LANGUAGE \'sql\'
');
$this->pass(st('PostgreSQL has initialized itself.'));
}
catch (Exception $e) {
$this->fail(st('Drupal could not be correctly setup with the existing database. Revise any errors.'));
}
}
}
-209
View File
@@ -1,209 +0,0 @@
<?php
/**
* @ingroup database
* @{
*/
/**
* @file
* Query code for PostgreSQL embedded database engine.
*/
class InsertQuery_pgsql extends InsertQuery {
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
$stmt = $this->connection->prepareQuery((string) $this);
// Fetch the list of blobs and sequences used on that table.
$table_information = $this->connection->schema()->queryTableInformation($this->table);
$max_placeholder = 0;
$blobs = array();
$blob_count = 0;
foreach ($this->insertValues as $insert_values) {
foreach ($this->insertFields as $idx => $field) {
if (isset($table_information->blob_fields[$field])) {
$blobs[$blob_count] = fopen('php://memory', 'a');
fwrite($blobs[$blob_count], $insert_values[$idx]);
rewind($blobs[$blob_count]);
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], PDO::PARAM_LOB);
// Pre-increment is faster in PHP than increment.
++$blob_count;
}
else {
$stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]);
}
}
// Check if values for a serial field has been passed.
if (!empty($table_information->serial_fields)) {
foreach ($table_information->serial_fields as $index => $serial_field) {
$serial_key = array_search($serial_field, $this->insertFields);
if ($serial_key !== FALSE) {
$serial_value = $insert_values[$serial_key];
// Force $last_insert_id to the specified value. This is only done
// if $index is 0.
if ($index == 0) {
$last_insert_id = $serial_value;
}
// Set the sequence to the bigger value of either the passed
// value or the max value of the column. It can happen that another
// thread calls nextval() which could lead to a serial number being
// used twice. However, trying to insert a value into a serial
// column should only be done in very rare cases and is not thread
// safe by definition.
$this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", array(':serial_value' => (int)$serial_value));
}
}
}
}
if (!empty($this->fromQuery)) {
// bindParam stores only a reference to the variable that is followed when
// the statement is executed. We pass $arguments[$key] instead of $value
// because the second argument to bindParam is passed by reference and
// the foreach statement assigns the element to the existing reference.
$arguments = $this->fromQuery->getArguments();
foreach ($arguments as $key => $value) {
$stmt->bindParam($key, $arguments[$key]);
}
}
// PostgreSQL requires the table name to be specified explicitly
// when requesting the last insert ID, so we pass that in via
// the options array.
$options = $this->queryOptions;
if (!empty($table_information->sequences)) {
$options['sequence_name'] = $table_information->sequences[0];
}
// If there are no sequences then we can't get a last insert id.
elseif ($options['return'] == Database::RETURN_INSERT_ID) {
$options['return'] = Database::RETURN_NULL;
}
// Only use the returned last_insert_id if it is not already set.
if (!empty($last_insert_id)) {
$this->connection->query($stmt, array(), $options);
}
else {
$last_insert_id = $this->connection->query($stmt, array(), $options);
}
// Re-initialize the values array so that we can re-use this query.
$this->insertValues = array();
return $last_insert_id;
}
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields);
// If we're selecting from a SelectQuery, finish building the query and
// pass it back, as any remaining options are irrelevant.
if (!empty($this->fromQuery)) {
return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery;
}
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
$max_placeholder = 0;
$values = array();
if (count($this->insertValues)) {
foreach ($this->insertValues as $insert_values) {
$placeholders = array();
// Default fields aren't really placeholders, but this is the most convenient
// way to handle them.
$placeholders = array_pad($placeholders, count($this->defaultFields), 'default');
$new_placeholder = $max_placeholder + count($insert_values);
for ($i = $max_placeholder; $i < $new_placeholder; ++$i) {
$placeholders[] = ':db_insert_placeholder_' . $i;
}
$max_placeholder = $new_placeholder;
$values[] = '(' . implode(', ', $placeholders) . ')';
}
}
else {
// If there are no values, then this is a default-only query. We still need to handle that.
$placeholders = array_fill(0, count($this->defaultFields), 'default');
$values[] = '(' . implode(', ', $placeholders) . ')';
}
$query .= implode(', ', $values);
return $query;
}
}
class UpdateQuery_pgsql extends UpdateQuery {
public function execute() {
$max_placeholder = 0;
$blobs = array();
$blob_count = 0;
// Because we filter $fields the same way here and in __toString(), the
// placeholders will all match up properly.
$stmt = $this->connection->prepareQuery((string) $this);
// Fetch the list of blobs and sequences used on that table.
$table_information = $this->connection->schema()->queryTableInformation($this->table);
// Expressions take priority over literal fields, so we process those first
// and remove any literal fields that conflict.
$fields = $this->fields;
$expression_fields = array();
foreach ($this->expressionFields as $field => $data) {
if (!empty($data['arguments'])) {
foreach ($data['arguments'] as $placeholder => $argument) {
// We assume that an expression will never happen on a BLOB field,
// which is a fairly safe assumption to make since in most cases
// it would be an invalid query anyway.
$stmt->bindParam($placeholder, $data['arguments'][$placeholder]);
}
}
unset($fields[$field]);
}
foreach ($fields as $field => $value) {
$placeholder = ':db_update_placeholder_' . ($max_placeholder++);
if (isset($table_information->blob_fields[$field])) {
$blobs[$blob_count] = fopen('php://memory', 'a');
fwrite($blobs[$blob_count], $value);
rewind($blobs[$blob_count]);
$stmt->bindParam($placeholder, $blobs[$blob_count], PDO::PARAM_LOB);
++$blob_count;
}
else {
$stmt->bindParam($placeholder, $fields[$field]);
}
}
if (count($this->condition)) {
$this->condition->compile($this->connection, $this);
$arguments = $this->condition->arguments();
foreach ($arguments as $placeholder => $value) {
$stmt->bindParam($placeholder, $arguments[$placeholder]);
}
}
$options = $this->queryOptions;
$options['already_prepared'] = TRUE;
$this->connection->query($stmt, $options);
return $stmt->rowCount();
}
}
-617
View File
@@ -1,617 +0,0 @@
<?php
/**
* @file
* Database schema code for PostgreSQL database servers.
*/
/**
* @ingroup schemaapi
* @{
*/
class DatabaseSchema_pgsql extends DatabaseSchema {
/**
* A cache of information about blob columns and sequences of tables.
*
* This is collected by DatabaseConnection_pgsql->queryTableInformation(),
* by introspecting the database.
*
* @see DatabaseConnection_pgsql->queryTableInformation()
* @var array
*/
protected $tableInformation = array();
/**
* Fetch the list of blobs and sequences used on a table.
*
* We introspect the database to collect the information required by insert
* and update queries.
*
* @param $table_name
* The non-prefixed name of the table.
* @return
* An object with two member variables:
* - 'blob_fields' that lists all the blob fields in the table.
* - 'sequences' that lists the sequences used in that table.
*/
public function queryTableInformation($table) {
// Generate a key to reference this table's information on.
$key = $this->connection->prefixTables('{' . $table . '}');
if (!strpos($key, '.')) {
$key = 'public.' . $key;
}
if (!isset($this->tableInformation[$key])) {
// Split the key into schema and table for querying.
list($schema, $table_name) = explode('.', $key);
$table_information = (object) array(
'blob_fields' => array(),
'sequences' => array(),
);
// Don't use {} around information_schema.columns table.
$result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(
':schema' => $schema,
':table' => $table_name,
':default' => '%nextval%',
));
foreach ($result as $column) {
if ($column->data_type == 'bytea') {
$table_information->blob_fields[$column->column_name] = TRUE;
}
elseif (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) {
// We must know of any sequences in the table structure to help us
// return the last insert id. If there is more than 1 sequences the
// first one (index 0 of the sequences array) will be used.
$table_information->sequences[] = $matches[1];
$table_information->serial_fields[] = $column->column_name;
}
}
$this->tableInformation[$key] = $table_information;
}
return $this->tableInformation[$key];
}
/**
* Fetch the list of CHECK constraints used on a field.
*
* We introspect the database to collect the information required by field
* alteration.
*
* @param $table
* The non-prefixed name of the table.
* @param $field
* The name of the field.
* @return
* An array of all the checks for the field.
*/
public function queryFieldInformation($table, $field) {
$prefixInfo = $this->getPrefixInfo($table, TRUE);
// Split the key into schema and table for querying.
$schema = $prefixInfo['schema'];
$table_name = $prefixInfo['table'];
$field_information = (object) array(
'checks' => array(),
);
$checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array(
':schema' => $schema,
':table' => $table_name,
':column' => $field,
));
$field_information = $checks->fetchCol();
return $field_information;
}
/**
* 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.
*/
protected function createTableSql($name, $table) {
$sql_fields = array();
foreach ($table['fields'] as $field_name => $field) {
$sql_fields[] = $this->createFieldSql($field_name, $this->processField($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 ' . $this->prefixNonTable($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[] = $this->_createIndexSql($name, $key_name, $key);
}
}
// Add table comment.
if (!empty($table['description'])) {
$statements[] = 'COMMENT ON TABLE {' . $name . '} IS ' . $this->prepareComment($table['description']);
}
// Add column comments.
foreach ($table['fields'] as $field_name => $field) {
if (!empty($field['description'])) {
$statements[] = 'COMMENT ON COLUMN {' . $name . '}.' . $field_name . ' IS ' . $this->prepareComment($field['description']);
}
}
return $statements;
}
/**
* 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.
*/
protected function createFieldSql($name, $spec) {
$sql = $name . ' ' . $spec['pgsql_type'];
if (isset($spec['type']) && $spec['type'] == 'serial') {
unset($spec['not null']);
}
if (in_array($spec['pgsql_type'], array('varchar', 'character', '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'])) {
if ($spec['not null']) {
$sql .= ' NOT NULL';
}
else {
$sql .= ' NULL';
}
}
if (isset($spec['default'])) {
$default = is_string($spec['default']) ? "'" . $spec['default'] . "'" : $spec['default'];
$sql .= " default $default";
}
return $sql;
}
/**
* Set database-engine specific properties for a field.
*
* @param $field
* A field description array, as specified in the schema documentation.
*/
protected function processField($field) {
if (!isset($field['size'])) {
$field['size'] = 'normal';
}
// Set the correct database-engine specific datatype.
// In case one is already provided, force it to lowercase.
if (isset($field['pgsql_type'])) {
$field['pgsql_type'] = drupal_strtolower($field['pgsql_type']);
}
else {
$map = $this->getFieldTypeMap();
$field['pgsql_type'] = $map[$field['type'] . ':' . $field['size']];
}
if (!empty($field['unsigned'])) {
// Unsigned datatypes are not supported in PostgreSQL 8.3. In MySQL,
// they are used to ensure a positive number is inserted and it also
// doubles the maximum integer size that can be stored in a field.
// The PostgreSQL schema in Drupal creates a check constraint
// to ensure that a value inserted is >= 0. To provide the extra
// integer capacity, here, we bump up the column field size.
if (!isset($map)) {
$map = $this->getFieldTypeMap();
}
switch ($field['pgsql_type']) {
case 'smallint':
$field['pgsql_type'] = $map['int:medium'];
break;
case 'int' :
$field['pgsql_type'] = $map['int:big'];
break;
}
}
if (isset($field['type']) && $field['type'] == 'serial') {
unset($field['not null']);
}
return $field;
}
/**
* This maps a generic data type in combination with its data size
* to the engine-specific data type.
*/
function getFieldTypeMap() {
// 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 does not use drupal_static as its value never changes.
static $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',
'serial:tiny' => 'serial',
'serial:small' => 'serial',
'serial:medium' => 'serial',
'serial:big' => 'bigserial',
'serial:normal' => 'serial',
);
return $map;
}
protected function _createKeySql($fields) {
$return = array();
foreach ($fields as $field) {
if (is_array($field)) {
$return[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')';
}
else {
$return[] = '"' . $field . '"';
}
}
return implode(', ', $return);
}
function renameTable($table, $new_name) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name)));
}
if ($this->tableExists($new_name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name)));
}
// Get the schema and tablename for the old table.
$old_full_name = $this->connection->prefixTables('{' . $table . '}');
list($old_schema, $old_table_name) = strpos($old_full_name, '.') ? explode('.', $old_full_name) : array('public', $old_full_name);
// Index names and constraint names are global in PostgreSQL, so we need to
// rename them when renaming the table.
$indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name));
foreach ($indexes as $index) {
if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)$/', $index->indexname, $matches)) {
$index_name = $matches[1];
$this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name);
}
}
// Now rename the table.
// Ensure the new table name does not include schema syntax.
$prefixInfo = $this->getPrefixInfo($new_name);
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $prefixInfo['table']);
}
public function dropTable($table) {
if (!$this->tableExists($table)) {
return FALSE;
}
$this->connection->query('DROP TABLE {' . $table . '}');
return TRUE;
}
public function addField($table, $field, $spec, $new_keys = array()) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table)));
}
if ($this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table)));
}
$fixnull = FALSE;
if (!empty($spec['not null']) && !isset($spec['default'])) {
$fixnull = TRUE;
$spec['not null'] = FALSE;
}
$query = 'ALTER TABLE {' . $table . '} ADD COLUMN ';
$query .= $this->createFieldSql($field, $this->processField($spec));
$this->connection->query($query);
if (isset($spec['initial'])) {
$this->connection->update($table)
->fields(array($field => $spec['initial']))
->execute();
}
if ($fixnull) {
$this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL");
}
if (isset($new_keys)) {
$this->_createKeys($table, $new_keys);
}
// Add column comment.
if (!empty($spec['description'])) {
$this->connection->query('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description']));
}
}
public function dropField($table, $field) {
if (!$this->fieldExists($table, $field)) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"');
return TRUE;
}
public function fieldSetDefault($table, $field, $default) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
}
if (!isset($default)) {
$default = 'NULL';
}
else {
$default = is_string($default) ? "'$default'" : $default;
}
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default);
}
public function fieldSetNoDefault($table, $field) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
}
public function indexExists($table, $name) {
// Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html
$index_name = '{' . $table . '}_' . $name . '_idx';
return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField();
}
/**
* Helper function: check if a constraint (PK, FK, UK) exists.
*
* @param $table
* The name of the table.
* @param $name
* The name of the constraint (typically 'pkey' or '[constraint]_key').
*/
protected function constraintExists($table, $name) {
$constraint_name = '{' . $table . '}_' . $name;
return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField();
}
public function addPrimaryKey($table, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table)));
}
if ($this->constraintExists($table, 'pkey')) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')');
}
public function dropPrimaryKey($table) {
if (!$this->constraintExists($table, 'pkey')) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->prefixNonTable($table, 'pkey'));
return TRUE;
}
function addUniqueKey($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
}
if ($this->constraintExists($table, $name . '_key')) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name)));
}
$this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')');
}
public function dropUniqueKey($table, $name) {
if (!$this->constraintExists($table, $name . '_key')) {
return FALSE;
}
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '"');
return TRUE;
}
public function addIndex($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
}
if ($this->indexExists($table, $name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name)));
}
$this->connection->query($this->_createIndexSql($table, $name, $fields));
}
public function dropIndex($table, $name) {
if (!$this->indexExists($table, $name)) {
return FALSE;
}
$this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx'));
return TRUE;
}
public function changeField($table, $field, $field_new, $spec, $new_keys = array()) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field)));
}
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new)));
}
$spec = $this->processField($spec);
// We need to typecast the new column to best be able to transfer the data
// Schema_pgsql::getFieldTypeMap() will return possibilities that are not
// 'cast-able' such as 'serial' - so they need to be casted int instead.
if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) {
$typecast = 'int';
}
else {
$typecast = $spec['pgsql_type'];
}
if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
$typecast .= '(' . $spec['length'] . ')';
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$typecast .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
}
// Remove old check constraints.
$field_info = $this->queryFieldInformation($table, $field);
foreach ($field_info as $check) {
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $check . '"');
}
// Remove old default.
$this->fieldSetNoDefault($table, $field);
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast);
if (isset($spec['not null'])) {
if ($spec['not null']) {
$nullaction = 'SET NOT NULL';
}
else {
$nullaction = 'DROP NOT NULL';
}
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" ' . $nullaction);
}
if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) {
// Type "serial" is known to PostgreSQL, but *only* during table creation,
// not when altering. Because of that, the sequence needs to be created
// and initialized by hand.
$seq = "{" . $table . "}_" . $field_new . "_seq";
$this->connection->query("CREATE SEQUENCE " . $seq);
// Set sequence to maximal field value to not conflict with existing
// entries.
$this->connection->query("SELECT setval('" . $seq . "', MAX(\"" . $field . '")) FROM {' . $table . "}");
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" SET DEFAULT nextval(\'' . $seq . '\')');
}
// Rename the column if necessary.
if ($field != $field_new) {
$this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field_new . '"');
}
// Add unsigned check if necessary.
if (!empty($spec['unsigned'])) {
$this->connection->query('ALTER TABLE {' . $table . '} ADD CHECK ("' . $field_new . '" >= 0)');
}
// Add default if necessary.
if (isset($spec['default'])) {
$this->fieldSetDefault($table, $field_new, $spec['default']);
}
// Change description if necessary.
if (!empty($spec['description'])) {
$this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description']));
}
if (isset($new_keys)) {
$this->_createKeys($table, $new_keys);
}
}
protected function _createIndexSql($table, $name, $fields) {
$query = 'CREATE INDEX "' . $this->prefixNonTable($table, $name, 'idx') . '" ON {' . $table . '} (';
$query .= $this->_createKeySql($fields) . ')';
return $query;
}
protected function _createKeys($table, $new_keys) {
if (isset($new_keys['primary key'])) {
$this->addPrimaryKey($table, $new_keys['primary key']);
}
if (isset($new_keys['unique keys'])) {
foreach ($new_keys['unique keys'] as $name => $fields) {
$this->addUniqueKey($table, $name, $fields);
}
}
if (isset($new_keys['indexes'])) {
foreach ($new_keys['indexes'] as $name => $fields) {
$this->addIndex($table, $name, $fields);
}
}
}
/**
* Retrieve a table or column comment.
*/
public function getComment($table, $column = NULL) {
$info = $this->getPrefixInfo($table);
// Don't use {} around pg_class, pg_attribute tables.
if (isset($column)) {
return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField();
}
else {
return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
}
}
}
-108
View File
@@ -1,108 +0,0 @@
<?php
/**
* @file
* Select builder for PostgreSQL database engine.
*/
/**
* @addtogroup database
* @{
*/
class SelectQuery_pgsql extends SelectQuery {
public function orderRandom() {
$alias = $this->addExpression('RANDOM()', 'random_field');
$this->orderBy($alias);
return $this;
}
/**
* Overrides SelectQuery::orderBy().
*
* PostgreSQL adheres strictly to the SQL-92 standard and requires that when
* using DISTINCT or GROUP BY conditions, fields and expressions that are
* ordered on also need to be selected. This is a best effort implementation
* to handle the cases that can be automated by adding the field if it is not
* yet selected.
*
* @code
* $query = db_select('node', 'n');
* $query->join('node_revision', 'nr', 'n.vid = nr.vid');
* $query
* ->distinct()
* ->fields('n')
* ->orderBy('timestamp');
* @endcode
*
* In this query, it is not possible (without relying on the schema) to know
* whether timestamp belongs to node_revisions and needs to be added or
* belongs to node and is already selected. Queries like this will need to be
* corrected in the original query by adding an explicit call to
* SelectQuery::addField() or SelectQuery::fields().
*
* Since this has a small performance impact, both by the additional
* processing in this function and in the database that needs to return the
* additional fields, this is done as an override instead of implementing it
* directly in SelectQuery::orderBy().
*/
public function orderBy($field, $direction = 'ASC') {
// Call parent function to order on this.
$return = parent::orderBy($field, $direction);
// If there is a table alias specified, split it up.
if (strpos($field, '.') !== FALSE) {
list($table, $table_field) = explode('.', $field);
}
// Figure out if the field has already been added.
foreach ($this->fields as $existing_field) {
if (!empty($table)) {
// If table alias is given, check if field and table exists.
if ($existing_field['table'] == $table && $existing_field['field'] == $table_field) {
return $return;
}
}
else {
// If there is no table, simply check if the field exists as a field or
// an aliased field.
if ($existing_field['alias'] == $field) {
return $return;
}
}
}
// Also check expression aliases.
foreach ($this->expressions as $expression) {
if ($expression['alias'] == $field) {
return $return;
}
}
// If a table loads all fields, it can not be added again. It would
// result in an ambigious alias error because that field would be loaded
// twice: Once through table_alias.* and once directly. If the field
// actually belongs to a different table, it must be added manually.
foreach ($this->tables as $table) {
if (!empty($table['all_fields'])) {
return $return;
}
}
// If $field contains an characters which are not allowed in a field name
// it is considered an expression, these can't be handeld automatically
// either.
if ($this->connection->escapeField($field) != $field) {
return $return;
}
// This is a case that can be handled automatically, add the field.
$this->addField(NULL, $field);
return $return;
}
}
/**
* @} End of "addtogroup database".
*/
-507
View File
@@ -1,507 +0,0 @@
<?php
/**
* @file
* Database interface code for engines that need complete control over their
* result sets. For example, SQLite will prefix some column names by the name
* of the table. We post-process the data, by renaming the column names
* using the same convention as MySQL and PostgreSQL.
*/
/**
* @addtogroup database
* @{
*/
/**
* An implementation of DatabaseStatementInterface that prefetches all data.
*
* This class behaves very similar to a PDOStatement but as it always fetches
* every row it is possible to manipulate those results.
*/
class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
/**
* The query string.
*
* @var string
*/
protected $queryString;
/**
* Driver-specific options. Can be used by child classes.
*
* @var Array
*/
protected $driverOptions;
/**
* Reference to the database connection object for this statement.
*
* The name $dbh is inherited from PDOStatement.
*
* @var DatabaseConnection
*/
public $dbh;
/**
* Main data store.
*
* @var Array
*/
protected $data = array();
/**
* The current row, retrieved in PDO::FETCH_ASSOC format.
*
* @var Array
*/
protected $currentRow = NULL;
/**
* The key of the current row.
*
* @var int
*/
protected $currentKey = NULL;
/**
* The list of column names in this result set.
*
* @var Array
*/
protected $columnNames = NULL;
/**
* The number of rows affected by the last query.
*
* @var int
*/
protected $rowCount = NULL;
/**
* The number of rows in this result set.
*
* @var int
*/
protected $resultRowCount = 0;
/**
* Holds the current fetch style (which will be used by the next fetch).
* @see PDOStatement::fetch()
*
* @var int
*/
protected $fetchStyle = PDO::FETCH_OBJ;
/**
* Holds supplementary current fetch options (which will be used by the next fetch).
*
* @var Array
*/
protected $fetchOptions = array(
'class' => 'stdClass',
'constructor_args' => array(),
'object' => NULL,
'column' => 0,
);
/**
* Holds the default fetch style.
*
* @var int
*/
protected $defaultFetchStyle = PDO::FETCH_OBJ;
/**
* Holds supplementary default fetch options.
*
* @var Array
*/
protected $defaultFetchOptions = array(
'class' => 'stdClass',
'constructor_args' => array(),
'object' => NULL,
'column' => 0,
);
public function __construct(DatabaseConnection $connection, $query, array $driver_options = array()) {
$this->dbh = $connection;
$this->queryString = $query;
$this->driverOptions = $driver_options;
}
/**
* Executes a prepared statement.
*
* @param $args
* An array of values with as many elements as there are bound parameters in the SQL statement being executed.
* @param $options
* An array of options for this query.
* @return
* TRUE on success, or FALSE on failure.
*/
public function execute($args = array(), $options = array()) {
if (isset($options['fetch'])) {
if (is_string($options['fetch'])) {
// Default to an object. Note: db fields will be added to the object
// before the constructor is run. If you need to assign fields after
// the constructor is run, see http://drupal.org/node/315092.
$this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']);
}
else {
$this->setFetchMode($options['fetch']);
}
}
$logger = $this->dbh->getLogger();
if (!empty($logger)) {
$query_start = microtime(TRUE);
}
// Prepare the query.
$statement = $this->getStatement($this->queryString, $args);
if (!$statement) {
$this->throwPDOException();
}
$return = $statement->execute($args);
if (!$return) {
$this->throwPDOException();
}
// Fetch all the data from the reply, in order to release any lock
// as soon as possible.
$this->rowCount = $statement->rowCount();
$this->data = $statement->fetchAll(PDO::FETCH_ASSOC);
// Destroy the statement as soon as possible. See
// DatabaseConnection_sqlite::PDOPrepare() for explanation.
unset($statement);
$this->resultRowCount = count($this->data);
if ($this->resultRowCount) {
$this->columnNames = array_keys($this->data[0]);
}
else {
$this->columnNames = array();
}
if (!empty($logger)) {
$query_end = microtime(TRUE);
$logger->log($this, $args, $query_end - $query_start);
}
// Initialize the first row in $this->currentRow.
$this->next();
return $return;
}
/**
* Throw a PDO Exception based on the last PDO error.
*/
protected function throwPDOException() {
$error_info = $this->dbh->errorInfo();
// We rebuild a message formatted in the same way as PDO.
$exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]);
$exception->errorInfo = $error_info;
throw $exception;
}
/**
* Grab a PDOStatement object from a given query and its arguments.
*
* Some drivers (including SQLite) will need to perform some preparation
* themselves to get the statement right.
*
* @param $query
* The query.
* @param array $args
* An array of arguments.
* @return PDOStatement
* A PDOStatement object.
*/
protected function getStatement($query, &$args = array()) {
return $this->dbh->prepare($query);
}
/**
* Return the object's SQL query string.
*/
public function getQueryString() {
return $this->queryString;
}
/**
* @see PDOStatement::setFetchMode()
*/
public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) {
$this->defaultFetchStyle = $fetchStyle;
switch ($fetchStyle) {
case PDO::FETCH_CLASS:
$this->defaultFetchOptions['class'] = $a2;
if ($a3) {
$this->defaultFetchOptions['constructor_args'] = $a3;
}
break;
case PDO::FETCH_COLUMN:
$this->defaultFetchOptions['column'] = $a2;
break;
case PDO::FETCH_INTO:
$this->defaultFetchOptions['object'] = $a2;
break;
}
// Set the values for the next fetch.
$this->fetchStyle = $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
}
/**
* Return the current row formatted according to the current fetch style.
*
* This is the core method of this class. It grabs the value at the current
* array position in $this->data and format it according to $this->fetchStyle
* and $this->fetchMode.
*
* @return
* The current row formatted as requested.
*/
public function current() {
if (isset($this->currentRow)) {
switch ($this->fetchStyle) {
case PDO::FETCH_ASSOC:
return $this->currentRow;
case PDO::FETCH_BOTH:
// PDO::FETCH_BOTH returns an array indexed by both the column name
// and the column number.
return $this->currentRow + array_values($this->currentRow);
case PDO::FETCH_NUM:
return array_values($this->currentRow);
case PDO::FETCH_LAZY:
// We do not do lazy as everything is fetched already. Fallback to
// PDO::FETCH_OBJ.
case PDO::FETCH_OBJ:
return (object) $this->currentRow;
case PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE:
$class_name = array_unshift($this->currentRow);
// Deliberate no break.
case PDO::FETCH_CLASS:
if (!isset($class_name)) {
$class_name = $this->fetchOptions['class'];
}
if (count($this->fetchOptions['constructor_args'])) {
$reflector = new ReflectionClass($class_name);
$result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']);
}
else {
$result = new $class_name();
}
foreach ($this->currentRow as $k => $v) {
$result->$k = $v;
}
return $result;
case PDO::FETCH_INTO:
foreach ($this->currentRow as $k => $v) {
$this->fetchOptions['object']->$k = $v;
}
return $this->fetchOptions['object'];
case PDO::FETCH_COLUMN:
if (isset($this->columnNames[$this->fetchOptions['column']])) {
return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]];
}
else {
return;
}
}
}
}
/* Implementations of Iterator. */
public function key() {
return $this->currentKey;
}
public function rewind() {
// Nothing to do: our DatabaseStatement can't be rewound.
}
public function next() {
if (!empty($this->data)) {
$this->currentRow = reset($this->data);
$this->currentKey = key($this->data);
unset($this->data[$this->currentKey]);
}
else {
$this->currentRow = NULL;
}
}
public function valid() {
return isset($this->currentRow);
}
/* Implementations of DatabaseStatementInterface. */
public function rowCount() {
return $this->rowCount;
}
public function fetch($fetch_style = NULL, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) {
if (isset($this->currentRow)) {
// Set the fetch parameter.
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
// Grab the row in the format specified above.
$return = $this->current();
// Advance the cursor.
$this->next();
// Reset the fetch parameters to the value stored using setFetchMode().
$this->fetchStyle = $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
return $return;
}
else {
return FALSE;
}
}
public function fetchColumn($index = 0) {
if (isset($this->currentRow) && isset($this->columnNames[$index])) {
// We grab the value directly from $this->data, and format it.
$return = $this->currentRow[$this->columnNames[$index]];
$this->next();
return $return;
}
else {
return FALSE;
}
}
public function fetchField($index = 0) {
return $this->fetchColumn($index);
}
public function fetchObject($class_name = NULL, $constructor_args = array()) {
if (isset($this->currentRow)) {
if (!isset($class_name)) {
// Directly cast to an object to avoid a function call.
$result = (object) $this->currentRow;
}
else {
$this->fetchStyle = PDO::FETCH_CLASS;
$this->fetchOptions = array('constructor_args' => $constructor_args);
// Grab the row in the format specified above.
$result = $this->current();
// Reset the fetch parameters to the value stored using setFetchMode().
$this->fetchStyle = $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
}
$this->next();
return $result;
}
else {
return FALSE;
}
}
public function fetchAssoc() {
if (isset($this->currentRow)) {
$result = $this->currentRow;
$this->next();
return $result;
}
else {
return FALSE;
}
}
public function fetchAll($fetch_style = NULL, $fetch_column = NULL, $constructor_args = NULL) {
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
if (isset($fetch_column)) {
$this->fetchOptions['column'] = $fetch_column;
}
if (isset($constructor_args)) {
$this->fetchOptions['constructor_args'] = $constructor_args;
}
$result = array();
// Traverse the array as PHP would have done.
while (isset($this->currentRow)) {
// Grab the row in the format specified above.
$result[] = $this->current();
$this->next();
}
// Reset the fetch parameters to the value stored using setFetchMode().
$this->fetchStyle = $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
return $result;
}
public function fetchCol($index = 0) {
if (isset($this->columnNames[$index])) {
$column = $this->columnNames[$index];
$result = array();
// Traverse the array as PHP would have done.
while (isset($this->currentRow)) {
$result[] = $this->currentRow[$this->columnNames[$index]];
$this->next();
}
return $result;
}
else {
return array();
}
}
public function fetchAllKeyed($key_index = 0, $value_index = 1) {
if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index]))
return array();
$key = $this->columnNames[$key_index];
$value = $this->columnNames[$value_index];
$result = array();
// Traverse the array as PHP would have done.
while (isset($this->currentRow)) {
$result[$this->currentRow[$key]] = $this->currentRow[$value];
$this->next();
}
return $result;
}
public function fetchAllAssoc($key, $fetch_style = NULL) {
$this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
$result = array();
// Traverse the array as PHP would have done.
while (isset($this->currentRow)) {
// Grab the row in its raw PDO::FETCH_ASSOC format.
$row = $this->currentRow;
// Grab the row in the format specified above.
$result_row = $this->current();
$result[$this->currentRow[$key]] = $result_row;
$this->next();
}
// Reset the fetch parameters to the value stored using setFetchMode().
$this->fetchStyle = $this->defaultFetchStyle;
$this->fetchOptions = $this->defaultFetchOptions;
return $result;
}
}
/**
* @} End of "addtogroup database".
*/
File diff suppressed because it is too large Load Diff
-727
View File
@@ -1,727 +0,0 @@
<?php
/**
* @file
* Generic Database schema code.
*/
require_once dirname(__FILE__) . '/query.inc';
/**
* @defgroup schemaapi Schema API
* @{
* API to handle database schemas.
*
* 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 in non-markup plain text 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 in non-markup plain text 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_revision}.vid value for this nid."
* - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int',
* 'float', 'numeric', or 'serial'. 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.
* - 'mysql_type', 'pgsql_type', 'sqlite_type', etc.: If you need to
* use a record type not included in the officially supported list
* of types above, you can specify a type for each database
* backend. In this case, you can leave out the type parameter,
* but be advised that your schema will fail to load on backends that
* do not have a type specified. A possible solution can be to
* use the "text" type as a fallback.
* - '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
* DatabaseSchema::getFieldTypeMap() 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.
* - 'binary': A boolean indicating that MySQL should force 'char',
* 'varchar' or 'text' fields to use case-sensitive binary collation.
* This has no effect on other database types for which case sensitivity
* is already the default behavior.
* All parameters apart from 'type' are optional except that type
* 'numeric' columns must specify 'precision' and 'scale', and type
* 'varchar' must specify the 'length' parameter.
* - '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.
* - 'foreign keys': An associative array of relations ('my_relation' =>
* specification). Each specification is an array containing the name of
* the referenced table ('table'), and an array of column mappings
* ('columns'). Column mappings are defined by key pairs ('source_column' =>
* 'referenced_column').
* - 'indexes': An associative array of indexes ('indexname' =>
* 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(
* 'description' => 'The base table for nodes.',
* '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' => ''),
* 'language' => array('type' => 'varchar','length' => 12,'not null' => TRUE,'default' => ''),
* 'title' => array('type' => 'varchar','length' => 255,'not null' => TRUE, 'default' => ''),
* 'uid' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
* 'status' => array('type' => 'int', 'not null' => TRUE, 'default' => 1),
* 'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
* 'changed' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
* 'comment' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
* 'promote' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
* 'moderate' => array('type' => 'int', 'not null' => TRUE,'default' => 0),
* 'sticky' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
* 'tnid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0),
* 'translate' => array('type' => 'int', 'not null' => TRUE, 'default' => 0),
* ),
* 'indexes' => array(
* 'node_changed' => array('changed'),
* 'node_created' => array('created'),
* 'node_moderate' => array('moderate'),
* 'node_frontpage' => array('promote', 'status', 'sticky', 'created'),
* 'node_status_type' => array('status', 'type', 'nid'),
* 'node_title_type' => array('title', array('type', 4)),
* 'node_type' => array(array('type', 4)),
* 'uid' => array('uid'),
* 'tnid' => array('tnid'),
* 'translate' => array('translate'),
* ),
* 'unique keys' => array(
* 'vid' => array('vid'),
* ),
* 'foreign keys' => array(
* 'node_revision' => array(
* 'table' => 'node_revision',
* 'columns' => array('vid' => 'vid'),
* ),
* 'node_author' => array(
* 'table' => 'users',
* 'columns' => array('uid' => 'uid'),
* ),
* ),
* 'primary key' => array('nid'),
* );
* @endcode
*
* @see drupal_install_schema()
*/
abstract class DatabaseSchema implements QueryPlaceholderInterface {
protected $connection;
/**
* The placeholder counter.
*/
protected $placeholder = 0;
/**
* Definition of prefixInfo array structure.
*
* Rather than redefining DatabaseSchema::getPrefixInfo() for each driver,
* by defining the defaultSchema variable only MySQL has to re-write the
* method.
*
* @see DatabaseSchema::getPrefixInfo()
*/
protected $defaultSchema = 'public';
/**
* A unique identifier for this query object.
*/
protected $uniqueIdentifier;
public function __construct($connection) {
$this->uniqueIdentifier = uniqid('', TRUE);
$this->connection = $connection;
}
/**
* Implements the magic __clone function.
*/
public function __clone() {
$this->uniqueIdentifier = uniqid('', TRUE);
}
/**
* Implements QueryPlaceHolderInterface::uniqueIdentifier().
*/
public function uniqueIdentifier() {
return $this->uniqueIdentifier;
}
/**
* Implements QueryPlaceHolderInterface::nextPlaceholder().
*/
public function nextPlaceholder() {
return $this->placeholder++;
}
/**
* Get information about the table name and schema from the prefix.
*
* @param
* Name of table to look prefix up for. Defaults to 'default' because thats
* default key for prefix.
* @param $add_prefix
* Boolean that indicates whether the given table name should be prefixed.
*
* @return
* A keyed array with information about the schema, table name and prefix.
*/
protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
$info = array(
'schema' => $this->defaultSchema,
'prefix' => $this->connection->tablePrefix($table),
);
if ($add_prefix) {
$table = $info['prefix'] . $table;
}
// If the prefix contains a period in it, then that means the prefix also
// contains a schema reference in which case we will change the schema key
// to the value before the period in the prefix. Everything after the dot
// will be prefixed onto the front of the table.
if (($pos = strpos($table, '.')) !== FALSE) {
// Grab everything before the period.
$info['schema'] = substr($table, 0, $pos);
// Grab everything after the dot.
$info['table'] = substr($table, ++$pos);
}
else {
$info['table'] = $table;
}
return $info;
}
/**
* Create names for indexes, primary keys and constraints.
*
* This prevents using {} around non-table names like indexes and keys.
*/
function prefixNonTable($table) {
$args = func_get_args();
$info = $this->getPrefixInfo($table);
$args[0] = $info['table'];
return implode('_', $args);
}
/**
* Build a condition to match a table name against a standard information_schema.
*
* The information_schema is a SQL standard that provides information about the
* database server and the databases, schemas, tables, columns and users within
* it. This makes information_schema a useful tool to use across the drupal
* database drivers and is used by a few different functions. The function below
* describes the conditions to be meet when querying information_schema.tables
* for drupal tables or information associated with drupal tables. Even though
* this is the standard method, not all databases follow standards and so this
* method should be overwritten by a database driver if the database provider
* uses alternate methods. Because information_schema.tables is used in a few
* different functions, a database driver will only need to override this function
* to make all the others work. For example see includes/databases/mysql/schema.inc.
*
* @param $table_name
* The name of the table in question.
* @param $operator
* The operator to apply on the 'table' part of the condition.
* @param $add_prefix
* Boolean to indicate whether the table name needs to be prefixed.
*
* @return QueryConditionInterface
* A DatabaseCondition object.
*/
protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) {
$info = $this->connection->getConnectionOptions();
// Retrive the table name and schema
$table_info = $this->getPrefixInfo($table_name, $add_prefix);
$condition = new DatabaseCondition('AND');
$condition->condition('table_catalog', $info['database']);
$condition->condition('table_schema', $table_info['schema']);
$condition->condition('table_name', $table_info['table'], $operator);
return $condition;
}
/**
* Check if a table exists.
*
* @param $table
* The name of the table in drupal (no prefixing).
*
* @return
* TRUE if the given table exists, otherwise FALSE.
*/
public function tableExists($table) {
$condition = $this->buildTableNameCondition($table);
$condition->compile($this->connection, $this);
// Normally, we would heartily discourage the use of string
// concatenation for conditionals like this however, we
// couldn't use db_select() here because it would prefix
// information_schema.tables and the query would fail.
// Don't use {} around information_schema.tables table.
return (bool) $this->connection->query("SELECT 1 FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField();
}
/**
* Find all tables that are like the specified base table name.
*
* @param $table_expression
* An SQL expression, for example "simpletest%" (without the quotes).
* BEWARE: this is not prefixed, the caller should take care of that.
*
* @return
* Array, both the keys and the values are the matching tables.
*/
public function findTables($table_expression) {
$condition = $this->buildTableNameCondition($table_expression, 'LIKE', FALSE);
$condition->compile($this->connection, $this);
// Normally, we would heartily discourage the use of string
// concatenation for conditionals like this however, we
// couldn't use db_select() here because it would prefix
// information_schema.tables and the query would fail.
// Don't use {} around information_schema.tables table.
return $this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchAllKeyed(0, 0);
}
/**
* Check if a column exists in the given table.
*
* @param $table
* The name of the table in drupal (no prefixing).
* @param $name
* The name of the column.
*
* @return
* TRUE if the given column exists, otherwise FALSE.
*/
public function fieldExists($table, $column) {
$condition = $this->buildTableNameCondition($table);
$condition->condition('column_name', $column);
$condition->compile($this->connection, $this);
// Normally, we would heartily discourage the use of string
// concatenation for conditionals like this however, we
// couldn't use db_select() here because it would prefix
// information_schema.tables and the query would fail.
// Don't use {} around information_schema.columns table.
return (bool) $this->connection->query("SELECT 1 FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField();
}
/**
* Returns a mapping of Drupal schema field names to DB-native field types.
*
* Because different field types do not map 1:1 between databases, Drupal has
* its own normalized field type names. This function returns a driver-specific
* mapping table from Drupal names to the native names for each database.
*
* @return array
* An array of Schema API field types to driver-specific field types.
*/
abstract public function getFieldTypeMap();
/**
* Rename a table.
*
* @param $table
* The table to be renamed.
* @param $new_name
* The new name for the table.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table doesn't exist.
* @throws DatabaseSchemaObjectExistsException
* If a table with the specified new name already exists.
*/
abstract public function renameTable($table, $new_name);
/**
* Drop a table.
*
* @param $table
* The table to be dropped.
*
* @return
* TRUE if the table was successfully dropped, FALSE if there was no table
* by that name to begin with.
*/
abstract public function dropTable($table);
/**
* Add a new field to a table.
*
* @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.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table doesn't exist.
* @throws DatabaseSchemaObjectExistsException
* If the specified table already has a field by that name.
*/
abstract public function addField($table, $field, $spec, $keys_new = array());
/**
* Drop a field.
*
* @param $table
* The table to be altered.
* @param $field
* The field to be dropped.
*
* @return
* TRUE if the field was successfully dropped, FALSE if there was no field
* by that name to begin with.
*/
abstract public function dropField($table, $field);
/**
* Set the default value for a field.
*
* @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'.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table or field doesn't exist.
*/
abstract public function fieldSetDefault($table, $field, $default);
/**
* Set a field to have no default value.
*
* @param $table
* The table to be altered.
* @param $field
* The field to be altered.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table or field doesn't exist.
*/
abstract public function fieldSetNoDefault($table, $field);
/**
* Checks if an index exists in the given table.
*
* @param $table
* The name of the table in drupal (no prefixing).
* @param $name
* The name of the index in drupal (no prefixing).
*
* @return
* TRUE if the given index exists, otherwise FALSE.
*/
abstract public function indexExists($table, $name);
/**
* Add a primary key.
*
* @param $table
* The table to be altered.
* @param $fields
* Fields for the primary key.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table doesn't exist.
* @throws DatabaseSchemaObjectExistsException
* If the specified table already has a primary key.
*/
abstract public function addPrimaryKey($table, $fields);
/**
* Drop the primary key.
*
* @param $table
* The table to be altered.
*
* @return
* TRUE if the primary key was successfully dropped, FALSE if there was no
* primary key on this table to begin with.
*/
abstract public function dropPrimaryKey($table);
/**
* Add a unique key.
*
* @param $table
* The table to be altered.
* @param $name
* The name of the key.
* @param $fields
* An array of field names.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table doesn't exist.
* @throws DatabaseSchemaObjectExistsException
* If the specified table already has a key by that name.
*/
abstract public function addUniqueKey($table, $name, $fields);
/**
* Drop a unique key.
*
* @param $table
* The table to be altered.
* @param $name
* The name of the key.
*
* @return
* TRUE if the key was successfully dropped, FALSE if there was no key by
* that name to begin with.
*/
abstract public function dropUniqueKey($table, $name);
/**
* Add an index.
*
* @param $table
* The table to be altered.
* @param $name
* The name of the index.
* @param $fields
* An array of field names.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table doesn't exist.
* @throws DatabaseSchemaObjectExistsException
* If the specified table already has an index by that name.
*/
abstract public function addIndex($table, $name, $fields);
/**
* Drop an index.
*
* @param $table
* The table to be altered.
* @param $name
* The name of the index.
*
* @return
* TRUE if the index was successfully dropped, FALSE if there was no index
* by that name to begin with.
*/
abstract public function dropIndex($table, $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('foo');
* db_change_field('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 $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.
*
* @throws DatabaseSchemaObjectDoesNotExistException
* If the specified table or source field doesn't exist.
* @throws DatabaseSchemaObjectExistsException
* If the specified destination field already exists.
*/
abstract public function changeField($table, $field, $field_new, $spec, $keys_new = array());
/**
* Create a new table from a Drupal table definition.
*
* @param $name
* The name of the table to create.
* @param $table
* A Schema API table definition array.
*
* @throws DatabaseSchemaObjectExistsException
* If the specified table already exists.
*/
public function createTable($name, $table) {
if ($this->tableExists($name)) {
throw new DatabaseSchemaObjectExistsException(t('Table %name already exists.', array('%name' => $name)));
}
$statements = $this->createTableSql($name, $table);
foreach ($statements as $statement) {
$this->connection->query($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.
*/
public function fieldNames($fields) {
$return = array();
foreach ($fields as $field) {
if (is_array($field)) {
$return[] = $field[0];
}
else {
$return[] = $field;
}
}
return $return;
}
/**
* Prepare a table or column comment for database query.
*
* @param $comment
* The comment string to prepare.
* @param $length
* Optional upper limit on the returned string length.
*
* @return
* The prepared comment.
*/
public function prepareComment($comment, $length = NULL) {
return $this->connection->quote($comment);
}
}
/**
* Exception thrown if an object being created already exists.
*
* For example, this exception should be thrown whenever there is an attempt to
* create a new database table, field, or index that already exists in the
* database schema.
*/
class DatabaseSchemaObjectExistsException extends Exception {}
/**
* Exception thrown if an object being modified doesn't exist yet.
*
* For example, this exception should be thrown whenever there is an attempt to
* modify a database table, field, or index that does not currently exist in
* the database schema.
*/
class DatabaseSchemaObjectDoesNotExistException extends Exception {}
/**
* @} End of "defgroup schemaapi".
*/
File diff suppressed because it is too large Load Diff
-519
View File
@@ -1,519 +0,0 @@
<?php
/**
* @file
* Database interface code for SQLite embedded database engine.
*/
/**
* @addtogroup database
* @{
*/
include_once DRUPAL_ROOT . '/includes/database/prefetch.inc';
/**
* Specific SQLite implementation of DatabaseConnection.
*/
class DatabaseConnection_sqlite extends DatabaseConnection {
/**
* Whether this database connection supports savepoints.
*
* Version of sqlite lower then 3.6.8 can't use savepoints.
* See http://www.sqlite.org/releaselog/3_6_8.html
*
* @var boolean
*/
protected $savepointSupport = FALSE;
/**
* Whether or not the active transaction (if any) will be rolled back.
*
* @var boolean
*/
protected $willRollback;
/**
* All databases attached to the current database. This is used to allow
* prefixes to be safely handled without locking the table
*
* @var array
*/
protected $attachedDatabases = array();
/**
* Whether or not a table has been dropped this request: the destructor will
* only try to get rid of unnecessary databases if there is potential of them
* being empty.
*
* This variable is set to public because DatabaseSchema_sqlite needs to
* access it. However, it should not be manually set.
*
* @var boolean
*/
var $tableDropped = FALSE;
public function __construct(array $connection_options = array()) {
// We don't need a specific PDOStatement class here, we simulate it below.
$this->statementClass = NULL;
// This driver defaults to transaction support, except if explicitly passed FALSE.
$this->transactionSupport = $this->transactionalDDLSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE;
$this->connectionOptions = $connection_options;
// Allow PDO options to be overridden.
$connection_options += array(
'pdo' => array(),
);
$connection_options['pdo'] += array(
// Convert numeric values to strings when fetching.
PDO::ATTR_STRINGIFY_FETCHES => TRUE,
);
parent::__construct('sqlite:' . $connection_options['database'], '', '', $connection_options['pdo']);
// Attach one database for each registered prefix.
$prefixes = $this->prefixes;
foreach ($prefixes as $table => &$prefix) {
// Empty prefix means query the main database -- no need to attach anything.
if (!empty($prefix)) {
// Only attach the database once.
if (!isset($this->attachedDatabases[$prefix])) {
$this->attachedDatabases[$prefix] = $prefix;
$this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix));
}
// Add a ., so queries become prefix.table, which is proper syntax for
// querying an attached database.
$prefix .= '.';
}
}
// Regenerate the prefixes replacement table.
$this->setPrefix($prefixes);
// Detect support for SAVEPOINT.
$version = $this->query('SELECT sqlite_version()')->fetchField();
$this->savepointSupport = (version_compare($version, '3.6.8') >= 0);
// Create functions needed by SQLite.
$this->sqliteCreateFunction('if', array($this, 'sqlFunctionIf'));
$this->sqliteCreateFunction('greatest', array($this, 'sqlFunctionGreatest'));
$this->sqliteCreateFunction('pow', 'pow', 2);
$this->sqliteCreateFunction('length', 'strlen', 1);
$this->sqliteCreateFunction('md5', 'md5', 1);
$this->sqliteCreateFunction('concat', array($this, 'sqlFunctionConcat'));
$this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3);
$this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3);
$this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand'));
// Execute sqlite init_commands.
if (isset($connection_options['init_commands'])) {
$this->exec(implode('; ', $connection_options['init_commands']));
}
}
/**
* Destructor for the SQLite connection.
*
* We prune empty databases on destruct, but only if tables have been
* dropped. This is especially needed when running the test suite, which
* creates and destroy databases several times in a row.
*/
public function __destruct() {
if ($this->tableDropped && !empty($this->attachedDatabases)) {
foreach ($this->attachedDatabases as $prefix) {
// Check if the database is now empty, ignore the internal SQLite tables.
try {
$count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField();
// We can prune the database file if it doesn't have any tables.
if ($count == 0) {
// Detach the database.
$this->query('DETACH DATABASE :schema', array(':schema' => $prefix));
// Destroy the database file.
unlink($this->connectionOptions['database'] . '-' . $prefix);
}
}
catch (Exception $e) {
// Ignore the exception and continue. There is nothing we can do here
// to report the error or fail safe.
}
}
}
}
/**
* SQLite compatibility implementation for the IF() SQL function.
*/
public function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
return $condition ? $expr1 : $expr2;
}
/**
* SQLite compatibility implementation for the GREATEST() SQL function.
*/
public function sqlFunctionGreatest() {
$args = func_get_args();
foreach ($args as $k => $v) {
if (!isset($v)) {
unset($args);
}
}
if (count($args)) {
return max($args);
}
else {
return NULL;
}
}
/**
* SQLite compatibility implementation for the CONCAT() SQL function.
*/
public function sqlFunctionConcat() {
$args = func_get_args();
return implode('', $args);
}
/**
* SQLite compatibility implementation for the SUBSTRING() SQL function.
*/
public function sqlFunctionSubstring($string, $from, $length) {
return substr($string, $from - 1, $length);
}
/**
* SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function.
*/
public function sqlFunctionSubstringIndex($string, $delimiter, $count) {
// If string is empty, simply return an empty string.
if (empty($string)) {
return '';
}
$end = 0;
for ($i = 0; $i < $count; $i++) {
$end = strpos($string, $delimiter, $end + 1);
if ($end === FALSE) {
$end = strlen($string);
}
}
return substr($string, 0, $end);
}
/**
* SQLite compatibility implementation for the RAND() SQL function.
*/
public function sqlFunctionRand($seed = NULL) {
if (isset($seed)) {
mt_srand($seed);
}
return mt_rand() / mt_getrandmax();
}
/**
* SQLite-specific implementation of DatabaseConnection::prepare().
*
* We don't use prepared statements at all at this stage. We just create
* a DatabaseStatement_sqlite object, that will create a PDOStatement
* using the semi-private PDOPrepare() method below.
*/
public function prepare($query, $options = array()) {
return new DatabaseStatement_sqlite($this, $query, $options);
}
/**
* NEVER CALL THIS FUNCTION: YOU MIGHT DEADLOCK YOUR PHP PROCESS.
*
* This is a wrapper around the parent PDO::prepare method. However, as
* the PDO SQLite driver only closes SELECT statements when the PDOStatement
* destructor is called and SQLite does not allow data change (INSERT,
* UPDATE etc) on a table which has open SELECT statements, you should never
* call this function and keep a PDOStatement object alive as that can lead
* to a deadlock. This really, really should be private, but as
* DatabaseStatement_sqlite needs to call it, we have no other choice but to
* expose this function to the world.
*/
public function PDOPrepare($query, array $options = array()) {
return parent::prepare($query, $options);
}
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options);
}
public function queryTemporary($query, array $args = array(), array $options = array()) {
// Generate a new temporary table name and protect it from prefixing.
// SQLite requires that temporary tables to be non-qualified.
$tablename = $this->generateTemporaryTableName();
$prefixes = $this->prefixes;
$prefixes[$tablename] = '';
$this->setPrefix($prefixes);
$this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE ' . $tablename . ' AS SELECT', $query), $args, $options);
return $tablename;
}
public function driver() {
return 'sqlite';
}
public function databaseType() {
return 'sqlite';
}
public function mapConditionOperator($operator) {
// We don't want to override any of the defaults.
static $specials = array(
'LIKE' => array('postfix' => " ESCAPE '\\'"),
'NOT LIKE' => array('postfix' => " ESCAPE '\\'"),
);
return isset($specials[$operator]) ? $specials[$operator] : NULL;
}
public function prepareQuery($query) {
return $this->prepare($this->prefixTables($query));
}
public function nextId($existing_id = 0) {
$transaction = $this->startTransaction();
// We can safely use literal queries here instead of the slower query
// builder because if a given database breaks here then it can simply
// override nextId. However, this is unlikely as we deal with short strings
// and integers and no known databases require special handling for those
// simple cases. If another transaction wants to write the same row, it will
// wait until this transaction commits.
$stmt = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array(
':existing_id' => $existing_id,
));
if (!$stmt->rowCount()) {
$this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array(
':existing_id' => $existing_id,
));
}
// The transaction gets committed when the transaction object gets destroyed
// because it gets out of scope.
return $this->query('SELECT value FROM {sequences}')->fetchField();
}
public function rollback($savepoint_name = 'drupal_transaction') {
if ($this->savepointSupport) {
return parent::rollBack($savepoint_name);
}
if (!$this->inTransaction()) {
throw new DatabaseTransactionNoActiveException();
}
// A previous rollback to an earlier savepoint may mean that the savepoint
// in question has already been rolled back.
if (!in_array($savepoint_name, $this->transactionLayers)) {
return;
}
// We need to find the point we're rolling back to, all other savepoints
// before are no longer needed.
while ($savepoint = array_pop($this->transactionLayers)) {
if ($savepoint == $savepoint_name) {
// Mark whole stack of transactions as needed roll back.
$this->willRollback = TRUE;
// If it is the last the transaction in the stack, then it is not a
// savepoint, it is the transaction itself so we will need to roll back
// the transaction rather than a savepoint.
if (empty($this->transactionLayers)) {
break;
}
return;
}
}
if ($this->supportsTransactions()) {
PDO::rollBack();
}
}
public function pushTransaction($name) {
if ($this->savepointSupport) {
return parent::pushTransaction($name);
}
if (!$this->supportsTransactions()) {
return;
}
if (isset($this->transactionLayers[$name])) {
throw new DatabaseTransactionNameNonUniqueException($name . " is already in use.");
}
if (!$this->inTransaction()) {
PDO::beginTransaction();
}
$this->transactionLayers[$name] = $name;
}
public function popTransaction($name) {
if ($this->savepointSupport) {
return parent::popTransaction($name);
}
if (!$this->supportsTransactions()) {
return;
}
if (!$this->inTransaction()) {
throw new DatabaseTransactionNoActiveException();
}
// Commit everything since SAVEPOINT $name.
while($savepoint = array_pop($this->transactionLayers)) {
if ($savepoint != $name) continue;
// If there are no more layers left then we should commit or rollback.
if (empty($this->transactionLayers)) {
// If there was any rollback() we should roll back whole transaction.
if ($this->willRollback) {
$this->willRollback = FALSE;
PDO::rollBack();
}
elseif (!PDO::commit()) {
throw new DatabaseTransactionCommitFailedException();
}
}
else {
break;
}
}
}
}
/**
* Specific SQLite implementation of DatabaseConnection.
*
* See DatabaseConnection_sqlite::PDOPrepare() for reasons why we must prefetch
* the data instead of using PDOStatement.
*
* @see DatabaseConnection_sqlite::PDOPrepare()
*/
class DatabaseStatement_sqlite extends DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface {
/**
* SQLite specific implementation of getStatement().
*
* The PDO SQLite layer doesn't replace numeric placeholders in queries
* correctly, and this makes numeric expressions (such as COUNT(*) >= :count)
* fail. We replace numeric placeholders in the query ourselves to work
* around this bug.
*
* See http://bugs.php.net/bug.php?id=45259 for more details.
*/
protected function getStatement($query, &$args = array()) {
if (count($args)) {
// Check if $args is a simple numeric array.
if (range(0, count($args) - 1) === array_keys($args)) {
// In that case, we have unnamed placeholders.
$count = 0;
$new_args = array();
foreach ($args as $value) {
if (is_float($value) || is_int($value)) {
if (is_float($value)) {
// Force the conversion to float so as not to loose precision
// in the automatic cast.
$value = sprintf('%F', $value);
}
$query = substr_replace($query, $value, strpos($query, '?'), 1);
}
else {
$placeholder = ':db_statement_placeholder_' . $count++;
$query = substr_replace($query, $placeholder, strpos($query, '?'), 1);
$new_args[$placeholder] = $value;
}
}
$args = $new_args;
}
else {
// Else, this is using named placeholders.
foreach ($args as $placeholder => $value) {
if (is_float($value) || is_int($value)) {
if (is_float($value)) {
// Force the conversion to float so as not to loose precision
// in the automatic cast.
$value = sprintf('%F', $value);
}
// We will remove this placeholder from the query as PDO throws an
// exception if the number of placeholders in the query and the
// arguments does not match.
unset($args[$placeholder]);
// PDO allows placeholders to not be prefixed by a colon. See
// http://marc.info/?l=php-internals&m=111234321827149&w=2 for
// more.
if ($placeholder[0] != ':') {
$placeholder = ":$placeholder";
}
// When replacing the placeholders, make sure we search for the
// exact placeholder. For example, if searching for
// ':db_placeholder_1', do not replace ':db_placeholder_11'.
$query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query);
}
}
}
}
return $this->dbh->PDOPrepare($query);
}
public function execute($args = array(), $options = array()) {
try {
$return = parent::execute($args, $options);
}
catch (PDOException $e) {
if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) {
// The schema has changed. SQLite specifies that we must resend the query.
$return = parent::execute($args, $options);
}
else {
// Rethrow the exception.
throw $e;
}
}
// In some weird cases, SQLite will prefix some column names by the name
// of the table. We post-process the data, by renaming the column names
// using the same convention as MySQL and PostgreSQL.
$rename_columns = array();
foreach ($this->columnNames as $k => $column) {
// In some SQLite versions, SELECT DISTINCT(field) will return "(field)"
// instead of "field".
if (preg_match("/^\((.*)\)$/", $column, $matches)) {
$rename_columns[$column] = $matches[1];
$this->columnNames[$k] = $matches[1];
$column = $matches[1];
}
// Remove "table." prefixes.
if (preg_match("/^.*\.(.*)$/", $column, $matches)) {
$rename_columns[$column] = $matches[1];
$this->columnNames[$k] = $matches[1];
}
}
if ($rename_columns) {
// DatabaseStatementPrefetch already extracted the first row,
// put it back into the result set.
if (isset($this->currentRow)) {
$this->data[0] = &$this->currentRow;
}
// Then rename all the columns across the result set.
foreach ($this->data as $k => $row) {
foreach ($rename_columns as $old_column => $new_column) {
$this->data[$k][$new_column] = $this->data[$k][$old_column];
unset($this->data[$k][$old_column]);
}
}
// Finally, extract the first row again.
$this->currentRow = $this->data[0];
unset($this->data[0]);
}
return $return;
}
}
/**
* @} End of "addtogroup database".
*/
-51
View File
@@ -1,51 +0,0 @@
<?php
/**
* @file
* SQLite specific install functions
*/
class DatabaseTasks_sqlite extends DatabaseTasks {
protected $pdoDriver = 'sqlite';
public function name() {
return st('SQLite');
}
/**
* Minimum engine version.
*
* @todo: consider upping to 3.6.8 in Drupal 8 to get SAVEPOINT support.
*/
public function minimumVersion() {
return '3.3.7';
}
public function getFormOptions($database) {
$form = parent::getFormOptions($database);
// Remove the options that only apply to client/server style databases.
unset($form['username'], $form['password'], $form['advanced_options']['host'], $form['advanced_options']['port']);
// Make the text more accurate for SQLite.
$form['database']['#title'] = st('Database file');
$form['database']['#description'] = st('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name()));
$default_database = conf_path(FALSE, TRUE) . '/files/.ht.sqlite';
$form['database']['#default_value'] = empty($database['database']) ? $default_database : $database['database'];
return $form;
}
public function validateDatabaseSettings($database) {
// Perform standard validation.
$errors = parent::validateDatabaseSettings($database);
// Verify the database is writable.
$db_directory = new SplFileInfo(dirname($database['database']));
if (!$db_directory->isWritable()) {
$errors[$database['driver'] . '][database'] = st('The directory you specified is not writable by the web server.');
}
return $errors;
}
}
-160
View File
@@ -1,160 +0,0 @@
<?php
/**
* @file
* Query code for SQLite embedded database engine.
*/
/**
* @addtogroup database
* @{
*/
/**
* SQLite specific implementation of InsertQuery.
*
* We ignore all the default fields and use the clever SQLite syntax:
* INSERT INTO table DEFAULT VALUES
* for degenerated "default only" queries.
*/
class InsertQuery_sqlite extends InsertQuery {
public function execute() {
if (!$this->preExecute()) {
return NULL;
}
if (count($this->insertFields)) {
return parent::execute();
}
else {
return $this->connection->query('INSERT INTO {' . $this->table . '} DEFAULT VALUES', array(), $this->queryOptions);
}
}
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
// Produce as many generic placeholders as necessary.
$placeholders = array_fill(0, count($this->insertFields), '?');
// If we're selecting from a SelectQuery, finish building the query and
// pass it back, as any remaining options are irrelevant.
if (!empty($this->fromQuery)) {
return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') ' . $this->fromQuery;
}
return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')';
}
}
/**
* SQLite specific implementation of UpdateQuery.
*
* SQLite counts all the rows that match the conditions as modified, even if they
* will not be affected by the query. We workaround this by ensuring that
* we don't select those rows.
*
* A query like this one:
* UPDATE test SET name = 'newname' WHERE tid = 1
* will become:
* UPDATE test SET name = 'newname' WHERE tid = 1 AND name <> 'newname'
*/
class UpdateQuery_sqlite extends UpdateQuery {
/**
* Helper function that removes the fields that are already in a condition.
*
* @param $fields
* The fields.
* @param QueryConditionInterface $condition
* A database condition.
*/
protected function removeFieldsInCondition(&$fields, QueryConditionInterface $condition) {
foreach ($condition->conditions() as $child_condition) {
if ($child_condition['field'] instanceof QueryConditionInterface) {
$this->removeFieldsInCondition($fields, $child_condition['field']);
}
else {
unset($fields[$child_condition['field']]);
}
}
}
public function execute() {
if (!empty($this->queryOptions['sqlite_return_matched_rows'])) {
return parent::execute();
}
// Get the fields used in the update query, and remove those that are already
// in the condition.
$fields = $this->expressionFields + $this->fields;
$this->removeFieldsInCondition($fields, $this->condition);
// Add the inverse of the fields to the condition.
$condition = new DatabaseCondition('OR');
foreach ($fields as $field => $data) {
if (is_array($data)) {
// The field is an expression.
$condition->where($field . ' <> ' . $data['expression']);
$condition->isNull($field);
}
elseif (!isset($data)) {
// The field will be set to NULL.
$condition->isNotNull($field);
}
else {
$condition->condition($field, $data, '<>');
$condition->isNull($field);
}
}
if (count($condition)) {
$condition->compile($this->connection, $this);
$this->condition->where((string) $condition, $condition->arguments());
}
return parent::execute();
}
}
/**
* SQLite specific implementation of DeleteQuery.
*
* When the WHERE is omitted from a DELETE statement and the table being deleted
* has no triggers, SQLite uses an optimization to erase the entire table content
* without having to visit each row of the table individually.
*
* Prior to SQLite 3.6.5, SQLite does not return the actual number of rows deleted
* by that optimized "truncate" optimization.
*/
class DeleteQuery_sqlite extends DeleteQuery {
public function execute() {
if (!count($this->condition)) {
$total_rows = $this->connection->query('SELECT COUNT(*) FROM {' . $this->connection->escapeTable($this->table) . '}')->fetchField();
parent::execute();
return $total_rows;
}
else {
return parent::execute();
}
}
}
/**
* SQLite specific implementation of TruncateQuery.
*
* SQLite doesn't support TRUNCATE, but a DELETE query with no condition has
* exactly the effect (it is implemented by DROPing the table).
*/
class TruncateQuery_sqlite extends TruncateQuery {
public function __toString() {
// Create a sanitized comment string to prepend to the query.
$comments = $this->connection->makeComment($this->comments);
return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
}
}
/**
* @} End of "addtogroup database".
*/
-683
View File
@@ -1,683 +0,0 @@
<?php
/**
* @file
* Database schema code for SQLite databases.
*/
/**
* @ingroup schemaapi
* @{
*/
class DatabaseSchema_sqlite extends DatabaseSchema {
/**
* Override DatabaseSchema::$defaultSchema
*/
protected $defaultSchema = 'main';
public function tableExists($table) {
$info = $this->getPrefixInfo($table);
// Don't use {} around sqlite_master table.
return (bool) $this->connection->query('SELECT 1 FROM ' . $info['schema'] . '.sqlite_master WHERE type = :type AND name = :name', array(':type' => 'table', ':name' => $info['table']))->fetchField();
}
public function fieldExists($table, $column) {
$schema = $this->introspectSchema($table);
return !empty($schema['fields'][$column]);
}
/**
* 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.
*/
public function createTableSql($name, $table) {
$sql = array();
$sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumsSql($name, $table) . "\n);\n";
return array_merge($sql, $this->createIndexSql($name, $table));
}
/**
* Build the SQL expression for indexes.
*/
protected function createIndexSql($tablename, $schema) {
$sql = array();
$info = $this->getPrefixInfo($tablename);
if (!empty($schema['unique keys'])) {
foreach ($schema['unique keys'] as $key => $fields) {
$sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
}
}
if (!empty($schema['indexes'])) {
foreach ($schema['indexes'] as $key => $fields) {
$sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n";
}
}
return $sql;
}
/**
* Build the SQL expression for creating columns.
*/
protected function createColumsSql($tablename, $schema) {
$sql_array = array();
// Add the SQL statement for each field.
foreach ($schema['fields'] as $name => $field) {
if (isset($field['type']) && $field['type'] == 'serial') {
if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== FALSE) {
unset($schema['primary key'][$key]);
}
}
$sql_array[] = $this->createFieldSql($name, $this->processField($field));
}
// Process keys.
if (!empty($schema['primary key'])) {
$sql_array[] = " PRIMARY KEY (" . $this->createKeySql($schema['primary key']) . ")";
}
return implode(", \n", $sql_array);
}
/**
* Build the SQL expression for keys.
*/
protected function createKeySql($fields) {
$return = array();
foreach ($fields as $field) {
if (is_array($field)) {
$return[] = $field[0];
}
else {
$return[] = $field;
}
}
return implode(', ', $return);
}
/**
* Set database-engine specific properties for a field.
*
* @param $field
* A field description array, as specified in the schema documentation.
*/
protected function processField($field) {
if (!isset($field['size'])) {
$field['size'] = 'normal';
}
// Set the correct database-engine specific datatype.
// In case one is already provided, force it to uppercase.
if (isset($field['sqlite_type'])) {
$field['sqlite_type'] = drupal_strtoupper($field['sqlite_type']);
}
else {
$map = $this->getFieldTypeMap();
$field['sqlite_type'] = $map[$field['type'] . ':' . $field['size']];
}
if (isset($field['type']) && $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_processField().
*
* @param $name
* Name of the field.
* @param $spec
* The field specification, as per the schema data structure format.
*/
protected function createFieldSql($name, $spec) {
if (!empty($spec['auto_increment'])) {
$sql = $name . " INTEGER PRIMARY KEY AUTOINCREMENT";
if (!empty($spec['unsigned'])) {
$sql .= ' CHECK (' . $name . '>= 0)';
}
}
else {
$sql = $name . ' ' . $spec['sqlite_type'];
if (in_array($spec['sqlite_type'], array('VARCHAR', 'TEXT')) && isset($spec['length'])) {
$sql .= '(' . $spec['length'] . ')';
}
if (isset($spec['not null'])) {
if ($spec['not null']) {
$sql .= ' NOT NULL';
}
else {
$sql .= ' NULL';
}
}
if (!empty($spec['unsigned'])) {
$sql .= ' CHECK (' . $name . '>= 0)';
}
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.
*/
public function getFieldTypeMap() {
// 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 does not use drupal_static as its value never changes.
static $map = array(
'varchar:normal' => 'VARCHAR',
'char:normal' => 'CHAR',
'text:tiny' => 'TEXT',
'text:small' => 'TEXT',
'text:medium' => 'TEXT',
'text:big' => 'TEXT',
'text:normal' => 'TEXT',
'serial:tiny' => 'INTEGER',
'serial:small' => 'INTEGER',
'serial:medium' => 'INTEGER',
'serial:big' => 'INTEGER',
'serial:normal' => 'INTEGER',
'int:tiny' => 'INTEGER',
'int:small' => 'INTEGER',
'int:medium' => 'INTEGER',
'int:big' => 'INTEGER',
'int:normal' => 'INTEGER',
'float:tiny' => 'FLOAT',
'float:small' => 'FLOAT',
'float:medium' => 'FLOAT',
'float:big' => 'FLOAT',
'float:normal' => 'FLOAT',
'numeric:normal' => 'NUMERIC',
'blob:big' => 'BLOB',
'blob:normal' => 'BLOB',
);
return $map;
}
public function renameTable($table, $new_name) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name)));
}
if ($this->tableExists($new_name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name)));
}
$schema = $this->introspectSchema($table);
// SQLite doesn't allow you to rename tables outside of the current
// database. So the syntax '...RENAME TO database.table' would fail.
// So we must determine the full table name here rather than surrounding
// the table with curly braces incase the db_prefix contains a reference
// to a database outside of our existsing database.
$info = $this->getPrefixInfo($new_name);
$this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']);
// Drop the indexes, there is no RENAME INDEX command in SQLite.
if (!empty($schema['unique keys'])) {
foreach ($schema['unique keys'] as $key => $fields) {
$this->dropIndex($table, $key);
}
}
if (!empty($schema['indexes'])) {
foreach ($schema['indexes'] as $index => $fields) {
$this->dropIndex($table, $index);
}
}
// Recreate the indexes.
$statements = $this->createIndexSql($new_name, $schema);
foreach ($statements as $statement) {
$this->connection->query($statement);
}
}
public function dropTable($table) {
if (!$this->tableExists($table)) {
return FALSE;
}
$this->connection->tableDropped = TRUE;
$this->connection->query('DROP TABLE {' . $table . '}');
return TRUE;
}
public function addField($table, $field, $specification, $keys_new = array()) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table)));
}
if ($this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table)));
}
// SQLite doesn't have a full-featured ALTER TABLE statement. It only
// supports adding new fields to a table, in some simple cases. In most
// cases, we have to create a new table and copy the data over.
if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) {
// When we don't have to create new keys and we are not creating a
// NOT NULL column without a default value, we can use the quicker version.
$query = 'ALTER TABLE {' . $table . '} ADD ' . $this->createFieldSql($field, $this->processField($specification));
$this->connection->query($query);
// Apply the initial value if set.
if (isset($specification['initial'])) {
$this->connection->update($table)
->fields(array($field => $specification['initial']))
->execute();
}
}
else {
// We cannot add the field directly. Use the slower table alteration
// method, starting from the old schema.
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
// Add the new field.
$new_schema['fields'][$field] = $specification;
// Build the mapping between the old fields and the new fields.
$mapping = array();
if (isset($specification['initial'])) {
// If we have a initial value, copy it over.
$mapping[$field] = array(
'expression' => ':newfieldinitial',
'arguments' => array(':newfieldinitial' => $specification['initial']),
);
}
else {
// Else use the default of the field.
$mapping[$field] = NULL;
}
// Add the new indexes.
$new_schema += $keys_new;
$this->alterTable($table, $old_schema, $new_schema, $mapping);
}
}
/**
* Create a table with a new schema containing the old content.
*
* As SQLite does not support ALTER TABLE (with a few exceptions) it is
* necessary to create a new table and copy over the old content.
*
* @param $table
* Name of the table to be altered.
* @param $old_schema
* The old schema array for the table.
* @param $new_schema
* The new schema array for the table.
* @param $mapping
* An optional mapping between the fields of the old specification and the
* fields of the new specification. An associative array, whose keys are
* the fields of the new table, and values can take two possible forms:
* - a simple string, which is interpreted as the name of a field of the
* old table,
* - an associative array with two keys 'expression' and 'arguments',
* that will be used as an expression field.
*/
protected function alterTable($table, $old_schema, $new_schema, array $mapping = array()) {
$i = 0;
do {
$new_table = $table . '_' . $i++;
} while ($this->tableExists($new_table));
$this->createTable($new_table, $new_schema);
// Build a SQL query to migrate the data from the old table to the new.
$select = $this->connection->select($table);
// Complete the mapping.
$possible_keys = array_keys($new_schema['fields']);
$mapping += array_combine($possible_keys, $possible_keys);
// Now add the fields.
foreach ($mapping as $field_alias => $field_source) {
// Just ignore this field (ie. use it's default value).
if (!isset($field_source)) {
continue;
}
if (is_array($field_source)) {
$select->addExpression($field_source['expression'], $field_alias, $field_source['arguments']);
}
else {
$select->addField($table, $field_source, $field_alias);
}
}
// Execute the data migration query.
$this->connection->insert($new_table)
->from($select)
->execute();
$old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField();
$new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField();
if ($old_count == $new_count) {
$this->dropTable($table);
$this->renameTable($new_table, $table);
}
}
/**
* Find out the schema of a table.
*
* This function uses introspection methods provided by the database to
* create a schema array. This is useful, for example, during update when
* the old schema is not available.
*
* @param $table
* Name of the table.
* @return
* An array representing the schema, from drupal_get_schema().
* @see drupal_get_schema()
*/
protected function introspectSchema($table) {
$mapped_fields = array_flip($this->getFieldTypeMap());
$schema = array(
'fields' => array(),
'primary key' => array(),
'unique keys' => array(),
'indexes' => array(),
);
$info = $this->getPrefixInfo($table);
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.table_info(' . $info['table'] . ')');
foreach ($result as $row) {
if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) {
$type = $matches[1];
$length = $matches[2];
}
else {
$type = $row->type;
$length = NULL;
}
if (isset($mapped_fields[$type])) {
list($type, $size) = explode(':', $mapped_fields[$type]);
$schema['fields'][$row->name] = array(
'type' => $type,
'size' => $size,
'not null' => !empty($row->notnull),
'default' => trim($row->dflt_value, "'"),
);
if ($length) {
$schema['fields'][$row->name]['length'] = $length;
}
if ($row->pk) {
$schema['primary key'][] = $row->name;
}
}
else {
new Exception("Unable to parse the column type " . $row->type);
}
}
$indexes = array();
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')');
foreach ($result as $row) {
if (strpos($row->name, 'sqlite_autoindex_') !== 0) {
$indexes[] = array(
'schema_key' => $row->unique ? 'unique keys' : 'indexes',
'name' => $row->name,
);
}
}
foreach ($indexes as $index) {
$name = $index['name'];
// Get index name without prefix.
$index_name = substr($name, strlen($info['table']) + 1);
$result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $name . ')');
foreach ($result as $row) {
$schema[$index['schema_key']][$index_name][] = $row->name;
}
}
return $schema;
}
public function dropField($table, $field) {
if (!$this->fieldExists($table, $field)) {
return FALSE;
}
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
unset($new_schema['fields'][$field]);
foreach ($new_schema['indexes'] as $index => $fields) {
foreach ($fields as $key => $field_name) {
if ($field_name == $field) {
unset($new_schema['indexes'][$index][$key]);
}
}
// If this index has no more fields then remove it.
if (empty($new_schema['indexes'][$index])) {
unset($new_schema['indexes'][$index]);
}
}
$this->alterTable($table, $old_schema, $new_schema);
return TRUE;
}
public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field)));
}
if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new)));
}
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
// Map the old field to the new field.
if ($field != $field_new) {
$mapping[$field_new] = $field;
}
else {
$mapping = array();
}
// Remove the previous definition and swap in the new one.
unset($new_schema['fields'][$field]);
$new_schema['fields'][$field_new] = $spec;
// Map the former indexes to the new column name.
$new_schema['primary key'] = $this->mapKeyDefinition($new_schema['primary key'], $mapping);
foreach (array('unique keys', 'indexes') as $k) {
foreach ($new_schema[$k] as &$key_definition) {
$key_definition = $this->mapKeyDefinition($key_definition, $mapping);
}
}
// Add in the keys from $keys_new.
if (isset($keys_new['primary key'])) {
$new_schema['primary key'] = $keys_new['primary key'];
}
foreach (array('unique keys', 'indexes') as $k) {
if (!empty($keys_new[$k])) {
$new_schema[$k] = $keys_new[$k] + $new_schema[$k];
}
}
$this->alterTable($table, $old_schema, $new_schema, $mapping);
}
/**
* Utility method: rename columns in an index definition according to a new mapping.
*
* @param $key_definition
* The key definition.
* @param $mapping
* The new mapping.
*/
protected function mapKeyDefinition(array $key_definition, array $mapping) {
foreach ($key_definition as &$field) {
// The key definition can be an array($field, $length).
if (is_array($field)) {
$field = &$field[0];
}
if (isset($mapping[$field])) {
$field = $mapping[$field];
}
}
return $key_definition;
}
public function addIndex($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
}
if ($this->indexExists($table, $name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name)));
}
$schema['indexes'][$name] = $fields;
$statements = $this->createIndexSql($table, $schema);
foreach ($statements as $statement) {
$this->connection->query($statement);
}
}
public function indexExists($table, $name) {
$info = $this->getPrefixInfo($table);
return $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $info['table'] . '_' . $name . ')')->fetchField() != '';
}
public function dropIndex($table, $name) {
if (!$this->indexExists($table, $name)) {
return FALSE;
}
$info = $this->getPrefixInfo($table);
$this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
return TRUE;
}
public function addUniqueKey($table, $name, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name)));
}
if ($this->indexExists($table, $name)) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name)));
}
$schema['unique keys'][$name] = $fields;
$statements = $this->createIndexSql($table, $schema);
foreach ($statements as $statement) {
$this->connection->query($statement);
}
}
public function dropUniqueKey($table, $name) {
if (!$this->indexExists($table, $name)) {
return FALSE;
}
$info = $this->getPrefixInfo($table);
$this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name);
return TRUE;
}
public function addPrimaryKey($table, $fields) {
if (!$this->tableExists($table)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table)));
}
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
if (!empty($new_schema['primary key'])) {
throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table)));
}
$new_schema['primary key'] = $fields;
$this->alterTable($table, $old_schema, $new_schema);
}
public function dropPrimaryKey($table) {
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
if (empty($new_schema['primary key'])) {
return FALSE;
}
unset($new_schema['primary key']);
$this->alterTable($table, $old_schema, $new_schema);
return TRUE;
}
public function fieldSetDefault($table, $field, $default) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
}
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
$new_schema['fields'][$field]['default'] = $default;
$this->alterTable($table, $old_schema, $new_schema);
}
public function fieldSetNoDefault($table, $field) {
if (!$this->fieldExists($table, $field)) {
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field)));
}
$old_schema = $this->introspectSchema($table);
$new_schema = $old_schema;
unset($new_schema['fields'][$field]['default']);
$this->alterTable($table, $old_schema, $new_schema);
}
public function findTables($table_expression) {
// Don't add the prefix, $table_expression already includes the prefix.
$info = $this->getPrefixInfo($table_expression, FALSE);
// Can't use query placeholders for the schema because the query would have
// to be :prefixsqlite_master, which does not work.
$result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array(
':type' => 'table',
':table_name' => $info['table'],
));
return $result->fetchAllKeyed(0, 0);
}
}
-27
View File
@@ -1,27 +0,0 @@
<?php
/**
* @file
* Select builder for SQLite embedded database engine.
*/
/**
* @addtogroup database
* @{
*/
/**
* SQLite specific query builder for SELECT statements.
*/
class SelectQuery_sqlite extends SelectQuery {
public function forUpdate($set = TRUE) {
// SQLite does not support FOR UPDATE so nothing to do.
return $this;
}
}
/**
* @} End of "addtogroup database".
*/
-196
View File
@@ -1,196 +0,0 @@
<?php
/**
* @file
* Initializes the list of date formats and their locales.
*/
/**
* Provides a default system list of date formats for system_date_formats().
*/
function system_default_date_formats() {
$formats = array();
// Short date formats.
$formats[] = array(
'type' => 'short',
'format' => 'Y-m-d H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'm/d/Y - H:i',
'locales' => array('en-us'),
);
$formats[] = array(
'type' => 'short',
'format' => 'd/m/Y - H:i',
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y/m/d - H:i',
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
);
$formats[] = array(
'type' => 'short',
'format' => 'd.m.Y - H:i',
'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'),
);
$formats[] = array(
'type' => 'short',
'format' => 'm/d/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'd/m/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y/m/d - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'M j Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'j M Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y M j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'M j Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'j M Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'short',
'format' => 'Y M j - g:ia',
'locales' => array(),
);
// Medium date formats.
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y-m-d H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, m/d/Y - H:i',
'locales' => array('en-us'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, d/m/Y - H:i',
'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y/m/d - H:i',
'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'),
);
$formats[] = array(
'type' => 'medium',
'format' => 'F j, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j F, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'Y, F j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, m/d/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, d/m/Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'D, Y/m/d - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'F j, Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j F Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'Y, F j - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'medium',
'format' => 'j. F Y - G:i',
'locales' => array(),
);
// Long date formats.
$formats[] = array(
'type' => 'long',
'format' => 'l, F j, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j F, Y - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, Y, F j - H:i',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, F j, Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j F Y - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, Y, F j - g:ia',
'locales' => array(),
);
$formats[] = array(
'type' => 'long',
'format' => 'l, j. F Y - G:i',
'locales' => array(),
);
return $formats;
}
-1365
View File
File diff suppressed because it is too large Load Diff
-286
View File
@@ -1,286 +0,0 @@
<?php
/**
* @file
* Functions for error handling.
*/
/**
* Maps PHP error constants to watchdog severity levels.
*
* The error constants are documented at
* http://php.net/manual/en/errorfunc.constants.php
*
* @ingroup logging_severity_levels
*/
function drupal_error_levels() {
$types = array(
E_ERROR => array('Error', WATCHDOG_ERROR),
E_WARNING => array('Warning', WATCHDOG_WARNING),
E_PARSE => array('Parse error', WATCHDOG_ERROR),
E_NOTICE => array('Notice', WATCHDOG_NOTICE),
E_CORE_ERROR => array('Core error', WATCHDOG_ERROR),
E_CORE_WARNING => array('Core warning', WATCHDOG_WARNING),
E_COMPILE_ERROR => array('Compile error', WATCHDOG_ERROR),
E_COMPILE_WARNING => array('Compile warning', WATCHDOG_WARNING),
E_USER_ERROR => array('User error', WATCHDOG_ERROR),
E_USER_WARNING => array('User warning', WATCHDOG_WARNING),
E_USER_NOTICE => array('User notice', WATCHDOG_NOTICE),
E_STRICT => array('Strict warning', WATCHDOG_DEBUG),
E_RECOVERABLE_ERROR => array('Recoverable fatal error', WATCHDOG_ERROR),
);
// E_DEPRECATED and E_USER_DEPRECATED were added in PHP 5.3.0.
if (defined('E_DEPRECATED')) {
$types[E_DEPRECATED] = array('Deprecated function', WATCHDOG_DEBUG);
$types[E_USER_DEPRECATED] = array('User deprecated function', WATCHDOG_DEBUG);
}
return $types;
}
/**
* Provides custom PHP error handling.
*
* @param $error_level
* The level of the error raised.
* @param $message
* The error message.
* @param $filename
* The filename that the error was raised in.
* @param $line
* The line number the error was raised at.
* @param $context
* An array that points to the active symbol table at the point the error
* occurred.
*/
function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) {
if ($error_level & error_reporting()) {
$types = drupal_error_levels();
list($severity_msg, $severity_level) = $types[$error_level];
$caller = _drupal_get_last_caller(debug_backtrace());
if (!function_exists('filter_xss_admin')) {
require_once DRUPAL_ROOT . '/includes/common.inc';
}
// We treat recoverable errors as fatal.
_drupal_log_error(array(
'%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
// The standard PHP error handler considers that the error messages
// are HTML. We mimick this behavior here.
'!message' => filter_xss_admin($message),
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
'severity_level' => $severity_level,
), $error_level == E_RECOVERABLE_ERROR);
}
}
/**
* Decodes an exception and retrieves the correct caller.
*
* @param $exception
* The exception object that was thrown.
*
* @return
* An error in the format expected by _drupal_log_error().
*/
function _drupal_decode_exception($exception) {
$message = $exception->getMessage();
$backtrace = $exception->getTrace();
// Add the line throwing the exception to the backtrace.
array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile()));
// For PDOException errors, we try to return the initial caller,
// skipping internal functions of the database layer.
if ($exception instanceof PDOException) {
// The first element in the stack is the call, the second element gives us the caller.
// We skip calls that occurred in one of the classes of the database layer
// or in one of its global functions.
$db_functions = array('db_query', 'db_query_range');
while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
in_array($caller['function'], $db_functions))) {
// We remove that call.
array_shift($backtrace);
}
if (isset($exception->query_string, $exception->args)) {
$message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
}
}
$caller = _drupal_get_last_caller($backtrace);
return array(
'%type' => get_class($exception),
// The standard PHP exception handler considers that the exception message
// is plain-text. We mimick this behavior here.
'!message' => check_plain($message),
'%function' => $caller['function'],
'%file' => $caller['file'],
'%line' => $caller['line'],
'severity_level' => WATCHDOG_ERROR,
);
}
/**
* Renders an exception error message without further exceptions.
*
* @param $exception
* The exception object that was thrown.
* @return
* An error message.
*/
function _drupal_render_exception_safe($exception) {
return check_plain(strtr('%type: !message in %function (line %line of %file).', _drupal_decode_exception($exception)));
}
/**
* Determines whether an error should be displayed.
*
* When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL,
* all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error
* will be examined to determine if it should be displayed.
*
* @param $error
* Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
*
* @return
* TRUE if an error should be displayed.
*/
function error_displayable($error = NULL) {
$error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL);
$updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update');
$all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL);
$error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME &&
isset($error) && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning');
return ($updating || $all_errors_displayed || $error_needs_display);
}
/**
* Logs a PHP error or exception and displays an error page in fatal cases.
*
* @param $error
* An array with the following keys: %type, !message, %function, %file, %line
* and severity_level. All the parameters are plain-text, with the exception
* of !message, which needs to be a safe HTML string.
* @param $fatal
* TRUE if the error is fatal.
*/
function _drupal_log_error($error, $fatal = FALSE) {
// Initialize a maintenance theme if the boostrap was not complete.
// Do it early because drupal_set_message() triggers a drupal_theme_initialize().
if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) {
unset($GLOBALS['theme']);
if (!defined('MAINTENANCE_MODE')) {
define('MAINTENANCE_MODE', 'error');
}
drupal_maintenance_theme();
}
// When running inside the testing framework, we relay the errors
// to the tested site by the way of HTTP headers.
$test_info = &$GLOBALS['drupal_test_info'];
if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) {
// $number does not use drupal_static as it should not be reset
// as it uniquely identifies each PHP error.
static $number = 0;
$assertion = array(
$error['!message'],
$error['%type'],
array(
'function' => $error['%function'],
'file' => $error['%file'],
'line' => $error['%line'],
),
);
header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion)));
$number++;
}
watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']);
if ($fatal) {
drupal_add_http_header('Status', '500 Service unavailable (with message)');
}
if (drupal_is_cli()) {
if ($fatal) {
// When called from CLI, simply output a plain text message.
print html_entity_decode(strip_tags(t('%type: !message in %function (line %line of %file).', $error))). "\n";
exit;
}
}
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
if ($fatal) {
if (error_displayable($error)) {
// When called from JavaScript, simply output the error message.
print t('%type: !message in %function (line %line of %file).', $error);
}
exit;
}
}
else {
// Display the message if the current error reporting level allows this type
// of message to be displayed, and unconditionnaly in update.php.
if (error_displayable($error)) {
$class = 'error';
// If error type is 'User notice' then treat it as debug information
// instead of an error message, see dd().
if ($error['%type'] == 'User notice') {
$error['%type'] = 'Debug';
$class = 'status';
}
drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class);
}
if ($fatal) {
drupal_set_title(t('Error'));
// We fallback to a maintenance page at this point, because the page generation
// itself can generate errors.
print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.')));
exit;
}
}
}
/**
* Gets the last caller from a backtrace.
*
* @param $backtrace
* A standard PHP backtrace.
*
* @return
* An associative array with keys 'file', 'line' and 'function'.
*/
function _drupal_get_last_caller($backtrace) {
// Errors that occur inside PHP internal functions do not generate
// information about file and line. Ignore black listed functions.
$blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler');
while (($backtrace && !isset($backtrace[0]['line'])) ||
(isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) {
array_shift($backtrace);
}
// The first trace is the call itself.
// It gives us the line and the file of the last call.
$call = $backtrace[0];
// The second call give us the function where the call originated.
if (isset($backtrace[1])) {
if (isset($backtrace[1]['class'])) {
$call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
}
else {
$call['function'] = $backtrace[1]['function'] . '()';
}
}
else {
$call['function'] = 'main()';
}
return $call;
}
-2504
View File
File diff suppressed because it is too large Load Diff
-859
View File
@@ -1,859 +0,0 @@
<?php
/**
* @file
* Provides mimetype mappings.
*/
/**
* Return an array of MIME extension mappings.
*
* Returns the mapping after modules have altered the default mapping.
*
* @return
* Array of mimetypes correlated to the extensions that relate to them.
*
* @see file_get_mimetype()
*/
function file_mimetype_mapping() {
$mapping = &drupal_static(__FUNCTION__);
if (!isset($mapping)) {
$mapping = file_default_mimetype_mapping();
// Allow modules to alter the default mapping.
drupal_alter('file_mimetype_mapping', $mapping);
}
return $mapping;
}
/**
* Default MIME extension mapping.
*
* @return
* Array of mimetypes correlated to the extensions that relate to them.
*
* @see file_get_mimetype()
*/
function file_default_mimetype_mapping() {
return array(
'mimetypes' => array(
0 => 'application/andrew-inset',
1 => 'application/atom',
2 => 'application/atomcat+xml',
3 => 'application/atomserv+xml',
4 => 'application/cap',
5 => 'application/cu-seeme',
6 => 'application/dsptype',
7 => 'application/hta',
8 => 'application/java-archive',
9 => 'application/java-serialized-object',
10 => 'application/java-vm',
11 => 'application/mac-binhex40',
12 => 'application/mathematica',
13 => 'application/msaccess',
14 => 'application/msword',
15 => 'application/octet-stream',
16 => 'application/oda',
17 => 'application/ogg',
18 => 'application/pdf',
19 => 'application/pgp-keys',
20 => 'application/pgp-signature',
21 => 'application/pics-rules',
22 => 'application/postscript',
23 => 'application/rar',
24 => 'application/rdf+xml',
25 => 'application/rss+xml',
26 => 'application/rtf',
27 => 'application/smil',
28 => 'application/vnd.cinderella',
29 => 'application/vnd.google-earth.kml+xml',
30 => 'application/vnd.google-earth.kmz',
31 => 'application/vnd.mozilla.xul+xml',
32 => 'application/vnd.ms-excel',
33 => 'application/vnd.ms-excel.addin.macroEnabled.12',
34 => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
35 => 'application/vnd.ms-excel.sheet.macroEnabled.12',
36 => 'application/vnd.ms-excel.template.macroEnabled.12',
37 => 'application/vnd.ms-pki.seccat',
38 => 'application/vnd.ms-pki.stl',
39 => 'application/vnd.ms-powerpoint',
40 => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
41 => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
42 => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
43 => 'application/vnd.ms-powerpoint.template.macroEnabled.12',
44 => 'application/vnd.ms-word.document.macroEnabled.12',
45 => 'application/vnd.ms-word.template.macroEnabled.12',
46 => 'application/vnd.ms-xpsdocument',
47 => 'application/vnd.oasis.opendocument.chart',
48 => 'application/vnd.oasis.opendocument.database',
49 => 'application/vnd.oasis.opendocument.formula',
50 => 'application/vnd.oasis.opendocument.graphics',
51 => 'application/vnd.oasis.opendocument.graphics-template',
52 => 'application/vnd.oasis.opendocument.image',
53 => 'application/vnd.oasis.opendocument.presentation',
54 => 'application/vnd.oasis.opendocument.presentation-template',
55 => 'application/vnd.oasis.opendocument.spreadsheet',
56 => 'application/vnd.oasis.opendocument.spreadsheet-template',
57 => 'application/vnd.oasis.opendocument.text',
58 => 'application/vnd.oasis.opendocument.text-master',
59 => 'application/vnd.oasis.opendocument.text-template',
60 => 'application/vnd.oasis.opendocument.text-web',
61 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
62 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
63 => 'application/vnd.openxmlformats-officedocument.presentationml.template',
64 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
66 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
67 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
68 => 'application/vnd.rim.cod',
69 => 'application/vnd.smaf',
70 => 'application/vnd.stardivision.calc',
71 => 'application/vnd.stardivision.chart',
72 => 'application/vnd.stardivision.draw',
73 => 'application/vnd.stardivision.impress',
74 => 'application/vnd.stardivision.math',
75 => 'application/vnd.stardivision.writer',
76 => 'application/vnd.stardivision.writer-global',
77 => 'application/vnd.sun.xml.calc',
78 => 'application/vnd.sun.xml.calc.template',
79 => 'application/vnd.sun.xml.draw',
80 => 'application/vnd.sun.xml.draw.template',
81 => 'application/vnd.sun.xml.impress',
82 => 'application/vnd.sun.xml.impress.template',
83 => 'application/vnd.sun.xml.math',
84 => 'application/vnd.sun.xml.writer',
85 => 'application/vnd.sun.xml.writer.global',
86 => 'application/vnd.sun.xml.writer.template',
87 => 'application/vnd.symbian.install',
88 => 'application/vnd.visio',
89 => 'application/vnd.wap.wbxml',
90 => 'application/vnd.wap.wmlc',
91 => 'application/vnd.wap.wmlscriptc',
92 => 'application/wordperfect',
93 => 'application/wordperfect5.1',
94 => 'application/x-123',
95 => 'application/x-7z-compressed',
96 => 'application/x-abiword',
97 => 'application/x-apple-diskimage',
98 => 'application/x-bcpio',
99 => 'application/x-bittorrent',
100 => 'application/x-cab',
101 => 'application/x-cbr',
102 => 'application/x-cbz',
103 => 'application/x-cdf',
104 => 'application/x-cdlink',
105 => 'application/x-chess-pgn',
106 => 'application/x-cpio',
107 => 'application/x-debian-package',
108 => 'application/x-director',
109 => 'application/x-dms',
110 => 'application/x-doom',
111 => 'application/x-dvi',
112 => 'application/x-flac',
113 => 'application/x-font',
114 => 'application/x-freemind',
115 => 'application/x-futuresplash',
116 => 'application/x-gnumeric',
117 => 'application/x-go-sgf',
118 => 'application/x-graphing-calculator',
119 => 'application/x-gtar',
120 => 'application/x-hdf',
121 => 'application/x-httpd-eruby',
122 => 'application/x-httpd-php',
123 => 'application/x-httpd-php-source',
124 => 'application/x-httpd-php3',
125 => 'application/x-httpd-php3-preprocessed',
126 => 'application/x-httpd-php4',
127 => 'application/x-ica',
128 => 'application/x-internet-signup',
129 => 'application/x-iphone',
130 => 'application/x-iso9660-image',
131 => 'application/x-java-jnlp-file',
132 => 'application/x-javascript',
133 => 'application/x-jmol',
134 => 'application/x-kchart',
135 => 'application/x-killustrator',
136 => 'application/x-koan',
137 => 'application/x-kpresenter',
138 => 'application/x-kspread',
139 => 'application/x-kword',
140 => 'application/x-latex',
141 => 'application/x-lha',
142 => 'application/x-lyx',
143 => 'application/x-lzh',
144 => 'application/x-lzx',
145 => 'application/x-maker',
146 => 'application/x-mif',
147 => 'application/x-ms-wmd',
148 => 'application/x-ms-wmz',
149 => 'application/x-msdos-program',
150 => 'application/x-msi',
151 => 'application/x-netcdf',
152 => 'application/x-ns-proxy-autoconfig',
153 => 'application/x-nwc',
154 => 'application/x-object',
155 => 'application/x-oz-application',
156 => 'application/x-pkcs7-certreqresp',
157 => 'application/x-pkcs7-crl',
158 => 'application/x-python-code',
159 => 'application/x-quicktimeplayer',
160 => 'application/x-redhat-package-manager',
161 => 'application/x-shar',
162 => 'application/x-shockwave-flash',
163 => 'application/x-stuffit',
164 => 'application/x-sv4cpio',
165 => 'application/x-sv4crc',
166 => 'application/x-tar',
167 => 'application/x-tcl',
168 => 'application/x-tex-gf',
169 => 'application/x-tex-pk',
170 => 'application/x-texinfo',
171 => 'application/x-trash',
172 => 'application/x-troff',
173 => 'application/x-troff-man',
174 => 'application/x-troff-me',
175 => 'application/x-troff-ms',
176 => 'application/x-ustar',
177 => 'application/x-wais-source',
178 => 'application/x-wingz',
179 => 'application/x-x509-ca-cert',
180 => 'application/x-xcf',
181 => 'application/x-xfig',
182 => 'application/x-xpinstall',
183 => 'application/xhtml+xml',
184 => 'application/xml',
185 => 'application/zip',
186 => 'audio/basic',
187 => 'audio/midi',
346 => 'audio/mp4',
188 => 'audio/mpeg',
189 => 'audio/ogg',
190 => 'audio/prs.sid',
191 => 'audio/x-aiff',
192 => 'audio/x-gsm',
193 => 'audio/x-mpegurl',
194 => 'audio/x-ms-wax',
195 => 'audio/x-ms-wma',
196 => 'audio/x-pn-realaudio',
197 => 'audio/x-realaudio',
198 => 'audio/x-scpls',
199 => 'audio/x-sd2',
200 => 'audio/x-wav',
201 => 'chemical/x-alchemy',
202 => 'chemical/x-cache',
203 => 'chemical/x-cache-csf',
204 => 'chemical/x-cactvs-binary',
205 => 'chemical/x-cdx',
206 => 'chemical/x-cerius',
207 => 'chemical/x-chem3d',
208 => 'chemical/x-chemdraw',
209 => 'chemical/x-cif',
210 => 'chemical/x-cmdf',
211 => 'chemical/x-cml',
212 => 'chemical/x-compass',
213 => 'chemical/x-crossfire',
214 => 'chemical/x-csml',
215 => 'chemical/x-ctx',
216 => 'chemical/x-cxf',
217 => 'chemical/x-embl-dl-nucleotide',
218 => 'chemical/x-galactic-spc',
219 => 'chemical/x-gamess-input',
220 => 'chemical/x-gaussian-checkpoint',
221 => 'chemical/x-gaussian-cube',
222 => 'chemical/x-gaussian-input',
223 => 'chemical/x-gaussian-log',
224 => 'chemical/x-gcg8-sequence',
225 => 'chemical/x-genbank',
226 => 'chemical/x-hin',
227 => 'chemical/x-isostar',
228 => 'chemical/x-jcamp-dx',
229 => 'chemical/x-kinemage',
230 => 'chemical/x-macmolecule',
231 => 'chemical/x-macromodel-input',
232 => 'chemical/x-mdl-molfile',
233 => 'chemical/x-mdl-rdfile',
234 => 'chemical/x-mdl-rxnfile',
235 => 'chemical/x-mdl-sdfile',
236 => 'chemical/x-mdl-tgf',
237 => 'chemical/x-mmcif',
238 => 'chemical/x-mol2',
239 => 'chemical/x-molconn-Z',
240 => 'chemical/x-mopac-graph',
241 => 'chemical/x-mopac-input',
242 => 'chemical/x-mopac-out',
243 => 'chemical/x-mopac-vib',
244 => 'chemical/x-ncbi-asn1-ascii',
245 => 'chemical/x-ncbi-asn1-binary',
246 => 'chemical/x-ncbi-asn1-spec',
247 => 'chemical/x-pdb',
248 => 'chemical/x-rosdal',
249 => 'chemical/x-swissprot',
250 => 'chemical/x-vamas-iso14976',
251 => 'chemical/x-vmd',
252 => 'chemical/x-xtel',
253 => 'chemical/x-xyz',
254 => 'image/gif',
255 => 'image/ief',
256 => 'image/jpeg',
257 => 'image/pcx',
258 => 'image/png',
259 => 'image/svg+xml',
260 => 'image/tiff',
261 => 'image/vnd.djvu',
262 => 'image/vnd.microsoft.icon',
263 => 'image/vnd.wap.wbmp',
264 => 'image/x-cmu-raster',
265 => 'image/x-coreldraw',
266 => 'image/x-coreldrawpattern',
267 => 'image/x-coreldrawtemplate',
268 => 'image/x-corelphotopaint',
269 => 'image/x-jg',
270 => 'image/x-jng',
271 => 'image/x-ms-bmp',
272 => 'image/x-photoshop',
273 => 'image/x-portable-anymap',
274 => 'image/x-portable-bitmap',
275 => 'image/x-portable-graymap',
276 => 'image/x-portable-pixmap',
277 => 'image/x-rgb',
278 => 'image/x-xbitmap',
279 => 'image/x-xpixmap',
280 => 'image/x-xwindowdump',
281 => 'message/rfc822',
282 => 'model/iges',
283 => 'model/mesh',
284 => 'model/vrml',
285 => 'text/calendar',
286 => 'text/css',
287 => 'text/csv',
288 => 'text/h323',
289 => 'text/html',
290 => 'text/iuls',
291 => 'text/mathml',
292 => 'text/plain',
293 => 'text/richtext',
294 => 'text/scriptlet',
295 => 'text/tab-separated-values',
296 => 'text/texmacs',
297 => 'text/vnd.sun.j2me.app-descriptor',
298 => 'text/vnd.wap.wml',
299 => 'text/vnd.wap.wmlscript',
300 => 'text/x-bibtex',
301 => 'text/x-boo',
302 => 'text/x-c++hdr',
303 => 'text/x-c++src',
304 => 'text/x-chdr',
305 => 'text/x-component',
306 => 'text/x-csh',
307 => 'text/x-csrc',
308 => 'text/x-diff',
309 => 'text/x-dsrc',
310 => 'text/x-haskell',
311 => 'text/x-java',
312 => 'text/x-literate-haskell',
313 => 'text/x-moc',
314 => 'text/x-pascal',
315 => 'text/x-pcs-gcd',
316 => 'text/x-perl',
317 => 'text/x-python',
318 => 'text/x-setext',
319 => 'text/x-sh',
320 => 'text/x-tcl',
321 => 'text/x-tex',
322 => 'text/x-vcalendar',
323 => 'text/x-vcard',
324 => 'video/3gpp',
325 => 'video/dl',
326 => 'video/dv',
327 => 'video/fli',
328 => 'video/gl',
329 => 'video/mp4',
330 => 'video/mpeg',
331 => 'video/ogg',
332 => 'video/quicktime',
333 => 'video/vnd.mpegurl',
347 => 'video/x-flv',
334 => 'video/x-la-asf',
348 => 'video/x-m4v',
335 => 'video/x-mng',
336 => 'video/x-ms-asf',
337 => 'video/x-ms-wm',
338 => 'video/x-ms-wmv',
339 => 'video/x-ms-wmx',
340 => 'video/x-ms-wvx',
341 => 'video/x-msvideo',
342 => 'video/x-sgi-movie',
343 => 'x-conference/x-cooltalk',
344 => 'x-epoc/x-sisx-app',
345 => 'x-world/x-vrml',
),
// Extensions added to this list MUST be lower-case.
'extensions' => array(
'ez' => 0,
'atom' => 1,
'atomcat' => 2,
'atomsrv' => 3,
'cap' => 4,
'pcap' => 4,
'cu' => 5,
'tsp' => 6,
'hta' => 7,
'jar' => 8,
'ser' => 9,
'class' => 10,
'hqx' => 11,
'nb' => 12,
'mdb' => 13,
'dot' => 14,
'doc' => 14,
'bin' => 15,
'oda' => 16,
'ogx' => 17,
'pdf' => 18,
'key' => 19,
'pgp' => 20,
'prf' => 21,
'eps' => 22,
'ai' => 22,
'ps' => 22,
'rar' => 23,
'rdf' => 24,
'rss' => 25,
'rtf' => 26,
'smi' => 27,
'smil' => 27,
'cdy' => 28,
'kml' => 29,
'kmz' => 30,
'xul' => 31,
'xlb' => 32,
'xlt' => 32,
'xls' => 32,
'xlam' => 33,
'xlsb' => 34,
'xlsm' => 35,
'xltm' => 36,
'cat' => 37,
'stl' => 38,
'pps' => 39,
'ppt' => 39,
'ppam' => 40,
'pptm' => 41,
'ppsm' => 42,
'potm' => 43,
'docm' => 44,
'dotm' => 45,
'xps' => 46,
'odc' => 47,
'odb' => 48,
'odf' => 49,
'odg' => 50,
'otg' => 51,
'odi' => 52,
'odp' => 53,
'otp' => 54,
'ods' => 55,
'ots' => 56,
'odt' => 57,
'odm' => 58,
'ott' => 59,
'oth' => 60,
'pptx' => 61,
'ppsx' => 62,
'potx' => 63,
'xlsx' => 64,
'xltx' => 65,
'docx' => 66,
'dotx' => 67,
'cod' => 68,
'mmf' => 69,
'sdc' => 70,
'sds' => 71,
'sda' => 72,
'sdd' => 73,
'sdw' => 75,
'sgl' => 76,
'sxc' => 77,
'stc' => 78,
'sxd' => 79,
'std' => 80,
'sxi' => 81,
'sti' => 82,
'sxm' => 83,
'sxw' => 84,
'sxg' => 85,
'stw' => 86,
'sis' => 87,
'vsd' => 88,
'wbxml' => 89,
'wmlc' => 90,
'wmlsc' => 91,
'wpd' => 92,
'wp5' => 93,
'wk' => 94,
'7z' => 95,
'abw' => 96,
'dmg' => 97,
'bcpio' => 98,
'torrent' => 99,
'cab' => 100,
'cbr' => 101,
'cbz' => 102,
'cdf' => 103,
'vcd' => 104,
'pgn' => 105,
'cpio' => 106,
'udeb' => 107,
'deb' => 107,
'dir' => 108,
'dxr' => 108,
'dcr' => 108,
'dms' => 109,
'wad' => 110,
'dvi' => 111,
'flac' => 112,
'pfa' => 113,
'pfb' => 113,
'pcf' => 113,
'gsf' => 113,
'pcf.z' => 113,
'mm' => 114,
'spl' => 115,
'gnumeric' => 116,
'sgf' => 117,
'gcf' => 118,
'taz' => 119,
'gtar' => 119,
'tgz' => 119,
'hdf' => 120,
'rhtml' => 121,
'phtml' => 122,
'pht' => 122,
'php' => 122,
'phps' => 123,
'php3' => 124,
'php3p' => 125,
'php4' => 126,
'ica' => 127,
'ins' => 128,
'isp' => 128,
'iii' => 129,
'iso' => 130,
'jnlp' => 131,
'js' => 132,
'jmz' => 133,
'chrt' => 134,
'kil' => 135,
'skp' => 136,
'skd' => 136,
'skm' => 136,
'skt' => 136,
'kpr' => 137,
'kpt' => 137,
'ksp' => 138,
'kwd' => 139,
'kwt' => 139,
'latex' => 140,
'lha' => 141,
'lyx' => 142,
'lzh' => 143,
'lzx' => 144,
'maker' => 145,
'frm' => 145,
'frame' => 145,
'fm' => 145,
'book' => 145,
'fb' => 145,
'fbdoc' => 145,
'mif' => 146,
'wmd' => 147,
'wmz' => 148,
'dll' => 149,
'bat' => 149,
'exe' => 149,
'com' => 149,
'msi' => 150,
'nc' => 151,
'pac' => 152,
'nwc' => 153,
'o' => 154,
'oza' => 155,
'p7r' => 156,
'crl' => 157,
'pyo' => 158,
'pyc' => 158,
'qtl' => 159,
'rpm' => 160,
'shar' => 161,
'swf' => 162,
'swfl' => 162,
'sitx' => 163,
'sit' => 163,
'sv4cpio' => 164,
'sv4crc' => 165,
'tar' => 166,
'gf' => 168,
'pk' => 169,
'texi' => 170,
'texinfo' => 170,
'sik' => 171,
'~' => 171,
'bak' => 171,
'%' => 171,
'old' => 171,
't' => 172,
'roff' => 172,
'tr' => 172,
'man' => 173,
'me' => 174,
'ms' => 175,
'ustar' => 176,
'src' => 177,
'wz' => 178,
'crt' => 179,
'xcf' => 180,
'fig' => 181,
'xpi' => 182,
'xht' => 183,
'xhtml' => 183,
'xml' => 184,
'xsl' => 184,
'zip' => 185,
'au' => 186,
'snd' => 186,
'mid' => 187,
'midi' => 187,
'kar' => 187,
'mpega' => 188,
'mpga' => 188,
'm4a' => 188,
'mp3' => 188,
'mp2' => 188,
'ogg' => 189,
'oga' => 189,
'spx' => 189,
'sid' => 190,
'aif' => 191,
'aiff' => 191,
'aifc' => 191,
'gsm' => 192,
'm3u' => 193,
'wax' => 194,
'wma' => 195,
'rm' => 196,
'ram' => 196,
'ra' => 197,
'pls' => 198,
'sd2' => 199,
'wav' => 200,
'alc' => 201,
'cac' => 202,
'cache' => 202,
'csf' => 203,
'cascii' => 204,
'cbin' => 204,
'ctab' => 204,
'cdx' => 205,
'cer' => 206,
'c3d' => 207,
'chm' => 208,
'cif' => 209,
'cmdf' => 210,
'cml' => 211,
'cpa' => 212,
'bsd' => 213,
'csml' => 214,
'csm' => 214,
'ctx' => 215,
'cxf' => 216,
'cef' => 216,
'emb' => 217,
'embl' => 217,
'spc' => 218,
'gam' => 219,
'inp' => 219,
'gamin' => 219,
'fchk' => 220,
'fch' => 220,
'cub' => 221,
'gau' => 222,
'gjf' => 222,
'gjc' => 222,
'gal' => 223,
'gcg' => 224,
'gen' => 225,
'hin' => 226,
'istr' => 227,
'ist' => 227,
'dx' => 228,
'jdx' => 228,
'kin' => 229,
'mcm' => 230,
'mmd' => 231,
'mmod' => 231,
'mol' => 232,
'rd' => 233,
'rxn' => 234,
'sdf' => 235,
'sd' => 235,
'tgf' => 236,
'mcif' => 237,
'mol2' => 238,
'b' => 239,
'gpt' => 240,
'mopcrt' => 241,
'zmt' => 241,
'mpc' => 241,
'dat' => 241,
'mop' => 241,
'moo' => 242,
'mvb' => 243,
'prt' => 244,
'aso' => 245,
'val' => 245,
'asn' => 246,
'ent' => 247,
'pdb' => 247,
'ros' => 248,
'sw' => 249,
'vms' => 250,
'vmd' => 251,
'xtel' => 252,
'xyz' => 253,
'gif' => 254,
'ief' => 255,
'jpeg' => 256,
'jpe' => 256,
'jpg' => 256,
'pcx' => 257,
'png' => 258,
'svgz' => 259,
'svg' => 259,
'tif' => 260,
'tiff' => 260,
'djvu' => 261,
'djv' => 261,
'ico' => 262,
'wbmp' => 263,
'ras' => 264,
'cdr' => 265,
'pat' => 266,
'cdt' => 267,
'cpt' => 268,
'art' => 269,
'jng' => 270,
'bmp' => 271,
'psd' => 272,
'pnm' => 273,
'pbm' => 274,
'pgm' => 275,
'ppm' => 276,
'rgb' => 277,
'xbm' => 278,
'xpm' => 279,
'xwd' => 280,
'eml' => 281,
'igs' => 282,
'iges' => 282,
'silo' => 283,
'msh' => 283,
'mesh' => 283,
'icz' => 285,
'ics' => 285,
'css' => 286,
'csv' => 287,
'323' => 288,
'html' => 289,
'htm' => 289,
'shtml' => 289,
'uls' => 290,
'mml' => 291,
'txt' => 292,
'pot' => 292,
'text' => 292,
'asc' => 292,
'rtx' => 293,
'wsc' => 294,
'sct' => 294,
'tsv' => 295,
'ts' => 296,
'tm' => 296,
'jad' => 297,
'wml' => 298,
'wmls' => 299,
'bib' => 300,
'boo' => 301,
'hpp' => 302,
'hh' => 302,
'h++' => 302,
'hxx' => 302,
'cxx' => 303,
'cc' => 303,
'cpp' => 303,
'c++' => 303,
'h' => 304,
'htc' => 305,
'csh' => 306,
'c' => 307,
'patch' => 308,
'diff' => 308,
'd' => 309,
'hs' => 310,
'java' => 311,
'lhs' => 312,
'moc' => 313,
'pas' => 314,
'p' => 314,
'gcd' => 315,
'pm' => 316,
'pl' => 316,
'py' => 317,
'etx' => 318,
'sh' => 319,
'tk' => 320,
'tcl' => 320,
'cls' => 321,
'ltx' => 321,
'sty' => 321,
'tex' => 321,
'vcs' => 322,
'vcf' => 323,
'3gp' => 324,
'dl' => 325,
'dif' => 326,
'dv' => 326,
'fli' => 327,
'gl' => 328,
'mp4' => 329,
'f4v' => 329,
'f4p' => 329,
'mpe' => 330,
'mpeg' => 330,
'mpg' => 330,
'ogv' => 331,
'qt' => 332,
'mov' => 332,
'mxu' => 333,
'lsf' => 334,
'lsx' => 334,
'mng' => 335,
'asx' => 336,
'asf' => 336,
'wm' => 337,
'wmv' => 338,
'wmx' => 339,
'wvx' => 340,
'avi' => 341,
'movie' => 342,
'ice' => 343,
'sisx' => 344,
'wrl' => 345,
'vrm' => 345,
'vrml' => 345,
'f4a' => 346,
'f4b' => 346,
'flv' => 347,
'm4v' => 348,
),
);
}
-417
View File
@@ -1,417 +0,0 @@
<?php
/*
* Base FileTransfer class.
*
* Classes extending this class perform file operations on directories not
* writable by the webserver. To achieve this, the class should connect back
* to the server using some backend (for example FTP or SSH). To keep security,
* the password should always be asked from the user and never stored. For
* safety, all methods operate only inside a "jail", by default the Drupal root.
*/
abstract class FileTransfer {
protected $username;
protected $password;
protected $hostname = 'localhost';
protected $port;
/**
* The constructor for the UpdateConnection class. This method is also called
* from the classes that extend this class and override this method.
*/
function __construct($jail) {
$this->jail = $jail;
}
/**
* Classes that extend this class must override the factory() static method.
*
* @param string $jail
* The full path where all file operations performed by this object will
* be restricted to. This prevents the FileTransfer classes from being
* able to touch other parts of the filesystem.
* @param array $settings
* An array of connection settings for the FileTransfer subclass. If the
* getSettingsForm() method uses any nested settings, the same structure
* will be assumed here.
* @return object
* New instance of the appropriate FileTransfer subclass.
*/
static function factory($jail, $settings) {
throw new FileTransferException('FileTransfer::factory() static method not overridden by FileTransfer subclass.');
}
/**
* Implementation of the magic __get() method.
*
* If the connection isn't set to anything, this will call the connect() method
* and set it to and return the result; afterwards, the connection will be
* returned directly without using this method.
*/
function __get($name) {
if ($name == 'connection') {
$this->connect();
return $this->connection;
}
if ($name == 'chroot') {
$this->setChroot();
return $this->chroot;
}
}
/**
* Connect to the server.
*/
abstract protected function connect();
/**
* Copies a directory.
*
* @param $source
* The source path.
* @param $destination
* The destination path.
*/
public final function copyDirectory($source, $destination) {
$source = $this->sanitizePath($source);
$destination = $this->fixRemotePath($destination);
$this->checkPath($destination);
$this->copyDirectoryJailed($source, $destination);
}
/**
* @see http://php.net/chmod
*
* @param string $path
* @param long $mode
* @param bool $recursive
*/
public final function chmod($path, $mode, $recursive = FALSE) {
if (!in_array('FileTransferChmodInterface', class_implements(get_class($this)))) {
throw new FileTransferException('Unable to change file permissions');
}
$path = $this->sanitizePath($path);
$path = $this->fixRemotePath($path);
$this->checkPath($path);
$this->chmodJailed($path, $mode, $recursive);
}
/**
* Creates a directory.
*
* @param $directory
* The directory to be created.
*/
public final function createDirectory($directory) {
$directory = $this->fixRemotePath($directory);
$this->checkPath($directory);
$this->createDirectoryJailed($directory);
}
/**
* Removes a directory.
*
* @param $directory
* The directory to be removed.
*/
public final function removeDirectory($directory) {
$directory = $this->fixRemotePath($directory);
$this->checkPath($directory);
$this->removeDirectoryJailed($directory);
}
/**
* Copies a file.
*
* @param $source
* The source file.
* @param $destination
* The destination file.
*/
public final function copyFile($source, $destination) {
$source = $this->sanitizePath($source);
$destination = $this->fixRemotePath($destination);
$this->checkPath($destination);
$this->copyFileJailed($source, $destination);
}
/**
* Removes a file.
*
* @param $destination
* The destination file to be removed.
*/
public final function removeFile($destination) {
$destination = $this->fixRemotePath($destination);
$this->checkPath($destination);
$this->removeFileJailed($destination);
}
/**
* Checks that the path is inside the jail and throws an exception if not.
*
* @param $path
* A path to check against the jail.
*/
protected final function checkPath($path) {
$full_jail = $this->chroot . $this->jail;
$full_path = drupal_realpath(substr($this->chroot . $path, 0, strlen($full_jail)));
$full_path = $this->fixRemotePath($full_path, FALSE);
if ($full_jail !== $full_path) {
throw new FileTransferException('@directory is outside of the @jail', NULL, array('@directory' => $path, '@jail' => $this->jail));
}
}
/**
* Returns a modified path suitable for passing to the server.
* If a path is a windows path, makes it POSIX compliant by removing the drive letter.
* If $this->chroot has a value, it is stripped from the path to allow for
* chroot'd filetransfer systems.
*
* @param $path
* @param $strip_chroot
*
* @return string
*/
protected final function fixRemotePath($path, $strip_chroot = TRUE) {
$path = $this->sanitizePath($path);
$path = preg_replace('|^([a-z]{1}):|i', '', $path); // Strip out windows driveletter if its there.
if ($strip_chroot) {
if ($this->chroot && strpos($path, $this->chroot) === 0) {
$path = ($path == $this->chroot) ? '' : substr($path, strlen($this->chroot));
}
}
return $path;
}
/**
* Changes backslashes to slashes, also removes a trailing slash.
*
* @param string $path
* @return string
*/
function sanitizePath($path) {
$path = str_replace('\\', '/', $path); // Windows path sanitization.
if (substr($path, -1) == '/') {
$path = substr($path, 0, -1);
}
return $path;
}
/**
* Copies a directory.
*
* We need a separate method to make the $destination is in the jail.
*
* @param $source
* The source path.
* @param $destination
* The destination path.
*/
protected function copyDirectoryJailed($source, $destination) {
if ($this->isDirectory($destination)) {
$destination = $destination . '/' . drupal_basename($source);
}
$this->createDirectory($destination);
foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) {
$relative_path = substr($filename, strlen($source));
if ($file->isDir()) {
$this->createDirectory($destination . $relative_path);
}
else {
$this->copyFile($file->getPathName(), $destination . $relative_path);
}
}
}
/**
* Creates a directory.
*
* @param $directory
* The directory to be created.
*/
abstract protected function createDirectoryJailed($directory);
/**
* Removes a directory.
*
* @param $directory
* The directory to be removed.
*/
abstract protected function removeDirectoryJailed($directory);
/**
* Copies a file.
*
* @param $source
* The source file.
* @param $destination
* The destination file.
*/
abstract protected function copyFileJailed($source, $destination);
/**
* Removes a file.
*
* @param $destination
* The destination file to be removed.
*/
abstract protected function removeFileJailed($destination);
/**
* Checks if a particular path is a directory
*
* @param $path
* The path to check
*
* @return boolean
*/
abstract public function isDirectory($path);
/**
* Checks if a particular path is a file (not a directory).
*
* @param $path
* The path to check
*
* @return boolean
*/
abstract public function isFile($path);
/**
* Return the chroot property for this connection.
*
* It does this by moving up the tree until it finds itself. If successful,
* it will return the chroot, otherwise FALSE.
*
* @return
* The chroot path for this connection or FALSE.
*/
function findChroot() {
// If the file exists as is, there is no chroot.
$path = __FILE__;
$path = $this->fixRemotePath($path, FALSE);
if ($this->isFile($path)) {
return FALSE;
}
$path = dirname(__FILE__);
$path = $this->fixRemotePath($path, FALSE);
$parts = explode('/', $path);
$chroot = '';
while (count($parts)) {
$check = implode($parts, '/');
if ($this->isFile($check . '/' . drupal_basename(__FILE__))) {
// Remove the trailing slash.
return substr($chroot, 0, -1);
}
$chroot .= array_shift($parts) . '/';
}
return FALSE;
}
/**
* Sets the chroot and changes the jail to match the correct path scheme
*
*/
function setChroot() {
$this->chroot = $this->findChroot();
$this->jail = $this->fixRemotePath($this->jail);
}
/**
* Returns a form to collect connection settings credentials.
*
* Implementing classes can either extend this form with fields collecting the
* specific information they need, or override it entirely.
*/
public function getSettingsForm() {
$form['username'] = array(
'#type' => 'textfield',
'#title' => t('Username'),
);
$form['password'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#description' => t('Your password is not saved in the database and is only used to establish a connection.'),
);
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['advanced']['hostname'] = array(
'#type' => 'textfield',
'#title' => t('Host'),
'#default_value' => 'localhost',
'#description' => t('The connection will be created between your web server and the machine hosting the web server files. In the vast majority of cases, this will be the same machine, and "localhost" is correct.'),
);
$form['advanced']['port'] = array(
'#type' => 'textfield',
'#title' => t('Port'),
'#default_value' => NULL,
);
return $form;
}
}
/**
* FileTransferException class.
*/
class FileTransferException extends Exception {
public $arguments;
function __construct($message, $code = 0, $arguments = array()) {
parent::__construct($message, $code);
$this->arguments = $arguments;
}
}
/**
* A FileTransfer Class implementing this interface can be used to chmod files.
*/
interface FileTransferChmodInterface {
/**
* Changes the permissions of the file / directory specified in $path
*
* @param string $path
* Path to change permissions of.
* @param long $mode
* The new file permission mode to be passed to chmod().
* @param boolean $recursive
* Pass TRUE to recursively chmod the entire directory specified in $path.
*/
function chmodJailed($path, $mode, $recursive);
}
/**
* Provides an interface for iterating recursively over filesystem directories.
*
* Manually skips '.' and '..' directories, since no existing method is
* available in PHP 5.2.
*
* @todo Depreciate in favor of RecursiveDirectoryIterator::SKIP_DOTS once PHP
* 5.3 or later is required.
*/
class SkipDotsRecursiveDirectoryIterator extends RecursiveDirectoryIterator {
/**
* Constructs a SkipDotsRecursiveDirectoryIterator
*
* @param $path
* The path of the directory to be iterated over.
*/
function __construct($path) {
parent::__construct($path);
}
function next() {
parent::next();
while ($this->isDot()) {
parent::next();
}
}
}
-144
View File
@@ -1,144 +0,0 @@
<?php
/**
* Base class for FTP implementations.
*/
abstract class FileTransferFTP extends FileTransfer {
public function __construct($jail, $username, $password, $hostname, $port) {
$this->username = $username;
$this->password = $password;
$this->hostname = $hostname;
$this->port = $port;
parent::__construct($jail);
}
/**
* Return an object which can implement the FTP protocol.
*
* @param string $jail
* @param array $settings
* @return FileTransferFTP
* The appropriate FileTransferFTP subclass based on the available
* options. If the FTP PHP extension is available, use it.
*/
static function factory($jail, $settings) {
$username = empty($settings['username']) ? '' : $settings['username'];
$password = empty($settings['password']) ? '' : $settings['password'];
$hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname'];
$port = empty($settings['advanced']['port']) ? 21 : $settings['advanced']['port'];
if (function_exists('ftp_connect')) {
$class = 'FileTransferFTPExtension';
}
else {
throw new FileTransferException('No FTP backend available.');
}
return new $class($jail, $username, $password, $hostname, $port);
}
/**
* Returns the form to configure the FileTransfer class for FTP.
*/
public function getSettingsForm() {
$form = parent::getSettingsForm();
$form['advanced']['port']['#default_value'] = 21;
return $form;
}
}
class FileTransferFTPExtension extends FileTransferFTP implements FileTransferChmodInterface {
public function connect() {
$this->connection = ftp_connect($this->hostname, $this->port);
if (!$this->connection) {
throw new FileTransferException("Cannot connect to FTP Server, check settings");
}
if (!ftp_login($this->connection, $this->username, $this->password)) {
throw new FileTransferException("Cannot log in to FTP server. Check username and password");
}
}
protected function copyFileJailed($source, $destination) {
if (!@ftp_put($this->connection, $destination, $source, FTP_BINARY)) {
throw new FileTransferException("Cannot move @source to @destination", NULL, array("@source" => $source, "@destination" => $destination));
}
}
protected function createDirectoryJailed($directory) {
if (!ftp_mkdir($this->connection, $directory)) {
throw new FileTransferException("Cannot create directory @directory", NULL, array("@directory" => $directory));
}
}
protected function removeDirectoryJailed($directory) {
$pwd = ftp_pwd($this->connection);
if (!ftp_chdir($this->connection, $directory)) {
throw new FileTransferException("Unable to change to directory @directory", NULL, array('@directory' => $directory));
}
$list = @ftp_nlist($this->connection, '.');
if (!$list) {
$list = array();
}
foreach ($list as $item){
if ($item == '.' || $item == '..') {
continue;
}
if (@ftp_chdir($this->connection, $item)){
ftp_cdup($this->connection);
$this->removeDirectory(ftp_pwd($this->connection) . '/' . $item);
}
else {
$this->removeFile(ftp_pwd($this->connection) . '/' . $item);
}
}
ftp_chdir($this->connection, $pwd);
if (!ftp_rmdir($this->connection, $directory)) {
throw new FileTransferException("Unable to remove to directory @directory", NULL, array('@directory' => $directory));
}
}
protected function removeFileJailed($destination) {
if (!ftp_delete($this->connection, $destination)) {
throw new FileTransferException("Unable to remove to file @file", NULL, array('@file' => $destination));
}
}
public function isDirectory($path) {
$result = FALSE;
$curr = ftp_pwd($this->connection);
if (@ftp_chdir($this->connection, $path)) {
$result = TRUE;
}
ftp_chdir($this->connection, $curr);
return $result;
}
public function isFile($path) {
return ftp_size($this->connection, $path) != -1;
}
function chmodJailed($path, $mode, $recursive) {
if (!ftp_chmod($this->connection, $mode, $path)) {
throw new FileTransferException("Unable to set permissions on %file", NULL, array ('%file' => $path));
}
if ($this->isDirectory($path) && $recursive) {
$filelist = @ftp_nlist($this->connection, $path);
if (!$filelist) {
//empty directory - returns false
return;
}
foreach ($filelist as $file) {
$this->chmodJailed($file, $mode, $recursive);
}
}
}
}
if (!function_exists('ftp_chmod')) {
function ftp_chmod($ftp_stream, $mode, $filename) {
return ftp_site($ftp_stream, sprintf('CHMOD %o %s', $mode, $filename));
}
}
-76
View File
@@ -1,76 +0,0 @@
<?php
/**
* The local connection class for copying files as the httpd user.
*/
class FileTransferLocal extends FileTransfer implements FileTransferChmodInterface {
function connect() {
// No-op
}
static function factory($jail, $settings) {
return new FileTransferLocal($jail);
}
protected function copyFileJailed($source, $destination) {
if (@!copy($source, $destination)) {
throw new FileTransferException('Cannot copy %source to %destination.', NULL, array('%source' => $source, '%destination' => $destination));
}
}
protected function createDirectoryJailed($directory) {
if (!is_dir($directory) && @!mkdir($directory, 0777, TRUE)) {
throw new FileTransferException('Cannot create directory %directory.', NULL, array('%directory' => $directory));
}
}
protected function removeDirectoryJailed($directory) {
if (!is_dir($directory)) {
// Programmer error assertion, not something we expect users to see.
throw new FileTransferException('removeDirectoryJailed() called with a path (%directory) that is not a directory.', NULL, array('%directory' => $directory));
}
foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) {
if ($file->isDir()) {
if (@!drupal_rmdir($filename)) {
throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $filename));
}
}
elseif ($file->isFile()) {
if (@!drupal_unlink($filename)) {
throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $filename));
}
}
}
if (@!drupal_rmdir($directory)) {
throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $directory));
}
}
protected function removeFileJailed($file) {
if (@!drupal_unlink($file)) {
throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $file));
}
}
public function isDirectory($path) {
return is_dir($path);
}
public function isFile($path) {
return is_file($path);
}
public function chmodJailed($path, $mode, $recursive) {
if ($recursive && is_dir($path)) {
foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) {
if (@!chmod($filename, $mode)) {
throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $filename));
}
}
}
elseif (@!chmod($path, $mode)) {
throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $path));
}
}
}
-108
View File
@@ -1,108 +0,0 @@
<?php
/**
* The SSH connection class for the update module.
*/
class FileTransferSSH extends FileTransfer implements FileTransferChmodInterface {
function __construct($jail, $username, $password, $hostname = "localhost", $port = 22) {
$this->username = $username;
$this->password = $password;
$this->hostname = $hostname;
$this->port = $port;
parent::__construct($jail);
}
function connect() {
$this->connection = @ssh2_connect($this->hostname, $this->port);
if (!$this->connection) {
throw new FileTransferException('SSH Connection failed to @host:@port', NULL, array('@host' => $this->hostname, '@port' => $this->port));
}
if (!@ssh2_auth_password($this->connection, $this->username, $this->password)) {
throw new FileTransferException('The supplied username/password combination was not accepted.');
}
}
static function factory($jail, $settings) {
$username = empty($settings['username']) ? '' : $settings['username'];
$password = empty($settings['password']) ? '' : $settings['password'];
$hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname'];
$port = empty($settings['advanced']['port']) ? 22 : $settings['advanced']['port'];
return new FileTransferSSH($jail, $username, $password, $hostname, $port);
}
protected function copyFileJailed($source, $destination) {
if (!@ssh2_scp_send($this->connection, $source, $destination)) {
throw new FileTransferException('Cannot copy @source_file to @destination_file.', NULL, array('@source' => $source, '@destination' => $destination));
}
}
protected function copyDirectoryJailed($source, $destination) {
if (@!ssh2_exec($this->connection, 'cp -Rp ' . escapeshellarg($source) . ' ' . escapeshellarg($destination))) {
throw new FileTransferException('Cannot copy directory @directory.', NULL, array('@directory' => $source));
}
}
protected function createDirectoryJailed($directory) {
if (@!ssh2_exec($this->connection, 'mkdir ' . escapeshellarg($directory))) {
throw new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory));
}
}
protected function removeDirectoryJailed($directory) {
if (@!ssh2_exec($this->connection, 'rm -Rf ' . escapeshellarg($directory))) {
throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory));
}
}
protected function removeFileJailed($destination) {
if (!@ssh2_exec($this->connection, 'rm ' . escapeshellarg($destination))) {
throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $destination));
}
}
/**
* WARNING: This is untested. It is not currently used, but should do the trick.
*/
public function isDirectory($path) {
$directory = escapeshellarg($path);
$cmd = "[ -d {$directory} ] && echo 'yes'";
if ($output = @ssh2_exec($this->connection, $cmd)) {
if ($output == 'yes') {
return TRUE;
}
return FALSE;
} else {
throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path));
}
}
public function isFile($path) {
$file = escapeshellarg($path);
$cmd = "[ -f {$file} ] && echo 'yes'";
if ($output = @ssh2_exec($this->connection, $cmd)) {
if ($output == 'yes') {
return TRUE;
}
return FALSE;
} else {
throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path));
}
}
function chmodJailed($path, $mode, $recursive) {
$cmd = sprintf("chmod %s%o %s", $recursive ? '-R ' : '', $mode, escapeshellarg($path));
if (@!ssh2_exec($this->connection, $cmd)) {
throw new FileTransferException('Cannot change permissions of @path.', NULL, array('@path' => $path));
}
}
/**
* Returns the form to configure the FileTransfer class for SSH.
*/
public function getSettingsForm() {
$form = parent::getSettingsForm();
$form['advanced']['port']['#default_value'] = 22;
return $form;
}
}
-4635
View File
File diff suppressed because it is too large Load Diff
-145
View File
@@ -1,145 +0,0 @@
<?php
/**
* @file
* Directed acyclic graph manipulation.
*/
/**
* Performs a depth-first search and sort on a directed acyclic graph.
*
* @param $graph
* A three dimensional associated array, with the first keys being the names
* of the vertices, these can be strings or numbers. The second key is
* 'edges' and the third one are again vertices, each such key representing
* an edge. Values of array elements are copied over.
*
* Example:
* @code
* $graph[1]['edges'][2] = 1;
* $graph[2]['edges'][3] = 1;
* $graph[2]['edges'][4] = 1;
* $graph[3]['edges'][4] = 1;
* @endcode
*
* On return you will also have:
* @code
* $graph[1]['paths'][2] = 1;
* $graph[1]['paths'][3] = 1;
* $graph[2]['reverse_paths'][1] = 1;
* $graph[3]['reverse_paths'][1] = 1;
* @endcode
*
* @return
* The passed-in $graph with more secondary keys filled in:
* - 'paths': Contains a list of vertices than can be reached on a path from
* this vertex.
* - 'reverse_paths': Contains a list of vertices that has a path from them
* to this vertex.
* - 'weight': If there is a path from a vertex to another then the weight of
* the latter is higher.
* - 'component': Vertices in the same component have the same component
* identifier.
*
* @see _drupal_depth_first_search()
*/
function drupal_depth_first_search(&$graph) {
$state = array(
// The order of last visit of the depth first search. This is the reverse
// of the topological order if the graph is acyclic.
'last_visit_order' => array(),
// The components of the graph.
'components' => array(),
);
// Perform the actual search.
foreach ($graph as $start => $data) {
_drupal_depth_first_search($graph, $state, $start);
}
// We do such a numbering that every component starts with 0. This is useful
// for module installs as we can install every 0 weighted module in one
// request, and then every 1 weighted etc.
$component_weights = array();
foreach ($state['last_visit_order'] as $vertex) {
$component = $graph[$vertex]['component'];
if (!isset($component_weights[$component])) {
$component_weights[$component] = 0;
}
$graph[$vertex]['weight'] = $component_weights[$component]--;
}
}
/**
* Performs a depth-first search on a graph.
*
* @param $graph
* A three dimensional associated graph array.
* @param $state
* An associative array. The key 'last_visit_order' stores a list of the
* vertices visited. The key components stores list of vertices belonging
* to the same the component.
* @param $start
* An arbitrary vertex where we started traversing the graph.
* @param $component
* The component of the last vertex.
*
* @see drupal_depth_first_search()
*/
function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL) {
// Assign new component for each new vertex, i.e. when not called recursively.
if (!isset($component)) {
$component = $start;
}
// Nothing to do, if we already visited this vertex.
if (isset($graph[$start]['paths'])) {
return;
}
// Mark $start as visited.
$graph[$start]['paths'] = array();
// Assign $start to the current component.
$graph[$start]['component'] = $component;
$state['components'][$component][] = $start;
// Visit edges of $start.
if (isset($graph[$start]['edges'])) {
foreach ($graph[$start]['edges'] as $end => $v) {
// Mark that $start can reach $end.
$graph[$start]['paths'][$end] = $v;
if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) {
// This vertex already has a component, use that from now on and
// reassign all the previously explored vertices.
$new_component = $graph[$end]['component'];
foreach ($state['components'][$component] as $vertex) {
$graph[$vertex]['component'] = $new_component;
$state['components'][$new_component][] = $vertex;
}
unset($state['components'][$component]);
$component = $new_component;
}
// Only visit existing vertices.
if (isset($graph[$end])) {
// Visit the connected vertex.
_drupal_depth_first_search($graph, $state, $end, $component);
// All vertices reachable by $end are also reachable by $start.
$graph[$start]['paths'] += $graph[$end]['paths'];
}
}
}
// Now that any other subgraph has been explored, add $start to all reverse
// paths.
foreach ($graph[$start]['paths'] as $end => $v) {
if (isset($graph[$end])) {
$graph[$end]['reverse_paths'][$start] = $v;
}
}
// Record the order of the last visit. This is the reverse of the
// topological order if the graph is acyclic.
$state['last_visit_order'][] = $start;
}
-435
View File
@@ -1,435 +0,0 @@
<?php
/**
* @file
* API for manipulating images.
*/
/**
* @defgroup image Image toolkits
* @{
* Functions for image file manipulations.
*
* 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 ImageMagick 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 discovered based on the associated module's
* hook_image_toolkits. Additionally the image toolkit include file
* must be identified in the files array in the module.info file. The
* toolkit must then be enabled using the admin/config/media/image-toolkit
* form.
*
* Only one toolkit may be 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.
*/
/**
* Gets a list of available toolkits.
*
* @return
* An array with the toolkit names as keys and the descriptions as values.
*/
function image_get_available_toolkits() {
// hook_image_toolkits returns an array of toolkit names.
$toolkits = module_invoke_all('image_toolkits');
$output = array();
foreach ($toolkits as $name => $info) {
// Only allow modules that aren't marked as unavailable.
if ($info['available']) {
$output[$name] = $info['title'];
}
}
return $output;
}
/**
* Gets 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 (!isset($toolkit)) {
$toolkits = image_get_available_toolkits();
$toolkit = variable_get('image_toolkit', 'gd');
if (!isset($toolkits[$toolkit]) || !function_exists('image_' . $toolkit . '_load')) {
// The selected toolkit isn't available so return the first one found. If
// none are available this will return FALSE.
reset($toolkits);
$toolkit = key($toolkits);
}
}
return $toolkit;
}
/**
* Invokes the given method using the currently selected toolkit.
*
* @param $method
* A string containing the method to invoke.
* @param $image
* An image object returned by image_load().
* @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, stdClass $image, array $params = array()) {
$function = 'image_' . $image->toolkit . '_' . $method;
if (function_exists($function)) {
array_unshift($params, $image);
return call_user_func_array($function, $params);
}
watchdog('image', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR);
return FALSE;
}
/**
* Gets details about an image.
*
* Drupal supports GIF, JPG and PNG file formats when used with the GD
* toolkit, and may support others, depending on which toolkits are
* installed.
*
* @param $filepath
* String specifying the path of the image file.
* @param $toolkit
* An optional image toolkit name to override the default.
*
* @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($filepath, $toolkit = FALSE) {
$details = FALSE;
if (!is_file($filepath) && !is_uploaded_file($filepath)) {
return $details;
}
if (!$toolkit) {
$toolkit = image_get_toolkit();
}
if ($toolkit) {
$image = new stdClass();
$image->source = $filepath;
$image->toolkit = $toolkit;
$details = image_toolkit_invoke('get_info', $image);
if (isset($details) && is_array($details)) {
$details['file_size'] = filesize($filepath);
}
}
return $details;
}
/**
* Scales an image to the exact width and height given.
*
* This function achieves the target aspect ratio by cropping the original image
* equally on both sides, or equally on the top and bottom. This function is
* useful to create uniform sized avatars from larger images.
*
* The resulting image always has the exact target dimensions.
*
* @param $image
* An image object returned by image_load().
* @param $width
* The target width, in pixels.
* @param $height
* The target height, in pixels.
*
* @return
* TRUE on success, FALSE on failure.
*
* @see image_load()
* @see image_resize()
* @see image_crop()
*/
function image_scale_and_crop(stdClass $image, $width, $height) {
$scale = max($width / $image->info['width'], $height / $image->info['height']);
$x = ($image->info['width'] * $scale - $width) / 2;
$y = ($image->info['height'] * $scale - $height) / 2;
if (image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) {
return image_crop($image, $x, $y, $width, $height);
}
return FALSE;
}
/**
* Scales image dimensions while maintaining aspect ratio.
*
* The resulting dimensions can be smaller for one or both target dimensions.
*
* @param $dimensions
* Dimensions to be modified - an array with components width and height, in
* pixels.
* @param $width
* The target width, in pixels. If this value is NULL then the scaling will be
* based only on the height value.
* @param $height
* The target height, in pixels. If this value is NULL then the scaling will
* be based only on the width value.
* @param $upscale
* Boolean indicating that images smaller than the target dimensions will be
* scaled up. This generally results in a low quality image.
*
* @return
* TRUE if $dimensions was modified, FALSE otherwise.
*
* @see image_scale()
*/
function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) {
$aspect = $dimensions['height'] / $dimensions['width'];
// Calculate one of the dimensions from the other target dimension,
// ensuring the same aspect ratio as the source dimensions. If one of the
// target dimensions is missing, that is the one that is calculated. If both
// are specified then the dimension calculated is the one that would not be
// calculated to be bigger than its target.
if (($width && !$height) || ($width && $height && $aspect < $height / $width)) {
$height = (int) round($width * $aspect);
}
else {
$width = (int) round($height / $aspect);
}
// Don't upscale if the option isn't enabled.
if (!$upscale && ($width >= $dimensions['width'] || $height >= $dimensions['height'])) {
return FALSE;
}
$dimensions['width'] = $width;
$dimensions['height'] = $height;
return TRUE;
}
/**
* Scales an image while maintaining aspect ratio.
*
* The resulting image can be smaller for one or both target dimensions.
*
* @param $image
* An image object returned by image_load().
* @param $width
* The target width, in pixels. This value is omitted then the scaling will
* based only on the height value.
* @param $height
* The target height, in pixels. This value is omitted then the scaling will
* based only on the width value.
* @param $upscale
* Boolean indicating that files smaller than the dimensions will be scaled
* up. This generally results in a low quality image.
*
* @return
* TRUE on success, FALSE on failure.
*
* @see image_dimensions_scale()
* @see image_load()
* @see image_scale_and_crop()
*/
function image_scale(stdClass $image, $width = NULL, $height = NULL, $upscale = FALSE) {
$dimensions = $image->info;
// Scale the dimensions - if they don't change then just return success.
if (!image_dimensions_scale($dimensions, $width, $height, $upscale)) {
return TRUE;
}
return image_resize($image, $dimensions['width'], $dimensions['height']);
}
/**
* Resizes an image to the given dimensions (ignoring aspect ratio).
*
* @param $image
* An image object returned by image_load().
* @param $width
* The target width, in pixels.
* @param $height
* The target height, in pixels.
*
* @return
* TRUE on success, FALSE on failure.
*
* @see image_load()
* @see image_gd_resize()
*/
function image_resize(stdClass $image, $width, $height) {
$width = (int) round($width);
$height = (int) round($height);
return image_toolkit_invoke('resize', $image, array($width, $height));
}
/**
* Rotates an image by the given number of degrees.
*
* @param $image
* An image object returned by image_load().
* @param $degrees
* The number of (clockwise) degrees to rotate the image.
* @param $background
* An hexadecimal 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. For images that support
* transparency, this will default to transparent. Otherwise it will
* be white.
*
* @return
* TRUE on success, FALSE on failure.
*
* @see image_load()
* @see image_gd_rotate()
*/
function image_rotate(stdClass $image, $degrees, $background = NULL) {
return image_toolkit_invoke('rotate', $image, array($degrees, $background));
}
/**
* Crops an image to a rectangle specified by the given dimensions.
*
* @param $image
* An image object returned by image_load().
* @param $x
* The top left coordinate, in pixels, of the crop area (x axis value).
* @param $y
* The top left coordinate, 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 on success, FALSE on failure.
*
* @see image_load()
* @see image_scale_and_crop()
* @see image_gd_crop()
*/
function image_crop(stdClass $image, $x, $y, $width, $height) {
$aspect = $image->info['height'] / $image->info['width'];
if (empty($height)) $height = $width / $aspect;
if (empty($width)) $width = $height * $aspect;
$width = (int) round($width);
$height = (int) round($height);
return image_toolkit_invoke('crop', $image, array($x, $y, $width, $height));
}
/**
* Converts an image to grayscale.
*
* @param $image
* An image object returned by image_load().
*
* @return
* TRUE on success, FALSE on failure.
*
* @see image_load()
* @see image_gd_desaturate()
*/
function image_desaturate(stdClass $image) {
return image_toolkit_invoke('desaturate', $image);
}
/**
* Loads an image file and returns an image object.
*
* Any changes to the file are not saved until image_save() is called.
*
* @param $file
* Path to an image file.
* @param $toolkit
* An optional, image toolkit name to override the default.
*
* @return
* An image object or FALSE if there was a problem loading the file. The
* image object has the following properties:
* - 'source' - The original file path.
* - 'info' - The array of information returned by image_get_info()
* - 'toolkit' - The name of the image toolkit requested when the image was
* loaded.
* Image toolkits may add additional properties. The caller is advised not to
* monkey about with them.
*
* @see image_save()
* @see image_get_info()
* @see image_get_available_toolkits()
* @see image_gd_load()
*/
function image_load($file, $toolkit = FALSE) {
if (!$toolkit) {
$toolkit = image_get_toolkit();
}
if ($toolkit) {
$image = new stdClass();
$image->source = $file;
$image->info = image_get_info($file, $toolkit);
if (isset($image->info) && is_array($image->info)) {
$image->toolkit = $toolkit;
if (image_toolkit_invoke('load', $image)) {
return $image;
}
}
}
return FALSE;
}
/**
* Closes the image and saves the changes to a file.
*
* @param $image
* An image object returned by image_load(). The object's 'info' property
* will be updated if the file is saved successfully.
* @param $destination
* Destination path where the image should be saved. If it is empty the
* original image file will be overwritten.
*
* @return
* TRUE on success, FALSE on failure.
*
* @see image_load()
* @see image_gd_save()
*/
function image_save(stdClass $image, $destination = NULL) {
if (empty($destination)) {
$destination = $image->source;
}
if ($return = image_toolkit_invoke('save', $image, array($destination))) {
// Clear the cached file size and refresh the image information.
clearstatcache();
$image->info = image_get_info($destination, $image->toolkit);
if (drupal_chmod($destination)) {
return $return;
}
}
return FALSE;
}
/**
* @} End of "defgroup image".
*/
File diff suppressed because it is too large Load Diff
-1320
View File
File diff suppressed because it is too large Load Diff
-483
View File
@@ -1,483 +0,0 @@
<?php
/**
* @file
* Provides a list of countries and languages based on ISO standards.
*/
/**
* Get an array of all country code => country name pairs.
*
* Get an array of all country code => country name pairs as laid out
* in ISO 3166-1 alpha-2.
* Grabbed from location project (http://drupal.org/project/location).
* @return
* An array of all country code => country name pairs.
*/
function _country_get_predefined_list() {
static $countries;
if (isset($countries)) {
return $countries;
}
$t = get_t();
$countries = array(
'AD' => $t('Andorra'),
'AE' => $t('United Arab Emirates'),
'AF' => $t('Afghanistan'),
'AG' => $t('Antigua and Barbuda'),
'AI' => $t('Anguilla'),
'AL' => $t('Albania'),
'AM' => $t('Armenia'),
'AN' => $t('Netherlands Antilles'),
'AO' => $t('Angola'),
'AQ' => $t('Antarctica'),
'AR' => $t('Argentina'),
'AS' => $t('American Samoa'),
'AT' => $t('Austria'),
'AU' => $t('Australia'),
'AW' => $t('Aruba'),
'AX' => $t('Aland Islands'),
'AZ' => $t('Azerbaijan'),
'BA' => $t('Bosnia and Herzegovina'),
'BB' => $t('Barbados'),
'BD' => $t('Bangladesh'),
'BE' => $t('Belgium'),
'BF' => $t('Burkina Faso'),
'BG' => $t('Bulgaria'),
'BH' => $t('Bahrain'),
'BI' => $t('Burundi'),
'BJ' => $t('Benin'),
'BL' => $t('Saint Barthélemy'),
'BM' => $t('Bermuda'),
'BN' => $t('Brunei'),
'BO' => $t('Bolivia'),
'BR' => $t('Brazil'),
'BS' => $t('Bahamas'),
'BT' => $t('Bhutan'),
'BV' => $t('Bouvet Island'),
'BW' => $t('Botswana'),
'BY' => $t('Belarus'),
'BZ' => $t('Belize'),
'CA' => $t('Canada'),
'CC' => $t('Cocos (Keeling) Islands'),
'CD' => $t('Congo (Kinshasa)'),
'CF' => $t('Central African Republic'),
'CG' => $t('Congo (Brazzaville)'),
'CH' => $t('Switzerland'),
'CI' => $t('Ivory Coast'),
'CK' => $t('Cook Islands'),
'CL' => $t('Chile'),
'CM' => $t('Cameroon'),
'CN' => $t('China'),
'CO' => $t('Colombia'),
'CR' => $t('Costa Rica'),
'CU' => $t('Cuba'),
'CW' => $t('Curaçao'),
'CV' => $t('Cape Verde'),
'CX' => $t('Christmas Island'),
'CY' => $t('Cyprus'),
'CZ' => $t('Czech Republic'),
'DE' => $t('Germany'),
'DJ' => $t('Djibouti'),
'DK' => $t('Denmark'),
'DM' => $t('Dominica'),
'DO' => $t('Dominican Republic'),
'DZ' => $t('Algeria'),
'EC' => $t('Ecuador'),
'EE' => $t('Estonia'),
'EG' => $t('Egypt'),
'EH' => $t('Western Sahara'),
'ER' => $t('Eritrea'),
'ES' => $t('Spain'),
'ET' => $t('Ethiopia'),
'FI' => $t('Finland'),
'FJ' => $t('Fiji'),
'FK' => $t('Falkland Islands'),
'FM' => $t('Micronesia'),
'FO' => $t('Faroe Islands'),
'FR' => $t('France'),
'GA' => $t('Gabon'),
'GB' => $t('United Kingdom'),
'GD' => $t('Grenada'),
'GE' => $t('Georgia'),
'GF' => $t('French Guiana'),
'GG' => $t('Guernsey'),
'GH' => $t('Ghana'),
'GI' => $t('Gibraltar'),
'GL' => $t('Greenland'),
'GM' => $t('Gambia'),
'GN' => $t('Guinea'),
'GP' => $t('Guadeloupe'),
'GQ' => $t('Equatorial Guinea'),
'GR' => $t('Greece'),
'GS' => $t('South Georgia and the South Sandwich Islands'),
'GT' => $t('Guatemala'),
'GU' => $t('Guam'),
'GW' => $t('Guinea-Bissau'),
'GY' => $t('Guyana'),
'HK' => $t('Hong Kong S.A.R., China'),
'HM' => $t('Heard Island and McDonald Islands'),
'HN' => $t('Honduras'),
'HR' => $t('Croatia'),
'HT' => $t('Haiti'),
'HU' => $t('Hungary'),
'ID' => $t('Indonesia'),
'IE' => $t('Ireland'),
'IL' => $t('Israel'),
'IM' => $t('Isle of Man'),
'IN' => $t('India'),
'IO' => $t('British Indian Ocean Territory'),
'IQ' => $t('Iraq'),
'IR' => $t('Iran'),
'IS' => $t('Iceland'),
'IT' => $t('Italy'),
'JE' => $t('Jersey'),
'JM' => $t('Jamaica'),
'JO' => $t('Jordan'),
'JP' => $t('Japan'),
'KE' => $t('Kenya'),
'KG' => $t('Kyrgyzstan'),
'KH' => $t('Cambodia'),
'KI' => $t('Kiribati'),
'KM' => $t('Comoros'),
'KN' => $t('Saint Kitts and Nevis'),
'KP' => $t('North Korea'),
'KR' => $t('South Korea'),
'KW' => $t('Kuwait'),
'KY' => $t('Cayman Islands'),
'KZ' => $t('Kazakhstan'),
'LA' => $t('Laos'),
'LB' => $t('Lebanon'),
'LC' => $t('Saint Lucia'),
'LI' => $t('Liechtenstein'),
'LK' => $t('Sri Lanka'),
'LR' => $t('Liberia'),
'LS' => $t('Lesotho'),
'LT' => $t('Lithuania'),
'LU' => $t('Luxembourg'),
'LV' => $t('Latvia'),
'LY' => $t('Libya'),
'MA' => $t('Morocco'),
'MC' => $t('Monaco'),
'MD' => $t('Moldova'),
'ME' => $t('Montenegro'),
'MF' => $t('Saint Martin (French part)'),
'MG' => $t('Madagascar'),
'MH' => $t('Marshall Islands'),
'MK' => $t('Macedonia'),
'ML' => $t('Mali'),
'MM' => $t('Myanmar'),
'MN' => $t('Mongolia'),
'MO' => $t('Macao S.A.R., China'),
'MP' => $t('Northern Mariana Islands'),
'MQ' => $t('Martinique'),
'MR' => $t('Mauritania'),
'MS' => $t('Montserrat'),
'MT' => $t('Malta'),
'MU' => $t('Mauritius'),
'MV' => $t('Maldives'),
'MW' => $t('Malawi'),
'MX' => $t('Mexico'),
'MY' => $t('Malaysia'),
'MZ' => $t('Mozambique'),
'NA' => $t('Namibia'),
'NC' => $t('New Caledonia'),
'NE' => $t('Niger'),
'NF' => $t('Norfolk Island'),
'NG' => $t('Nigeria'),
'NI' => $t('Nicaragua'),
'NL' => $t('Netherlands'),
'NO' => $t('Norway'),
'NP' => $t('Nepal'),
'NR' => $t('Nauru'),
'NU' => $t('Niue'),
'NZ' => $t('New Zealand'),
'OM' => $t('Oman'),
'PA' => $t('Panama'),
'PE' => $t('Peru'),
'PF' => $t('French Polynesia'),
'PG' => $t('Papua New Guinea'),
'PH' => $t('Philippines'),
'PK' => $t('Pakistan'),
'PL' => $t('Poland'),
'PM' => $t('Saint Pierre and Miquelon'),
'PN' => $t('Pitcairn'),
'PR' => $t('Puerto Rico'),
'PS' => $t('Palestinian Territory'),
'PT' => $t('Portugal'),
'PW' => $t('Palau'),
'PY' => $t('Paraguay'),
'QA' => $t('Qatar'),
'RE' => $t('Reunion'),
'RO' => $t('Romania'),
'RS' => $t('Serbia'),
'RU' => $t('Russia'),
'RW' => $t('Rwanda'),
'SA' => $t('Saudi Arabia'),
'SB' => $t('Solomon Islands'),
'SC' => $t('Seychelles'),
'SD' => $t('Sudan'),
'SE' => $t('Sweden'),
'SG' => $t('Singapore'),
'SH' => $t('Saint Helena'),
'SI' => $t('Slovenia'),
'SJ' => $t('Svalbard and Jan Mayen'),
'SK' => $t('Slovakia'),
'SL' => $t('Sierra Leone'),
'SM' => $t('San Marino'),
'SN' => $t('Senegal'),
'SO' => $t('Somalia'),
'SR' => $t('Suriname'),
'ST' => $t('Sao Tome and Principe'),
'SV' => $t('El Salvador'),
'SY' => $t('Syria'),
'SZ' => $t('Swaziland'),
'TC' => $t('Turks and Caicos Islands'),
'TD' => $t('Chad'),
'TF' => $t('French Southern Territories'),
'TG' => $t('Togo'),
'TH' => $t('Thailand'),
'TJ' => $t('Tajikistan'),
'TK' => $t('Tokelau'),
'TL' => $t('Timor-Leste'),
'TM' => $t('Turkmenistan'),
'TN' => $t('Tunisia'),
'TO' => $t('Tonga'),
'TR' => $t('Turkey'),
'TT' => $t('Trinidad and Tobago'),
'TV' => $t('Tuvalu'),
'TW' => $t('Taiwan'),
'TZ' => $t('Tanzania'),
'UA' => $t('Ukraine'),
'UG' => $t('Uganda'),
'UM' => $t('United States Minor Outlying Islands'),
'US' => $t('United States'),
'UY' => $t('Uruguay'),
'UZ' => $t('Uzbekistan'),
'VA' => $t('Vatican'),
'VC' => $t('Saint Vincent and the Grenadines'),
'VE' => $t('Venezuela'),
'VG' => $t('British Virgin Islands'),
'VI' => $t('U.S. Virgin Islands'),
'VN' => $t('Vietnam'),
'VU' => $t('Vanuatu'),
'WF' => $t('Wallis and Futuna'),
'WS' => $t('Samoa'),
'YE' => $t('Yemen'),
'YT' => $t('Mayotte'),
'ZA' => $t('South Africa'),
'ZM' => $t('Zambia'),
'ZW' => $t('Zimbabwe'),
);
// Sort the list.
natcasesort($countries);
return $countries;
}
/**
* @ingroup locale-api-predefined List of predefined languages
* @{
*/
/**
* Some of the common languages with their English and native names
*
* Based on ISO 639 and http://people.w3.org/rishida/names/languages.html
*/
function _locale_get_predefined_list() {
return array(
'aa' => array('Afar'),
'ab' => array('Abkhazian', 'аҧсуа бызшәа'),
'ae' => array('Avestan'),
'af' => array('Afrikaans'),
'ak' => array('Akan'),
'am' => array('Amharic', 'አማርኛ'),
'ar' => array('Arabic', /* Left-to-right marker "" */ 'العربية', LANGUAGE_RTL),
'as' => array('Assamese'),
'ast' => array('Asturian'),
'av' => array('Avar'),
'ay' => array('Aymara'),
'az' => array('Azerbaijani', 'azərbaycan'),
'ba' => array('Bashkir'),
'be' => array('Belarusian', 'Беларуская'),
'bg' => array('Bulgarian', 'Български'),
'bh' => array('Bihari'),
'bi' => array('Bislama'),
'bm' => array('Bambara', 'Bamanankan'),
'bn' => array('Bengali'),
'bo' => array('Tibetan'),
'br' => array('Breton'),
'bs' => array('Bosnian', 'Bosanski'),
'ca' => array('Catalan', 'Català'),
'ce' => array('Chechen'),
'ch' => array('Chamorro'),
'co' => array('Corsican'),
'cr' => array('Cree'),
'cs' => array('Czech', 'Čeština'),
'cu' => array('Old Slavonic'),
'cv' => array('Chuvash'),
'cy' => array('Welsh', 'Cymraeg'),
'da' => array('Danish', 'Dansk'),
'de' => array('German', 'Deutsch'),
'dv' => array('Maldivian'),
'dz' => array('Bhutani'),
'ee' => array('Ewe', 'Ɛʋɛ'),
'el' => array('Greek', 'Ελληνικά'),
'en' => array('English'),
'en-gb' => array('English, British'),
'eo' => array('Esperanto'),
'es' => array('Spanish', 'Español'),
'et' => array('Estonian', 'Eesti'),
'eu' => array('Basque', 'Euskera'),
'fa' => array('Persian', /* Left-to-right marker "" */ 'فارسی', LANGUAGE_RTL),
'ff' => array('Fulah', 'Fulfulde'),
'fi' => array('Finnish', 'Suomi'),
'fil' => array('Filipino'),
'fj' => array('Fiji'),
'fo' => array('Faeroese'),
'fr' => array('French', 'Français'),
'fy' => array('Frisian', 'Frysk'),
'ga' => array('Irish', 'Gaeilge'),
'gd' => array('Scots Gaelic'),
'gl' => array('Galician', 'Galego'),
'gn' => array('Guarani'),
'gsw-berne' => array('Swiss German'),
'gu' => array('Gujarati'),
'gv' => array('Manx'),
'ha' => array('Hausa'),
'he' => array('Hebrew', /* Left-to-right marker "" */ 'עברית', LANGUAGE_RTL),
'hi' => array('Hindi', 'हिन्दी'),
'ho' => array('Hiri Motu'),
'hr' => array('Croatian', 'Hrvatski'),
'ht' => array('Haitian Creole'),
'hu' => array('Hungarian', 'Magyar'),
'hy' => array('Armenian', 'Հայերեն'),
'hz' => array('Herero'),
'ia' => array('Interlingua'),
'id' => array('Indonesian', 'Bahasa Indonesia'),
'ie' => array('Interlingue'),
'ig' => array('Igbo'),
'ik' => array('Inupiak'),
'is' => array('Icelandic', 'Íslenska'),
'it' => array('Italian', 'Italiano'),
'iu' => array('Inuktitut'),
'ja' => array('Japanese', '日本語'),
'jv' => array('Javanese'),
'ka' => array('Georgian'),
'kg' => array('Kongo'),
'ki' => array('Kikuyu'),
'kj' => array('Kwanyama'),
'kk' => array('Kazakh', 'Қазақ'),
'kl' => array('Greenlandic'),
'km' => array('Cambodian'),
'kn' => array('Kannada', 'ಕನ್ನಡ'),
'ko' => array('Korean', '한국어'),
'kr' => array('Kanuri'),
'ks' => array('Kashmiri'),
'ku' => array('Kurdish', 'Kurdî'),
'kv' => array('Komi'),
'kw' => array('Cornish'),
'ky' => array('Kyrgyz', 'Кыргызча'),
'la' => array('Latin', 'Latina'),
'lb' => array('Luxembourgish'),
'lg' => array('Luganda'),
'ln' => array('Lingala'),
'lo' => array('Laothian'),
'lt' => array('Lithuanian', 'Lietuvių'),
'lv' => array('Latvian', 'Latviešu'),
'mg' => array('Malagasy'),
'mh' => array('Marshallese'),
'mi' => array('Māori'),
'mk' => array('Macedonian', 'Македонски'),
'ml' => array('Malayalam', 'മലയാളം'),
'mn' => array('Mongolian'),
'mo' => array('Moldavian'),
'mr' => array('Marathi'),
'ms' => array('Malay', 'Bahasa Melayu'),
'mt' => array('Maltese', 'Malti'),
'my' => array('Burmese'),
'na' => array('Nauru'),
'nd' => array('North Ndebele'),
'ne' => array('Nepali'),
'ng' => array('Ndonga'),
'nl' => array('Dutch', 'Nederlands'),
'nb' => array('Norwegian Bokmål', 'Bokmål'),
'nn' => array('Norwegian Nynorsk', 'Nynorsk'),
'nr' => array('South Ndebele'),
'nv' => array('Navajo'),
'ny' => array('Chichewa'),
'oc' => array('Occitan'),
'om' => array('Oromo'),
'or' => array('Oriya'),
'os' => array('Ossetian'),
'pa' => array('Punjabi'),
'pi' => array('Pali'),
'pl' => array('Polish', 'Polski'),
'ps' => array('Pashto', /* Left-to-right marker "" */ 'پښتو', LANGUAGE_RTL),
'pt' => array('Portuguese, International'),
'pt-pt' => array('Portuguese, Portugal', 'Português'),
'pt-br' => array('Portuguese, Brazil', 'Português'),
'qu' => array('Quechua'),
'rm' => array('Rhaeto-Romance'),
'rn' => array('Kirundi'),
'ro' => array('Romanian', 'Română'),
'ru' => array('Russian', 'Русский'),
'rw' => array('Kinyarwanda'),
'sa' => array('Sanskrit'),
'sc' => array('Sardinian'),
'sco' => array('Scots'),
'sd' => array('Sindhi'),
'se' => array('Northern Sami'),
'sg' => array('Sango'),
'sh' => array('Serbo-Croatian'),
'si' => array('Sinhala', 'සිංහල'),
'sk' => array('Slovak', 'Slovenčina'),
'sl' => array('Slovenian', 'Slovenščina'),
'sm' => array('Samoan'),
'sn' => array('Shona'),
'so' => array('Somali'),
'sq' => array('Albanian', 'Shqip'),
'sr' => array('Serbian', 'Српски'),
'ss' => array('Siswati'),
'st' => array('Sesotho'),
'su' => array('Sudanese'),
'sv' => array('Swedish', 'Svenska'),
'sw' => array('Swahili', 'Kiswahili'),
'ta' => array('Tamil', 'தமிழ்'),
'te' => array('Telugu', 'తెలుగు'),
'tg' => array('Tajik'),
'th' => array('Thai', 'ภาษาไทย'),
'ti' => array('Tigrinya'),
'tk' => array('Turkmen'),
'tl' => array('Tagalog'),
'tn' => array('Setswana'),
'to' => array('Tonga'),
'tr' => array('Turkish', 'Türkçe'),
'ts' => array('Tsonga'),
'tt' => array('Tatar', 'Tatarça'),
'tw' => array('Twi'),
'ty' => array('Tahitian'),
'ug' => array('Uyghur'),
'uk' => array('Ukrainian', 'Українська'),
'ur' => array('Urdu', /* Left-to-right marker "" */ 'اردو', LANGUAGE_RTL),
'uz' => array('Uzbek', "o'zbek"),
've' => array('Venda'),
'vi' => array('Vietnamese', 'Tiếng Việt'),
'wo' => array('Wolof'),
'xh' => array('Xhosa', 'isiXhosa'),
'xx-lolspeak' => array('Lolspeak'),
'yi' => array('Yiddish'),
'yo' => array('Yoruba', 'Yorùbá'),
'za' => array('Zhuang'),
'zh-hans' => array('Chinese, Simplified', '简体中文'),
'zh-hant' => array('Chinese, Traditional', '繁體中文'),
'zu' => array('Zulu', 'isiZulu'),
);
}
/**
* @} End of "locale-api-languages-predefined"
*/
-102
View File
@@ -1,102 +0,0 @@
<?php
/**
* @file
* Provides a helper to properly encode HTML-safe JSON prior to PHP 5.3.0.
*/
/**
* Encodes a PHP variable to HTML-safe JSON for PHP versions below 5.3.0.
*
* @see drupal_json_encode()
*/
function drupal_json_encode_helper($var) {
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false'; // Lowercase necessary!
case 'integer':
case 'double':
return $var;
case 'resource':
case 'string':
// Always use Unicode escape sequences (\u0022) over JSON escape
// sequences (\") to prevent browsers interpreting these as
// special characters.
$replace_pairs = array(
// ", \ and U+0000 - U+001F must be escaped according to RFC 4627.
'\\' => '\u005C',
'"' => '\u0022',
"\x00" => '\u0000',
"\x01" => '\u0001',
"\x02" => '\u0002',
"\x03" => '\u0003',
"\x04" => '\u0004',
"\x05" => '\u0005',
"\x06" => '\u0006',
"\x07" => '\u0007',
"\x08" => '\u0008',
"\x09" => '\u0009',
"\x0a" => '\u000A',
"\x0b" => '\u000B',
"\x0c" => '\u000C',
"\x0d" => '\u000D',
"\x0e" => '\u000E',
"\x0f" => '\u000F',
"\x10" => '\u0010',
"\x11" => '\u0011',
"\x12" => '\u0012',
"\x13" => '\u0013',
"\x14" => '\u0014',
"\x15" => '\u0015',
"\x16" => '\u0016',
"\x17" => '\u0017',
"\x18" => '\u0018',
"\x19" => '\u0019',
"\x1a" => '\u001A',
"\x1b" => '\u001B',
"\x1c" => '\u001C',
"\x1d" => '\u001D',
"\x1e" => '\u001E',
"\x1f" => '\u001F',
// Prevent browsers from interpreting these as as special.
"'" => '\u0027',
'<' => '\u003C',
'>' => '\u003E',
'&' => '\u0026',
// Prevent browsers from interpreting the solidus as special and
// non-compliant JSON parsers from interpreting // as a comment.
'/' => '\u002F',
// While these are allowed unescaped according to ECMA-262, section
// 15.12.2, they cause problems in some JSON parsers.
"\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator.
"\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator.
);
return '"' . strtr($var, $replace_pairs) . '"';
case 'array':
// Arrays in JSON can't be associative. If the array is empty or if it
// has sequential whole number keys starting with 0, it's not associative
// so we can go ahead and convert it as an array.
if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
$output = array();
foreach ($var as $v) {
$output[] = drupal_json_encode_helper($v);
}
return '[ ' . implode(', ', $output) . ' ]';
}
// Otherwise, fall through to convert the array as an object.
case 'object':
$output = array();
foreach ($var as $k => $v) {
$output[] = drupal_json_encode_helper(strval($k)) . ':' . drupal_json_encode_helper($v);
}
return '{' . implode(', ', $output) . '}';
default:
return 'null';
}
}
-484
View File
@@ -1,484 +0,0 @@
<?php
/**
* @file
* Multiple language handling functionality.
*/
/**
* No language negotiation. The default language is used.
*/
define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default');
/**
* Return all the defined language types.
*
* @return
* An array of language type names. The name will be used as the global
* variable name the language value will be stored in.
*/
function language_types_info() {
$language_types = &drupal_static(__FUNCTION__);
if (!isset($language_types)) {
$language_types = module_invoke_all('language_types_info');
// Let other modules alter the list of language types.
drupal_alter('language_types_info', $language_types);
}
return $language_types;
}
/**
* Return only the configurable language types.
*
* A language type maybe configurable or fixed. A fixed language type is a type
* whose negotiation values are unchangeable and defined while defining the
* language type itself.
*
* @param $stored
* Optional. By default retrieves values from the 'language_types' variable to
* avoid unnecessary hook invocations.
* If set to FALSE retrieves values from the actual language type definitions.
* This allows to react to alterations performed on the definitions by modules
* installed after the 'language_types' variable is set.
*
* @return
* An array of language type names.
*/
function language_types_configurable($stored = TRUE) {
$configurable = &drupal_static(__FUNCTION__);
if ($stored && !isset($configurable)) {
$types = variable_get('language_types', drupal_language_types());
$configurable = array_keys(array_filter($types));
}
if (!$stored) {
$result = array();
foreach (language_types_info() as $type => $info) {
if (!isset($info['fixed'])) {
$result[] = $type;
}
}
return $result;
}
return $configurable;
}
/**
* Disable the given language types.
*
* @param $types
* An array of language types.
*/
function language_types_disable($types) {
$enabled_types = variable_get('language_types', drupal_language_types());
foreach ($types as $type) {
unset($enabled_types[$type]);
}
variable_set('language_types', $enabled_types);
}
/**
* Updates the language type configuration.
*/
function language_types_set() {
// Ensure that we are getting the defined language negotiation information. An
// invocation of module_enable() or module_disable() could outdate the cached
// information.
drupal_static_reset('language_types_info');
drupal_static_reset('language_negotiation_info');
// Determine which language types are configurable and which not by checking
// whether the 'fixed' key is defined. Non-configurable (fixed) language types
// have their language negotiation settings stored there.
$defined_providers = language_negotiation_info();
foreach (language_types_info() as $type => $info) {
if (isset($info['fixed'])) {
$language_types[$type] = FALSE;
$negotiation = array();
foreach ($info['fixed'] as $weight => $id) {
if (isset($defined_providers[$id])) {
$negotiation[$id] = $weight;
}
}
language_negotiation_set($type, $negotiation);
}
else {
$language_types[$type] = TRUE;
}
}
// Save language types.
variable_set('language_types', $language_types);
// Ensure that subsequent calls of language_types_configurable() return the
// updated language type information.
drupal_static_reset('language_types_configurable');
}
/**
* Check if a language provider is enabled.
*
* This has two possible behaviors:
* - If $provider_id is given return its ID if enabled, FALSE otherwise.
* - If no ID is passed the first enabled language provider is returned.
*
* @param $type
* The language negotiation type.
* @param $provider_id
* The language provider ID.
*
* @return
* The provider ID if it is enabled, FALSE otherwise.
*/
function language_negotiation_get($type, $provider_id = NULL) {
$negotiation = variable_get("language_negotiation_$type", array());
if (empty($negotiation)) {
return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE;
}
if (empty($provider_id)) {
return key($negotiation);
}
if (isset($negotiation[$provider_id])) {
return $provider_id;
}
return FALSE;
}
/**
* Check if the given language provider is enabled for any configurable language
* type.
*
* @param $provider_id
* The language provider ID.
*
* @return
* TRUE if there is at least one language type for which the give language
* provider is enabled, FALSE otherwise.
*/
function language_negotiation_get_any($provider_id) {
foreach (language_types_configurable() as $type) {
if (language_negotiation_get($type, $provider_id)) {
return TRUE;
}
}
return FALSE;
}
/**
* Return the language switch links for the given language.
*
* @param $type
* The language negotiation type.
* @param $path
* The internal path the switch links will be relative to.
*
* @return
* A keyed array of links ready to be themed.
*/
function language_negotiation_get_switch_links($type, $path) {
$links = FALSE;
$negotiation = variable_get("language_negotiation_$type", array());
// Only get the languages if we have more than one.
if (count(language_list()) >= 2) {
$language = language_initialize($type);
}
foreach ($negotiation as $id => $provider) {
if (isset($provider['callbacks']['switcher'])) {
if (isset($provider['file'])) {
require_once DRUPAL_ROOT . '/' . $provider['file'];
}
$callback = $provider['callbacks']['switcher'];
$result = $callback($type, $path);
// Add support for WCAG 2.0's Language of Parts to add language identifiers.
// http://www.w3.org/TR/UNDERSTANDING-WCAG20/meaning-other-lang-id.html
foreach ($result as $langcode => $link) {
$result[$langcode]['attributes']['lang'] = $langcode;
}
if (!empty($result)) {
// Allow modules to provide translations for specific links.
drupal_alter('language_switch_links', $result, $type, $path);
$links = (object) array('links' => $result, 'provider' => $id);
break;
}
}
}
return $links;
}
/**
* Updates language configuration to remove any language provider that is no longer defined.
*/
function language_negotiation_purge() {
// Ensure that we are getting the defined language negotiation information. An
// invocation of module_enable() or module_disable() could outdate the cached
// information.
drupal_static_reset('language_negotiation_info');
drupal_static_reset('language_types_info');
$defined_providers = language_negotiation_info();
foreach (language_types_info() as $type => $type_info) {
$weight = 0;
$negotiation = array();
foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) {
if (isset($defined_providers[$id])) {
$negotiation[$id] = $weight++;
}
}
language_negotiation_set($type, $negotiation);
}
}
/**
* Save a list of language providers.
*
* @param $type
* The language negotiation type.
* @param $language_providers
* An array of language provider weights keyed by id.
* @see language_provider_weight()
*/
function language_negotiation_set($type, $language_providers) {
// Save only the necessary fields.
$provider_fields = array('callbacks', 'file', 'cache');
$negotiation = array();
$providers_weight = array();
$defined_providers = language_negotiation_info();
$default_types = language_types_configurable(FALSE);
// Initialize the providers weight list.
foreach ($language_providers as $id => $provider) {
$providers_weight[$id] = language_provider_weight($provider);
}
// Order providers list by weight.
asort($providers_weight);
foreach ($providers_weight as $id => $weight) {
if (isset($defined_providers[$id])) {
$provider = $defined_providers[$id];
// If the provider does not express any preference about types, make it
// available for any configurable type.
$types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types);
// Check if the provider is defined and has the right type.
if (isset($types[$type])) {
$provider_data = array();
foreach ($provider_fields as $field) {
if (isset($provider[$field])) {
$provider_data[$field] = $provider[$field];
}
}
$negotiation[$id] = $provider_data;
}
}
}
variable_set("language_negotiation_$type", $negotiation);
}
/**
* Return all the defined language providers.
*
* @return
* An array of language providers.
*/
function language_negotiation_info() {
$language_providers = &drupal_static(__FUNCTION__);
if (!isset($language_providers)) {
// Collect all the module-defined language negotiation providers.
$language_providers = module_invoke_all('language_negotiation_info');
// Add the default language provider.
$language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array(
'callbacks' => array('language' => 'language_from_default'),
'weight' => 10,
'name' => t('Default'),
'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->native)),
);
// Let other modules alter the list of language providers.
drupal_alter('language_negotiation_info', $language_providers);
}
return $language_providers;
}
/**
* Helper function used to cache the language providers results.
*
* @param $provider_id
* The language provider ID.
* @param $provider
* The language provider to be invoked. If not passed it will be explicitly
* loaded through language_negotiation_info().
*
* @return
* The language provider's return value.
*/
function language_provider_invoke($provider_id, $provider = NULL) {
$results = &drupal_static(__FUNCTION__);
if (!isset($results[$provider_id])) {
global $user;
// Get languages grouped by status and select only the enabled ones.
$languages = language_list('enabled');
$languages = $languages[1];
if (!isset($provider)) {
$providers = language_negotiation_info();
$provider = $providers[$provider_id];
}
if (isset($provider['file'])) {
require_once DRUPAL_ROOT . '/' . $provider['file'];
}
// If the language provider has no cache preference or this is satisfied
// we can execute the callback.
$cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0);
$callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE;
$langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE;
$results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
}
// Since objects are resources we need to return a clone to prevent the
// provider cache to be unintentionally altered. The same providers might be
// used with different language types based on configuration.
return !empty($results[$provider_id]) ? clone($results[$provider_id]) : $results[$provider_id];
}
/**
* Return the passed language provider weight or a default value.
*
* @param $provider
* A language provider data structure.
*
* @return
* A numeric weight.
*/
function language_provider_weight($provider) {
$default = is_numeric($provider) ? $provider : 0;
return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default;
}
/**
* Choose a language for the given type based on language negotiation settings.
*
* @param $type
* The language type.
*
* @return
* The negotiated language object.
*/
function language_initialize($type) {
// Execute the language providers in the order they were set up and return the
// first valid language found.
$negotiation = variable_get("language_negotiation_$type", array());
foreach ($negotiation as $provider_id => $provider) {
$language = language_provider_invoke($provider_id, $provider);
if ($language) {
$language->provider = $provider_id;
return $language;
}
}
// If no other language was found use the default one.
$language = language_default();
$language->provider = LANGUAGE_NEGOTIATION_DEFAULT;
return $language;
}
/**
* Default language provider.
*
* @return
* The default language code.
*/
function language_from_default() {
return language_default()->language;
}
/**
* Splits the given path into prefix and actual path.
*
* Parse the given path and return the language object identified by the
* prefix and the actual path.
*
* @param $path
* The path to split.
* @param $languages
* An array of valid languages.
*
* @return
* An array composed of:
* - A language object corresponding to the identified prefix on success,
* FALSE otherwise.
* - The path without the prefix on success, the given path otherwise.
*/
function language_url_split_prefix($path, $languages) {
$args = empty($path) ? array() : explode('/', $path);
$prefix = array_shift($args);
// Search prefix within enabled languages.
foreach ($languages as $language) {
if (!empty($language->prefix) && $language->prefix == $prefix) {
// Rebuild $path with the language removed.
return array($language, implode('/', $args));
}
}
return array(FALSE, $path);
}
/**
* Returns the possible fallback languages ordered by language weight.
*
* @param
* (optional) The language type. Defaults to LANGUAGE_TYPE_CONTENT.
*
* @return
* An array of language codes.
*/
function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) {
$fallback_candidates = &drupal_static(__FUNCTION__);
if (!isset($fallback_candidates)) {
$fallback_candidates = array();
// Get languages ordered by weight.
// Use array keys to avoid duplicated entries.
foreach (language_list('weight') as $languages) {
foreach ($languages as $language) {
$fallback_candidates[$language->language] = NULL;
}
}
$fallback_candidates = array_keys($fallback_candidates);
$fallback_candidates[] = LANGUAGE_NONE;
// Let other modules hook in and add/change candidates.
drupal_alter('language_fallback_candidates', $fallback_candidates);
}
return $fallback_candidates;
}
-2442
View File
File diff suppressed because it is too large Load Diff
-274
View File
@@ -1,274 +0,0 @@
<?php
/**
* @file
* A database-mediated implementation of a locking mechanism.
*/
/**
* @defgroup lock Locking mechanisms
* @{
* 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 operation 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_initialize() {
global $locks;
$locks = array();
}
/**
* Helper function to get this request's unique id.
*/
function _lock_id() {
// Do not use drupal_static(). This identifier refers to the current
// client request, and must not be changed under any circumstances
// else the shutdown handler may fail to release our locks.
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.
drupal_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 (minimum of 0.001).
*
* @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);
$expire = microtime(TRUE) + $timeout;
if (isset($locks[$name])) {
// Try to extend the expiration of a lock we already acquired.
$success = (bool) db_update('semaphore')
->fields(array('expire' => $expire))
->condition('name', $name)
->condition('value', _lock_id())
->execute();
if (!$success) {
// The lock was broken.
unset($locks[$name]);
}
return $success;
}
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 {
try {
db_insert('semaphore')
->fields(array(
'name' => $name,
'value' => _lock_id(),
'expire' => $expire,
))
->execute();
// We track all acquired locks in the global variable.
$locks[$name] = TRUE;
// We never need to try again.
$retry = FALSE;
}
catch (PDOException $e) {
// 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_query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc();
if (!$lock) {
return TRUE;
}
$expire = (float) $lock['expire'];
$now = microtime(TRUE);
if ($now > $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.
return (bool) db_delete('semaphore')
->condition('name', $name)
->condition('value', $lock['value'])
->condition('expire', 0.0001 + $expire, '<=')
->execute();
}
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 while waiting.
*
* @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) {
// Pause the process for short periods between calling
// lock_may_be_available(). This prevents hitting the database with constant
// database queries while waiting, which could lead to performance issues.
// However, if the wait period is too long, there is the potential for a
// large number of processes to be blocked waiting for a lock, especially
// if the item being rebuilt is commonly requested. To address both of these
// concerns, begin waiting for 25ms, then add 25ms to the wait period each
// time until it reaches 500ms. After this point polling will continue every
// 500ms until $delay is reached.
// $delay is passed in seconds, but we will be using usleep(), which takes
// microseconds as a parameter. Multiply it by 1 million so that all
// further numbers are equivalent.
$delay = (int) $delay * 1000000;
// Begin sleeping at 25ms.
$sleep = 25000;
while ($delay > 0) {
// 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.
usleep($sleep);
// After each sleep, increase the value of $sleep until it reaches
// 500ms, to reduce the potential for a lock stampede.
$delay = $delay - $sleep;
$sleep = min(500000, $sleep + 25000, $delay);
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_delete('semaphore')
->condition('name', $name)
->condition('value', _lock_id())
->execute();
}
/**
* 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_delete('semaphore')
->condition('value', $lock_id)
->execute();
}
/**
* @} End of "defgroup lock".
*/
-618
View File
@@ -1,618 +0,0 @@
<?php
/**
* @file
* API functions for processing and sending e-mail.
*/
/**
* Auto-detect appropriate line endings for e-mails.
*
* $conf['mail_line_endings'] will override this setting.
*/
define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE ? "\r\n" : "\n");
/**
* Composes and optionally sends 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_system()->mail() 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) {
* $data['user'] = $params['account'];
* $options['language'] = $message['language'];
* user_mail_tokens($variables, $data, $options);
* switch($key) {
* case 'notice':
* // If the recipient can receive such notices by instant-message, do
* // not send by email.
* if (example_im_send($key, $message, $params)) {
* $message['send'] = FALSE;
* break;
* }
* $langcode = $message['language']->language;
* $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode));
* $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode));
* break;
* }
* }
* @endcode
*
* Another example, which uses drupal_mail() to format a message for sending
* later:
*
* @code
* $params = array('current_conditions' => $data);
* $to = 'user@example.com';
* $message = drupal_mail('example', 'notice', $to, $language, $params, FALSE);
* // Only add to the spool if sending was not canceled.
* if ($message['send']) {
* example_spool_message($message);
* }
* @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 RFC 2822. 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
* If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver
* the message, and store the result in $message['result']. Modules
* implementing hook_mail_alter() may cancel sending by setting
* $message['send'] to FALSE.
*
* @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,
'module' => $module,
'key' => $key,
'to' => $to,
'from' => isset($from) ? $from : $default_from,
'language' => $language,
'params' => $params,
'send' => TRUE,
'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.
$headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $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);
// Retrieve the responsible implementation for this message.
$system = drupal_mail_system($module, $key);
// Format the message body.
$message = $system->format($message);
// Optionally send e-mail.
if ($send) {
// The original caller requested sending. Sending was canceled by one or
// more hook_mail_alter() implementations. We set 'result' to NULL, because
// FALSE indicates an error in sending.
if (empty($message['send'])) {
$message['result'] = NULL;
}
// Sending was originally requested and was not canceled.
else {
$message['result'] = $system->mail($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. Contact the site administrator if the problem persists.'), 'error');
}
}
}
return $message;
}
/**
* Returns an object that implements the MailSystemInterface interface.
*
* Allows for one or more custom mail backends to format and send mail messages
* composed using drupal_mail().
*
* An implementation needs to implement the following methods:
* - format: Allows to preprocess, format, and postprocess a mail
* message before it is passed to the sending system. By default, all messages
* may contain HTML and are converted to plain-text by the DefaultMailSystem
* implementation. For example, an alternative implementation could override
* the default implementation and additionally sanitize the HTML for usage in
* a MIME-encoded e-mail, but still invoking the DefaultMailSystem
* implementation to generate an alternate plain-text version for sending.
* - mail: Sends a message through a custom mail sending engine.
* By default, all messages are sent via PHP's mail() function by the
* DefaultMailSystem implementation.
*
* The selection of a particular implementation is controlled via the variable
* 'mail_system', which is a keyed array. The default implementation
* is the class whose name is the value of 'default-system' key. A more specific
* match first to key and then to module will be used in preference to the
* default. To specificy a different class for all mail sent by one module, set
* the class name as the value for the key corresponding to the module name. To
* specificy a class for a particular message sent by one module, set the class
* name as the value for the array key that is the message id, which is
* "${module}_${key}".
*
* For example to debug all mail sent by the user module by logging it to a
* file, you might set the variable as something like:
*
* @code
* array(
* 'default-system' => 'DefaultMailSystem',
* 'user' => 'DevelMailLog',
* );
* @endcode
*
* Finally, a different system can be specified for a specific e-mail ID (see
* the $key param), such as one of the keys used by the contact module:
*
* @code
* array(
* 'default-system' => 'DefaultMailSystem',
* 'user' => 'DevelMailLog',
* 'contact_page_autoreply' => 'DrupalDevNullMailSend',
* );
* @endcode
*
* Other possible uses for system include a mail-sending class that actually
* sends (or duplicates) each message to SMS, Twitter, instant message, etc, or
* a class that queues up a large number of messages for more efficient bulk
* sending or for sending via a remote gateway so as to reduce the load
* on the local server.
*
* @param $module
* The module name which was used by drupal_mail() to invoke hook_mail().
* @param $key
* A key to identify the e-mail sent. The final e-mail ID for the e-mail
* alter hook in drupal_mail() would have been {$module}_{$key}.
*
* @return MailSystemInterface
*/
function drupal_mail_system($module, $key) {
$instances = &drupal_static(__FUNCTION__, array());
$id = $module . '_' . $key;
$configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem'));
// Look for overrides for the default class, starting from the most specific
// id, and falling back to the module name.
if (isset($configuration[$id])) {
$class = $configuration[$id];
}
elseif (isset($configuration[$module])) {
$class = $configuration[$module];
}
else {
$class = $configuration['default-system'];
}
if (empty($instances[$class])) {
$interfaces = class_implements($class);
if (isset($interfaces['MailSystemInterface'])) {
$instances[$class] = new $class();
}
else {
throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'MailSystemInterface')));
}
}
return $instances[$class];
}
/**
* An interface for pluggable mail back-ends.
*/
interface MailSystemInterface {
/**
* Format a message composed by drupal_mail() prior sending.
*
* @param $message
* A message array, as described in hook_mail_alter().
*
* @return
* The formatted $message.
*/
public function format(array $message);
/**
* Send a message composed by drupal_mail().
*
* @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 RFC 2822. Some examples:
* - 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 additional mail headers not
* defined by one of the other parameters. PHP's mail() looks for Cc
* and Bcc headers and sends the mail to addresses in these headers too.
*
* @return
* TRUE if the mail was successfully accepted for delivery, otherwise FALSE.
*/
public function mail(array $message);
}
/**
* Performs 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.
*
* @return
* The content of the email as a string with formatting applied.
*/
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;
}
/**
* Transforms an HTML string into plain text, preserving its structure.
*
* 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[] = isset($lists[0]) && 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; not removing line-breaks or
// white-space, since that breaks newlines when sanitizing plain-text.
$value = trim(decode_entities($value));
if (drupal_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)) . MAIL_LINE_ENDINGS;
// Remove non-quotation markers from indentation.
$indent = array_map('_drupal_html_to_text_clean', $indent);
}
$tag = !$tag;
}
return $output . $footnotes;
}
/**
* Wraps words on a single line.
*
* Callback for array_walk() winthin drupal_wrap_mail().
*/
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");
}
/**
* Keeps track of URLs and replaces them with placeholder tokens.
*
* Callback for preg_replace_callback() within drupal_html_to_text().
*/
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;
}
/**
* Replaces non-quotation markers from a given piece of indentation with spaces.
*
* Callback for array_map() within drupal_html_to_text().
*/
function _drupal_html_to_text_clean($indent) {
return preg_replace('/[^>]/', ' ', $indent);
}
/**
* Pads the last line with the given character.
*
* @see drupal_html_to_text()
*/
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";
}
-3882
View File
File diff suppressed because it is too large Load Diff
-1058
View File
File diff suppressed because it is too large Load Diff
-664
View File
@@ -1,664 +0,0 @@
<?php
/**
* @file
* Functions to aid in presenting database results as a set of pages.
*/
/**
* Query extender for pager queries.
*
* This is the "default" pager mechanism. It creates a paged query with a fixed
* number of entries per page.
*/
class PagerDefault extends SelectQueryExtender {
/**
* The highest element we've autogenerated so far.
*
* @var int
*/
static $maxElement = 0;
/**
* The number of elements per page to allow.
*
* @var int
*/
protected $limit = 10;
/**
* The unique ID of this pager on this page.
*
* @var int
*/
protected $element = NULL;
/**
* The count query that will be used for this pager.
*
* @var SelectQueryInterface
*/
protected $customCountQuery = FALSE;
public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
parent::__construct($query, $connection);
// Add pager tag. Do this here to ensure that it is always added before
// preExecute() is called.
$this->addTag('pager');
}
/**
* Override the execute method.
*
* Before we run the query, we need to add pager-based range() instructions
* to it.
*/
public function execute() {
// Add convenience tag to mark that this is an extended query. We have to
// do this in the constructor to ensure that it is set before preExecute()
// gets called.
if (!$this->preExecute($this)) {
return NULL;
}
// A NULL limit is the "kill switch" for pager queries.
if (empty($this->limit)) {
return;
}
$this->ensureElement();
$total_items = $this->getCountQuery()->execute()->fetchField();
$current_page = pager_default_initialize($total_items, $this->limit, $this->element);
$this->range($current_page * $this->limit, $this->limit);
// Now that we've added our pager-based range instructions, run the query normally.
return $this->query->execute();
}
/**
* Ensure that there is an element associated with this query.
* If an element was not specified previously, then the value of the
* $maxElement counter is taken, after which the counter is incremented.
*
* After running this method, access $this->element to get the element for this
* query.
*/
protected function ensureElement() {
if (!isset($this->element)) {
$this->element = self::$maxElement++;
}
}
/**
* Specify the count query object to use for this pager.
*
* You will rarely need to specify a count query directly. If not specified,
* one is generated off of the pager query itself.
*
* @param SelectQueryInterface $query
* The count query object. It must return a single row with a single column,
* which is the total number of records.
*/
public function setCountQuery(SelectQueryInterface $query) {
$this->customCountQuery = $query;
}
/**
* Retrieve the count query for this pager.
*
* The count query may be specified manually or, by default, taken from the
* query we are extending.
*
* @return SelectQueryInterface
* A count query object.
*/
public function getCountQuery() {
if ($this->customCountQuery) {
return $this->customCountQuery;
}
else {
return $this->query->countQuery();
}
}
/**
* Specify the maximum number of elements per page for this query.
*
* The default if not specified is 10 items per page.
*
* @param $limit
* An integer specifying the number of elements per page. If passed a false
* value (FALSE, 0, NULL), the pager is disabled.
*/
public function limit($limit = 10) {
$this->limit = $limit;
return $this;
}
/**
* Specify the element ID for this pager query.
*
* The element is used to differentiate different pager queries on the same
* page so that they may be operated independently. If you do not specify an
* element, every pager query on the page will get a unique element. If for
* whatever reason you want to explicitly define an element for a given query,
* you may do so here.
*
* Setting the element here also increments the static $maxElement counter,
* which is used for determining the $element when there's none specified.
*
* Note that no collision detection is done when setting an element ID
* explicitly, so it is possible for two pagers to end up using the same ID
* if both are set explicitly.
*
* @param $element
*/
public function element($element) {
$this->element = $element;
if ($element >= self::$maxElement) {
self::$maxElement = $element + 1;
}
return $this;
}
}
/**
* Returns the current page being requested for display within a pager.
*
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
*
* @return
* The number of the current requested page, within the pager represented by
* $element. This is determined from the URL query parameter $_GET['page'], or
* 0 by default. Note that this number may differ from the actual page being
* displayed. For example, if a search for "example text" brings up three
* pages of results, but a users visits search/node/example+text?page=10, this
* function will return 10, even though the default pager implementation
* adjusts for this and still displays the third page of search results at
* that URL.
*
* @see pager_default_initialize()
*/
function pager_find_page($element = 0) {
$page = isset($_GET['page']) ? $_GET['page'] : '';
$page_array = explode(',', $page);
if (!isset($page_array[$element])) {
$page_array[$element] = 0;
}
return (int) $page_array[$element];
}
/**
* Initializes a pager for theme('pager').
*
* This function sets up the necessary global variables so that future calls
* to theme('pager') will render a pager that correctly corresponds to the
* items being displayed.
*
* If the items being displayed result from a database query performed using
* Drupal's database API, and if you have control over the construction of the
* database query, you do not need to call this function directly; instead, you
* can simply extend the query object with the 'PagerDefault' extender before
* executing it. For example:
* @code
* $query = db_select('some_table')->extend('PagerDefault');
* @endcode
*
* However, if you are using a different method for generating the items to be
* paged through, then you should call this function in preparation.
*
* The following example shows how this function can be used in a page callback
* that invokes an external datastore with an SQL-like syntax:
* @code
* // First find the total number of items and initialize the pager.
* $where = "status = 1";
* $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result();
* $num_per_page = variable_get('mymodule_num_per_page', 10);
* $page = pager_default_initialize($total, $num_per_page);
*
* // Next, retrieve and display the items for the current page.
* $offset = $num_per_page * $page;
* $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll();
* $output = theme('mymodule_results', array('result' => $result));
*
* // Finally, display the pager controls, and return.
* $output .= theme('pager');
* return $output;
* @endcode
*
* A second example involves a page callback that invokes an external search
* service where the total number of matching results is provided as part of
* the returned set (so that we do not need a separate query in order to obtain
* this information). Here, we call pager_find_page() to calculate the desired
* offset before the search is invoked:
* @code
* // Perform the query, using the requested offset from pager_find_page().
* // This comes from a URL parameter, so here we are assuming that the URL
* // parameter corresponds to an actual page of results that will exist
* // within the set.
* $page = pager_find_page();
* $num_per_page = variable_get('mymodule_num_per_page', 10);
* $offset = $num_per_page * $page;
* $result = mymodule_remote_search($keywords, $offset, $num_per_page);
*
* // Now that we have the total number of results, initialize the pager.
* pager_default_initialize($result->total, $num_per_page);
*
* // Display the search results.
* $output = theme('search_results', array('results' => $result->data, 'type' => 'remote'));
*
* // Finally, display the pager controls, and return.
* $output .= theme('pager');
* return $output;
* @endcode
*
* @param $total
* The total number of items to be paged.
* @param $limit
* The number of items the calling code will display per page.
* @param $element
* An optional integer to distinguish between multiple pagers on one page.
*
* @return
* The number of the current page, within the pager represented by $element.
* This is determined from the URL query parameter $_GET['page'], or 0 by
* default. However, if a page that does not correspond to the actual range
* of the result set was requested, this function will return the closest
* page actually within the result set.
*/
function pager_default_initialize($total, $limit, $element = 0) {
global $pager_page_array, $pager_total, $pager_total_items, $pager_limits;
$page = pager_find_page($element);
// We calculate the total of pages as ceil(items / limit).
$pager_total_items[$element] = $total;
$pager_total[$element] = ceil($pager_total_items[$element] / $limit);
$pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1));
$pager_limits[$element] = $limit;
return $pager_page_array[$element];
}
/**
* Compose a URL query parameter array for pager links.
*
* @return
* A URL query parameter array that consists of all components of the current
* page request except for those pertaining to paging.
*/
function pager_get_query_parameters() {
$query = &drupal_static(__FUNCTION__);
if (!isset($query)) {
$query = drupal_get_query_parameters($_GET, array('q', 'page'));
}
return $query;
}
/**
* 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 $variables
* An associative array containing:
* - tags: An array of labels for the controls in the pager.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager links.
* - quantity: The number of pages in the list.
*
* @ingroup themeable
*/
function theme_pager($variables) {
$tags = $variables['tags'];
$element = $variables['element'];
$parameters = $variables['parameters'];
$quantity = $variables['quantity'];
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', array('text' => (isset($tags[0]) ? $tags[0] : t('« first')), 'element' => $element, 'parameters' => $parameters));
$li_previous = theme('pager_previous', array('text' => (isset($tags[1]) ? $tags[1] : t(' previous')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
$li_next = theme('pager_next', array('text' => (isset($tags[3]) ? $tags[3] : t('next ')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters));
$li_last = theme('pager_last', array('text' => (isset($tags[4]) ? $tags[4] : t('last »')), 'element' => $element, 'parameters' => $parameters));
if ($pager_total[$element] > 1) {
if ($li_first) {
$items[] = array(
'class' => array('pager-first'),
'data' => $li_first,
);
}
if ($li_previous) {
$items[] = array(
'class' => array('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' => array('pager-ellipsis'),
'data' => '…',
);
}
// Now generate the actual pager piece.
for (; $i <= $pager_last && $i <= $pager_max; $i++) {
if ($i < $pager_current) {
$items[] = array(
'class' => array('pager-item'),
'data' => theme('pager_previous', array('text' => $i, 'element' => $element, 'interval' => ($pager_current - $i), 'parameters' => $parameters)),
);
}
if ($i == $pager_current) {
$items[] = array(
'class' => array('pager-current'),
'data' => $i,
);
}
if ($i > $pager_current) {
$items[] = array(
'class' => array('pager-item'),
'data' => theme('pager_next', array('text' => $i, 'element' => $element, 'interval' => ($i - $pager_current), 'parameters' => $parameters)),
);
}
}
if ($i < $pager_max) {
$items[] = array(
'class' => array('pager-ellipsis'),
'data' => '…',
);
}
}
// End generation.
if ($li_next) {
$items[] = array(
'class' => array('pager-next'),
'data' => $li_next,
);
}
if ($li_last) {
$items[] = array(
'class' => array('pager-last'),
'data' => $li_last,
);
}
return '<h2 class="element-invisible">' . t('Pages') . '</h2>' . theme('item_list', array(
'items' => $items,
'attributes' => array('class' => array('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 the "first page" link in a query pager.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @ingroup themeable
*/
function theme_pager_first($variables) {
$text = $variables['text'];
$element = $variables['element'];
$parameters = $variables['parameters'];
global $pager_page_array;
$output = '';
// If we are anywhere but the first page
if ($pager_page_array[$element] > 0) {
$output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
}
return $output;
}
/**
* Returns HTML for the "previous page" link in a query pager.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - interval: The number of pages to move backward when the link is clicked.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @ingroup themeable
*/
function theme_pager_previous($variables) {
$text = $variables['text'];
$element = $variables['element'];
$interval = $variables['interval'];
$parameters = $variables['parameters'];
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', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
}
// The previous page is not the first page.
else {
$output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
}
}
return $output;
}
/**
* Returns HTML for the "next page" link in a query pager.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - interval: The number of pages to move forward when the link is clicked.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @ingroup themeable
*/
function theme_pager_next($variables) {
$text = $variables['text'];
$element = $variables['element'];
$interval = $variables['interval'];
$parameters = $variables['parameters'];
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', array('text' => $text, 'element' => $element, 'parameters' => $parameters));
}
// The next page is not the last page.
else {
$output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters));
}
}
return $output;
}
/**
* Returns HTML for the "last page" link in query pager.
*
* @param $variables
* An associative array containing:
* - text: The name (or image) of the link.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager links.
*
* @ingroup themeable
*/
function theme_pager_last($variables) {
$text = $variables['text'];
$element = $variables['element'];
$parameters = $variables['parameters'];
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', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters));
}
return $output;
}
/**
* Returns HTML for a link to a specific query result page.
*
* @param $variables
* An associative array containing:
* - text: The link text. Also used to figure out the title attribute of the
* link, if it is not provided in $variables['attributes']['title']; in
* this case, $variables['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').
* - page_new: The first result to display on the linked page.
* - element: An optional integer to distinguish between multiple pagers on
* one page.
* - parameters: An associative array of query string parameters to append to
* the pager link.
* - attributes: An associative array of HTML attributes to apply to the
* pager link.
*
* @see theme_pager()
*
* @ingroup themeable
*/
function theme_pager_link($variables) {
$text = $variables['text'];
$page_new = $variables['page_new'];
$element = $variables['element'];
$parameters = $variables['parameters'];
$attributes = $variables['attributes'];
$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_get_query_parameters($parameters, array());
}
if ($query_pager = pager_get_query_parameters()) {
$query = array_merge($query, $query_pager);
}
// 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];
}
elseif (is_numeric($text)) {
$attributes['title'] = t('Go to page @number', array('@number' => $text));
}
}
// @todo l() cannot be used here, since it adds an 'active' class based on the
// path only (which is always the current path for pager links). Apparently,
// none of the pager links is active at any time - but it should still be
// possible to use l() here.
// @see http://drupal.org/node/1410574
$attributes['href'] = url($_GET['q'], array('query' => $query));
return '<a' . drupal_attributes($attributes) . '>' . check_plain($text) . '</a>';
}
/**
* @} 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 (empty($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;
}
-287
View File
@@ -1,287 +0,0 @@
<?php
/**
* @file
* Secure password hashing functions for user authentication.
*
* Based on the Portable PHP password hashing framework.
* @see http://www.openwall.com/phpass/
*
* An alternative or custom version of this password hashing API may be
* used by setting the variable password_inc to the name of the PHP file
* containing replacement user_hash_password(), user_check_password(), and
* user_needs_new_hash() functions.
*/
/**
* The standard log2 number of iterations for password stretching. This should
* increase by 1 every Drupal version in order to counteract increases in the
* speed and power of computers available to crack the hashes.
*/
define('DRUPAL_HASH_COUNT', 15);
/**
* The minimum allowed log2 number of iterations for password stretching.
*/
define('DRUPAL_MIN_HASH_COUNT', 7);
/**
* The maximum allowed log2 number of iterations for password stretching.
*/
define('DRUPAL_MAX_HASH_COUNT', 30);
/**
* The expected (and maximum) number of characters in a hashed password.
*/
define('DRUPAL_HASH_LENGTH', 55);
/**
* Returns a string for mapping an int to the corresponding base 64 character.
*/
function _password_itoa64() {
return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
}
/**
* Encode bytes into printable base 64 using the *nix standard from crypt().
*
* @param $input
* The string containing bytes to encode.
* @param $count
* The number of characters (bytes) to encode.
*
* @return
* Encoded string
*/
function _password_base64_encode($input, $count) {
$output = '';
$i = 0;
$itoa64 = _password_itoa64();
do {
$value = ord($input[$i++]);
$output .= $itoa64[$value & 0x3f];
if ($i < $count) {
$value |= ord($input[$i]) << 8;
}
$output .= $itoa64[($value >> 6) & 0x3f];
if ($i++ >= $count) {
break;
}
if ($i < $count) {
$value |= ord($input[$i]) << 16;
}
$output .= $itoa64[($value >> 12) & 0x3f];
if ($i++ >= $count) {
break;
}
$output .= $itoa64[($value >> 18) & 0x3f];
} while ($i < $count);
return $output;
}
/**
* Generates a random base 64-encoded salt prefixed with settings for the hash.
*
* Proper use of salts may defeat a number of attacks, including:
* - The ability to try candidate passwords against multiple hashes at once.
* - The ability to use pre-hashed lists of candidate passwords.
* - The ability to determine whether two users have the same (or different)
* password without actually having to guess one of the passwords.
*
* @param $count_log2
* Integer that determines the number of iterations used in the hashing
* process. A larger value is more secure, but takes more time to complete.
*
* @return
* A 12 character string containing the iteration count and a random salt.
*/
function _password_generate_salt($count_log2) {
$output = '$S$';
// Ensure that $count_log2 is within set bounds.
$count_log2 = _password_enforce_log2_boundaries($count_log2);
// We encode the final log2 iteration count in base 64.
$itoa64 = _password_itoa64();
$output .= $itoa64[$count_log2];
// 6 bytes is the standard salt for a portable phpass hash.
$output .= _password_base64_encode(drupal_random_bytes(6), 6);
return $output;
}
/**
* Ensures that $count_log2 is within set bounds.
*
* @param $count_log2
* Integer that determines the number of iterations used in the hashing
* process. A larger value is more secure, but takes more time to complete.
*
* @return
* Integer within set bounds that is closest to $count_log2.
*/
function _password_enforce_log2_boundaries($count_log2) {
if ($count_log2 < DRUPAL_MIN_HASH_COUNT) {
return DRUPAL_MIN_HASH_COUNT;
}
elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) {
return DRUPAL_MAX_HASH_COUNT;
}
return (int) $count_log2;
}
/**
* Hash a password using a secure stretched hash.
*
* By using a salt and repeated hashing the password is "stretched". Its
* security is increased because it becomes much more computationally costly
* for an attacker to try to break the hash by brute-force computation of the
* hashes of a large number of plain-text words or strings to find a match.
*
* @param $algo
* The string name of a hashing algorithm usable by hash(), like 'sha256'.
* @param $password
* The plain-text password to hash.
* @param $setting
* An existing hash or the output of _password_generate_salt(). Must be
* at least 12 characters (the settings and salt).
*
* @return
* A string containing the hashed password (and salt) or FALSE on failure.
* The return string will be truncated at DRUPAL_HASH_LENGTH characters max.
*/
function _password_crypt($algo, $password, $setting) {
// The first 12 characters of an existing hash are its setting string.
$setting = substr($setting, 0, 12);
if ($setting[0] != '$' || $setting[2] != '$') {
return FALSE;
}
$count_log2 = _password_get_count_log2($setting);
// Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT
if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) {
return FALSE;
}
$salt = substr($setting, 4, 8);
// Hashes must have an 8 character salt.
if (strlen($salt) != 8) {
return FALSE;
}
// Convert the base 2 logarithm into an integer.
$count = 1 << $count_log2;
// We rely on the hash() function being available in PHP 5.2+.
$hash = hash($algo, $salt . $password, TRUE);
do {
$hash = hash($algo, $hash . $password, TRUE);
} while (--$count);
$len = strlen($hash);
$output = $setting . _password_base64_encode($hash, $len);
// _password_base64_encode() of a 16 byte MD5 will always be 22 characters.
// _password_base64_encode() of a 64 byte sha512 will always be 86 characters.
$expected = 12 + ceil((8 * $len) / 6);
return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE;
}
/**
* Parse the log2 iteration count from a stored hash or setting string.
*/
function _password_get_count_log2($setting) {
$itoa64 = _password_itoa64();
return strpos($itoa64, $setting[3]);
}
/**
* Hash a password using a secure hash.
*
* @param $password
* A plain-text password.
* @param $count_log2
* Optional integer to specify the iteration count. Generally used only during
* mass operations where a value less than the default is needed for speed.
*
* @return
* A string containing the hashed password (and a salt), or FALSE on failure.
*/
function user_hash_password($password, $count_log2 = 0) {
if (empty($count_log2)) {
// Use the standard iteration count.
$count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT);
}
return _password_crypt('sha512', $password, _password_generate_salt($count_log2));
}
/**
* Check whether a plain text password matches a stored hashed password.
*
* Alternative implementations of this function may use other data in the
* $account object, for example the uid to look up the hash in a custom table
* or remote database.
*
* @param $password
* A plain-text password
* @param $account
* A user object with at least the fields from the {users} table.
*
* @return
* TRUE or FALSE.
*/
function user_check_password($password, $account) {
if (substr($account->pass, 0, 2) == 'U$') {
// This may be an updated password from user_update_7000(). Such hashes
// have 'U' added as the first character and need an extra md5().
$stored_hash = substr($account->pass, 1);
$password = md5($password);
}
else {
$stored_hash = $account->pass;
}
$type = substr($stored_hash, 0, 3);
switch ($type) {
case '$S$':
// A normal Drupal 7 password using sha512.
$hash = _password_crypt('sha512', $password, $stored_hash);
break;
case '$H$':
// phpBB3 uses "$H$" for the same thing as "$P$".
case '$P$':
// A phpass password generated using md5. This is an
// imported password or from an earlier Drupal version.
$hash = _password_crypt('md5', $password, $stored_hash);
break;
default:
return FALSE;
}
return ($hash && $stored_hash == $hash);
}
/**
* Check whether a user's hashed password needs to be replaced with a new hash.
*
* This is typically called during the login process when the plain text
* password is available. A new hash is needed when the desired iteration count
* has changed through a change in the variable password_count_log2 or
* DRUPAL_HASH_COUNT or if the user's password hash was generated in an update
* like user_update_7000().
*
* Alternative implementations of this function might use other criteria based
* on the fields in $account.
*
* @param $account
* A user object with at least the fields from the {users} table.
*
* @return
* TRUE or FALSE.
*/
function user_needs_new_hash($account) {
// Check whether this was an updated password.
if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) {
return TRUE;
}
// Ensure that $count_log2 is within set bounds.
$count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT));
// Check whether the iteration count used differs from the standard number.
return (_password_get_count_log2($account->pass) !== $count_log2);
}
-587
View File
@@ -1,587 +0,0 @@
<?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_boot() or hook exit() can make them available, by
* executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);".
*/
/**
* Initialize the $_GET['q'] variable to the proper normal path.
*/
function drupal_path_initialize() {
// Ensure $_GET['q'] is set before calling drupal_normal_path(), to support
// path caching with hook_url_inbound_alter().
if (empty($_GET['q'])) {
$_GET['q'] = variable_get('site_frontpage', 'node');
}
$_GET['q'] = drupal_get_normal_path($_GET['q']);
}
/**
* 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 = NULL) {
global $language_url;
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
}
$cache = &$drupal_static_fast['cache'];
if (!isset($cache)) {
$cache = array(
'map' => array(),
'no_source' => array(),
'whitelist' => NULL,
'system_paths' => array(),
'no_aliases' => array(),
'first_call' => TRUE,
);
}
// Retrieve the path alias whitelist.
if (!isset($cache['whitelist'])) {
$cache['whitelist'] = variable_get('path_alias_whitelist', NULL);
if (!isset($cache['whitelist'])) {
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
}
}
// If no language is explicitly specified we default to the current URL
// language. If we used a language different from the one conveyed by the
// requested URL, we might end up being unable to check if there is a path
// alias matching the URL path.
$path_language = $path_language ? $path_language : $language_url->language;
if ($action == 'wipe') {
$cache = array();
$cache['whitelist'] = drupal_path_alias_whitelist_rebuild();
}
elseif ($cache['whitelist'] && $path != '') {
if ($action == 'alias') {
// During the first call to drupal_lookup_path() per language, load the
// expected system paths for the page from cache.
if (!empty($cache['first_call'])) {
$cache['first_call'] = FALSE;
$cache['map'][$path_language] = array();
// Load system paths from cache.
$cid = current_path();
if ($cached = cache_get($cid, 'cache_path')) {
$cache['system_paths'] = $cached->data;
// Now fetch the aliases corresponding to these system paths.
$args = array(
':system' => $cache['system_paths'],
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
);
// Always get the language-specific alias before the language-neutral
// one. For example 'de' is less than 'und' so the order needs to be
// ASC, while 'xx-lolspeak' is more than 'und' so the order needs to
// be DESC. We also order by pid ASC so that fetchAllKeyed() returns
// the most recently created alias for each source. Subsequent queries
// using fetchField() must use pid DESC to have the same effect.
// For performance reasons, the query builder is not used here.
if ($path_language == LANGUAGE_NONE) {
// Prevent PDO from complaining about a token the query doesn't use.
unset($args[':language']);
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language = :language_none ORDER BY pid ASC', $args);
}
elseif ($path_language < LANGUAGE_NONE) {
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC', $args);
}
else {
$result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language DESC, pid ASC', $args);
}
$cache['map'][$path_language] = $result->fetchAllKeyed();
// Keep a record of paths with no alias to avoid querying twice.
$cache['no_aliases'][$path_language] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$path_language])));
}
}
// If the alias has already been loaded, return it.
if (isset($cache['map'][$path_language][$path])) {
return $cache['map'][$path_language][$path];
}
// Check the path whitelist, if the top_level part before the first /
// is not in the list, then there is no need to do anything further,
// it is not in the database.
elseif (!isset($cache['whitelist'][strtok($path, '/')])) {
return FALSE;
}
// For system paths which were not cached, query aliases individually.
elseif (!isset($cache['no_aliases'][$path_language][$path])) {
$args = array(
':source' => $path,
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
);
// See the queries above.
if ($path_language == LANGUAGE_NONE) {
unset($args[':language']);
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language = :language_none ORDER BY pid DESC", $args)->fetchField();
}
elseif ($path_language > LANGUAGE_NONE) {
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args)->fetchField();
}
else {
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args)->fetchField();
}
$cache['map'][$path_language][$path] = $alias;
return $alias;
}
}
// Check $no_source for this $path in case we've already determined that there
// isn't a path that has this alias
elseif ($action == 'source' && !isset($cache['no_source'][$path_language][$path])) {
// Look for the value $path within the cached $map
$source = FALSE;
if (!isset($cache['map'][$path_language]) || !($source = array_search($path, $cache['map'][$path_language]))) {
$args = array(
':alias' => $path,
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
);
// See the queries above.
if ($path_language == LANGUAGE_NONE) {
unset($args[':language']);
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language = :language_none ORDER BY pid DESC", $args);
}
elseif ($path_language > LANGUAGE_NONE) {
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args);
}
else {
$result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args);
}
if ($source = $result->fetchField()) {
$cache['map'][$path_language][$source] = $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_source.
$cache['no_source'][$path_language][$path] = TRUE;
}
}
return $source;
}
}
return FALSE;
}
/**
* Cache system paths for a page.
*
* Cache an array of the system paths available on each page. We assume
* that aliases will be needed for the majority of these paths during
* subsequent requests, and load them in a single query during
* drupal_lookup_path().
*/
function drupal_cache_system_paths() {
// Check if the system paths for this page were loaded from cache in this
// request to avoid writing to cache on every request.
$cache = &drupal_static('drupal_lookup_path', array());
if (empty($cache['system_paths']) && !empty($cache['map'])) {
// Generate a cache ID (cid) specifically for this page.
$cid = current_path();
// The static $map array used by drupal_lookup_path() includes all
// system paths for the page request.
if ($paths = current($cache['map'])) {
$data = array_keys($paths);
$expire = REQUEST_TIME + (60 * 60 * 24);
cache_set($cid, $data, 'cache_path', $expire);
}
}
}
/**
* Given an internal Drupal path, return the alias set by the administrator.
*
* If no path is provided, the function will return the alias of the current
* page.
*
* @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 = NULL, $path_language = NULL) {
// If no path is specified, use the current page's path.
if ($path == NULL) {
$path = $_GET['q'];
}
$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 = NULL) {
$original_path = $path;
// Lookup the path alias first.
if ($source = drupal_lookup_path('source', $path, $path_language)) {
$path = $source;
}
// Allow other modules to alter the inbound URL. We cannot use drupal_alter()
// here because we need to run hook_url_inbound_alter() in the reverse order
// of hook_url_outbound_alter().
foreach (array_reverse(module_implements('url_inbound_alter')) as $module) {
$function = $module . '_url_inbound_alter';
$function($path, $original_path, $path_language);
}
return $path;
}
/**
* 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() {
// Use the advanced drupal_static() pattern, since this is called very often.
static $drupal_static_fast;
if (!isset($drupal_static_fast)) {
$drupal_static_fast['is_front_page'] = &drupal_static(__FUNCTION__);
}
$is_front_page = &$drupal_static_fast['is_front_page'];
if (!isset($is_front_page)) {
// As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path,
// we can check it against the 'site_frontpage' variable.
$is_front_page = ($_GET['q'] == 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
* Boolean value: TRUE if the path matches a pattern, FALSE otherwise.
*/
function drupal_match_path($path, $patterns) {
$regexps = &drupal_static(__FUNCTION__);
if (!isset($regexps[$patterns])) {
// Convert path settings to a regular expression.
// Therefore replace newlines with a logical or, /* with asterisks and the <front> with the frontpage.
$to_replace = array(
'/(\r\n?|\n)/', // newlines
'/\\\\\*/', // asterisks
'/(^|\|)\\\\<front\\\\>($|\|)/' // <front>
);
$replacements = array(
'|',
'.*',
'\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\2'
);
$patterns_quoted = preg_quote($patterns, '/');
$regexps[$patterns] = '/^(' . preg_replace($to_replace, $replacements, $patterns_quoted) . ')$/';
}
return (bool)preg_match($regexps[$patterns], $path);
}
/**
* Return the current URL path of the page being viewed.
*
* Examples:
* - http://example.com/node/306 returns "node/306".
* - http://example.com/drupalfolder/node/306 returns "node/306" while
* base_path() returns "/drupalfolder/".
* - http://example.com/path/alias (which is a path alias for node/306) returns
* "node/306" as opposed to the path alias.
*
* This function is not available in hook_boot() so use $_GET['q'] instead.
* However, be careful when doing that because in the case of Example #3
* $_GET['q'] will contain "path/alias". If "node/306" is needed, calling
* drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available.
*
* @return
* The current Drupal URL path.
*
* @see request_path()
*/
function current_path() {
return $_GET['q'];
}
/**
* Rebuild the path alias white list.
*
* @param $source
* An optional system path for which an alias is being inserted.
*
* @return
* An array containing a white list of path aliases.
*/
function drupal_path_alias_whitelist_rebuild($source = NULL) {
// When paths are inserted, only rebuild the whitelist if the system path
// has a top level component which is not already in the whitelist.
if (!empty($source)) {
$whitelist = variable_get('path_alias_whitelist', NULL);
if (isset($whitelist[strtok($source, '/')])) {
return $whitelist;
}
}
// For each alias in the database, get the top level component of the system
// path it corresponds to. This is the portion of the path before the first
// '/', if present, otherwise the whole path itself.
$whitelist = array();
$result = db_query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}");
foreach ($result as $row) {
$whitelist[$row->path] = TRUE;
}
variable_set('path_alias_whitelist', $whitelist);
return $whitelist;
}
/**
* Fetch a specific URL alias from the database.
*
* @param $conditions
* A string representing the source, a number representing the pid, or an
* array of query conditions.
*
* @return
* FALSE if no alias was found or an associative array containing the
* following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: Unique path alias identifier.
* - language: The language of the alias.
*/
function path_load($conditions) {
if (is_numeric($conditions)) {
$conditions = array('pid' => $conditions);
}
elseif (is_string($conditions)) {
$conditions = array('source' => $conditions);
}
elseif (!is_array($conditions)) {
return FALSE;
}
$select = db_select('url_alias');
foreach ($conditions as $field => $value) {
$select->condition($field, $value);
}
return $select
->fields('url_alias')
->execute()
->fetchAssoc();
}
/**
* Save a path alias to the database.
*
* @param $path
* An associative array containing the following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: (optional) Unique path alias identifier.
* - language: (optional) The language of the alias.
*/
function path_save(&$path) {
$path += array('language' => LANGUAGE_NONE);
// Load the stored alias, if any.
if (!empty($path['pid']) && !isset($path['original'])) {
$path['original'] = path_load($path['pid']);
}
if (empty($path['pid'])) {
drupal_write_record('url_alias', $path);
module_invoke_all('path_insert', $path);
}
else {
drupal_write_record('url_alias', $path, array('pid'));
module_invoke_all('path_update', $path);
}
// Clear internal properties.
unset($path['original']);
// Clear the static alias cache.
drupal_clear_path_cache($path['source']);
}
/**
* Delete a URL alias.
*
* @param $criteria
* A number representing the pid or an array of criteria.
*/
function path_delete($criteria) {
if (!is_array($criteria)) {
$criteria = array('pid' => $criteria);
}
$path = path_load($criteria);
$query = db_delete('url_alias');
foreach ($criteria as $field => $value) {
$query->condition($field, $value);
}
$query->execute();
module_invoke_all('path_delete', $path);
drupal_clear_path_cache($path['source']);
}
/**
* Determine whether a path is in the administrative section of the site.
*
* By default, paths are considered to be non-administrative. If a path does not
* match any of the patterns in path_get_admin_paths(), or if it matches both
* administrative and non-administrative patterns, it is considered
* non-administrative.
*
* @param $path
* A Drupal path.
*
* @return
* TRUE if the path is administrative, FALSE otherwise.
*
* @see path_get_admin_paths()
* @see hook_admin_paths()
* @see hook_admin_paths_alter()
*/
function path_is_admin($path) {
$path_map = &drupal_static(__FUNCTION__);
if (!isset($path_map['admin'][$path])) {
$patterns = path_get_admin_paths();
$path_map['admin'][$path] = drupal_match_path($path, $patterns['admin']);
$path_map['non_admin'][$path] = drupal_match_path($path, $patterns['non_admin']);
}
return $path_map['admin'][$path] && !$path_map['non_admin'][$path];
}
/**
* Get a list of administrative and non-administrative paths.
*
* @return array
* An associative array containing the following keys:
* 'admin': An array of administrative paths and regular expressions
* in a format suitable for drupal_match_path().
* 'non_admin': An array of non-administrative paths and regular expressions.
*
* @see hook_admin_paths()
* @see hook_admin_paths_alter()
*/
function path_get_admin_paths() {
$patterns = &drupal_static(__FUNCTION__);
if (!isset($patterns)) {
$paths = module_invoke_all('admin_paths');
drupal_alter('admin_paths', $paths);
// Combine all admin paths into one array, and likewise for non-admin paths,
// for easier handling.
$patterns = array();
$patterns['admin'] = array();
$patterns['non_admin'] = array();
foreach ($paths as $path => $enabled) {
if ($enabled) {
$patterns['admin'][] = $path;
}
else {
$patterns['non_admin'][] = $path;
}
}
$patterns['admin'] = implode("\n", $patterns['admin']);
$patterns['non_admin'] = implode("\n", $patterns['non_admin']);
}
return $patterns;
}
/**
* Checks a path exists and the current user has access to it.
*
* @param $path
* The path to check.
* @param $dynamic_allowed
* Whether paths with menu wildcards (like user/%) should be allowed.
*
* @return
* TRUE if it is a valid path AND the current user has access permission,
* FALSE otherwise.
*/
function drupal_valid_path($path, $dynamic_allowed = FALSE) {
global $menu_admin;
// We indicate that a menu administrator is running the menu access check.
$menu_admin = TRUE;
if ($path == '<front>' || url_is_external($path)) {
$item = array('access' => TRUE);
}
elseif ($dynamic_allowed && preg_match('/\/\%/', $path)) {
// Path is dynamic (ie 'user/%'), so check directly against menu_router table.
if ($item = db_query("SELECT * FROM {menu_router} where path = :path", array(':path' => $path))->fetchAssoc()) {
$item['link_path'] = $form_item['link_path'];
$item['link_title'] = $form_item['link_title'];
$item['external'] = FALSE;
$item['options'] = '';
_menu_link_translate($item);
}
}
else {
$item = menu_get_item($path);
}
$menu_admin = FALSE;
return $item && $item['access'];
}
/**
* Clear the path cache.
*
* @param $source
* An optional system path for which an alias is being changed.
*/
function drupal_clear_path_cache($source = NULL) {
// Clear the drupal_lookup_path() static cache.
drupal_static_reset('drupal_lookup_path');
drupal_path_alias_whitelist_rebuild($source);
}

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