Skip to content
Commits on Source (939)
# Drupal editor configuration normalization
# @see http://editorconfig.org/
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
......@@ -3,7 +3,7 @@
#
# 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)$">
<FilesMatch "\.(engine|inc|info|install|make|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\..*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock))$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig\.save)$">
Order allow,deny
</FilesMatch>
......@@ -56,6 +56,17 @@ DirectoryIndex index.php index.html index.htm
<IfModule mod_rewrite.c>
RewriteEngine on
# Set "protossl" to "s" if we were accessed via https://. This is used later
# if you enable "www." stripping or enforcement, in order to ensure that
# you don't bounce between http and https.
RewriteRule ^ - [E=protossl]
RewriteCond %{HTTPS} on
RewriteRule ^ - [E=protossl:s]
# Make sure Authorization HTTP header is available to PHP
# even when running as CGI or FastCGI.
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# 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
......@@ -78,14 +89,15 @@ DirectoryIndex index.php index.html index.htm
# 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} .
# RewriteCond %{HTTP_HOST} !^www\. [NC]
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# RewriteRule ^ http%{ENV:protossl}://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]
# RewriteRule ^ http%{ENV:protossl}://%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.
......@@ -129,3 +141,9 @@ DirectoryIndex index.php index.html index.htm
</FilesMatch>
</IfModule>
</IfModule>
# Add headers to all responses.
<IfModule mod_headers.c>
# Disable content sniffing, since it's an attack vector.
Header always set X-Content-Type-Options nosniff
</IfModule>
This diff is collapsed.
All Drupal code is Copyright 2001 - 2012 by the original authors.
All Drupal code is Copyright 2001 - 2013 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
......
......@@ -20,18 +20,21 @@ initial database files. Next you must log in and set the access database rights:
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.*
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER,
CREATE TEMPORARY TABLES ON databasename.*
TO 'username'@'localhost' IDENTIFIED BY 'password';
where
where:
'databasename' is the name of your database
'username@localhost' is the username of your MySQL account
'username' is the username of your MySQL account
'localhost' is the web server host where Drupal is installed
'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.
Note: Unless the database user/host combination for your Drupal installation
has all of the privileges listed above (except possibly CREATE TEMPORARY TABLES,
which is currently only used by Drupal core automated tests and some
contributed modules), you will not be able to install or run Drupal.
If successful, MySQL will reply with:
......
......@@ -20,8 +20,10 @@ Drupal requires:
- 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.
- Percona Server 5.1.70 (or greater) (http://www.percona.com/). Percona
Server is a backwards-compatible replacement for MySQL.
- PostgreSQL 8.3 (or greater) (http://www.postgresql.org/).
- SQLite 3.4.2 (or greater) (http://www.sqlite.org/).
- SQLite 3.3.7 (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"
......@@ -89,8 +91,8 @@ INSTALLATION
- 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 install profile,
- 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/
......
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.
contribute in other ways -- see https://www.drupal.org/contribute to find out
how.
Branch maintainers
------------------
......@@ -9,150 +10,155 @@ 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
- Dries Buytaert 'dries' https://www.drupal.org/u/dries
- Angela Byron 'webchick' https://www.drupal.org/u/webchick
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0
Component maintainers
---------------------
The Drupal Core component maintainers oversee the development of Drupal
subsystems. See http://drupal.org/contribute/core-maintainers for more
subsystems. See https://www.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
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos
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
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
Batch system
- Yves Chedemois 'yched' http://drupal.org/user/39567
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
Cache system
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
Cron system
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Derek Wright 'dww' http://drupal.org/user/46549
- Derek Wright 'dww' https://www.drupal.org/u/dww
Database system
- Larry Garfield 'Crell' http://drupal.org/user/26398
- Larry Garfield 'Crell' https://www.drupal.org/u/crell
- MySQL driver
- Larry Garfield 'Crell' http://drupal.org/user/26398
- David Strauss 'David Strauss' http://drupal.org/user/93254
- Larry Garfield 'Crell' https://www.drupal.org/u/crell
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
- PostgreSQL driver
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Josh Waihi 'fiasco' http://drupal.org/user/188162
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
- Josh Waihi 'fiasco' https://www.drupal.org/u/josh-waihi
- Sqlite driver
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
Database update system
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Ashok Modi 'btmash' http://drupal.org/user/60422
- Ashok Modi 'BTMash' https://www.drupal.org/u/btmash
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
- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
File system
- Andrew Morton 'drewish' http://drupal.org/user/34869
- Aaron Winborn 'aaron' http://drupal.org/user/33420
- Andrew Morton 'drewish' https://www.drupal.org/u/drewish
- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron
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
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
- Wolfgang Ziegler 'fago' https://www.drupal.org/u/fago
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
Image system
- Andrew Morton 'drewish' http://drupal.org/user/34869
- Nathan Haug 'quicksketch' http://drupal.org/user/35821
- Andrew Morton 'drewish' https://www.drupal.org/u/drewish
- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
Install system
- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
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
- Théodore Biadala 'nod_' https://www.drupal.org/u/nod_
- Steve De Jonghe 'seutje' https://www.drupal.org/u/seutje
Language system
- Francesco Placella 'plach' http://drupal.org/user/183211
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
- Francesco Placella 'plach' https://www.drupal.org/u/plach
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Lock system
- Damien Tournoud 'DamZ' http://drupal.org/user/22211
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
Mail system
- ?
Markup
- Jacine Luisi 'Jacine' http://drupal.org/user/88931
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
- Jacine Luisi 'Jacine' https://www.drupal.org/u/jacine
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Menu system
- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
Path system
- Dave Reid 'davereid' http://drupal.org/user/53892
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
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
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
- Franz Heinzmann 'Frando' https://www.drupal.org/u/frando
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
- Earl Miles 'merlinofchaos' https://www.drupal.org/u/merlinofchaos
- Alex Bronstein 'effulgentsia' https://www.drupal.org/u/effulgentsia
- Joon Park 'dvessel' https://www.drupal.org/u/dvessel
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
Token system
- Dave Reid 'davereid' http://drupal.org/user/53892
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
XML-RPC system
- Frederic G. Marand 'fgm' http://drupal.org/user/27985
- Frederic G. Marand 'fgm' https://www.drupal.org/u/fgm
Topic coordinators
------------------
Accessibility
- Everett Zufelt 'Everett Zufelt' http://drupal.org/user/406552
- Brandon Bowersox-Johnson 'bowersox' http://drupal.org/user/186415
- Everett Zufelt 'Everett Zufelt' https://www.drupal.org/u/everett-zufelt
- Brandon Bowersox-Johnson 'bowersox' https://www.drupal.org/u/bowersox
Documentation
- Jennifer Hodgdon 'jhodgdon' http://drupal.org/user/155601
Security
- Greg Knaddison 'greggles' http://drupal.org/user/36762
- Jennifer Hodgdon 'jhodgdon' https://www.drupal.org/u/jhodgdon
Translations
- Gerhard Killesreiter 'killes' http://drupal.org/user/83
- Gerhard Killesreiter 'killes' https://www.drupal.org/u/gerhard-killesreiter
User experience and usability
- Roy Scholten 'yoroy' http://drupal.org/user/41502
- Bojhan Somers 'Bojhan' http://drupal.org/user/87969
- Roy Scholten 'yoroy' https://www.drupal.org/u/yoroy
- Bojhan Somers 'Bojhan' https://www.drupal.org/u/bojhan
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
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
- Ken Rickard 'agentrickard' https://www.drupal.org/u/agentrickard
Security team
-----------------
To report a security issue, see: https://www.drupal.org/security-team/report-issue
The Drupal security team provides Security Advisories for vulnerabilities,
assists developers in resolving security issues, and provides security
documentation. See https://www.drupal.org/security-team for more information.
The security team lead is:
- Michael Hess 'mlhess' https://www.drupal.org/u/mlhess
Module maintainers
------------------
......@@ -161,143 +167,141 @@ Aggregator module
- ?
Block module
- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
Blog module
- ?
Book module
- Peter Wolanin 'pwolanin' http://drupal.org/user/49851
- Peter Wolanin 'pwolanin' https://www.drupal.org/u/pwolanin
Color module
- ?
Comment module
- Nathaniel Catchpole 'catch' http://drupal.org/user/35733
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
Contact module
- Dave Reid 'davereid' http://drupal.org/user/53892
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
Contextual module
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Dashboard module
- ?
Database logging module
- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063
- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
Field module
- Yves Chedemois 'yched' http://drupal.org/user/39567
- Barry Jaspan 'bjaspan' http://drupal.org/user/46413
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
- Barry Jaspan 'bjaspan' https://www.drupal.org/u/bjaspan
Field UI module
- Yves Chedemois 'yched' http://drupal.org/user/39567
- Yves Chedemois 'yched' https://www.drupal.org/u/yched
File module
- Aaron Winborn 'aaron' http://drupal.org/user/33420
- Aaron Winborn 'aaron' https://www.drupal.org/u/aaron
Filter module
- Daniel F. Kudwien 'sun' http://drupal.org/user/54136
- Daniel F. Kudwien 'sun' https://www.drupal.org/u/sun
Forum module
- Lee Rowlands 'larowlan' http://drupal.org/user/395439
- Lee Rowlands 'larowlan' https://www.drupal.org/u/larowlan
Help module
- ?
Image module
- Nathan Haug 'quicksketch' http://drupal.org/user/35821
- Nathan Haug 'quicksketch' https://www.drupal.org/u/quicksketch
Locale module
- Gábor Hojtsy 'Gábor Hojtsy' http://drupal.org/user/4166
- Gábor Hojtsy 'Gábor Hojtsy' https://www.drupal.org/u/gábor-hojtsy
Menu module
- ?
Node module
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
- David Strauss 'David Strauss' http://drupal.org/user/93254
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
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
- Vojtech Kusy 'wojtha' https://www.drupal.org/u/wojtha
- Christian Schmidt 'c960657' https://www.drupal.org/u/c960657
- Damien Tournoud 'DamZ' https://www.drupal.org/u/damien-tournoud
Overlay module
- Katherine Senzee 'ksenzee' http://drupal.org/user/139855
- Katherine Senzee 'ksenzee' https://www.drupal.org/u/ksenzee
Path module
- Dave Reid 'davereid' http://drupal.org/user/53892
- Dave Reid 'davereid' https://www.drupal.org/u/dave-reid
PHP module
- ?
Poll module
- Andrei Mateescu 'amateescu' http://drupal.org/user/729614
- Andrei Mateescu 'amateescu' https://www.drupal.org/u/amateescu
Profile module
- ?
RDF module
- Stéphane Corlosquet 'scor' http://drupal.org/user/52142
- Stéphane Corlosquet 'scor' https://www.drupal.org/u/scor
Search module
- Doug Green 'douggreen' http://drupal.org/user/29191
- Doug Green 'douggreen' https://www.drupal.org/u/douggreen
Shortcut module
- David Rothstein 'David_Rothstein' http://drupal.org/user/124982
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
Simpletest module
- Jimmy Berry 'boombatower' http://drupal.org/user/214218
- Károly Négyesi 'chx' http://drupal.org/user/9446
- Jimmy Berry 'boombatower' https://www.drupal.org/u/boombatower
Statistics module
- Tim Millwood 'timmillwood' http://drupal.org/user/227849
- Tim Millwood 'timmillwood' https://www.drupal.org/u/timmillwood
Syslog module
- Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063
- Khalid Baheyeldin 'kbahey' https://www.drupal.org/u/kbahey
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
- Nathaniel Catchpole 'catch' https://www.drupal.org/u/catch
- Benjamin Doherty 'bangpound' https://www.drupal.org/u/bangpound
Toolbar module
- ?
Tracker module
- David Strauss 'David Strauss' http://drupal.org/user/93254
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
Translation module
- Francesco Placella 'plach' http://drupal.org/user/183211
- Francesco Placella 'plach' https://www.drupal.org/u/plach
Trigger module
- ?
Update module
- Derek Wright 'dww' http://drupal.org/user/46549
- Derek Wright 'dww' https://www.drupal.org/u/dww
User module
- Moshe Weitzman 'moshe weitzman' http://drupal.org/user/23
- David Strauss 'David Strauss' http://drupal.org/user/93254
- Moshe Weitzman 'moshe weitzman' https://www.drupal.org/u/moshe-weitzman
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
Theme maintainers
-----------------
Bartik theme
- Jen Simmons 'jensimmons' http://drupal.org/user/140882
- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393
- Jen Simmons 'jensimmons' https://www.drupal.org/u/jensimmons
- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz
Garland theme
- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
Seven theme
- Jeff Burns 'Jeff Burnz' http://drupal.org/user/61393
- Jeff Burns 'Jeff Burnz' https://www.drupal.org/u/jeff-burnz
Stark theme
- John Albin Wilkins 'JohnAlbin' http://drupal.org/user/32095
- John Albin Wilkins 'JohnAlbin' https://www.drupal.org/u/johnalbin
......@@ -4,6 +4,7 @@ CONTENTS OF THIS FILE
* About Drupal
* Configuration and features
* Installation profiles
* Appearance
* Developing for Drupal
......@@ -43,6 +44,40 @@ More about configuration:
http://drupal.org/project/modules
* See also: "Developing for Drupal" for writing your own modules, below.
INSTALLATION PROFILES
---------------------
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.
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.
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
APPEARANCE
----------
......
......@@ -64,6 +64,9 @@ following the instructions in the INTRODUCTION section at the top of this file:
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:
- Locate your settings.php file in the /sites/* directory. (Typically
sites/default.)
- 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
......@@ -74,6 +77,13 @@ following the instructions in the INTRODUCTION section at the top of this file:
database information, and you will also want to copy in any other
customizations you have added.
You can find the release notes for your version at
https://www.drupal.org/project/drupal. At bottom of the project page under
"Downloads" use the link for your version of Drupal to view the release
notes. If your version is not listed, use the 'View all releases' link. From
this page you can scroll down or use the filter to find your version and its
release notes.
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.
......
......@@ -4,16 +4,16 @@
* @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.
* 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:
......@@ -21,16 +21,17 @@
*/
/**
* Root directory of Drupal installation.
* Defines the root directory of the 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).
* Global flag to identify update.php and authorize.php runs.
*
* Identifies update.php and authorize.php runs, avoiding unwanted operations
* such as hook_init() and hook_exit() invokes, css/js preprocessing and
* translation, and solves some theming issues. The flag is checked in other
* places in Drupal code (not just authorize.php).
*/
define('MAINTENANCE_MODE', 'update');
......@@ -51,7 +52,7 @@ function authorize_access_denied_page() {
* have access to the 'administer software updates' permission.
*
* @return
* TRUE if the current user can run authorize.php, otherwise FALSE.
* TRUE if the current user can run authorize.php, and FALSE if not.
*/
function authorize_access_allowed() {
return variable_get('allow_authorize_operations', TRUE) && user_access('administer software updates');
......
......@@ -211,7 +211,7 @@
*
* When returning an Ajax command array, it is often useful to have
* status messages rendered along with other tasks in the command array.
* In that case the the Ajax commands array may be constructed like this:
* In that case the Ajax commands array may be constructed like this:
* @code
* $commands = array();
* $commands[] = ajax_command_replace(NULL, $output);
......@@ -230,6 +230,10 @@
* functions.
*/
function ajax_render($commands = array()) {
// Although ajax_deliver() does this, some contributed and custom modules
// render Ajax responses without using that delivery callback.
ajax_set_verification_header();
// Ajax responses aren't rendered with html.tpl.php, so we have to call
// drupal_get_css() and drupal_get_js() here, in order to have new files added
// during this request to be loaded by the page. We only want to send back
......@@ -251,8 +255,8 @@ function ajax_render($commands = array()) {
// reliably diffed with array_diff_key(), since the number can change
// due to factors unrelated to the inline content, so for now, we strip
// the inline items from Ajax responses, and can add support for them
// when drupal_add_css() and drupal_add_js() are changed to using md5()
// or some other hash of the inline content.
// when drupal_add_css() and drupal_add_js() are changed to use a hash
// of the inline content as the array key.
foreach ($items[$type] as $key => $item) {
if (is_numeric($key)) {
unset($items[$type][$key]);
......@@ -276,7 +280,7 @@ function ajax_render($commands = array()) {
$extra_commands = array();
if (!empty($styles)) {
$extra_commands[] = ajax_command_prepend('head', $styles);
$extra_commands[] = ajax_command_add_css($styles);
}
if (!empty($scripts_header)) {
$extra_commands[] = ajax_command_prepend('head', $scripts_header);
......@@ -292,7 +296,7 @@ function ajax_render($commands = array()) {
$scripts = drupal_add_js();
if (!empty($scripts['settings'])) {
$settings = $scripts['settings'];
array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
array_unshift($commands, ajax_command_settings(drupal_array_merge_deep_array($settings['data']), TRUE));
}
// Allow modules to alter any Ajax response.
......@@ -308,10 +312,11 @@ function ajax_render($commands = array()) {
* pulls the form info from $_POST.
*
* @return
* An array containing the $form and $form_state. Use the list() function
* to break these apart:
* An array containing the $form, $form_state, $form_id, $form_build_id and an
* initial list of Ajax $commands. Use the list() function to break these
* apart:
* @code
* list($form, $form_state, $form_id, $form_build_id) = ajax_get_form();
* list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
* @endcode
*/
function ajax_get_form() {
......@@ -331,6 +336,17 @@ function ajax_get_form() {
drupal_exit();
}
// When a page level cache is enabled, the form-build id might have been
// replaced from within form_get_cache. If this is the case, it is also
// necessary to update it in the browser by issuing an appropriate Ajax
// command.
$commands = array();
if (isset($form['#build_id_old']) && $form['#build_id_old'] != $form['#build_id']) {
// If the form build ID has changed, issue an Ajax command to update it.
$commands[] = ajax_command_update_build_id($form);
$form_build_id = $form['#build_id'];
}
// Since some of the submit handlers are run, redirects need to be disabled.
$form_state['no_redirect'] = TRUE;
......@@ -345,7 +361,7 @@ function ajax_get_form() {
$form_state['input'] = $_POST;
$form_id = $form['#form_id'];
return array($form, $form_state, $form_id, $form_build_id);
return array($form, $form_state, $form_id, $form_build_id, $commands);
}
/**
......@@ -366,7 +382,7 @@ function ajax_get_form() {
* @see system_menu()
*/
function ajax_form_callback() {
list($form, $form_state) = ajax_get_form();
list($form, $form_state, $form_id, $form_build_id, $commands) = ajax_get_form();
drupal_process_form($form['#form_id'], $form, $form_state);
// We need to return the part of the form (or some other content) that needs
......@@ -378,8 +394,20 @@ function ajax_form_callback() {
if (!empty($form_state['triggering_element'])) {
$callback = $form_state['triggering_element']['#ajax']['callback'];
}
if (!empty($callback) && function_exists($callback)) {
return $callback($form, $form_state);
if (!empty($callback) && is_callable($callback)) {
$result = $callback($form, $form_state);
if (!(is_array($result) && isset($result['#type']) && $result['#type'] == 'ajax')) {
// Turn the response into a #type=ajax array if it isn't one already.
$result = array(
'#type' => 'ajax',
'#commands' => ajax_prepare_response($result),
);
}
$result['#commands'] = array_merge($commands, $result['#commands']);
return $result;
}
}
......@@ -463,6 +491,9 @@ function ajax_deliver($page_callback_result) {
}
}
// Let ajax.js know that this response is safe to process.
ajax_set_verification_header();
// Print the response.
$commands = ajax_prepare_response($page_callback_result);
$json = ajax_render($commands);
......@@ -552,6 +583,29 @@ function ajax_prepare_response($page_callback_result) {
return $commands;
}
/**
* Sets a response header for ajax.js to trust the response body.
*
* It is not safe to invoke Ajax commands within user-uploaded files, so this
* header protects against those being invoked.
*
* @see Drupal.ajax.options.success()
*/
function ajax_set_verification_header() {
$added = &drupal_static(__FUNCTION__);
// User-uploaded files cannot set any response headers, so a custom header is
// used to indicate to ajax.js that this response is safe. Note that most
// Ajax requests bound using the Form API will be protected by having the URL
// flagged as trusted in Drupal.settings, so this header is used only for
// things like custom markup that gets Ajax behaviors attached.
if (empty($added)) {
drupal_add_http_header('X-Drupal-Ajax-Token', '1');
// Avoid sending the header twice.
$added = TRUE;
}
}
/**
* Performs end-of-Ajax-request tasks.
*
......@@ -740,7 +794,12 @@ function ajax_pre_render_element($element) {
$element['#attached']['js'][] = array(
'type' => 'setting',
'data' => array('ajax' => array($element['#id'] => $settings)),
'data' => array(
'ajax' => array($element['#id'] => $settings),
'urlIsAjaxTrusted' => array(
$settings['url'] => TRUE,
),
),
);
// Indicate that Ajax processing was successful.
......@@ -836,7 +895,8 @@ function ajax_command_insert($selector, $html, $settings = NULL) {
* @return
* An array suitable for use with the ajax_render() function.
*
* See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
* See
* @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink
*/
function ajax_command_replace($selector, $html, $settings = NULL) {
return array(
......@@ -1209,3 +1269,49 @@ function ajax_command_restripe($selector) {
'selector' => $selector,
);
}
/**
* Creates a Drupal Ajax 'update_build_id' command.
*
* This command updates the value of a hidden form_build_id input element on a
* form. It requires the form passed in to have keys for both the old build ID
* in #build_id_old and the new build ID in #build_id.
*
* The primary use case for this Ajax command is to serve a new build ID to a
* form served from the cache to an anonymous user, preventing one anonymous
* user from accessing the form state of another anonymous users on Ajax enabled
* forms.
*
* @param $form
* The form array representing the form whose build ID should be updated.
*/
function ajax_command_update_build_id($form) {
return array(
'command' => 'updateBuildId',
'old' => $form['#build_id_old'],
'new' => $form['#build_id'],
);
}
/**
* Creates a Drupal Ajax 'add_css' command.
*
* This method will add css via ajax in a cross-browser compatible way.
*
* This command is implemented by Drupal.ajax.prototype.commands.add_css()
* defined in misc/ajax.js.
*
* @param $styles
* A string that contains the styles to be added.
*
* @return
* An array suitable for use with the ajax_render() function.
*
* @see misc/ajax.js
*/
function ajax_command_add_css($styles) {
return array(
'command' => 'add_css',
'data' => $styles,
);
}
......@@ -460,10 +460,10 @@ function _batch_finished() {
if (isset($batch_set['file']) && is_file($batch_set['file'])) {
include_once DRUPAL_ROOT . '/' . $batch_set['file'];
}
if (function_exists($batch_set['finished'])) {
if (is_callable($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));
call_user_func($batch_set['finished'], $batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000));
}
}
}
......
This diff is collapsed.
......@@ -14,6 +14,7 @@
*
* @param $bin
* The cache bin for which the cache object should be returned.
*
* @return DrupalCacheInterface
* The cache object associated with the specified bin.
*
......@@ -80,43 +81,15 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
* 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.
* might break functionality or are extremely expensive to recalculate. The
* other bins are expired automatically by core. Contributed modules can add
* additional bins and get them expired automatically by implementing
* hook_flush_caches().
*
* 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
* - 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.
......@@ -125,15 +98,31 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
* 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.
* serialized before insertion. Strings will be stored as plain text and are
* not serialized. Some storage engines only allow objects up to a maximum of
* 1MB in size to be stored by default. When caching large arrays or similar,
* take care to ensure $data does not exceed this size.
* @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.
* (optional) The cache bin to store the data in. Valid core values are:
* - cache: (default) Generic cache storage bin (used for theme registry,
* locale date, list of simpletest tests, etc.).
* - cache_block: Stores the content of various blocks.
* - cache_bootstrap: Stores the class registry, the system list of modules,
* the list of which modules implement which hooks, and the Drupal variable
* list.
* - 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. This bin should not be flushed before its expired time.
* - 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 node 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.
* @param $expire
* One of the following values:
* (optional) 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
......@@ -141,6 +130,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
* - A Unix timestamp: Indicates that the item should be kept at least until
* the given time, after which it behaves like CACHE_TEMPORARY.
*
* @see _update_cache_set()
* @see cache_get()
*/
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
......@@ -150,18 +140,20 @@ function cache_set($cid, $data, $bin = 'cache', $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.
* If called with the arguments $cid and $bin set to NULL or omitted, then
* expirable entries will be cleared from the cache_page and cache_block bins,
* and the $wildcard argument is ignored.
*
* @param $cid
* If set, the cache ID to delete. Otherwise, all cache entries that can
* expire are deleted.
* If set, the cache ID or an array of cache IDs. Otherwise, all cache entries
* that can expire are deleted. The $wildcard argument will be ignored if set
* to NULL.
* @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.
* If TRUE, the $cid argument must contain a string value and 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 is emptied.
*/
function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
if (!isset($cid) && !isset($bin)) {
......@@ -230,13 +222,6 @@ function cache_is_empty($bin) {
* @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.
......@@ -272,10 +257,12 @@ function getMultiple(&$cids);
* 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.
* serialized before insertion. Strings will be stored as plain text and not
* serialized. Some storage engines only allow objects up to a maximum of
* 1MB in size to be stored by default. When caching large arrays or
* similar, take care to ensure $data does not exceed this size.
* @param $expire
* One of the following values:
* (optional) 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
......@@ -293,12 +280,14 @@ function set($cid, $data, $expire = CACHE_PERMANENT);
* cache_page and cache_block bins.
*
* @param $cid
* If set, the cache ID to delete. Otherwise, all cache entries that can
* expire are deleted.
* If set, the cache ID or an array of cache IDs. Otherwise, all cache
* entries that can expire are deleted. The $wildcard argument will be
* ignored if set to NULL.
* @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.
* If TRUE, the $cid argument must contain a string value and 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 is emptied.
*/
function clear($cid = NULL, $wildcard = FALSE);
......@@ -324,7 +313,10 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
protected $bin;
/**
* Constructs a new DrupalDatabaseCache object.
* Constructs a DrupalDatabaseCache object.
*
* @param $bin
* The cache bin for which the object is created.
*/
function __construct($bin) {
$this->bin = $bin;
......@@ -518,7 +510,16 @@ function clear($cid = NULL, $wildcard = FALSE) {
else {
if ($wildcard) {
if ($cid == '*') {
db_truncate($this->bin)->execute();
// Check if $this->bin is a cache table before truncating. Other
// cache_clear_all() operations throw a PDO error in this situation,
// so we don't need to verify them first. This ensures that non-cache
// tables cannot be truncated accidentally.
if ($this->isValidBin()) {
db_truncate($this->bin)->execute();
}
else {
throw new Exception(t('Invalid or missing cache bin specified: %bin', array('%bin' => $this->bin)));
}
}
else {
db_delete($this->bin)
......@@ -555,4 +556,25 @@ function isEmpty() {
->fetchField();
return empty($result);
}
/**
* Checks if $this->bin represents a valid cache table.
*
* This check is required to ensure that non-cache tables are not truncated
* accidentally when calling cache_clear_all().
*
* @return boolean
*/
function isValidBin() {
if ($this->bin == 'cache' || substr($this->bin, 0, 6) == 'cache_') {
// Skip schema check for bins with standard table names.
return TRUE;
}
// These fields are required for any cache table.
$fields = array('cid', 'data', 'expire', 'created', 'serialized');
// Load the table schema.
$schema = drupal_get_schema($this->bin);
// Confirm that all fields are present.
return isset($schema['fields']) && !array_diff($fields, array_keys($schema['fields']));
}
}
This diff is collapsed.
......@@ -28,18 +28,21 @@
* Most Drupal database SELECT queries are performed by a call to db_query() or
* db_query_range(). Module authors should also consider using the PagerDefault
* Extender for queries that return results that need to be presented on
* multiple pages, and the Tablesort Extender for generating appropriate queries
* for sortable tables.
* multiple pages (see https://drupal.org/node/508796), and the TableSort
* Extender for generating appropriate queries for sortable tables
* (see https://drupal.org/node/1848372).
*
* For example, one might wish to return a list of the most recent 10 nodes
* authored by a given user. Instead of directly issuing the SQL query
* @code
* SELECT n.nid, n.title, n.created FROM node n WHERE n.uid = $uid LIMIT 0, 10;
* SELECT n.nid, n.title, n.created FROM node n WHERE n.uid = $uid
* ORDER BY n.created DESC LIMIT 0, 10;
* @endcode
* one would instead call the Drupal functions:
* @code
* $result = db_query_range('SELECT n.nid, n.title, n.created
* FROM {node} n WHERE n.uid = :uid', 0, 10, array(':uid' => $uid));
* FROM {node} n WHERE n.uid = :uid
* ORDER BY n.created DESC', 0, 10, array(':uid' => $uid));
* foreach ($result as $record) {
* // Perform operations on $record->title, etc. here.
* }
......@@ -167,7 +170,7 @@
* }
* @endcode
*
* @link http://drupal.org/developing/api/database @endlink
* @see http://drupal.org/developing/api/database
*/
......@@ -179,7 +182,7 @@
* concrete implementation of it to support special handling required by that
* database.
*
* @see http://php.net/manual/en/book.pdo.php
* @see http://php.net/manual/book.pdo.php
*/
abstract class DatabaseConnection extends PDO {
......@@ -194,7 +197,7 @@ abstract class DatabaseConnection extends PDO {
/**
* The key representing this connection.
*
*
* The key is a unique string which identifies a database connection. A
* connection can be a single server or a cluster of master and slaves (use
* target to pick between master and slave).
......@@ -293,6 +296,20 @@ abstract class DatabaseConnection extends PDO {
*/
protected $prefixReplace = array();
/**
* List of escaped database, table, and field names, keyed by unescaped names.
*
* @var array
*/
protected $escapedNames = array();
/**
* List of escaped aliases names, keyed by unescaped aliases.
*
* @var array
*/
protected $escapedAliases = array();
function __construct($dsn, $username, $password, $driver_options = array()) {
// Initialize and prepare the connection prefix.
$this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : '');
......@@ -303,12 +320,28 @@ function __construct($dsn, $username, $password, $driver_options = array()) {
// Call PDO::__construct and PDO::setAttribute.
parent::__construct($dsn, $username, $password, $driver_options);
// Set a specific PDOStatement class if the driver requires that.
// Set a Statement class, unless the driver opted out.
if (!empty($this->statementClass)) {
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this)));
}
}
/**
* Destroys this Connection object.
*
* PHP does not destruct an object if it is still referenced in other
* variables. In case of PDO database connection objects, PHP only closes the
* connection when the PDO object is destructed, so any references to this
* object may cause the number of maximum allowed connections to be exceeded.
*/
public function destroy() {
// Destroy all references to this connection by setting them to NULL.
// The Statement class attribute only accepts a new value that presents a
// proper callable, so we reset it to PDOStatement.
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array()));
$this->schema = NULL;
}
/**
* Returns the default query options for any given query.
*
......@@ -607,7 +640,7 @@ public function makeComment($comments) {
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
return strtr($comment, array('*' => ' * '));
}
/**
......@@ -637,7 +670,7 @@ protected function filterComment($comment = '') {
* @return DatabaseStatementInterface
* This method will return one of: the executed statement, the number of
* rows affected by the query (not the number matched), or the generated
* insert IT of the last query, depending on the value of
* insert ID of the last query, depending on the value of
* $options['return']. Typically that value will be set by default or a
* query builder and should not be set by a user. If there is an error,
* this method will return NULL and may throw an exception if
......@@ -717,7 +750,7 @@ protected function expandArguments(&$query, &$args) {
// to expand it out into a comma-delimited set of placeholders.
foreach (array_filter($args, 'is_array') as $key => $data) {
$new_keys = array();
foreach ($data as $i => $value) {
foreach (array_values($data) as $i => $value) {
// This assumes that there are no other placeholders that use the same
// name. For example, if the array placeholder is defined as :example
// and there is already an :example_2 placeholder, this will generate
......@@ -900,11 +933,14 @@ public function schema() {
* For some database drivers, it may also wrap the table name in
* database-specific escape characters.
*
* @return
* @return string
* The sanitized table name string.
*/
public function escapeTable($table) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
if (!isset($this->escapedNames[$table])) {
$this->escapedNames[$table] = preg_replace('/[^A-Za-z0-9_.]+/', '', $table);
}
return $this->escapedNames[$table];
}
/**
......@@ -914,11 +950,14 @@ public function escapeTable($table) {
* For some database drivers, it may also wrap the field name in
* database-specific escape characters.
*
* @return
* @return string
* The sanitized field name string.
*/
public function escapeField($field) {
return preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
if (!isset($this->escapedNames[$field])) {
$this->escapedNames[$field] = preg_replace('/[^A-Za-z0-9_.]+/', '', $field);
}
return $this->escapedNames[$field];
}
/**
......@@ -929,11 +968,14 @@ public function escapeField($field) {
* DatabaseConnection::escapeTable(), this doesn't allow the period (".")
* because that is not allowed in aliases.
*
* @return
* @return string
* The sanitized field name string.
*/
public function escapeAlias($field) {
return preg_replace('/[^A-Za-z0-9_]+/', '', $field);
if (!isset($this->escapedAliases[$field])) {
$this->escapedAliases[$field] = preg_replace('/[^A-Za-z0-9_]+/', '', $field);
}
return $this->escapedAliases[$field];
}
/**
......@@ -1294,6 +1336,39 @@ public function commit() {
* also larger than the $existing_id if one was passed in.
*/
abstract public function nextId($existing_id = 0);
/**
* Checks whether utf8mb4 support is configurable in settings.php.
*
* @return bool
*/
public function utf8mb4IsConfigurable() {
// Since 4 byte UTF-8 is not supported by default, there is nothing to
// configure.
return FALSE;
}
/**
* Checks whether utf8mb4 support is currently active.
*
* @return bool
*/
public function utf8mb4IsActive() {
// Since 4 byte UTF-8 is not supported by default, there is nothing to
// activate.
return FALSE;
}
/**
* Checks whether utf8mb4 support is available on the current database system.
*
* @return bool
*/
public function utf8mb4IsSupported() {
// By default we assume that the database backend may not support 4 byte
// UTF-8.
return FALSE;
}
}
/**
......@@ -1627,8 +1702,8 @@ final public static function renameConnection($old_key, $new_key) {
*/
final public static function removeConnection($key) {
if (isset(self::$databaseInfo[$key])) {
self::closeConnection(NULL, $key);
unset(self::$databaseInfo[$key]);
unset(self::$connections[$key]);
return TRUE;
}
else {
......@@ -1694,11 +1769,24 @@ public static function closeConnection($target = NULL, $key = NULL) {
if (!isset($key)) {
$key = self::$activeKey;
}
// To close the connection, we need to unset the static variable.
// To close a connection, it needs to be set to NULL and removed from the
// static variable. In all cases, closeConnection() might be called for a
// connection that was not opened yet, in which case the key is not defined
// yet and we just ensure that the connection key is undefined.
if (isset($target)) {
if (isset(self::$connections[$key][$target])) {
self::$connections[$key][$target]->destroy();
self::$connections[$key][$target] = NULL;
}
unset(self::$connections[$key][$target]);
}
else {
if (isset(self::$connections[$key])) {
foreach (self::$connections[$key] as $target => $connection) {
self::$connections[$key][$target]->destroy();
self::$connections[$key][$target] = NULL;
}
}
unset(self::$connections[$key]);
}
}
......@@ -1852,8 +1940,8 @@ class DatabaseTransaction {
*/
protected $name;
public function __construct(DatabaseConnection &$connection, $name = NULL) {
$this->connection = &$connection;
public function __construct(DatabaseConnection $connection, $name = NULL) {
$this->connection = $connection;
// If there is no transaction depth, then no transaction has started. Name
// the transaction 'drupal_transaction'.
if (!$depth = $connection->transactionDepth()) {
......@@ -1957,7 +2045,7 @@ public function rowCount();
/**
* Sets the default fetch mode for this statement.
*
* See http://php.net/manual/en/pdo.constants.php for the definition of the
* See http://php.net/manual/pdo.constants.php for the definition of the
* constants used.
*
* @param $mode
......@@ -1976,7 +2064,7 @@ public function rowCount();
/**
* Fetches the next row from a result set.
*
* See http://php.net/manual/en/pdo.constants.php for the definition of the
* See http://php.net/manual/pdo.constants.php for the definition of the
* constants used.
*
* @param $mode
......@@ -2351,14 +2439,14 @@ function db_query_range($query, $from, $count, array $args = array(), array $opt
}
/**
* Executes a query string and saves the result set to a temporary table.
* Executes a SELECT query string and saves the result set to a temporary table.
*
* The execution of the query string happens against the active database.
*
* @param $query
* The prepared statement query to run. Although it will accept both named and
* unnamed placeholders, named placeholders are strongly preferred as they are
* more self-documenting.
* The prepared SELECT statement query to run. Although it will accept both
* named and unnamed placeholders, named placeholders are strongly preferred
* as they are more self-documenting.
* @param $args
* An array of values to substitute into the query. If the query uses named
* placeholders, this is an associative array in any order. If the query uses
......@@ -2800,7 +2888,7 @@ function db_drop_table($table) {
* 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
* (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
......@@ -2980,7 +3068,7 @@ function db_drop_index($table, $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
* (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.
*/
......
......@@ -13,11 +13,11 @@
class DatabaseConnection_mysql extends DatabaseConnection {
/**
* Flag to indicate if we have registered the nextID cleanup function.
* Flag to indicate if the cleanup function in __destruct() should run.
*
* @var boolean
*/
protected $shutdownRegistered = FALSE;
protected $needsCleanup = FALSE;
public function __construct(array $connection_options = array()) {
// This driver defaults to transaction support, except if explicitly passed FALSE.
......@@ -28,6 +28,12 @@ public function __construct(array $connection_options = array()) {
$this->connectionOptions = $connection_options;
$charset = 'utf8';
// Check if the charset is overridden to utf8mb4 in settings.php.
if ($this->utf8mb4IsActive()) {
$charset = 'utf8mb4';
}
// 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'];
......@@ -36,6 +42,10 @@ public function __construct(array $connection_options = array()) {
// Default to TCP connection on port 3306.
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
}
// Character set is added to dsn to ensure PDO uses the proper character
// set when escaping. This has security implications. See
// https://www.drupal.org/node/1201452 for further discussion.
$dsn .= ';charset=' . $charset;
$dsn .= ';dbname=' . $connection_options['database'];
// Allow PDO options to be overridden.
$connection_options += array(
......@@ -47,6 +57,11 @@ public function __construct(array $connection_options = array()) {
// Because MySQL's prepared statements skip the query cache, because it's dumb.
PDO::ATTR_EMULATE_PREPARES => TRUE,
);
if (defined('PDO::MYSQL_ATTR_MULTI_STATEMENTS')) {
// An added connection option in PHP 5.5.21+ to optionally limit SQL to a
// single statement like mysqli.
$connection_options['pdo'] += array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => FALSE);
}
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
......@@ -54,10 +69,10 @@ public function __construct(array $connection_options = array()) {
// 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']);
$this->exec('SET NAMES ' . $charset . ' COLLATE ' . $connection_options['collation']);
}
else {
$this->exec('SET NAMES utf8');
$this->exec('SET NAMES ' . $charset);
}
// Set MySQL init_commands if not already defined. Default Drupal's MySQL
......@@ -72,10 +87,18 @@ public function __construct(array $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'",
'sql_mode' => "SET sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,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']));
// Execute initial commands.
foreach ($connection_options['init_commands'] as $sql) {
$this->exec($sql);
}
}
public function __destruct() {
if ($this->needsCleanup) {
$this->nextIdDelete();
}
}
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
......@@ -84,7 +107,7 @@ public function queryRange($query, $from, $count, array $args = array(), array $
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);
$this->query('CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY ' . $query, $args, $options);
return $tablename;
}
......@@ -115,12 +138,7 @@ public function nextId($existing_id = 0) {
$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;
}
$this->needsCleanup = TRUE;
return $new_id;
}
......@@ -194,6 +212,42 @@ protected function popCommittableTransactions() {
}
}
}
public function utf8mb4IsConfigurable() {
return TRUE;
}
public function utf8mb4IsActive() {
return isset($this->connectionOptions['charset']) && $this->connectionOptions['charset'] === 'utf8mb4';
}
public function utf8mb4IsSupported() {
// Ensure that the MySQL driver supports utf8mb4 encoding.
$version = $this->getAttribute(PDO::ATTR_CLIENT_VERSION);
if (strpos($version, 'mysqlnd') !== FALSE) {
// The mysqlnd driver supports utf8mb4 starting at version 5.0.9.
$version = preg_replace('/^\D+([\d.]+).*/', '$1', $version);
if (version_compare($version, '5.0.9', '<')) {
return FALSE;
}
}
else {
// The libmysqlclient driver supports utf8mb4 starting at version 5.5.3.
if (version_compare($version, '5.5.3', '<')) {
return FALSE;
}
}
// Ensure that the MySQL server supports large prefixes and utf8mb4.
try {
$this->query("CREATE TABLE {drupal_utf8mb4_test} (id VARCHAR(255), PRIMARY KEY(id(255))) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ROW_FORMAT=DYNAMIC ENGINE=INNODB");
}
catch (Exception $e) {
return FALSE;
}
$this->query("DROP TABLE {drupal_utf8mb4_test}");
return TRUE;
}
}
......
......@@ -51,7 +51,8 @@ public function __toString() {
// 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;
$insert_fields_string = $insert_fields ? ' (' . implode(', ', $insert_fields) . ') ' : ' ';
return $comments . 'INSERT INTO {' . $this->table . '}' . $insert_fields_string . $this->fromQuery;
}
$query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES ';
......@@ -86,21 +87,7 @@ public function __toString() {
}
}
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();
}
}
}
class TruncateQuery_mysql extends TruncateQuery { }
/**
* @} End of "addtogroup database".
......
......@@ -39,8 +39,8 @@ protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) {
$info['table'] = substr($table, ++$pos);
}
else {
$db_info = Database::getConnectionInfo();
$info['database'] = $db_info['default']['database'];
$db_info = $this->connection->getConnectionOptions();
$info['database'] = $db_info['database'];
$info['table'] = $table;
}
return $info;
......@@ -81,7 +81,8 @@ protected function createTableSql($name, $table) {
// Provide defaults if needed.
$table += array(
'mysql_engine' => 'InnoDB',
'mysql_character_set' => 'utf8',
// Allow the default charset to be overridden in settings.php.
'mysql_character_set' => $this->connection->utf8mb4IsActive() ? 'utf8mb4' : 'utf8',
);
$sql = "CREATE TABLE {" . $name . "} (\n";
......@@ -109,6 +110,13 @@ protected function createTableSql($name, $table) {
$sql .= ' COLLATE ' . $info['collation'];
}
// The row format needs to be either DYNAMIC or COMPRESSED in order to allow
// for the innodb_large_prefix setting to take effect, see
// https://dev.mysql.com/doc/refman/5.6/en/create-table.html
if ($this->connection->utf8mb4IsActive()) {
$sql .= ' ROW_FORMAT=DYNAMIC';
}
// Add table comment.
if (!empty($table['description'])) {
$sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE);
......@@ -301,10 +309,10 @@ protected function createKeysSqlHelper($fields) {
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)));
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)));
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);
......@@ -322,10 +330,10 @@ public function dropTable($table) {
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)));
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)));
throw new DatabaseSchemaObjectExistsException(t("Cannot add field @table.@field: field already exists.", array('@field' => $field, '@table' => $table)));
}
$fixnull = FALSE;
......@@ -361,7 +369,7 @@ public function dropField($table, $field) {
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)));
throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
}
if (!isset($default)) {
......@@ -376,7 +384,7 @@ public function fieldSetDefault($table, $field, $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)));
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');
......@@ -391,10 +399,10 @@ public function indexExists($table, $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)));
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)));
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) . ')');
......@@ -411,10 +419,10 @@ public function dropPrimaryKey($table) {
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)));
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)));
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) . ')');
......@@ -431,10 +439,10 @@ public function dropUniqueKey($table, $name) {
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)));
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)));
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) . ')');
......@@ -451,10 +459,10 @@ public function dropIndex($table, $name) {
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)));
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)));
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));
......
This diff is collapsed.