updated core to 7.58 (right after the site was hacked)

This commit is contained in:
2018-04-20 23:48:40 +02:00
parent 18f4aba146
commit 9344a61b61
711 changed files with 99690 additions and 480 deletions

View File

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

View File

@@ -0,0 +1,12 @@
name = Access denied backtrace
description = Enable backtrace for Access Denied to get details about why was triggered
package = Development
core = 7.x
configure = admin/config/development/access-denied-backtrace/configure
; Information added by drupal.org packaging script on 2013-07-14
version = "7.x-1.6"
core = "7.x"
project = "adb"
datestamp = "1373817378"

View File

@@ -0,0 +1,83 @@
<?php
/**
* Implements hook_schema().
*/
function adb_schema() {
$schema['adb'] = array(
'description' => 'Table that contains logs of all system events.',
'fields' => array(
'adbid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique access deneid backtrace event ID.',
),
'uid' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'The {users}.uid of the user who triggered the event.',
),
'location' => array(
'type' => 'text',
'not null' => TRUE,
'description' => 'URL of the origin of the event.',
),
'node_access_denied' => array(
'type' => 'text',
'not null' => FALSE,
'size' => 'medium',
'description' => 'User permissions.',
),
'permissions' => array(
'type' => 'text',
'not null' => FALSE,
'size' => 'medium',
'description' => 'User permissions.',
),
'role_permissions' => array(
'type' => 'text',
'not null' => FALSE,
'size' => 'medium',
'description' => 'role permissions.',
),
'backtrace' => array(
'type' => 'text',
'not null' => TRUE,
'size' => 'big',
'description' => 'Text of backtrace log execution.',
),
'timestamp' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Unix timestamp of when event occurred.',
),
),
'primary key' => array('adbid'),
'indexes' => array(
'uid' => array('uid'),
),
);
return $schema;
}
/**
* Add field permissions in table adb.
*/
function adb_update_7150() {
db_add_field('adb', 'permissions', array('type' => 'text', 'not null' => FALSE, 'size' => 'medium'));
return t('Added permissions column to adb table.');
}
/**
* Add fields role_permissiones and node_access_denied in table adb.
*/
function adb_update_7160() {
db_add_field('adb', 'role_permissions', array('type' => 'text', 'not null' => FALSE, 'size' => 'medium'));
db_add_field('adb', 'node_access_denied', array('type' => 'text', 'not null' => FALSE, 'size' => 'medium'));
return t('Added role_permissions and node_access_denied columns to adb table.');
}

View File

@@ -0,0 +1,430 @@
<?php
define('ADB_DEVEL_MIN_TEXTAREA', 50);
/**
* Implements hook_menu().
*/
function adb_menu() {
$items['admin/reports/access-denied/backtrace'] = array(
'title' => "Last access denied errors backtrace",
'description' => "View 'access denied' errors backtrace (403s).",
'page callback' => 'adb_last',
'access arguments' => array('access site reports'),
);
$items['admin/reports/event/backtrace/%'] = array(
'title' => 'Details',
'page callback' => 'adb_event',
'page arguments' => array(4),
'access arguments' => array('access site reports'),
);
$items['admin/config/development/access-denied-backtrace/configure'] = array(
'title' => 'Access denied backtrace settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('adb_settings'),
'access arguments' => array('administer access denied backtrace'),
);
return $items;
}
/**
* Implements hook_permission().
*/
function adb_permission() {
return array(
'administer access denied backtrace' => array(
'title' => t('administer access denied backtrace'),
'description' => t('Change which roles are enabled to record access denied backtrace using the admin interface.'),
),
);
} // fontyourface_perm
/**
* Generate a adb settings form.
*
* @ingroup forms
* @see filter_admin_format_form_validate()
* @see filter_admin_format_form_submit()
*/
function adb_settings($form, &$form_state) {
// Add user role access selection.
$form['adb_roles'] = array(
'#type' => 'checkboxes',
'#title' => t('Roles'),
'#options' => array_map('check_plain', user_roles()),
'#default_value' => variable_get('adb_roles',array()),
'#description' => t('Configure what roles will be able to record access denied backtrace'),
);
return system_settings_form($form);
}
/**
* Menu callback; displays details about access denied backtrace log message.
*/
function adb_event($id) {
$severity = watchdog_severity_levels();
$result = db_query('SELECT adb.*, u.name, u.uid FROM {adb} adb INNER JOIN {users} u ON adb.uid = u.uid WHERE adb.adbid = :id', array(':id' => $id))->fetchObject();
if ($dblog = $result) {
$rows = array(
array(
array('data' => t('Date'), 'header' => TRUE),
format_date($dblog->timestamp, 'long'),
),
array(
array('data' => t('User'), 'header' => TRUE),
theme('username', array('account' => $dblog)),
),
array(
array('data' => t('Location'), 'header' => TRUE),
l($dblog->location, $dblog->location),
),
array(
array('data' => t('User permissions'), 'header' => TRUE),
$dblog->permissions,
),
array(
array('data' => t('User role permissions'), 'header' => TRUE),
$dblog->role_permissions,
),
array(
array('data' => t('Node access denied'), 'header' => TRUE),
$dblog->node_access_denied,
),
array(
array('data' => t('Backtrace'), 'header' => TRUE),
theme('adb_message', array('event' => $dblog)),
),
);
$build['dblog_table'] = array(
'#theme' => 'table',
'#rows' => $rows,
'#attributes' => array('class' => array('dblog-event')),
);
return $build;
}
else {
return '';
}
}
/**
* Implements hook_theme().
*/
function adb_theme() {
return array(
'adb_message' => array(
'variables' => array('event' => NULL),
),
);
}
/**
* Returns HTML for a log message.
*
* @param $variables
* An associative array containing:
* - event: An object with at least the message and variables properties.
* - link: (optional) Format message as link, event->wid is required.
*
* @ingroup themeable
*/
function theme_adb_message($variables) {
$event = $variables['event'];
$link = (isset($variables['link']))?$variables['link']:FALSE;
$output = $event->backtrace;
// Truncate message to 56 chars.
if($link) {
$output = truncate_utf8(filter_xss($output, array()), 56, TRUE, TRUE);
$output = l($output, 'admin/reports/event/backtrace/' . $event->adbid, array('html' => TRUE));
}
return $output;
}
/**
* Menu callback; generic function to display a page of the last access denied
* backtrace.
*
* Messages are not truncated because events from this page have no detail view.
*
*/
function adb_last() {
$rows = array();
$build['dblog_clear_log_form'] = drupal_get_form('adb_clear_log_form');
$header = array(
array('data' => t('Date'), 'field' => 'adb.adbid', 'sort' => 'desc'),
t('Backtrace'),
array('data' => t('User'), 'field' => 'u.name'),
);
$query = db_select('adb', 'adb')->extend('PagerDefault')->extend('TableSort');
$query->leftJoin('users', 'u', 'adb.uid = u.uid');
$query
->fields('adb', array('adbid', 'uid', 'timestamp', 'backtrace'))
->addField('u', 'name');
$result = $query
->limit(50)
->orderByHeader($header)
->execute();
foreach ($result as $dblog) {
$rows[] = array('data' =>
array(
// Cells
format_date($dblog->timestamp, 'short'),
theme('adb_message', array('event' => $dblog, 'link' => TRUE)),
theme('username', array('account' => $dblog)),
),
// Attributes for tr
'class' => array(drupal_html_class('adb')),
);
}
$build['dblog_table'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#attributes' => array('id' => 'admin-dblog'),
'#empty' => t('No access denied bractrace log available.'),
);
$build['dblog_pager'] = array('#theme' => 'pager');
return $build;
}
/**
* Invokes a hook in all enabled modules that implement it.
*
* @param $hook
* The name of the hook to invoke.
* @param ...
* Arguments to pass to the hook.
*
* @return
* An array of return values of the hook implementations. If modules return
* arrays from their implementations, those are merged into one array.
*/
function adb_validate_module_access($node, $op, $account) {
$args = array($node, $op, $account);
$hook = 'node_access';
$return = array();
foreach (module_implements($hook) as $module) {
$function = $module . '_' . $hook;
if (function_exists($function)) {
$result = call_user_func_array($function, $args);
if (isset($result) && is_array($result)) {
foreach ($result as $subaccess) {
if ($subaccess == NODE_ACCESS_DENY) {
$return[] = $module;
break;
}
}
$return = array_merge_recursive($return, $result);
} elseif (isset($result) && $result == NODE_ACCESS_DENY) {
$return[] = $module;
}
}
}
return t('Modules denying') . " " . implode(',', $return);
}
/**
* Implements hook_watchdog().
*
* If an 'access denied' error is logged and user role is enabled for debug, the
* execution trace is stored to try to fix what is wrong.
*/
function adb_watchdog($log_entry) {
$account = user_load($log_entry['uid']);
$adb_roles = array_filter(variable_get('adb_roles',array()));
$valid_role = array_intersect_key($adb_roles,$log_entry['user']->roles);
$account_rights = '';
$rights = drupal_static('node_access', array());
$permissions_denied = '';
if (isset($rights[$log_entry['uid']])) {
$account_rights = $rights[$log_entry['uid']];
foreach ($account_rights as $node => $rights) {
foreach ($rights as $action => $access) {
if (!$access) {
$permissions_denied .= $action . ' ' . $node . ". ";
$permissions_denied .= adb_validate_module_access($node, $action, $log_entry['user']) . ".<br/>";
}
}
}
}
else {
// Common message for annonymous users.
$permissions_denied = t('There are not explicit access denied for this user');
}
$user_access = drupal_static('user_access', array());
$role_permissions = user_role_permissions($account->roles);
//Validate if entry is access denied and current user belog to enabled role to
//record backtrace
if (!empty($valid_role) && $log_entry['type'] == 'access denied' ) {
$backtrace = _ddebug_backtrace(TRUE);
// Pop the stack up to the drupal_access_denied() call.
for ($i = 0; $i < 5; ++$i) {
array_shift($backtrace);
}
$user_role_permissions = (isset($role_permissions[$log_entry['uid']])?$role_permissions[$log_entry['uid']]:array());
$user_access_permissions = (isset($user_access[$log_entry['uid']]))?$user_access[$log_entry['uid']]:array();
/* print_r($user_access[$log_entry['uid']]);
print_r($user_role_permissions);*/
Database::getConnection('default', 'default')->insert('adb')
->fields(array(
'uid' => $log_entry['uid'],
'node_access_denied' => $permissions_denied,
'permissions' => _dprint_r($user_access_permissions, TRUE),
'role_permissions' => _dprint_r($user_role_permissions, TRUE),
'backtrace' => _dprint_r($backtrace, TRUE),
'location' => $log_entry['request_uri'],
'timestamp' => $log_entry['timestamp'],
))
->execute();
}
}
/**
* Return form for dblog clear button.
*
* @ingroup forms
* @see dblog_clear_log_submit()
*/
function adb_clear_log_form($form) {
$form['adb_clear'] = array(
'#type' => 'fieldset',
'#title' => t('Clear access denied backtraces'),
'#description' => t('This will permanently remove the access denied backtraces from the database.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['adb_clear']['clear'] = array(
'#type' => 'submit',
'#value' => t('Clear access denied backtraces'),
'#submit' => array('adb_clear_log_submit'),
);
return $form;
}
/**
* Submit callback: clear database with log messages.
*/
function adb_clear_log_submit() {
db_delete('adb')->execute();
drupal_set_message(t('Database access denied backtrace cleared.'));
}
/**
* Pretty-print a variable to the browser (no krumo).
* Displays only for users with proper permissions. If
* you want a string returned instead of a print, use the 2nd param.
* based in devel function dprint_r
*/
function _dprint_r($input, $return = FALSE, $name = NULL, $function = 'print_r', $check = TRUE) {
if ($name) {
$name .= ' => ';
}
if ($function == 'drupal_var_export') {
include_once DRUPAL_ROOT . '/includes/utility.inc';
$output = drupal_var_export($input);
} else {
ob_start();
$function($input);
$output = ob_get_clean();
}
if ($check) {
$output = check_plain($output);
}
if (count($input, COUNT_RECURSIVE) > ADB_DEVEL_MIN_TEXTAREA) {
// don't use fapi here because sometimes fapi will not be loaded
$printed_value = "<textarea rows=30 style=\"width: 100%;\">\n" . $name . $output . '</textarea>';
} else {
$printed_value = '<pre>' . $name . $output . '</pre>';
}
if ($return) {
return $printed_value;
} else {
print $printed_value;
}
}
/**
* Print the function call stack.
* copied from devel module but with access for all roles
*/
function _ddebug_backtrace($return = FALSE, $pop = 0) {
$backtrace = debug_backtrace();
while ($pop-- > 0) {
array_shift($backtrace);
}
$counter = count($backtrace);
$path = $backtrace[$counter - 1]['file'];
$path = substr($path, 0, strlen($path) - 10);
$paths[$path] = strlen($path) + 1;
$paths[DRUPAL_ROOT] = strlen(DRUPAL_ROOT) + 1;
$nbsp = "\xC2\xA0";
// Show message if error_level is ERROR_REPORTING_DISPLAY_SOME or higher.
// (This is Drupal's error_level, which is different from $error_level,
// and we purposely ignore the difference between _SOME and _ALL,
// see #970688!)
if (variable_get('error_level', 1) >= 1) {
while (!empty($backtrace)) {
$call = array();
if (isset($backtrace[0]['file'])) {
$call['file'] = $backtrace[0]['file'];
foreach ($paths as $path => $len) {
if (strpos($backtrace[0]['file'], $path) === 0) {
$call['file'] = substr($backtrace[0]['file'], $len);
}
}
$call['file'] .= ':' . $backtrace[0]['line'];
}
if (isset($backtrace[1])) {
if (isset($backtrace[1]['class'])) {
$function = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
} else {
$function = $backtrace[1]['function'] . '()';
}
$call['args'] = $backtrace[1]['args'];
} else {
$function = 'main()';
$call['args'] = $_GET;
}
$nicetrace[($counter <= 10 ? $nbsp : '') . --$counter . ': ' . $function] = $call;
array_shift($backtrace);
}
if ($return) {
return $nicetrace;
}
kprint_r($nicetrace);
}
}

View File

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

View File

@@ -0,0 +1,9 @@
This module provide admin interface for users/admins has the access to modules
page to reorder the module weights as they want.
INSTALLATION :
1. download the module and uncompresse it to sites/all/module and enable it
2. go to admin/config/system/modules-weight and reorder the modules weight :)
This module just display non-core module, that's because displaying core module in the configuration form will reorder the system core modules execution even if you didn't change them and as some might notice all core modules has 0 weight value by default.
Downloads

View File

@@ -0,0 +1,10 @@
name = Modules Weight
description = This module provide admin interface to order the modules execution order.
core = 7.x
configure = admin/config/system/modules-weight
; Information added by Drupal.org packaging script on 2015-07-31
version = "7.x-1.4+4-dev"
core = "7.x"
project = "modules_weight"
datestamp = "1438365840"

View File

@@ -0,0 +1,186 @@
<?php
/**
* @file
* Modules weight functionlity implementation.
*/
/**
* Implements hook_menu().
*/
function modules_weight_menu() {
$items = array();
$items['admin/config/system/modules-weight'] = array(
'title' => 'Modules Weight',
'description' => 'Provide admin interface to order the modules execution.',
'type' => MENU_NORMAL_ITEM,
'page callback' => 'drupal_get_form',
'page arguments' => array('modules_weight_admin_config_page_form'),
'access arguments' => array('administer site configuration'),
);
$items['admin/config/system/modules-weight/default'] = array(
'title' => 'Modules Weight',
'description' => 'jQuery twitter search block config',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 2,
);
$items['admin/config/system/modules-weight/configration'] = array(
'title' => 'Modules Weight configurations',
'description' => 'Configure Modules Weight.',
'page callback' => 'drupal_get_form',
'page arguments' => array('modules_weight_configuration_form'),
'access arguments' => array('administer system'),
'type' => MENU_LOCAL_TASK,
'weight' => 2,
);
return $items;
}
function modules_weight_admin_config_page_form($form, &$form_state) {
$form['modules_weight']['#tree'] = TRUE;
$result = db_select('system', 's')
->condition('s.type', 'module')
->condition('s.status', 1)
->fields('s', array('weight', 'info', 'name'))
->orderBy('weight', 'ASC')
->execute();
$show_system_module = variable_get('show_system_modules', 0);
foreach ($result as $module) {
$info = unserialize($module->info);
if ($info['package'] != 'Core' || $show_system_module) {
$delta = modules_weight_prepare_delta($module->weight);
$form['modules_weight'][$module->name] = array(
'name' => array(
'#markup' => t($info['name']),
),
'description' => array(
'#markup' => t($info['description']),
),
'weight' => array(
'#type' => 'weight',
'#title' => t('Weight'),
'#default_value' => $module->weight,
'#delta' => $delta,
'#title-display' => 'invisible',
),
'package' => array(
'#markup' => t($info['package']),
),
'old_weight_value' => array(
'#type' => 'hidden',
'#value' => $module->weight,
),
);
}
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save Changes'));
return $form;
}
/**
* Implements hook_theme().
*/
function modules_weight_theme() {
return array(
'modules_weight_admin_config_page_form' => array(
'render element' => 'form',
),
);
}
function theme_modules_weight_admin_config_page_form($variables) {
$form = $variables['form'];
$rows = array();
foreach (element_children($form['modules_weight']) as $id) {
$form['modules_weight'][$id]['weight']['#attributes']['class'] = array('module-weight');
$rows[] = array(
'data' => array(
drupal_render($form['modules_weight'][$id]['name']),
drupal_render($form['modules_weight'][$id]['description']),
drupal_render($form['modules_weight'][$id]['weight']),
drupal_render($form['modules_weight'][$id]['package']),
),
'class' => array('draggable'),
);
}
$header = array(t('Name'), t('Description'), t('Weight'), t('Package'));
$table_id = 'module-items-table';
$output = theme('table', array(
'header' => $header,
'rows' => $rows,
'attributes' => array('id' => $table_id),
));
$output .= drupal_render_children($form);
//Remove tabledrage functionlity due to issue related to re-weight all module
// for more info : https://www.drupal.org/node/2205787
//drupal_add_tabledrag($table_id, 'order', 'self', 'module-weight');
return $output;
}
/**
* Submit callback for the modules_weight_admin_config_page_form form.
*
* Updates the 'weight' column for each module in our table, taking into
* account that item's new order after the drag and drop actions have been
* performed.
*/
function modules_weight_admin_config_page_form_submit($form, $form_state) {
foreach ($form_state['values']['modules_weight'] as $name => $weight) {
if($weight['weight'] != $weight['old_weight_value']) {
db_query(
"UPDATE {system} SET weight = :weight WHERE name = :name",
array(':weight' => $weight['weight'], ':name' => $name)
);
}
}
}
function modules_weight_configuration_form() {
$form = array();
$form['show_system_modules'] = array(
'#type' => 'checkbox',
'#title' => t('Show system modules'),
'#return_value' => 1,
'#default_value' => variable_get('show_system_modules', 0),
);
$form['notice'] = array(
'#markup' => '<strong>' . t("cautions: This module just display non-core module by, if you check this option it will cause unexpected behavior in system, (USE IT ON YOUR OWN RISCK) that's because displaying core module in the configuration form will reorder the system core modules execution even if you didn't change them and as you might notice all core modules has 0 weight value by default.") . '',
);
return system_settings_form($form);
}
/**
* Prepares the delta for the weight field on the administration form.
* If a module has a weight higher then 100 (or lower than 100), it will use that
* value as delta and the '#weight' field will turn into a textfield most likely
*
* @param $weight
* @return int
*/
function modules_weight_prepare_delta($weight) {
$delta = 100;
if ((int) $weight > $delta) {
return (int) $weight;
}
if ((int) $weight < -100) {
return (int) $weight * -1;
}
return $delta;
}

View File

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

View File

@@ -0,0 +1,10 @@
name = Permission report
description = Calculates and displays the a permission report for a user and shows which roles grant what permission for a given user. Also provides ability to list users in a role, and dig down into complicated role and permission problems.
core = 7.x
package = Permissions
; Information added by Drupal.org packaging script on 2017-03-19
version = "7.x-1.1"
core = "7.x"
project = "permission_report"
datestamp = "1489907285"

View File

@@ -0,0 +1,342 @@
<?php
/**
* Drupal module to calculate the resultant set of permissions for a user.
*
* @author Nicholas Vahalik <nick@nickvahalik.com>
* @copyright Nicholas Vahalik 2013
* @package permission_report
**/
/**
* Implements hook_menu().
*/
function permission_report_menu() {
$items = array();
$items['admin/reports/permissions'] = array(
'title' => 'Permissions (role report)',
'page callback' => 'permission_report_role_list',
'access arguments' => array('view permission report'),
'description' => 'View roles and the users in them.',
);
$items['admin/reports/permissions/roles'] = array(
'title' => 'Roles',
'page callback' => 'permission_report_role_list',
'type' => MENU_DEFAULT_LOCAL_TASK,
'access arguments' => array('view permission report'),
'weight' => -5,
);
$items['admin/reports/permissions/perms'] = array(
'title' => 'Permissions',
'page callback' => 'permission_report_permission_list',
'type' => MENU_LOCAL_TASK,
'access arguments' => array('view permission report'),
);
$items['admin/reports/permissions/perms/%'] = array(
'title' => 'Permissions',
'type' => MENU_CALLBACK,
'page arguments' => array(4),
'page callback' => 'permission_report_user_having_perm',
'access arguments' => array('view permission report'),
);
$items['admin/reports/permissions/roles/%'] = array(
'title' => 'Roles',
'type' => MENU_CALLBACK,
'page callback' => 'permission_report_users_having_role',
'page arguments' => array(4),
'access arguments' => array('view permission report'),
);
$items['user/%user/permission_report'] = array(
'title' => 'Permission Report',
'page callback' => 'permission_report_user_report',
'page arguments' => array(1),
'type' => MENU_LOCAL_TASK,
'access arguments' => array('view permission report'),
'weight' => 2,
);
return $items;
}
/**
* Implements hook_admin_paths().
*/
function permission_report_admin_paths() {
$paths = array(
'user/*/permission_report' => TRUE,
);
return $paths;
}
/**
* Implements hook_user_view().
*/
function permission_report_user_view($account, $view_mode, $langcode) {
if (user_access('view permission report', $account)) {
$account->content['summary']['rsop'] = array(
'#type' => 'user_profile_item',
'#title' => t('Resultant Set of Permissions'),
'#markup' => l(t('View permission report report for !s', array('!s' => $account->name)), "user/$account->uid/permission_report"),
'#attributes' => array('class' => 'permission_report'),
);
}
}
/**
* Displays a permission report for a given user.
*
* @param $user User object.
*
* @return string
*/
function permission_report_user_report($user) {
// Render role/permission overview:
$options = array();
$row = array();
$can_admin_access = user_access('administer access control');
foreach (module_implements('permission') as $module) {
if ($permissions = module_invoke($module, 'permission')) {
$rows[] = array(array(
'data' => t('@module module', array('@module' => $module)),
'class' => 'module',
'id' => 'module-' . $module,
'colspan' => 3,
));
asort($permissions);
foreach ($permissions as $perm => $meta) {
$options = array();
$display_roles = array();
$roles = _permission_report_roles_having_perm($perm, $user);
if (array_key_exists('description', $meta)) {
$options = array('attributes' => array('alt' => $meta['description']));
}
foreach ($roles as $rid => $name) {
$display_roles[] = $can_admin_access ? l($name, "admin/reports/permissions/roles/$rid") : t($name);
}
$rows[] = array(
array('data' => l(strip_tags($meta['title']), "admin/reports/permissions/perms/$perm", $options)),
array('data' => user_access($perm, $user) ? 'Yes' : 'No'),
array('data' => implode(', ', $display_roles)),
);
}
}
}
return theme('table', array('header' => array('Permission', 'Access', 'Roles'), 'rows' => $rows, 'attributes' => array('id' => 'permissions')));
}
/**
* Return an array of users keyed by IDs that have access to a specific permission.
*
* @param $permission Permission string.
*
* @return array
**/
function _permission_report_users_having_perm($permission) {
$roles = _permission_report_roles_having_perm($permission);
if (count($roles) > 0) {
$query = db_select('users', 'u');
$query->innerJoin('users_roles', 'ur', 'ur.uid = u.uid');
$query->addField('u', 'uid');
$query->addField('u', 'name');
$query->condition('ur.rid', array_keys($roles), 'IN');
$users = $query->execute()->fetchAllKeyed();
return $users;
}
return array();
}
/**
* Gets a list of roles that have a permission, optionally limited
* to a specific role.
*/
function _permission_report_roles_having_perm($permission, $user = NULL) {
$query = db_select('role', 'r');
$query->addField('r', 'rid');
$query->addField('r', 'name');
$query->innerJoin('role_permission', 'p', 'r.rid = p.rid');
$query->condition('p.permission', $permission);
if ($user) {
$query->innerJoin('users_roles', 'ur', 'r.rid = ur.rid');
$query->condition('ur.uid', $user->uid);
}
return $query->execute()->fetchAllKeyed();
}
/**
* Generates a report of users having a particular role.
**/
function permission_report_users_having_role($rid) {
$users_having_roles = $rows = array();
$query = db_select('users', 'u');
$query->addField('u', 'uid');
$query->addField('u', 'name');
$query->innerJoin('users_roles', 'ur', 'ur.uid = u.uid');
$query->condition('ur.rid', $rid);
$query->condition('u.status', 1);
$users_having_role = $query->execute()->fetchAll();
$query = db_select('role', 'r');
$query->addField('r', 'name');
$query->condition('rid', $rid);
$role_name = $query->execute()->fetchField();
$view_users = user_access('access user profiles');
drupal_set_title(t('Users in "!name" role', array('!name' => $role_name)));
$users_header = array(array(
'data' => 'User',
'colspan' => 2,
));
foreach ($users_having_role as $user) {
$rows[] = array(
array('data' => ($user->uid !== 0) ? ($view_users ? l($user->name, "user/$user->uid") : $user->name) : variable_get('anonymous', t('Anonymous')))
,
array('data' => l('Permission report', "user/$user->uid/permission_report")),
);
}
return theme('table', array('header' => $users_header, 'rows' => $rows));
}
/**
* List of roles with the number of users in each role.
*/
function permission_report_role_list() {
$user_in_roles = db_query('SELECT r.rid rid, r.name name, COUNT(ur.uid) as user_count FROM {role} r INNER JOIN {users_roles} ur USING (rid) INNER JOIN {users} u USING(uid) WHERE u.status = :u_status GROUP BY r.rid ORDER BY r.name', array(':u_status' => 1))->fetchAllAssoc('rid');
$can_admin_access = user_access('administer access control');
$all_users = user_roles();
// Remove 'authenticated user' and 'anonymous user'.
unset($all_users[1], $all_users[2]);
$rows = array();
foreach ($all_users as $rid => $name) {
$count = (isset($user_in_roles[$rid])) ? $user_in_roles[$rid]->user_count : 0;
$rows[] = array(
$can_admin_access ? l($name, "admin/people/permissions/$rid") : t($name),
l(format_plural($count, '1 user', '@count users'), "admin/reports/permissions/roles/$rid"),
);
}
$roles_header = array(
array(
'data' => 'Role',
'colspan' => 2,
),
);
return theme('table', array('header' => $roles_header, 'rows' => $rows));
}
/**
* Creates a report showing which users have a specific permission.
*/
function permission_report_user_having_perm($permission) {
$view_users = user_access('access user profiles');
$can_admin_access = user_access('administer access control');
$output = '';
drupal_set_title(t('Permissions report for "!permission"', array('!permission' => $permission)));
foreach (_permission_report_users_having_perm($permission) as $uid => $name) {
$users_rows[] = array(
array('data' => ($uid !== 0) ? ($view_users ? l($name, "user/$uid") : $name) : variable_get('anonymous', t('Anonymous'))),
array('data' => l('Permission report', 'user/' . $uid . '/permission_report')),
);
}
$users_header = array(
array(
'data' => 'User',
'colspan' => 2,
),
);
foreach (_permission_report_roles_having_perm($permission) as $rid => $name) {
$roles_rows[] = array(
array('data' => $can_admin_access ? l($name, "admin/reports/permissions/roles/$rid") : t($name)),
array('data' => l('Permission report', "admin/reports/permissions/roles/$rid")),
);
}
$roles_header = array(
array(
'data' => 'Role',
'colspan' => 2,
),
);
$output .= '<h2 class="title"><a name="permission-report-users">Users</a></h2>';
$output .= theme('table', array('header' => $users_header, 'rows' => $users_rows));
$output .= '<h2 class="title"><a name="permission-report-roles">Roles</a></h2>';
$output .= theme('table', array('header' => $roles_header, 'rows' => $roles_rows));
return $output;
}
/**
* Creates a report which lists all permissions and the number of users which
* have those permissions.
*/
function permission_report_permission_list() {
$can_admin_access = user_access('administer access control');
foreach (module_implements('permission') as $module) {
if ($permissions = module_invoke($module, 'permission')) {
$rows[] = array(array(
'data' => t('@module module', array('@module' => $module)),
'class' => 'module',
'id' => 'module-' . $module,
'colspan' => 3,
));
asort($permissions);
foreach ($permissions as $perm => $meta) {
$display_roles = array();
$roles = _permission_report_roles_having_perm($perm);
foreach ($roles as $rid => $name) {
$display_roles[] = $can_admin_access ? l($name, "admin/reports/permissions/roles/$rid") : t($name);
}
$rows[] = array(
array('data' => $meta['title']),
array('data' => l(format_plural(count(_permission_report_users_having_perm($perm)), '1 user', '@count users'), "admin/reports/permissions/perms/$perm")),
);
}
}
}
return theme('table', array('header' => array('Permission', 'Users'), 'rows' => $rows, 'attributes' => array('id' => 'permissions', 'sticky' => TRUE)));
}
/**
* Implements hook_permission().
*/
function permission_report_permission() {
return array(
'view permission report' => array(
'title' => t('View permission report'),
'description' => t('Allows a user to view permission reports.'),
),
);
}

View File

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

View File

@@ -0,0 +1,62 @@
Examples for Developers
=======================
http://drupal.org/project/examples
What Is This?
-------------
This set of modules is intended to provide working examples of Drupal's
features and APIs. The modules strive to be simple, well documented and
modification friendly, in order to help developers quickly learn their inner
workings.
These examples are meant to teach you about code-level development for Drupal
7. Some solutions might be better served using a contributed module, so that
you don't end up having to re-invent the wheel in PHP.
How To Use The Examples
-----------------------
There are three main ways to interact with the examples in this project:
1. Enable the modules and use them within Drupal. Not all modules will have
obvious things to see within Drupal. For instance, while the Page and Form API
examples will show you forms, the Database API example will not show you much
within Drupal itself.
2. Read the code. Much effort has gone into making the example code readable,
not only in terms of the code itself, but also the extensive inline comments
and documentation blocks.
3. Browse the code and documentation on the web. There are two main places to
do this:
* https://api.drupal.org/api/examples is the main API site for all of Drupal.
It has all manner of cross-linked references between the example code and the
APIs being demonstrated.
* http://drupalcode.org/project/examples.git allows you to browse the git
repository for the Examples project.
How To Install The Modules
--------------------------
1. Install Examples for Developers (unpacking it to your Drupal
/sites/all/modules directory if you're installing by hand, for example).
2. Enable any Example modules in Admin menu > Site building > Modules.
3. Rebuild access permissions if you are prompted to.
4. Profit! The examples will appear in your Navigation menu (on the left
sidebar by default; you'll need to reenable it if you removed it).
Now you can read the code and its comments and see the result, experiment with
it, and hopefully quickly grasp how things work.
If you find a problem, incorrect comment, obsolete or improper code or such,
please search for an issue about it at http://drupal.org/project/issues/examples
If there isn't already an issue for it, please create a new one.

View File

@@ -0,0 +1,13 @@
name = Action example
description = Demonstrates providing actions that can be associated to triggers.
package = Example modules
core = 7.x
dependencies[] = trigger
files[] = action_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,375 @@
<?php
/**
* @file
* Action definition example module.
*/
/**
* @defgroup action_example Example: Action
* @ingroup examples
* @{
* Creating actions in Drupal 7
*
* Triggers and actions are a matched pair of Drupal features allowing some
* Drupal programming without using PHP. Using the appropriate action in a
* specific event, a site administrator can add new functionality.
*
* Examples are:
* - Send an email after a node is published or edited.
* - Display a message after a user has logged in.
* - Display a message and send an email after a node has been deleted.
*
* A trigger is a special function which can enqueue actions. The trigger module
* provides the interface allowing us to associate certain actions with certain
* triggers.
*
* Actions are the functions designed to be run by triggers.
*
* A trigger should build the appropriate context for the action to be fired.
* Actions are very often grouped by functionality: examples are 'user', 'node',
* 'taxonomy'. When actions are grouped it is because they expect the same
* arguments. This way, you can enqueue as many actions understanding the 'user'
* object as you want.
*
* Not all actions can be used in all triggers because they require different
* contexts. But some actions are generic enough that they do not require
* special objects in their contexts, and so can be used on every available
* trigger. This 'group' type is used by actions to be available for this
* trigger.
*
* What are good candidates to be triggers? Any function can be a trigger, as
* long as it has the code to call the enqueued actions, but to make Drupal
* more extensible, you will find hooks (from Drupal and contributed modules)
* very good candidates. A trigger should build the arguments, ask for enqueued
* actions and run them. You may define a function being a trigger, and run it
* through a button in the front page, or you may prepare a trigger for a hook,
* and everytime that hook is fired, your trigger will be.
*
* What are good candidates to be actions? any function is a possible action,
* the only problem is finding a trigger able to run it.
*
* This module describes how to create actions for Drupal. In this
* example we are providing three actions:
*
* - A generic action that can be used in any trigger, which is the most
* basic example of an action.
*
* - An action which which extends the capabilities of User triggers, even if
* associated with node or comment events.
*
* - An action which extends the capabilities of node triggers, but limited
* to certain events only, and using a customizable option.
*
* @link http://drupal.org/node/172152 Writing Actions in Drupal 6 @endlink
* @link http://drupal.org/node/199254 Triggers and Actions in Drupal 6 @endlink
*
* @see trigger_example
* @see hook_action_info()
*/
/**
* Implements hook_action_info().
*
* We call hook_action_info when we are defining the actions we provide.
* Actions are the actions fired by the associated triggers. In this example,
* we are registering our three new actions, providing the unique name (using
* Drupal's convention modulename_description_action), an easy to understand
* description of what the action does, the 'object' expected by this action
* (default options from core are node, user, comment and system, however other
* trigger modules may declare new object types), which are the triggers allowed
* to use these action, and if some customization is available. Please, note
* that the function name is not required to finish as _action to be declared as
* a Drupal action, and that only information provided by hook_trigger_info()
* will be considered for valid actions creation.
*
* These are the actions being provided in hook_action_info()
*
* - action_example_basic_action: this action is a dummy function which can be
* used by any trigger. The label describes that the action will do nothing,
* but is enough for a basic example. Type is set to system, so users will not
* be confused about the scope of this action (expecting a node, user, or any
* other object). This action is not configurable, and will appear as
* available in the list of action under the menu entry:
* 'admin/config/system/actions.
* - action_example_unblock_user_action: Unblocks a user.
* - action_example_node_sticky_action: This action is a complex action that is
* only available to Node type triggers, and can only be associated with the
* events node presave, node insert and node update. The action does not
* exist by default and it has to be created by user configuration. This makes
* it an "advanced action" in Drupal, so-called because it requires
* configuration or customization.
* In this example action, the action will promote nodes and make them sticky
* during presave, insert, or update, but only for particular users. As an
* advanced action, it first needs to be created in the actions management
* page (admin/config/system/actions). At the bottom of that page a selection
* list shows a list of advanced actions that will includes the option
* 'Promote to frontpage and sticky on top any content created by :'
* Selecting this option and clicking the 'Create' button, a configuration
* form will ask for an author name. When this action is associated to any
* of the possible Node trigger events, it will only be effective if the
* author of the content matches the author configured by the action.
*
* We return an associative array of action descriptions. The keys of the array
* are the names of the action functions, and each corresponding value
* is an associative array with the following key-value pairs:
*
* - 'type': The type of object this action acts upon. Core actions have types
* 'node', 'user', 'comment', and 'system', but additional types can be
* used, as long as the trigger and action agree on them.
* - 'label': The human-readable name of the action, which should be passed
* through the t() function for translation.
* - 'configurable': If FALSE, then the action doesn't require any extra
* configuration. If TRUE, then your module must define a form function with
* the same name as the action function with '_form' appended (e.g., the
* form for 'node_assign_owner_action' is 'node_assign_owner_action_form'.)
* This function takes $context as its only parameter, and is paired with
* the usual _submit function, and possibly an _validate function.
* - 'triggers': An array of the triggers that can trigger this
* action. For example: array('node_insert', 'user_update'). You can also
* declare support for any trigger by returning array('any') for this value.
* - 'behavior': (optional) A machine-readable array of behaviors of this
* action, used to signal additionally required actions that may need to be
* triggered. Currently recognized behaviors by Trigger module:
* - 'changes_property': If an action with this behavior is assigned to a
* trigger other than a "presave" hook, any save actions also assigned to
* this trigger are moved later in the list. If no save action is present,
* one will be added.
* Modules that are processing actions (like Trigger module) should take
* special care in the "presave" hook, in which case a dependent "save"
* action should NOT be invoked.
*
* @see hook_action_info()
*/
function action_example_action_info() {
return array(
'action_example_basic_action' => array(
'label' => t('Action Example: A basic example action that does nothing'),
'type' => 'system',
'configurable' => FALSE,
'triggers' => array('any'),
),
'action_example_unblock_user_action' => array(
'label' => t('Action Example: Unblock a user'),
'type' => 'user',
'configurable' => FALSE,
'triggers' => array('any'),
),
'action_example_node_sticky_action' => array(
'type' => 'node',
'label' => t('Action Example: Promote to frontpage and sticky on top any content created by :'),
'configurable' => TRUE,
'behavior' => array('changes_property'),
'triggers' => array('node_presave', 'node_insert', 'node_update'),
),
);
}
/**
* Implements hook_menu().
*
* Provides a menu entry which explains what the module does.
*/
function action_example_menu() {
$items['examples/action_example'] = array(
'title' => 'Action Example',
'description' => 'Provides a basic information page.',
'page callback' => '_action_example_page',
'access callback' => TRUE,
);
return $items;
}
/**
* A simple page to explain to the developer what to do.
*/
function _action_example_page() {
return t("The Action Example provides three example actions which can be configured on the <a href='@actions_url'>Actions configuration page</a> and assigned to triggers on the <a href='@triggers_url'>Triggers configuration page</a>.", array('@actions_url' => url('admin/config/system/actions'), '@triggers_url' => url('admin/structure/trigger/node')));
}
/**
* Action function for action_example_basic_action.
*
* This action is not expecting any type of entity object, and can be used with
* any trigger type or any event.
*
* @param object $entity
* An optional entity object.
* @param array $context
* Array with parameters for this action: depends on the trigger.
*
* @see action_example_action_info()
*/
function action_example_basic_action(&$entity, $context = array()) {
// In this case we are ignoring the entity and the context. This case of
// action is useful when your action does not depend on the context, and
// the function must do something regardless the scope of the trigger.
// Simply announces that the action was executed using a message.
drupal_set_message(t('action_example_basic_action fired'));
watchdog('action_example', 'action_example_basic_action fired.');
}
/**
* Action function for action_example_unblock_user_action.
*
* This action is expecting an entity object user, node or comment. If none of
* the above is provided (because it was not called from an user/node/comment
* trigger event), then the action will be taken on the current logged in user.
*
* Unblock an user. This action can be fired from different trigger types:
* - User trigger: this user will be unblocked.
* - Node/Comment trigger: the author of the node or comment will be unblocked.
* - Other: (including system or custom defined types), current user will be
* unblocked. (Yes, this seems like an incomprehensible use-case.)
*
* @param object $entity
* An optional user object (could be a user, or an author if context is
* node or comment)
* @param array $context
* Array with parameters for this action: depends on the trigger. The context
* is not used in this example.
*/
function action_example_unblock_user_action(&$entity, $context = array()) {
// First we check that entity is a user object. If this is the case, then this
// is a user-type trigger.
if (isset($entity->uid)) {
$uid = $entity->uid;
}
elseif (isset($context['uid'])) {
$uid = $context['uid'];
}
// If neither of those are valid, then block the current user.
else {
$uid = $GLOBALS['user']->uid;
}
$account = user_load($uid);
$account = user_save($account, array('status' => 1));
watchdog('action_example', 'Unblocked user %name.', array('%name' => $account->name));
drupal_set_message(t('Unblocked user %name', array('%name' => $account->name)));
}
/**
* Form function for action_example_node_sticky_action.
*
* Since we defined action_example_node_sticky_action as 'configurable' => TRUE,
* this action requires a configuration form to create/configure the action.
* In this circumstance, Drupal will attempt to call a function named by
* combining the action name (action_example_node_sticky_action) and _form, in
* this case yielding action_example_node_sticky_action_form.
*
* In Drupal, actions requiring creation and configuration are called 'advanced
* actions', because they must be customized to define their functionality.
*
* The 'action_example_node_sticky_action' allows creating rules to promote and
* set sticky content created by selected users on certain events. A form is
* used to configure which user is affected by this action, and this form
* includes the standard _validate and _submit hooks.
*/
/**
* Generates settings form for action_example_node_sticky_action().
*
* @param array $context
* An array of options of this action (in case it is being edited)
*
* @return array
* Settings form as Form API array.
*
* @see action_example_action_info()
*/
function action_example_node_sticky_action_form($context) {
/*
* We return a configuration form to set the requirements that will
* match this action before being executed. This is a regular Drupal form and
* may include any type of information you want, but all the fields of the
* form will be saved into the $context variable.
*
* In this case we are promoting all content types submitted by this user, but
* it is possible to extend these conditions providing more options in the
* settings form.
*/
$form['author'] = array(
'#title' => t('Author name'),
'#type' => 'textfield',
'#description' => t('Any content created, presaved or updated by this user will be promoted to front page and set as sticky.'),
'#default_value' => isset($context['author']) ? $context['author'] : '',
);
// Verify user permissions and provide an easier way to fill this field.
if (user_access('access user profiles')) {
$form['author']['#autocomplete_path'] = 'user/autocomplete';
}
// No more options, return the form.
return $form;
}
/**
* Validates settings form for action_example_node_sticky_action().
*
* Verifies that user exists before continuing.
*/
function action_example_node_sticky_action_validate($form, $form_state) {
if (!$account = user_load_by_name($form_state['values']['author'])) {
form_set_error('author', t('Please, provide a valid username'));
}
}
/**
* Submit handler for action_example_node_sticky_action.
*
* Returns an associative array of values which will be available in the
* $context when an action is executed.
*/
function action_example_node_sticky_action_submit($form, $form_state) {
return array('author' => $form_state['values']['author']);
}
/**
* Action function for action_example_node_sticky_action.
*
* Promote and set sticky flag. This is the special action that has been
* customized using the configuration form, validated with the validation
* function, and submitted with the submit function.
*
* @param object $node
* A node object provided by the associated trigger.
* @param array $context
* Array with the following elements:
* - 'author': username of the author's content this function will promote and
* set as sticky.
*/
function action_example_node_sticky_action($node, $context) {
if (function_exists('dsm')) {
dsm($node, 'action_example_node_sticky_action is firing. Here is the $node');
dsm($context, 'action_example_node_sticky_action is firing. Here is the $context');
}
// Get the user configured for this special action.
$account = user_load_by_name($context['author']);
// Is the node created by this user? then promote and set as sticky.
if ($account->uid == $node->uid) {
$node->promote = NODE_PROMOTED;
$node->sticky = NODE_STICKY;
watchdog('action',
'Set @type %title to sticky and promoted by special action for user %username.',
array(
'@type' => node_type_get_name($node),
'%title' => $node->title,
'%username' => $account->name,
)
);
drupal_set_message(
t('Set @type %title to sticky and promoted by special action for user %username.',
array(
'@type' => node_type_get_name($node),
'%title' => $node->title,
'%username' => $account->name,
)
)
);
}
}
/**
* @} End of "defgroup action_example".
*/

View File

@@ -0,0 +1,111 @@
<?php
/**
* @file
* test file for action_example module.
*/
/**
* Default test case for the action_example module.
*
* @ingroup action_example
*/
class ActionExampleTestCase extends TriggerWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Action example',
'description' => 'Perform various tests on action_example module.' ,
'group' => 'Examples',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp('trigger', 'action_example');
}
/**
* Test Action Example.
*
* 1. action_example_basic_action: Configure a action_example_basic_action to
* happen when user logs in.
* 2. action_example_unblock_user_action: When a user's profile is being
* viewed, unblock that user.
* 3. action_example_node_sticky_action: Create a user, configure that user
* to always be stickied using advanced configuration. Have the user
* create content; verify that it gets stickied.
*/
public function testActionExample() {
// Create an administrative user.
$admin_user = $this->drupalCreateUser(
array(
'administer actions',
'access comments',
'access content',
'post comments',
'skip comment approval',
'create article content',
'access user profiles',
'administer users',
)
);
$this->drupalLogin($admin_user);
// 1. Assign basic action; then logout and login user and see if it puts
// the message on the screen.
$hash = drupal_hash_base64('action_example_basic_action');
$edit = array('aid' => $hash);
$this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), 'trigger-user-login-assign-form');
$this->drupalLogout();
$this->drupalLogin($admin_user);
$this->assertText(t('action_example_basic_action fired'));
// 2. Unblock: When a user's profile is being viewed, unblock.
$normal_user = $this->drupalCreateUser();
// Create blocked user.
user_save($normal_user, array('status' => 0));
$normal_user = user_load($normal_user->uid, TRUE);
$this->assertFalse($normal_user->status, 'Normal user status has been set to blocked');
$hash = drupal_hash_base64('action_example_unblock_user_action');
$edit = array('aid' => $hash);
$this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), 'trigger-user-view-assign-form');
$this->drupalGet("user/$normal_user->uid");
$normal_user = user_load($normal_user->uid, TRUE);
$this->assertTrue($normal_user->status, 'Normal user status has been set to unblocked');
$this->assertRaw(t('Unblocked user %name', array('%name' => $normal_user->name)));
// 3. Create a user whose posts are always to be stickied.
$sticky_user = $this->drupalCreateUser(
array(
'access comments',
'access content',
'post comments',
'skip comment approval',
'create article content',
)
);
$action_label = $this->randomName();
$edit = array(
'actions_label' => $action_label,
'author' => $sticky_user->name,
);
$aid = $this->configureAdvancedAction('action_example_node_sticky_action', $edit);
$edit = array('aid' => drupal_hash_base64($aid));
$this->drupalPost('admin/structure/trigger/node', $edit, t('Assign'), array(), array(), 'trigger-node-insert-assign-form');
// Now create a node and verify that it gets stickied.
$this->drupalLogout();
$this->drupalLogin($sticky_user);
$node = $this->drupalCreateNode();
$this->assertTrue($node->sticky, 'Node was set to sticky on creation');
}
}

View File

@@ -0,0 +1,17 @@
/*
* @file
* CSS for ajax_example.
*
* See @link ajax_example_dependent_dropdown_degrades @endlink for
* details on what this file does. It is not used in any other example.
*/
/* Hides the next button when not degrading to non-javascript browser */
html.js .next-button {
display: none;
}
/* Makes the next/choose button align to the right of the select control */
.form-item-dropdown-first, .form-item-question-type-select {
display: inline-block;
}

View File

@@ -0,0 +1,12 @@
name = AJAX Example
description = An example module showing how to use Drupal AJAX forms
package = Example modules
core = 7.x
files[] = ajax_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,56 @@
<?php
/**
* @file
* AJAX Examples install file schema for ajax_example_form_node_form_alter()
*/
/**
* Implements hook_schema().
*/
function ajax_example_schema() {
$schema['ajax_example_node_form_alter'] = array(
'description' => 'Stores example settings for nodes.',
'fields' => array(
'nid' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The {node}.nid to store settings.',
),
'example_1' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Node Form Example 1 checkbox',
),
'example_2' => array(
'type' => 'varchar',
'length' => 256,
'not null' => FALSE,
'default' => '',
'description' => 'Node Form Example 2 textfield',
),
),
'primary key' => array('nid'),
'foreign keys' => array(
'dnv_node' => array(
'table' => 'node',
'columns' => array('nid' => 'nid'),
),
),
);
return $schema;
}
/**
* Add the new ajax_example_node_form_alter table.
*/
function ajax_example_update_7100() {
if (!db_table_exists('ajax_example_node_form_alter')) {
$schema = ajax_example_schema();
db_create_table('ajax_example_node_form_alter', $schema['ajax_example_node_form_alter']);
return st('Created table ajax_example_node_form_alter');
}
}

View File

@@ -0,0 +1,29 @@
/*
* @file
* JavaScript for ajax_example.
*
* See @link ajax_example_dependent_dropdown_degrades @endlink for
* details on what this file does. It is not used in any other example.
*/
(function($) {
// Re-enable form elements that are disabled for non-ajax situations.
Drupal.behaviors.enableFormItemsForAjaxForms = {
attach: function() {
// If ajax is enabled.
if (Drupal.ajax) {
$('.enabled-for-ajax').removeAttr('disabled');
}
// Below is only for the demo case of showing with js turned off.
// It overrides the behavior of the CSS that would normally turn off
// the 'ok' button when JS is enabled. Here, for demonstration purposes,
// we have AJAX disabled but JS turned on, so use this to simulate.
if (!Drupal.ajax) {
$('html.js .next-button').show();
}
}
};
})(jQuery);

View File

@@ -0,0 +1,693 @@
<?php
/**
* @file
* AJAX Examples module file with basic examples.
*/
/**
* @defgroup ajax_example Example: AJAX
* @ingroup examples
* @{
* These examples show basic AJAX concepts.
*
* General documentation is available at
* @link ajax AJAX Framework documentation @endlink and at the
* @link http://drupal.org/node/752056 AJAX Forms handbook page @endlink.
*
* The several examples here demonstrate basic AJAX usage.
*/
// The Node Form Alter example needs to be in another file.
module_load_include('inc', 'ajax_example', 'ajax_example_node_form_alter');
/**
* Implements hook_menu().
*
* Sets up calls to drupal_get_form() for all our example cases.
*
* @see menu_example.module
* @see menu_example_menu()
*/
function ajax_example_menu() {
$items = array();
$items['examples/ajax_example'] = array(
'title' => 'AJAX Example',
'page callback' => 'ajax_example_intro',
'access callback' => TRUE,
'expanded' => TRUE,
);
// Change the description of a form element.
$items['examples/ajax_example/simplest'] = array(
'title' => 'Simplest AJAX Example',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_simplest'),
'access callback' => TRUE,
'weight' => 0,
);
// Generate a changing number of checkboxes.
$items['examples/ajax_example/autocheckboxes'] = array(
'title' => 'Generate checkboxes',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_autocheckboxes'),
'access callback' => TRUE,
'weight' => 1,
);
// Generate different textfields based on form state.
$items['examples/ajax_example/autotextfields'] = array(
'title' => 'Generate textfields',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_autotextfields'),
'access callback' => TRUE,
'weight' => 2,
);
// Submit a form without a page reload.
$items['examples/ajax_example/submit_driven_ajax'] = array(
'title' => 'Submit-driven AJAX',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_submit_driven_ajax'),
'access callback' => TRUE,
'weight' => 3,
);
// Repopulate a dropdown based on form state.
$items['examples/ajax_example/dependent_dropdown'] = array(
'title' => 'Dependent dropdown',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_dependent_dropdown'),
'access callback' => TRUE,
'weight' => 4,
);
// Repopulate a dropdown, but this time with graceful degredation.
// See ajax_example_graceful_degradation.inc.
$items['examples/ajax_example/dependent_dropdown_degrades'] = array(
'title' => 'Dependent dropdown (with graceful degradation)',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_dependent_dropdown_degrades'),
'access callback' => TRUE,
'weight' => 5,
'file' => 'ajax_example_graceful_degradation.inc',
);
// The above example as it appears to users with no javascript.
$items['examples/ajax_example/dependent_dropdown_degrades_no_js'] = array(
'title' => 'Dependent dropdown with javascript off',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_dependent_dropdown_degrades', TRUE),
'access callback' => TRUE,
'file' => 'ajax_example_graceful_degradation.inc',
'weight' => 5,
);
// Populate a form section based on input in another element.
$items['examples/ajax_example/dynamic_sections'] = array(
'title' => 'Dynamic Sections (with graceful degradation)',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_dynamic_sections'),
'access callback' => TRUE,
'weight' => 6,
'file' => 'ajax_example_graceful_degradation.inc',
);
// The above example as it appears to users with no javascript.
$items['examples/ajax_example/dynamic_sections_no_js'] = array(
'title' => 'Dynamic Sections w/JS turned off',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_dynamic_sections', TRUE),
'access callback' => TRUE,
'weight' => 6,
'file' => 'ajax_example_graceful_degradation.inc',
);
// A classic multi-step wizard, but with no page reloads.
// See ajax_example_graceful_degradation.inc.
$items['examples/ajax_example/wizard'] = array(
'title' => 'Wizard (with graceful degradation)',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_wizard'),
'access callback' => TRUE,
'file' => 'ajax_example_graceful_degradation.inc',
'weight' => 7,
);
// The above example as it appears to users with no javascript.
$items['examples/ajax_example/wizard_no_js'] = array(
'title' => 'Wizard w/JS turned off',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_wizard', TRUE),
'access callback' => TRUE,
'file' => 'ajax_example_graceful_degradation.inc',
'weight' => 7,
);
// Add-more button that creates additional form elements.
// See ajax_example_graceful_degradation.inc.
$items['examples/ajax_example/add_more'] = array(
'title' => 'Add-more button (with graceful degradation)',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_add_more'),
'access callback' => TRUE,
'file' => 'ajax_example_graceful_degradation.inc',
'weight' => 8,
);
// The above example as it appears to users with no javascript.
$items['examples/ajax_example/add_more_no_js'] = array(
'title' => 'Add-more button w/JS turned off',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_add_more', TRUE),
'access callback' => TRUE,
'file' => 'ajax_example_graceful_degradation.inc',
'weight' => 8,
);
// Use the AJAX framework outside the context of a form using the use-ajax
// class. See ajax_example_misc.inc.
$items['examples/ajax_example/ajax_link'] = array(
'title' => 'Ajax Link ("use-ajax" class)',
'page callback' => 'ajax_example_render_link',
'access callback' => TRUE,
'file' => 'ajax_example_misc.inc',
'weight' => 9,
);
// Use the AJAX framework outside the context of a form using a renderable
// array of type link with the #ajax property. See ajax_example_misc.inc.
$items['examples/ajax_example/ajax_link_renderable'] = array(
'title' => 'Ajax Link (Renderable Array)',
'page callback' => 'ajax_example_render_link_ra',
'access callback' => TRUE,
'file' => 'ajax_example_misc.inc',
'weight' => 9,
);
// A menu callback is required when using ajax outside of the Form API.
$items['ajax_link_callback'] = array(
'page callback' => 'ajax_link_response',
'access callback' => 'user_access',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
'file' => 'ajax_example_misc.inc',
);
// Use AJAX framework commands outside of the #ajax form property.
// See ajax_example_advanced.inc.
$items['examples/ajax_example/advanced_commands'] = array(
'title' => 'AJAX framework commands',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_advanced_commands'),
'access callback' => TRUE,
'file' => 'ajax_example_advanced.inc',
'weight' => 100,
);
// Autocomplete examples.
$items['examples/ajax_example/simple_autocomplete'] = array(
'title' => 'Autocomplete (simple)',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_simple_autocomplete'),
'access arguments' => array('access user profiles'),
'file' => 'ajax_example_autocomplete.inc',
'weight' => 10,
);
$items['examples/ajax_example/simple_user_autocomplete_callback'] = array(
'page callback' => 'ajax_example_simple_user_autocomplete_callback',
'file' => 'ajax_example_autocomplete.inc',
'type' => MENU_CALLBACK,
'access arguments' => array('access user profiles'),
);
$items['examples/ajax_example/node_autocomplete'] = array(
'title' => 'Autocomplete (node with nid)',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_unique_autocomplete'),
'access arguments' => array('access content'),
'file' => 'ajax_example_autocomplete.inc',
'weight' => 11,
);
$items['examples/ajax_example/unique_node_autocomplete_callback'] = array(
'page callback' => 'ajax_example_unique_node_autocomplete_callback',
'file' => 'ajax_example_autocomplete.inc',
'type' => MENU_CALLBACK,
'access arguments' => array('access content'),
);
$items['examples/ajax_example/node_by_author'] = array(
'title' => 'Autocomplete (node limited by author)',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_node_by_author_autocomplete'),
'access callback' => TRUE,
'file' => 'ajax_example_autocomplete.inc',
'weight' => 12,
);
$items['examples/ajax_example/node_by_author_autocomplete'] = array(
'page callback' => 'ajax_example_node_by_author_node_autocomplete_callback',
'file' => 'ajax_example_autocomplete.inc',
'type' => MENU_CALLBACK,
'access arguments' => array('access content'),
);
// This is the landing page for the progress bar example. It uses
// drupal_get_form() in order to build the form.
$items['examples/ajax_example/progressbar'] = array(
'title' => 'Progress bar example',
'page callback' => 'drupal_get_form',
'page arguments' => array('ajax_example_progressbar_form'),
'access arguments' => array('access content'),
'file' => 'ajax_example_progressbar.inc',
);
// This is the callback route for the AJAX-based progress bar.
$items['examples/ajax_example/progressbar/progress/%'] = array(
'title' => 'Progress bar progress',
'page callback' => 'ajax_example_progressbar_progress',
'page arguments' => array(4),
'type' => MENU_CALLBACK,
'access arguments' => array('access content'),
'file' => 'ajax_example_progressbar.inc',
);
return $items;
}
/**
* A basic introduction page for the ajax_example module.
*/
function ajax_example_intro() {
$markup = t('The AJAX example module provides many examples of AJAX including forms, links, and AJAX commands.');
$list[] = l(t('Simplest AJAX Example'), 'examples/ajax_example/simplest');
$list[] = l(t('Generate checkboxes'), 'examples/ajax_example/autocheckboxes');
$list[] = l(t('Generate textfields'), 'examples/ajax_example/autotextfields');
$list[] = l(t('Submit-driven AJAX'), 'examples/ajax_example/submit_driven_ajax');
$list[] = l(t('Dependent dropdown'), 'examples/ajax_example/dependent_dropdown');
$list[] = l(t('Dependent dropdown (with graceful degradation)'), 'examples/ajax_example/dependent_dropdown_degrades');
$list[] = l(t('Dynamic Sections w/JS turned off'), 'examples/ajax_example/dependent_dropdown_degrades_no_js');
$list[] = l(t('Wizard (with graceful degradation)'), 'examples/ajax_example/wizard');
$list[] = l(t('Wizard w/JS turned off'), 'examples/ajax_example/wizard_no_js');
$list[] = l(t('Add-more button (with graceful degradation)'), 'examples/ajax_example/add_more');
$list[] = l(t('Add-more button w/JS turned off'), 'examples/ajax_example/add_more_no_js');
$list[] = l(t('Ajax Link ("use-ajax" class)'), 'examples/ajax_example/ajax_link');
$list[] = l(t('Ajax Link (Renderable Array)'), 'examples/ajax_example/ajax_link_renderable');
$list[] = l(t('AJAX framework commands'), 'examples/ajax_example/advanced_commands');
$list[] = l(t('Autocomplete (simple)'), 'examples/ajax_example/simple_autocomplete');
$list[] = l(t('Autocomplete (node with nid)'), 'examples/ajax_example/node_autocomplete');
$list[] = l(t('Autocomplete (node limited by author)'), 'examples/ajax_example/node_by_author');
$variables['items'] = $list;
$variables['type'] = 'ul';
$markup .= theme('item_list', $variables);
return $markup;
}
/**
* Basic AJAX callback example.
*
* Simple form whose ajax-enabled 'changethis' member causes a text change
* in the description of the 'replace_textfield' member.
*
* See @link http://drupal.org/node/262422 Form API Tutorial @endlink
*/
function ajax_example_simplest($form, &$form_state) {
$form = array();
$form['changethis'] = array(
'#title' => t("Choose something and explain why"),
'#type' => 'select',
'#options' => array(
'one' => 'one',
'two' => 'two',
'three' => 'three',
),
'#ajax' => array(
// #ajax has two required keys: callback and wrapper.
// 'callback' is a function that will be called when this element changes.
'callback' => 'ajax_example_simplest_callback',
// 'wrapper' is the HTML id of the page element that will be replaced.
'wrapper' => 'replace_textfield_div',
// There are also several optional keys - see ajax_example_autocheckboxes
// below for details on 'method', 'effect' and 'speed' and
// ajax_example_dependent_dropdown for 'event'.
),
);
// This entire form element will be replaced whenever 'changethis' is updated.
$form['replace_textfield'] = array(
'#type' => 'textfield',
'#title' => t("Why"),
// The prefix/suffix provide the div that we're replacing, named by
// #ajax['wrapper'] above.
'#prefix' => '<div id="replace_textfield_div">',
'#suffix' => '</div>',
);
// An AJAX request calls the form builder function for every change.
// We can change how we build the form based on $form_state.
if (!empty($form_state['values']['changethis'])) {
$form['replace_textfield']['#description'] = t("Say why you chose '@value'", array('@value' => $form_state['values']['changethis']));
}
return $form;
}
/**
* Callback for ajax_example_simplest.
*
* On an ajax submit, the form builder function is called again, then the $form
* and $form_state are passed to this callback function so it can select which
* portion of the form to send on to the client.
*
* @return array
* Renderable array (the textfield element)
*/
function ajax_example_simplest_callback($form, $form_state) {
// The form has already been submitted and updated. We can return the replaced
// item as it is.
return $form['replace_textfield'];
}
/**
* Form manipulation through AJAX.
*
* AJAX-enabled select element causes replacement of a set of checkboxes
* based on the selection.
*/
function ajax_example_autocheckboxes($form, &$form_state) {
// Since the form builder is called after every AJAX request, we rebuild
// the form based on $form_state.
$num_checkboxes = !empty($form_state['values']['howmany_select']) ? $form_state['values']['howmany_select'] : 1;
$form['howmany_select'] = array(
'#title' => t('How many checkboxes do you want?'),
'#type' => 'select',
'#options' => array(1 => 1, 2 => 2, 3 => 3, 4 => 4),
'#default_value' => $num_checkboxes,
'#ajax' => array(
'callback' => 'ajax_example_autocheckboxes_callback',
'wrapper' => 'checkboxes-div',
// 'method' defaults to replaceWith, but valid values also include
// append, prepend, before and after.
// 'method' => 'replaceWith',
// 'effect' defaults to none. Other valid values are 'fade' and 'slide'.
// See ajax_example_autotextfields for an example of 'fade'.
'effect' => 'slide',
// 'speed' defaults to 'slow'. You can also use 'fast'
// or a number of milliseconds for the animation to last.
// 'speed' => 'slow',
// Don't show any throbber...
'progress' => array('type' => 'none'),
),
);
$form['checkboxes_fieldset'] = array(
'#title' => t("Generated Checkboxes"),
// The prefix/suffix provide the div that we're replacing, named by
// #ajax['wrapper'] above.
'#prefix' => '<div id="checkboxes-div">',
'#suffix' => '</div>',
'#type' => 'fieldset',
'#description' => t('This is where we get automatically generated checkboxes'),
);
for ($i = 1; $i <= $num_checkboxes; $i++) {
$form['checkboxes_fieldset']["checkbox$i"] = array(
'#type' => 'checkbox',
'#title' => "Checkbox $i",
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Callback for autocheckboxes.
*
* Callback element needs only select the portion of the form to be updated.
* Since #ajax['callback'] return can be HTML or a renderable array (or an
* array of commands), we can just return a piece of the form.
* See @link ajax_example_advanced.inc AJAX Advanced Commands for more details
* on AJAX framework commands.
*
* @return array
* Renderable array (the checkboxes fieldset)
*/
function ajax_example_autocheckboxes_callback($form, $form_state) {
return $form['checkboxes_fieldset'];
}
/**
* Show/hide textfields based on AJAX-enabled checkbox clicks.
*/
function ajax_example_autotextfields($form, &$form_state) {
$form['ask_first_name'] = array(
'#type' => 'checkbox',
'#title' => t('Ask me my first name'),
'#ajax' => array(
'callback' => 'ajax_example_autotextfields_callback',
'wrapper' => 'textfields',
'effect' => 'fade',
),
);
$form['ask_last_name'] = array(
'#type' => 'checkbox',
'#title' => t('Ask me my last name'),
'#ajax' => array(
'callback' => 'ajax_example_autotextfields_callback',
'wrapper' => 'textfields',
'effect' => 'fade',
),
);
$form['textfields'] = array(
'#title' => t("Generated text fields for first and last name"),
'#prefix' => '<div id="textfields">',
'#suffix' => '</div>',
'#type' => 'fieldset',
'#description' => t('This is where we put automatically generated textfields'),
);
// Since checkboxes return TRUE or FALSE, we have to check that
// $form_state has been filled as well as what it contains.
if (!empty($form_state['values']['ask_first_name']) && $form_state['values']['ask_first_name']) {
$form['textfields']['first_name'] = array(
'#type' => 'textfield',
'#title' => t('First Name'),
);
}
if (!empty($form_state['values']['ask_last_name']) && $form_state['values']['ask_last_name']) {
$form['textfields']['last_name'] = array(
'#type' => 'textfield',
'#title' => t('Last Name'),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Click Me'),
);
return $form;
}
/**
* Callback for autotextfields.
*
* Selects the piece of the form we want to use as replacement text and returns
* it as a form (renderable array).
*
* @return array
* Renderable array (the textfields element)
*/
function ajax_example_autotextfields_callback($form, $form_state) {
return $form['textfields'];
}
/**
* A very basic form which with an AJAX-enabled submit.
*
* On submit, the markup in the #markup element is updated.
*/
function ajax_example_submit_driven_ajax($form, &$form_state) {
$form['box'] = array(
'#type' => 'markup',
'#prefix' => '<div id="box">',
'#suffix' => '</div>',
'#markup' => '<h1>Initial markup for box</h1>',
);
$form['submit'] = array(
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_example_submit_driven_callback',
'wrapper' => 'box',
),
'#value' => t('Submit'),
);
return $form;
}
/**
* Callback for submit_driven example.
*
* Select the 'box' element, change the markup in it, and return it as a
* renderable array.
*
* @return array
* Renderable array (the box element)
*/
function ajax_example_submit_driven_callback($form, $form_state) {
// In most cases, it is recommended that you put this logic in form generation
// rather than the callback. Submit driven forms are an exception, because
// you may not want to return the form at all.
$element = $form['box'];
$element['#markup'] = "Clicked submit ({$form_state['values']['op']}): " . date('c');
return $element;
}
/**
* AJAX-based dropdown example form.
*
* A form with a dropdown whose options are dependent on a
* choice made in a previous dropdown.
*
* On changing the first dropdown, the options in the second
* are updated.
*/
function ajax_example_dependent_dropdown($form, &$form_state) {
// Get the list of options to populate the first dropdown.
$options_first = _ajax_example_get_first_dropdown_options();
// If we have a value for the first dropdown from $form_state['values'] we use
// this both as the default value for the first dropdown and also as a
// parameter to pass to the function that retrieves the options for the
// second dropdown.
$selected = isset($form_state['values']['dropdown_first']) ? $form_state['values']['dropdown_first'] : key($options_first);
$form['dropdown_first'] = array(
'#type' => 'select',
'#title' => 'Instrument Type',
'#options' => $options_first,
'#default_value' => $selected,
// Bind an ajax callback to the change event (which is the default for the
// select form type) of the first dropdown. It will replace the second
// dropdown when rebuilt.
'#ajax' => array(
// When 'event' occurs, Drupal will perform an ajax request in the
// background. Usually the default value is sufficient (eg. change for
// select elements), but valid values include any jQuery event,
// most notably 'mousedown', 'blur', and 'submit'.
// 'event' => 'change',
'callback' => 'ajax_example_dependent_dropdown_callback',
'wrapper' => 'dropdown-second-replace',
),
);
$form['dropdown_second'] = array(
'#type' => 'select',
'#title' => $options_first[$selected] . ' ' . t('Instruments'),
// The entire enclosing div created here gets replaced when dropdown_first
// is changed.
'#prefix' => '<div id="dropdown-second-replace">',
'#suffix' => '</div>',
// When the form is rebuilt during ajax processing, the $selected variable
// will now have the new value and so the options will change.
'#options' => _ajax_example_get_second_dropdown_options($selected),
'#default_value' => isset($form_state['values']['dropdown_second']) ? $form_state['values']['dropdown_second'] : '',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Selects just the second dropdown to be returned for re-rendering.
*
* Since the controlling logic for populating the form is in the form builder
* function, all we do here is select the element and return it to be updated.
*
* @return array
* Renderable array (the second dropdown)
*/
function ajax_example_dependent_dropdown_callback($form, $form_state) {
return $form['dropdown_second'];
}
/**
* Helper function to populate the first dropdown.
*
* This would normally be pulling data from the database.
*
* @return array
* Dropdown options.
*/
function _ajax_example_get_first_dropdown_options() {
// drupal_map_assoc() just makes an array('String' => 'String'...).
return drupal_map_assoc(
array(
t('String'),
t('Woodwind'),
t('Brass'),
t('Percussion'),
)
);
}
/**
* Helper function to populate the second dropdown.
*
* This would normally be pulling data from the database.
*
* @param string $key
* This will determine which set of options is returned.
*
* @return array
* Dropdown options
*/
function _ajax_example_get_second_dropdown_options($key = '') {
$options = array(
t('String') => drupal_map_assoc(
array(
t('Violin'),
t('Viola'),
t('Cello'),
t('Double Bass'),
)
),
t('Woodwind') => drupal_map_assoc(
array(
t('Flute'),
t('Clarinet'),
t('Oboe'),
t('Bassoon'),
)
),
t('Brass') => drupal_map_assoc(
array(
t('Trumpet'),
t('Trombone'),
t('French Horn'),
t('Euphonium'),
)
),
t('Percussion') => drupal_map_assoc(
array(
t('Bass Drum'),
t('Timpani'),
t('Snare Drum'),
t('Tambourine'),
)
),
);
if (isset($options[$key])) {
return $options[$key];
}
else {
return array();
}
}
/**
* @} End of "defgroup ajax_example".
*/

View File

@@ -0,0 +1,75 @@
<?php
/**
* @file
* Test ajax example module.
*/
/**
* Functional tests for AJAX Example module.
*
* @ingroup ajax_example
*/
class AjaxExampleTestCase extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Ajax example',
'description' => 'Checks behavior of the Ajax Example',
'group' => 'Examples',
);
}
/**
* Enable module.
*/
public function setUp() {
parent::setUp('ajax_example');
}
/**
* Check the non-JS version of the "Dynamic Sections" example.
*/
public function testDynamicSectionsNoJs() {
// The path to the example form.
$path = 'examples/ajax_example/dynamic_sections_no_js';
// Confirmation text for right and wrong answers.
$wrong = t('Wrong answer. Try again. (Hint: The right answer is "George Washington".)');
$right = t('You got the right answer: George Washington');
// For each question style, choose some parameters.
$params = array(
t('Multiple Choice') => array(
'value' => t('Abraham Lincoln'),
'answer' => t('Abraham Lincoln'),
'response' => $wrong,
),
t('True/False') => array(
'value' => t('George Washington'),
'answer' => t('George Washington'),
'response' => $right,
),
t('Fill-in-the-blanks') => array(
'value' => NULL,
'answer' => t('George Washington'),
'response' => $right,
),
);
foreach ($params as $style => $q_and_a) {
// Submit the initial form.
$edit = array('question_type_select' => $style);
$this->drupalPost($path, $edit, t('Choose'));
$this->assertResponse(200, format_string('Question style "@style" selected.', array('@style' => $style)));
// For convenience, make variables out of the entries in $QandA.
extract($q_and_a);
// Check for the expected input field.
$this->assertFieldByName('question', $value);
// Now, submit the dynamically generated form.
$edit = array('question' => $answer);
$this->drupalPost(NULL, $edit, t('Submit your answer'));
$this->assertRaw($response, 'Dynamic form has been submitted.');
}
}
}

View File

@@ -0,0 +1,400 @@
<?php
/**
* @file
* AJAX Commands examples.
*
* This demonstrates each of the
* new AJAX commands. This is consolidated into a dense page because
* it's advanced material and because it would spread itself all over creation
* otherwise.
*/
/**
* Form to display the AJAX Commands.
*/
function ajax_example_advanced_commands($form, &$form_state) {
$form = array();
$form['intro'] = array(
'#type' => 'markup',
'#markup' => t("<div>Demonstrates how AJAX commands can be used.</div>"),
);
// Shows the 'after' command with a callback generating commands.
$form['after_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("This shows the Ajax 'after' command. Click to put something below the div that says 'Something can be inserted after this'"),
);
$form['after_command_example_fieldset']['after_command_example'] = array(
'#value' => t("AJAX 'After': Click to put something after the div"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_after_callback',
),
'#suffix' => "<div id='after_div'>Something can be inserted after this</div>
<div id='after_status'>'After' Command Status: Unknown</div>",
);
// Shows the 'alert' command.
$form['alert_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("Demonstrates the AJAX 'alert' command. Click the button."),
);
$form['alert_command_example_fieldset']['alert_command_example'] = array(
'#value' => t("AJAX 'Alert': Click to alert"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_alert_callback',
),
);
// Shows the 'append' command.
$form['append_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("This shows the Ajax 'append' command. Click to put something below the div that says 'Something can be inserted after this'"),
);
$form['append_command_example_fieldset']['append_command_example'] = array(
'#value' => t("AJAX 'Append': Click to append something"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_append_callback',
),
'#suffix' => "<div id='append_div'>Something can be appended inside this div... </div>
<div id='append_status'>'After' Command Status: Unknown</div>",
);
// Shows the 'before' command.
$form['before_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("This shows the Ajax 'before' command."),
);
$form['before_command_example_fieldset']['before_command_example'] = array(
'#value' => t("AJAX 'before': Click to put something before the div"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_before_callback',
),
'#suffix' => "<div id='before_div'>Something can be inserted before this</div>
<div id='before_status'>'before' Command Status: Unknown</div>",
);
// Shows the 'changed' command.
$form['changed_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("Demonstrates the AJAX 'changed' command. If region is 'changed', it is marked with CSS. This example also puts an asterisk by changed content."),
);
$form['changed_command_example_fieldset']['changed_command_example'] = array(
'#title' => t("AJAX changed: If checked, div is marked as changed."),
'#type' => 'checkbox',
'#default_value' => FALSE,
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_changed_callback',
),
'#suffix' => "<div id='changed_div'> <div id='changed_div_mark_this'>This div can be marked as changed or not.</div></div>
<div id='changed_status'>Status: Unknown</div>",
);
// Shows the AJAX 'css' command.
$form['css_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("Demonstrates the AJAX 'css' command."),
);
$form['css_command_example_fieldset']['css_command_example'] = array(
'#title' => t("AJAX CSS: Choose the color you'd like the '#box' div to be."),
'#type' => 'select',
// '#default_value' => 'green',
'#options' => array('green' => 'green', 'blue' => 'blue'),
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_css_callback',
),
'#suffix' => "<div id='css_div' style='height: 50px; width: 50px; border: 1px solid black'> box</div>
<div id='css_status'>Status: Unknown</div>",
);
// Shows the AJAX 'data' command. But there is no use of this information,
// as this would require a javascript client to use the data.
$form['data_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("Demonstrates the AJAX 'data' command."),
);
$form['data_command_example_fieldset']['data_command_example'] = array(
'#title' => t("AJAX data: Set a key/value pair on a selector."),
'#type' => 'textfield',
'#default_value' => 'color=green',
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_data_callback',
),
'#suffix' => "<div id='data_div'>This div should have key='time'/value='a time string' attached.</div>
<div id='data_status'>Status: Unknown</div>",
);
// Shows the AJAX 'html' command.
$form['html_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("Demonstrates the AJAX 'html' command."),
);
$form['html_command_example_fieldset']['html_command_example'] = array(
'#title' => t("AJAX html: Replace the HTML in a selector."),
'#type' => 'textfield',
'#default_value' => 'new value',
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_html_callback',
),
'#suffix' => "<div id='html_div'>Original contents</div>
<div id='html_status'>Status: Unknown</div>",
);
// Shows the AJAX 'prepend' command.
$form['prepend_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("This shows the AJAX 'prepend' command. Click to put something below the div that says 'Something can be inserted after this'"),
);
$form['prepend_command_example_fieldset']['prepend_command_example'] = array(
'#value' => t("AJAX 'prepend': Click to prepend something"),
'#type' => 'submit',
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_prepend_callback',
),
'#suffix' => "<div id='prepend_div'>Something can be prepended to this div... </div>
<div id='prepend_status'>'After' Command Status: Unknown</div>",
);
// Shows the AJAX 'remove' command.
$form['remove_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("Shows the Ajax 'remove' command."),
);
$form['remove_command_example_fieldset']['remove_command_example'] = array(
'#title' => t("AJAX 'remove': Check to remove text. Uncheck to add it back."),
'#type' => 'checkbox',
'#default_value' => FALSE,
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_remove_callback',
),
'#suffix' => "<div id='remove_div'><div id='remove_text'>text to be removed</div></div>
<div id='remove_status'>'After' Command Status: Unknown</div>",
);
// Show off the AJAX 'restripe' command. Also shows classic AJAX replacement
// on the "how many rows" processing.
$form['restripe_command_example_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t("Demonstrates the Ajax 'restripe' command."),
);
$form['restripe_command_example_fieldset']['restripe_num_rows'] = array(
'#type' => 'select',
'#default_value' => !empty($form_state['values']['restripe_num_rows']) ? $form_state['values']['restripe_num_rows'] : 1,
'#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_restripe_num_rows',
'method' => 'replace',
'wrapper' => 'restripe_table',
),
);
$form['restripe_command_example_fieldset']['restripe_restripe'] = array(
'#type' => 'submit',
'#value' => t("Restripe the table"),
'#ajax' => array(
'callback' => 'ajax_example_advanced_commands_restripe_callback',
),
'#suffix' => "<div id='restripe_div'>
<table id='restripe_table' style='border: 1px solid black' >
<tr id='table-first'><td>first row</td></tr>
</table>
</div>
<div id='restripe_status'>'Restripe' Command Status: Unknown</div>",
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Callback for 'after'.
*
* @see ajax_command_after()
*/
function ajax_example_advanced_commands_after_callback($form, $form_state) {
$selector = '#after_div';
$commands = array();
$commands[] = ajax_command_after($selector, "New 'after'...");
$commands[] = ajax_command_replace("#after_status", "<div id='after_status'>Updated after_command_example " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'alert'.
*
* @see ajax_command_alert()
*/
function ajax_example_advanced_commands_alert_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_alert("Alert requested at " . date('r'));
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'append'.
*
* @see ajax_command_append()
*/
function ajax_example_advanced_commands_append_callback($form, $form_state) {
$selector = '#append_div';
$commands = array();
$commands[] = ajax_command_append($selector, "Stuff...");
$commands[] = ajax_command_replace("#append_status", "<div id='append_status'>Updated append_command_example " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'before'.
*
* @see ajax_command_before()
*/
function ajax_example_advanced_commands_before_callback($form, $form_state) {
$selector = '#before_div';
$commands = array();
$commands[] = ajax_command_before($selector, "New 'before'...");
$commands[] = ajax_command_replace("#before_status", "<div id='before_status'>Updated before_command_example " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'changed'.
*
* @see ajax_command_changed()
*/
function ajax_example_advanced_commands_changed_callback($form, $form_state) {
$checkbox_value = $form_state['values']['changed_command_example'];
$checkbox_value_string = $checkbox_value ? "TRUE" : "FALSE";
$commands = array();
if ($checkbox_value) {
$commands[] = ajax_command_changed('#changed_div', '#changed_div_mark_this');
}
else {
$commands[] = ajax_command_replace('#changed_div', "<div id='changed_div'> <div id='changed_div_mark_this'>This div can be marked as changed or not.</div></div>");
}
$commands[] = ajax_command_replace("#changed_status", "<div id='changed_status'>Updated changed_command_example to $checkbox_value_string: " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'css'.
*
* @see ajax_command_css()
*/
function ajax_example_advanced_commands_css_callback($form, $form_state) {
$selector = '#css_div';
$color = $form_state['values']['css_command_example'];
$commands = array();
$commands[] = ajax_command_css($selector, array('background-color' => $color));
$commands[] = ajax_command_replace("#css_status", "<div id='css_status'>Updated css_command_example to '{$color}' " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'data'.
*
* @see ajax_command_data()
*/
function ajax_example_advanced_commands_data_callback($form, $form_state) {
$selector = '#data_div';
$text = $form_state['values']['data_command_example'];
list($key, $value) = preg_split('/=/', $text);
$commands = array();
$commands[] = ajax_command_data($selector, $key, $value);
$commands[] = ajax_command_replace("#data_status", "<div id='data_status'>Updated data_command_example with key=$key, value=$value; " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'html'.
*
* @see ajax_command_html()
*/
function ajax_example_advanced_commands_html_callback($form, $form_state) {
$text = $form_state['values']['html_command_example'];
$commands = array();
$commands[] = ajax_command_html('#html_div', $text);
$commands[] = ajax_command_replace("#html_status", "<div id='html_status'>Updated html_command_example with text=$text; " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'prepend'.
*
* @see ajax_command_prepend()
*/
function ajax_example_advanced_commands_prepend_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_prepend('#prepend_div', "Prepended Stuff...");
$commands[] = ajax_command_replace("#prepend_status", "<div id='prepend_status'>Updated prepend_command_example " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'remove'.
*
* @see ajax_command_remove()
*/
function ajax_example_advanced_commands_remove_callback($form, $form_state) {
$commands = array();
$should_remove = $form_state['values']['remove_command_example'];
$should_remove_string = $should_remove ? 'TRUE' : 'FALSE';
if ($should_remove) {
$commands[] = ajax_command_remove('#remove_text');
}
else {
$commands[] = ajax_command_html('#remove_div', "<div id='remove_text'>text to be removed</div>");
}
$commands[] = ajax_command_replace("#remove_status", "<div id='remove_status'>Updated remove_command_example (value={$should_remove_string} " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}
/**
* Callback for 'restripe'.
*
* Rebuilds the table with the selected number of rows.
*/
function ajax_example_advanced_commands_restripe_num_rows($form, $form_state) {
$num_rows = $form_state['values']['restripe_num_rows'];
$output = "<table id='restripe_table' style='border: 1px solid black'>";
for ($i = 1; $i <= $num_rows; $i++) {
$output .= "<tr><td>Row $i</td></tr>";
}
$output .= "</table>";
return $output;
}
/**
* Callback for 'restripe'.
*
* @see ajax_command_restripe()
*/
function ajax_example_advanced_commands_restripe_callback($form, $form_state) {
$commands = array();
$commands[] = ajax_command_restripe('#restripe_table');
$commands[] = ajax_command_replace("#restripe_status", "<div id='restripe_status'>Restriped table " . date('r') . "</div>");
return array('#type' => 'ajax', '#commands' => $commands);
}

View File

@@ -0,0 +1,458 @@
<?php
/**
* @file
* ajax_example_autocomplete.inc
*
* Demonstrates usage of the Form API's autocomplete features.
*
* This file provides three examples in increasing complexity:
* - A simple username autocomplete (usernames are unique, so little effort is
* required)
* - A node title autocomplete (titles are not unique, so we have to find the
* nid and stash it in the field)
* - A username autocomplete that updates a node title autocomplete with a
* changed #autocomplete_path so that the #autocomplete_path can have
* context (the username to use in the search).
*/
/**
* A simple autocomplete form which just looks up usernames in the user table.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form.
*
* @return array
* Form array.
*/
function ajax_example_simple_autocomplete($form, &$form_state) {
$form['info'] = array(
'#markup' => '<div>' . t("This example does a simplest possible autocomplete by username. You'll need a few users on your system for it to make sense.") . '</div>',
);
$form['user'] = array(
'#type' => 'textfield',
'#title' => t('Choose a user (or a people, depending on your usage preference)'),
// The autocomplete path is provided in hook_menu in ajax_example.module.
'#autocomplete_path' => 'examples/ajax_example/simple_user_autocomplete_callback',
);
return $form;
}
/**
* This is just a copy of user_autocomplete().
*
* It works simply by searching usernames (and of course in Drupal usernames
* are unique, so can be used for identifying a record.)
*
* The returned $matches array has
* * key: string which will be displayed once the autocomplete is selected
* * value: the value which will is displayed in the autocomplete pulldown.
*
* In the simplest cases (see user_autocomplete()) these are the same, and
* nothing needs to be done. However, more more complicated autocompletes
* require more work. Here we demonstrate the difference by displaying the UID
* along with the username in the dropdown.
*
* In the end, though, we'll be doing something with the value that ends up in
* the textfield, so it needs to uniquely identify the record we want to access.
* This is demonstrated in ajax_example_unique_autocomplete().
*
* @param string $string
* The string that will be searched.
*/
function ajax_example_simple_user_autocomplete_callback($string = "") {
$matches = array();
if ($string) {
$result = db_select('users')
->fields('users', array('name', 'uid'))
->condition('name', db_like($string) . '%', 'LIKE')
->range(0, 10)
->execute();
foreach ($result as $user) {
// In the simplest case (see user_autocomplete), the key and the value are
// the same. Here we'll display the uid along with the username in the
// dropdown.
$matches[$user->name] = check_plain($user->name) . " (uid=$user->uid)";
}
}
drupal_json_output($matches);
}
/**
* An autocomplete form to look up nodes by title.
*
* An autocomplete form which looks up nodes by title in the node table,
* but must keep track of the nid, because titles are certainly not guaranteed
* to be unique.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form state.
*
* * @return array
* Form array.
*/
function ajax_example_unique_autocomplete($form, &$form_state) {
$form['info'] = array(
'#markup' => '<div>' . t("This example does a node autocomplete by title. The difference between this and a username autocomplete is that the node title may not be unique, so we have to use the nid for uniqueness, placing it in a parseable location in the textfield.") . '</div>',
);
$form['node'] = array(
'#type' => 'textfield',
'#title' => t('Choose a node by title'),
// The autocomplete path is provided in hook_menu in ajax_example.module.
'#autocomplete_path' => 'examples/ajax_example/unique_node_autocomplete_callback',
);
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Node title validation handler.
*
* Validate handler to convert our string like "Some node title [3325]" into a
* nid.
*
* In case the user did not actually use the autocomplete or have a valid string
* there, we'll try to look up a result anyway giving it our best guess.
*
* Since the user chose a unique node, we must now use the same one in our
* submit handler, which means we need to look in the string for the nid.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form state.
*/
function ajax_example_unique_autocomplete_validate($form, &$form_state) {
$title = $form_state['values']['node'];
$matches = array();
// This preg_match() looks for the last pattern like [33334] and if found
// extracts the numeric portion.
$result = preg_match('/\[([0-9]+)\]$/', $title, $matches);
if ($result > 0) {
// If $result is nonzero, we found a match and can use it as the index into
// $matches.
$nid = $matches[$result];
// Verify that it's a valid nid.
$node = node_load($nid);
if (empty($node)) {
form_error($form['node'], t('Sorry, no node with nid %nid can be found', array('%nid' => $nid)));
return;
}
}
// BUT: Not everybody will have javascript turned on, or they might hit ESC
// and not use the autocomplete values offered. In that case, we can attempt
// to come up with a useful value. This is not absolutely necessary, and we
// *could* just emit a form_error() as below.
else {
$nid = db_select('node')
->fields('node', array('nid'))
->condition('title', db_like($title) . '%', 'LIKE')
->range(0, 1)
->execute()
->fetchField();
}
// Now, if we somehow found a nid, assign it to the node. If we failed, emit
// an error.
if (!empty($nid)) {
$form_state['values']['node'] = $nid;
}
else {
form_error($form['node'], t('Sorry, no node starting with %title can be found', array('%title' => $title)));
}
}
/**
* Submit handler for node lookup unique autocomplete example.
*
* Here the nid has already been placed in $form_state['values']['node'] by the
* validation handler.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form state.
*/
function ajax_example_unique_autocomplete_submit($form, &$form_state) {
$node = node_load($form_state['values']['node']);
drupal_set_message(t('You found node %nid with title %title', array('%nid' => $node->nid, '%title' => $node->title)));
}
/**
* Autocomplete callback for nodes by title.
*
* Searches for a node by title, but then identifies it by nid, so the actual
* returned value can be used later by the form.
*
* The returned $matches array has
* - key: The title, with the identifying nid in brackets, like "Some node
* title [3325]"
* - value: the title which will is displayed in the autocomplete pulldown.
*
* Note that we must use a key style that can be parsed successfully and
* unambiguously. For example, if we might have node titles that could have
* [3325] in them, then we'd have to use a more restrictive token.
*
* @param string $string
* The string that will be searched.
*/
function ajax_example_unique_node_autocomplete_callback($string = "") {
$matches = array();
if ($string) {
$result = db_select('node')
->fields('node', array('nid', 'title'))
->condition('title', db_like($string) . '%', 'LIKE')
->range(0, 10)
->execute();
foreach ($result as $node) {
$matches[$node->title . " [$node->nid]"] = check_plain($node->title);
}
}
drupal_json_output($matches);
}
/**
* Search by title and author.
*
* In this example, we'll look up nodes by title, but we want only nodes that
* have been authored by a particular user. That means that we'll have to make
* an autocomplete function which takes a username as an argument, and use
* #ajax to change the #autocomplete_path based on the selected user.
*
* Although the implementation of the validate handler may look complex, it's
* just ambitious. The idea here is:
* 1. Autcomplete to get a valid username.
* 2. Use #ajax to update the node element with a #autocomplete_callback that
* gives the context for the username.
* 3. Do an autcomplete on the node field that is limited by the username.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form state.
*
* @return array
* Form API array.
*/
function ajax_example_node_by_author_autocomplete($form, &$form_state) {
$form['intro'] = array(
'#markup' => '<div>' . t("This example uses a user autocomplete to dynamically change a node title autocomplete using #ajax.
This is a way to get past the fact that we have no other way to provide context to the autocomplete function.
It won't work very well unless you have a few users who have created some content that you can search for.") . '</div>',
);
$form['author'] = array(
'#type' => 'textfield',
'#title' => t('Choose the username that authored nodes you are interested in'),
// Since we just need simple user lookup, we can use the simplest function
// of them all, user_autocomplete().
'#autocomplete_path' => 'user/autocomplete',
'#ajax' => array(
'callback' => 'ajax_example_node_by_author_ajax_callback',
'wrapper' => 'autocomplete-by-node-ajax-replace',
),
);
// This form element with autocomplete will be replaced by #ajax whenever the
// author changes, allowing the search to be limited by user.
$form['node'] = array(
'#type' => 'textfield',
'#title' => t('Choose a node by title'),
'#prefix' => '<div id="autocomplete-by-node-ajax-replace">',
'#suffix' => '</div>',
'#disabled' => TRUE,
);
// When the author changes in the author field, we'll change the
// autocomplete_path to match.
if (!empty($form_state['values']['author'])) {
$author = user_load_by_name($form_state['values']['author']);
if (!empty($author)) {
$autocomplete_path = 'examples/ajax_example/node_by_author_autocomplete/' . $author->uid;
$form['node']['#autocomplete_path'] = $autocomplete_path;
$form['node']['#title'] = t('Choose a node title authored by %author', array('%author' => $author->name));
$form['node']['#disabled'] = FALSE;
}
}
$form['actions'] = array(
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* AJAX callback for author form element.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form state.
*
* @return array
* Form API array.
*/
function ajax_example_node_by_author_ajax_callback($form, $form_state) {
return $form['node'];
}
/**
* Validate handler to convert our title string into a nid.
*
* In case the user did not actually use the autocomplete or have a valid string
* there, we'll try to look up a result anyway giving it our best guess.
*
* Since the user chose a unique node, we must now use the same one in our
* submit handler, which means we need to look in the string for the nid.
*
* This handler looks complex because it's ambitious (and tries to punt and
* find a node if they've entered a valid username and part of a title), but
* you *could* just do a form_error() if nothing were found, forcing people to
* use the autocomplete to look up the relevant items.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form state.
*
* @return array
* Form API array.
*/
function ajax_example_node_by_author_autocomplete_validate($form, &$form_state) {
$title = $form_state['values']['node'];
$author = $form_state['values']['author'];
$matches = array();
// We must have a valid user.
$account = user_load_by_name($author);
if (empty($account)) {
form_error($form['author'], t('You must choose a valid author username'));
return;
}
// This preg_match() looks for the last pattern like [33334] and if found
// extracts the numeric portion.
$result = preg_match('/\[([0-9]+)\]$/', $title, $matches);
if ($result > 0) {
// If $result is nonzero, we found a match and can use it as the index into
// $matches.
$nid = $matches[$result];
// Verify that it's a valid nid.
$node = node_load($nid);
if (empty($node)) {
form_error($form['node'], t('Sorry, no node with nid %nid can be found', array('%nid' => $nid)));
return;
}
}
// BUT: Not everybody will have javascript turned on, or they might hit ESC
// and not use the autocomplete values offered. In that case, we can attempt
// to come up with a useful value. This is not absolutely necessary, and we
// *could* just emit a form_error() as below. Here we'll find the *first*
// matching title and assume that is adequate.
else {
$nid = db_select('node')
->fields('node', array('nid'))
->condition('uid', $account->uid)
->condition('title', db_like($title) . '%', 'LIKE')
->range(0, 1)
->execute()
->fetchField();
}
// Now, if we somehow found a nid, assign it to the node. If we failed, emit
// an error.
if (!empty($nid)) {
$form_state['values']['node'] = $nid;
}
else {
form_error($form['node'], t('Sorry, no node starting with %title can be found', array('%title' => $title)));
}
}
/**
* Submit handler for node lookup unique autocomplete example.
*
* Here the nid has already been placed in $form_state['values']['node'] by the
* validation handler.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form state.
*
* @return array
* Form API array.
*/
function ajax_example_node_by_author_autocomplete_submit($form, &$form_state) {
$node = node_load($form_state['values']['node']);
$account = user_load($node->uid);
drupal_set_message(t('You found node %nid with title !title_link, authored by !user_link',
array(
'%nid' => $node->nid,
'!title_link' => l($node->title, 'node/' . $node->nid),
'!user_link' => theme('username', array('account' => $account)),
)
));
}
/**
* Autocomplete callback for nodes by title but limited by author.
*
* Searches for a node by title given the passed-in author username.
*
* The returned $matches array has
* - key: The title, with the identifying nid in brackets, like "Some node
* title [3325]"
* - value: the title which will is displayed in the autocomplete pulldown.
*
* Note that we must use a key style that can be parsed successfully and
* unambiguously. For example, if we might have node titles that could have
* [3325] in them, then we'd have to use a more restrictive token.
*
* @param int $author_uid
* The author username to limit the search.
* @param string $string
* The string that will be searched.
*/
function ajax_example_node_by_author_node_autocomplete_callback($author_uid, $string = "") {
$matches = array();
if ($author_uid > 0 && trim($string)) {
$result = db_select('node')
->fields('node', array('nid', 'title'))
->condition('uid', $author_uid)
->condition('title', db_like($string) . '%', 'LIKE')
->range(0, 10)
->execute();
foreach ($result as $node) {
$matches[$node->title . " [$node->nid]"] = check_plain($node->title);
}
}
drupal_json_output($matches);
}

View File

@@ -0,0 +1,668 @@
<?php
/**
* @file
* Demonstrations of AJAX with graceful degradation.
*/
/**
* @defgroup ajax_degradation_example Example: AJAX Graceful Degradation
* @ingroup examples
* @{
* These examples show AJAX with graceful degradation when Javascript is not
* available.
*
* In each of these the key idea is that the form is rebuilt different ways
* depending on form input. In order to accomplish that, the formbuilder
* function is in charge of almost all logic.
*/
/**
* Dropdown form based on previous choices.
*
* A form with a dropdown whose options are dependent on a choice made in a
* previous dropdown.
*
* On changing the first dropdown, the options in the second
* are updated. Gracefully degrades if no javascript.
*
* A bit of CSS and javascript is required. The CSS hides the "add more" button
* if javascript is not enabled. The Javascript snippet is really only used
* to enable us to present the form in degraded mode without forcing the user
* to turn off Javascript. Both of these are loaded by using the
* #attached FAPI property, so it is a good example of how to use that.
*
* The extra argument $no_js_use is here only to allow presentation of this
* form as if Javascript were not enabled. ajax_example_menu() provides two
* ways to call this form, one normal ($no_js_use = FALSE) and one simulating
* Javascript disabled ($no_js_use = TRUE).
*/
function ajax_example_dependent_dropdown_degrades($form, &$form_state, $no_js_use = FALSE) {
// Get the list of options to populate the first dropdown.
$options_first = _ajax_example_get_first_dropdown_options();
// If we have a value for the first dropdown from $form_state['values'] we use
// this both as the default value for the first dropdown and also as a
// parameter to pass to the function that retrieves the options for the
// second dropdown.
$selected = isset($form_state['values']['dropdown_first']) ? $form_state['values']['dropdown_first'] : key($options_first);
// Attach the CSS and JS we need to show this with and without javascript.
// Without javascript we need an extra "Choose" button, and this is
// hidden when we have javascript enabled.
$form['#attached']['css'] = array(
drupal_get_path('module', 'ajax_example') . '/ajax_example.css',
);
$form['#attached']['js'] = array(
drupal_get_path('module', 'ajax_example') . '/ajax_example.js',
);
$form['dropdown_first_fieldset'] = array(
'#type' => 'fieldset',
);
$form['dropdown_first_fieldset']['dropdown_first'] = array(
'#type' => 'select',
'#title' => 'Instrument Type',
'#options' => $options_first,
'#attributes' => array('class' => array('enabled-for-ajax')),
// The '#ajax' property allows us to bind a callback to the server whenever
// this form element changes. See ajax_example_autocheckboxes and
// ajax_example_dependent_dropdown in ajax_example.module for more details.
'#ajax' => array(
'callback' => 'ajax_example_dependent_dropdown_degrades_first_callback',
'wrapper' => 'dropdown-second-replace',
),
);
// This simply allows us to demonstrate no-javascript use without
// actually turning off javascript in the browser. Removing the #ajax
// element turns off AJAX behaviors on that element and as a result
// ajax.js doesn't get loaded. This is for demonstration purposes only.
if ($no_js_use) {
unset($form['dropdown_first_fieldset']['dropdown_first']['#ajax']);
}
// Since we don't know if the user has js or not, we always need to output
// this element, then hide it with with css if javascript is enabled.
$form['dropdown_first_fieldset']['continue_to_second'] = array(
'#type' => 'submit',
'#value' => t('Choose'),
'#attributes' => array('class' => array('next-button')),
);
$form['dropdown_second_fieldset'] = array(
'#type' => 'fieldset',
);
$form['dropdown_second_fieldset']['dropdown_second'] = array(
'#type' => 'select',
'#title' => $options_first[$selected] . ' ' . t('Instruments'),
'#prefix' => '<div id="dropdown-second-replace">',
'#suffix' => '</div>',
'#attributes' => array('class' => array('enabled-for-ajax')),
// When the form is rebuilt during processing (either AJAX or multistep),
// the $selected variable will now have the new value and so the options
// will change.
'#options' => _ajax_example_get_second_dropdown_options($selected),
);
$form['dropdown_second_fieldset']['submit'] = array(
'#type' => 'submit',
'#value' => t('OK'),
// This class allows attached js file to override the disabled attribute,
// since it's not necessary in ajax-enabled form.
'#attributes' => array('class' => array('enabled-for-ajax')),
);
// Disable dropdown_second if a selection has not been made on dropdown_first.
if (empty($form_state['values']['dropdown_first'])) {
$form['dropdown_second_fieldset']['dropdown_second']['#disabled'] = TRUE;
$form['dropdown_second_fieldset']['dropdown_second']['#description'] = t('You must make your choice on the first dropdown before changing this second one.');
$form['dropdown_second_fieldset']['submit']['#disabled'] = TRUE;
}
return $form;
}
/**
* Submit function for ajax_example_dependent_dropdown_degrades().
*/
function ajax_example_dependent_dropdown_degrades_submit($form, &$form_state) {
// Now handle the case of the next, previous, and submit buttons.
// only submit will result in actual submission, all others rebuild.
switch ($form_state['triggering_element']['#value']) {
case t('OK'):
// Submit: We're done.
drupal_set_message(t('Your values have been submitted. dropdown_first=@first, dropdown_second=@second', array('@first' => $form_state['values']['dropdown_first'], '@second' => $form_state['values']['dropdown_second'])));
return;
}
// 'Choose' or anything else will cause rebuild of the form and present
// it again.
$form_state['rebuild'] = TRUE;
}
/**
* Selects just the second dropdown to be returned for re-rendering.
*
* @return array
* Renderable array (the second dropdown).
*/
function ajax_example_dependent_dropdown_degrades_first_callback($form, $form_state) {
return $form['dropdown_second_fieldset']['dropdown_second'];
}
/**
* Dynamically-enabled form with graceful no-JS degradation.
*
* Example of a form with portions dynamically enabled or disabled, but
* with graceful degradation in the case of no javascript.
*
* The idea here is that certain parts of the form don't need to be displayed
* unless a given option is selected, but then they should be displayed and
* configured.
*
* The third $no_js_use argument is strictly for demonstrating operation
* without javascript, without making the user/developer turn off javascript.
*/
function ajax_example_dynamic_sections($form, &$form_state, $no_js_use = FALSE) {
// Attach the CSS and JS we need to show this with and without javascript.
// Without javascript we need an extra "Choose" button, and this is
// hidden when we have javascript enabled.
$form['#attached']['css'] = array(
drupal_get_path('module', 'ajax_example') . '/ajax_example.css',
);
$form['#attached']['js'] = array(
drupal_get_path('module', 'ajax_example') . '/ajax_example.js',
);
$form['description'] = array(
'#type' => 'markup',
'#markup' => '<div>' . t('This example demonstrates a form which dynamically creates various sections based on the configuration in the form.
It deliberately allows graceful degradation to a non-javascript environment.
In a non-javascript environment, the "Choose" button next to the select control
is displayed; in a javascript environment it is hidden by the module CSS.
<br/><br/>The basic idea here is that the form is built up based on
the selection in the question_type_select field, and it is built the same
whether we are in a javascript/AJAX environment or not.
<br/><br/>
Try the <a href="!ajax_link">AJAX version</a> and the <a href="!non_ajax_link">simulated-non-AJAX version</a>.
', array('!ajax_link' => url('examples/ajax_example/dynamic_sections'), '!non_ajax_link' => url('examples/ajax_example/dynamic_sections_no_js'))) . '</div>',
);
$form['question_type_select'] = array(
'#type' => 'select',
'#title' => t('Question style'),
'#options' => drupal_map_assoc(
array(
t('Choose question style'),
t('Multiple Choice'),
t('True/False'),
t('Fill-in-the-blanks'),
)
),
'#ajax' => array(
'wrapper' => 'questions-fieldset-wrapper',
'callback' => 'ajax_example_dynamic_sections_select_callback',
),
);
// The CSS for this module hides this next button if JS is enabled.
$form['question_type_submit'] = array(
'#type' => 'submit',
'#value' => t('Choose'),
'#attributes' => array('class' => array('next-button')),
// No need to validate when submitting this.
'#limit_validation_errors' => array(),
'#validate' => array(),
);
// This simply allows us to demonstrate no-javascript use without
// actually turning off javascript in the browser. Removing the #ajax
// element turns off AJAX behaviors on that element and as a result
// ajax.js doesn't get loaded.
if ($no_js_use) {
// Remove the #ajax from the above, so ajax.js won't be loaded.
unset($form['question_type_select']['#ajax']);
}
// This fieldset just serves as a container for the part of the form
// that gets rebuilt.
$form['questions_fieldset'] = array(
'#type' => 'fieldset',
// These provide the wrapper referred to in #ajax['wrapper'] above.
'#prefix' => '<div id="questions-fieldset-wrapper">',
'#suffix' => '</div>',
);
if (!empty($form_state['values']['question_type_select'])) {
$form['questions_fieldset']['question'] = array(
'#markup' => t('Who was the first president of the U.S.?'),
);
$question_type = $form_state['values']['question_type_select'];
switch ($question_type) {
case t('Multiple Choice'):
$form['questions_fieldset']['question'] = array(
'#type' => 'radios',
'#title' => t('Who was the first president of the United States'),
'#options' => drupal_map_assoc(
array(
t('George Bush'),
t('Adam McGuire'),
t('Abraham Lincoln'),
t('George Washington'),
)
),
);
break;
case t('True/False'):
$form['questions_fieldset']['question'] = array(
'#type' => 'radios',
'#title' => t('Was George Washington the first president of the United States?'),
'#options' => array(t('George Washington') => t("True"), 0 => t("False")),
'#description' => t('Click "True" if you think George Washington was the first president of the United States.'),
);
break;
case t('Fill-in-the-blanks'):
$form['questions_fieldset']['question'] = array(
'#type' => 'textfield',
'#title' => t('Who was the first president of the United States'),
'#description' => t('Please type the correct answer to the question.'),
);
break;
}
$form['questions_fieldset']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit your answer'),
);
}
return $form;
}
/**
* Validation function for ajax_example_dynamic_sections().
*/
function ajax_example_dynamic_sections_validate($form, &$form_state) {
$answer = $form_state['values']['question'];
if ($answer !== t('George Washington')) {
form_set_error('question', t('Wrong answer. Try again. (Hint: The right answer is "George Washington".)'));
}
}
/**
* Submit function for ajax_example_dynamic_sections().
*/
function ajax_example_dynamic_sections_submit($form, &$form_state) {
// This is only executed when a button is pressed, not when the AJAXified
// select is changed.
// Now handle the case of the next, previous, and submit buttons.
// Only submit will result in actual submission, all others rebuild.
switch ($form_state['triggering_element']['#value']) {
case t('Submit your answer'):
// Submit: We're done.
$form_state['rebuild'] = FALSE;
$answer = $form_state['values']['question'];
// Special handling for the checkbox.
if ($answer == 1 && $form['questions_fieldset']['question']['#type'] == 'checkbox') {
$answer = $form['questions_fieldset']['question']['#title'];
}
if ($answer === t('George Washington')) {
drupal_set_message(t('You got the right answer: @answer', array('@answer' => $answer)));
}
else {
drupal_set_message(t('Sorry, your answer (@answer) is wrong', array('@answer' => $answer)));
}
return;
// Any other form element will cause rebuild of the form and present
// it again.
case t('Choose'):
$form_state['values']['question_type_select'] = $form_state['input']['question_type_select'];
// Fall through.
default:
$form_state['rebuild'] = TRUE;
}
}
/**
* Callback for the select element.
*
* This just selects and returns the questions_fieldset.
*/
function ajax_example_dynamic_sections_select_callback($form, $form_state) {
return $form['questions_fieldset'];
}
/**
* Wizard form.
*
* This example is a classic wizard, where a different and sequential form
* is presented on each step of the form.
*
* In the AJAX version, the form is replaced for each wizard section. In the
* multistep version, it causes a new page load.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form.
* @param bool $no_js_use
* Used for this demonstration only. If true means that the form should be
* built using a simulated no-javascript approach (ajax.js will not be
* loaded.)
*
* @return array
* Form array.
*/
function ajax_example_wizard($form, &$form_state, $no_js_use = FALSE) {
// Provide a wrapper around the entire form, since we'll replace the whole
// thing with each submit.
$form['#prefix'] = '<div id="wizard-form-wrapper">';
$form['#suffix'] = '</div>';
// We want to deal with hierarchical form values.
$form['#tree'] = TRUE;
$form['description'] = array(
'#markup' => '<div>' . t('This example is a step-by-step wizard. The <a href="!ajax">AJAX version</a> does it without page reloads; the <a href="!multistep">multistep version</a> is the same code but simulates a non-javascript environment, showing it with page reloads.',
array('!ajax' => url('examples/ajax_example/wizard'), '!multistep' => url('examples/ajax_example/wizard_no_js')))
. '</div>',
);
// $form_state['storage'] has no specific drupal meaning, but it is
// traditional to keep variables for multistep forms there.
$step = empty($form_state['storage']['step']) ? 1 : $form_state['storage']['step'];
$form_state['storage']['step'] = $step;
switch ($step) {
case 1:
$form['step1'] = array(
'#type' => 'fieldset',
'#title' => t('Step 1: Personal details'),
);
$form['step1']['name'] = array(
'#type' => 'textfield',
'#title' => t('Your name'),
'#default_value' => empty($form_state['values']['step1']['name']) ? '' : $form_state['values']['step1']['name'],
'#required' => TRUE,
);
break;
case 2:
$form['step2'] = array(
'#type' => 'fieldset',
'#title' => t('Step 2: Street address info'),
);
$form['step2']['address'] = array(
'#type' => 'textfield',
'#title' => t('Your street address'),
'#default_value' => empty($form_state['values']['step2']['address']) ? '' : $form_state['values']['step2']['address'],
'#required' => TRUE,
);
break;
case 3:
$form['step3'] = array(
'#type' => 'fieldset',
'#title' => t('Step 3: City info'),
);
$form['step3']['city'] = array(
'#type' => 'textfield',
'#title' => t('Your city'),
'#default_value' => empty($form_state['values']['step3']['city']) ? '' : $form_state['values']['step3']['city'],
'#required' => TRUE,
);
break;
}
if ($step == 3) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => t("Submit your information"),
);
}
if ($step < 3) {
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next step'),
'#ajax' => array(
'wrapper' => 'wizard-form-wrapper',
'callback' => 'ajax_example_wizard_callback',
),
);
}
if ($step > 1) {
$form['prev'] = array(
'#type' => 'submit',
'#value' => t("Previous step"),
// Since all info will be discarded, don't validate on 'prev'.
'#limit_validation_errors' => array(),
// #submit is required to use #limit_validation_errors
'#submit' => array('ajax_example_wizard_submit'),
'#ajax' => array(
'wrapper' => 'wizard-form-wrapper',
'callback' => 'ajax_example_wizard_callback',
),
);
}
// This simply allows us to demonstrate no-javascript use without
// actually turning off javascript in the browser. Removing the #ajax
// element turns off AJAX behaviors on that element and as a result
// ajax.js doesn't get loaded.
// For demonstration only! You don't need this.
if ($no_js_use) {
// Remove the #ajax from the above, so ajax.js won't be loaded.
// For demonstration only.
unset($form['next']['#ajax']);
unset($form['prev']['#ajax']);
}
return $form;
}
/**
* Wizard callback function.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form.
*
* @return array
* Form array.
*/
function ajax_example_wizard_callback($form, $form_state) {
return $form;
}
/**
* Submit function for ajax_example_wizard.
*
* In AJAX this is only submitted when the final submit button is clicked,
* but in the non-javascript situation, it is submitted with every
* button click.
*/
function ajax_example_wizard_submit($form, &$form_state) {
// Save away the current information.
$current_step = 'step' . $form_state['storage']['step'];
if (!empty($form_state['values'][$current_step])) {
$form_state['storage']['values'][$current_step] = $form_state['values'][$current_step];
}
// Increment or decrement the step as needed. Recover values if they exist.
if ($form_state['triggering_element']['#value'] == t('Next step')) {
$form_state['storage']['step']++;
// If values have already been entered for this step, recover them from
// $form_state['storage'] to pre-populate them.
$step_name = 'step' . $form_state['storage']['step'];
if (!empty($form_state['storage']['values'][$step_name])) {
$form_state['values'][$step_name] = $form_state['storage']['values'][$step_name];
}
}
if ($form_state['triggering_element']['#value'] == t('Previous step')) {
$form_state['storage']['step']--;
// Recover our values from $form_state['storage'] to pre-populate them.
$step_name = 'step' . $form_state['storage']['step'];
$form_state['values'][$step_name] = $form_state['storage']['values'][$step_name];
}
// If they're done, submit.
if ($form_state['triggering_element']['#value'] == t('Submit your information')) {
$value_message = t('Your information has been submitted:') . ' ';
foreach ($form_state['storage']['values'] as $step => $values) {
$value_message .= "$step: ";
foreach ($values as $key => $value) {
$value_message .= "$key=$value, ";
}
}
drupal_set_message($value_message);
$form_state['rebuild'] = FALSE;
return;
}
// Otherwise, we still have work to do.
$form_state['rebuild'] = TRUE;
}
/**
* Form with 'add more' and 'remove' buttons.
*
* This example shows a button to "add more" - add another textfield, and
* the corresponding "remove" button.
*
* It works equivalently with javascript or not, and does the same basic steps
* either way.
*
* The basic idea is that we build the form based on the setting of
* $form_state['num_names']. The custom submit functions for the "add-one"
* and "remove-one" buttons increment and decrement $form_state['num_names']
* and then force a rebuild of the form.
*
* The $no_js_use argument is simply for demonstration: When set, it prevents
* '#ajax' from being set, thus making the example behave as if javascript
* were disabled in the browser.
*/
function ajax_example_add_more($form, &$form_state, $no_js_use = FALSE) {
$form['description'] = array(
'#markup' => '<div>' . t('This example shows an add-more and a remove-last button. The <a href="!ajax">AJAX version</a> does it without page reloads; the <a href="!multistep">non-js version</a> is the same code but simulates a non-javascript environment, showing it with page reloads.',
array('!ajax' => url('examples/ajax_example/add_more'), '!multistep' => url('examples/ajax_example/add_more_no_js')))
. '</div>',
);
// Because we have many fields with the same values, we have to set
// #tree to be able to access them.
$form['#tree'] = TRUE;
$form['names_fieldset'] = array(
'#type' => 'fieldset',
'#title' => t('People coming to the picnic'),
// Set up the wrapper so that AJAX will be able to replace the fieldset.
'#prefix' => '<div id="names-fieldset-wrapper">',
'#suffix' => '</div>',
);
// Build the fieldset with the proper number of names. We'll use
// $form_state['num_names'] to determine the number of textfields to build.
if (empty($form_state['num_names'])) {
$form_state['num_names'] = 1;
}
for ($i = 0; $i < $form_state['num_names']; $i++) {
$form['names_fieldset']['name'][$i] = array(
'#type' => 'textfield',
'#title' => t('Name'),
);
}
$form['names_fieldset']['add_name'] = array(
'#type' => 'submit',
'#value' => t('Add one more'),
'#submit' => array('ajax_example_add_more_add_one'),
// See the examples in ajax_example.module for more details on the
// properties of #ajax.
'#ajax' => array(
'callback' => 'ajax_example_add_more_callback',
'wrapper' => 'names-fieldset-wrapper',
),
);
if ($form_state['num_names'] > 1) {
$form['names_fieldset']['remove_name'] = array(
'#type' => 'submit',
'#value' => t('Remove one'),
'#submit' => array('ajax_example_add_more_remove_one'),
'#ajax' => array(
'callback' => 'ajax_example_add_more_callback',
'wrapper' => 'names-fieldset-wrapper',
),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
// This simply allows us to demonstrate no-javascript use without
// actually turning off javascript in the browser. Removing the #ajax
// element turns off AJAX behaviors on that element and as a result
// ajax.js doesn't get loaded.
// For demonstration only! You don't need this.
if ($no_js_use) {
// Remove the #ajax from the above, so ajax.js won't be loaded.
if (!empty($form['names_fieldset']['remove_name']['#ajax'])) {
unset($form['names_fieldset']['remove_name']['#ajax']);
}
unset($form['names_fieldset']['add_name']['#ajax']);
}
return $form;
}
/**
* Callback for both ajax-enabled buttons.
*
* Selects and returns the fieldset with the names in it.
*/
function ajax_example_add_more_callback($form, $form_state) {
return $form['names_fieldset'];
}
/**
* Submit handler for the "add-one-more" button.
*
* Increments the max counter and causes a rebuild.
*/
function ajax_example_add_more_add_one($form, &$form_state) {
$form_state['num_names']++;
$form_state['rebuild'] = TRUE;
}
/**
* Submit handler for the "remove one" button.
*
* Decrements the max counter and causes a form rebuild.
*/
function ajax_example_add_more_remove_one($form, &$form_state) {
if ($form_state['num_names'] > 1) {
$form_state['num_names']--;
}
$form_state['rebuild'] = TRUE;
}
/**
* Final submit handler.
*
* Reports what values were finally set.
*/
function ajax_example_add_more_submit($form, &$form_state) {
$output = t('These people are coming to the picnic: @names',
array(
'@names' => implode(', ', $form_state['values']['names_fieldset']['name']),
)
);
drupal_set_message($output);
}
/**
* @} End of "defgroup ajax_degradation_example".
*/

View File

@@ -0,0 +1,116 @@
<?php
/**
* @file
* AJAX Miscellaneous Topics.
*/
/**
* Demonstrates a clickable AJAX-enabled link using the 'use-ajax' class.
*
* Because of the 'use-ajax' class applied here, the link submission is done
* without a page refresh.
*
* When using the AJAX framework outside the context of a form or a renderable
* array of type 'link', you have to include ajax.js explicitly.
*
* @return array
* Form API array.
*
* @ingroup ajax_example
*/
function ajax_example_render_link() {
// drupal_add_library is invoked automatically when a form element has the
// '#ajax' property, but since we are not rendering a form here, we have to
// do it ourselves.
drupal_add_library('system', 'drupal.ajax');
$explanation = t("
The link below has the <i>use-ajax</i> class applied to it, so if
javascript is enabled, ajax.js will try to submit it via an AJAX call instead
of a normal page load. The URL also contains the '/nojs/' magic string, which
is stripped if javascript is enabled, allowing the server code to tell by the
URL whether JS was enabled or not, letting it do different things based on that.");
$output = "<div>" . $explanation . "</div>";
// The use-ajax class is special, so that the link will call without causing
// a page reload. Note the /nojs portion of the path - if javascript is
// enabled, this part will be stripped from the path before it is called.
$link = l(t('Click here'), 'ajax_link_callback/nojs/', array('attributes' => array('class' => array('use-ajax'))));
$output .= "<div id='myDiv'></div><div>$link</div>";
return $output;
}
/**
* AJAX-enabled link in a renderable array.
*
* Demonstrates a clickable AJAX-enabled link using a renderable array with the
* #ajax property.
*
* A link that is constructed as a renderable array can have the #ajax property,
* which ensures that the link submission is done without a page refresh. The
* href of the link is used as the ajax callback, but it degrades gracefully
* without JavaScript because if the 'nojs' portion of the href is not stripped
* out by js, the callback will return content as required for a full page
* reload.
*
* The necessary JavaScript file, ajax.js, will be included on the page
* automatically.
*
* @return array
* Form API array.
*/
function ajax_example_render_link_ra() {
$explanation = "
The link below has been rendered as an element with the #ajax property, so if
javascript is enabled, ajax.js will try to submit it via an AJAX call instead
of a normal page load. The URL also contains the '/nojs/' magic string, which
is stripped if javascript is enabled, allowing the server code to tell by the
URL whether JS was enabled or not, letting it do different things based on that.";
$build['my_div'] = array(
'#markup' => $explanation . '<div id="myDiv"></div>',
);
$build['ajax_link'] = array(
'#type' => 'link',
'#title' => t('Click here'),
// Note the /nojs portion of the href - if javascript is enabled,
// this part will be stripped from the path before it is called.
'#href' => 'ajax_link_callback/nojs/',
'#id' => 'ajax_link',
'#ajax' => array(
'wrapper' => 'myDiv',
'method' => 'html',
),
);
return $build;
}
/**
* Callback for link example.
*
* Takes different logic paths based on whether Javascript was enabled.
* If $type == 'ajax', it tells this function that ajax.js has rewritten
* the URL and thus we are doing an AJAX and can return an array of commands.
*
* @param string $type
* Either 'ajax' or 'nojs. Type is simply the normal URL argument to this URL.
*
* @return string|array
* If $type == 'ajax', returns an array of AJAX Commands.
* Otherwise, just returns the content, which will end up being a page.
*
* @ingroup ajax_example
*/
function ajax_link_response($type = 'ajax') {
if ($type == 'ajax') {
$output = t("This is some content delivered via AJAX");
$commands = array();
// See ajax_example_advanced.inc for more details on the available commands
// and how to use them.
$commands[] = ajax_command_append('#myDiv', $output);
$page = array('#type' => 'ajax', '#commands' => $commands);
ajax_deliver($page);
}
else {
$output = t("This is some content delivered via a page load.");
return $output;
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* @file
* This example shows how to use AJAX when altering a node form.
*
* It maintains a table parallel to the node table containing attributes
* 'example_1' and 'example_2' which are displayed on the node form.
* 'example_2' is displayed dynamically when example_1 is checked.
*/
/**
* Implements hook_form_FORM_ID_alter().
*
* Adds two fields to the node form, second only appears after first is enabled.
*/
function ajax_example_form_node_form_alter(&$form, &$form_state, $form_id) {
$node = $form['#node'];
$form['ajax_example_1'] = array(
'#type' => 'checkbox',
'#title' => t('AJAX Example 1'),
'#description' => t('Enable to show second field.'),
'#default_value' => $node->ajax_example['example_1'],
'#ajax' => array(
'callback' => 'ajax_example_form_node_callback',
'wrapper' => 'ajax-example-form-node',
'effect' => 'fade',
),
);
$form['container'] = array(
'#prefix' => '<div id="ajax-example-form-node">',
'#suffix' => '</div>',
);
// If the state values exist and 'ajax_example_1' state value is 1 or
// if the state values don't exist and 'example1' variable is 1 then
// display the ajax_example_2 field.
if (!empty($form_state['values']['ajax_example_1']) && $form_state['values']['ajax_example_1'] == 1
|| empty($form_state['values']) && $node->ajax_example['example_1']) {
$form['container']['ajax_example_2'] = array(
'#type' => 'textfield',
'#title' => t('AJAX Example 2'),
'#description' => t('AJAX Example 2'),
'#default_value' => empty($form_state['values']['ajax_example_2']) ? $node->ajax_example['example_2'] : $form_state['values']['ajax_example_2'],
);
}
}
/**
* Returns changed part of the form.
*
* @return array
* Form API array.
*
* @see ajax_example_form_node_form_alter()
*/
function ajax_example_form_node_callback($form, $form_state) {
return $form['container'];
}
/**
* Implements hook_node_submit().
* @see ajax_example_form_node_form_alter()
*/
function ajax_example_node_submit($node, $form, &$form_state) {
$values = $form_state['values'];
// Move the new data into the node object.
$node->ajax_example['example_1'] = $values['ajax_example_1'];
// Depending on the state of ajax_example_1; it may not exist.
$node->ajax_example['example_2'] = isset($values['ajax_example_2']) ? $values['ajax_example_2'] : '';
}
/**
* Implements hook_node_prepare().
*
* @see ajax_example_form_node_form_alter()
*/
function ajax_example_node_prepare($node) {
if (empty($node->ajax_example)) {
// Set default values, since this only runs when adding a new node.
$node->ajax_example['example_1'] = 0;
$node->ajax_example['example_2'] = '';
}
}
/**
* Implements hook_node_load().
*
* @see ajax_example_form_node_form_alter()
*/
function ajax_example_node_load($nodes, $types) {
$result = db_query('SELECT * FROM {ajax_example_node_form_alter} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)))->fetchAllAssoc('nid');
foreach ($nodes as &$node) {
$node->ajax_example['example_1']
= isset($result[$node->nid]->example_1) ?
$result[$node->nid]->example_1 : 0;
$node->ajax_example['example_2']
= isset($result[$node->nid]->example_2) ?
$result[$node->nid]->example_2 : '';
}
}
/**
* Implements hook_node_insert().
*
* @see ajax_example_form_node_form_alter()
*/
function ajax_example_node_insert($node) {
if (isset($node->ajax_example)) {
db_insert('ajax_example_node_form_alter')
->fields(array(
'nid' => $node->nid,
'example_1' => $node->ajax_example['example_1'],
'example_2' => $node->ajax_example['example_2'],
))
->execute();
}
}
/**
* Implements hook_node_update().
* @see ajax_example_form_node_form_alter()
*/
function ajax_example_node_update($node) {
if (db_select('ajax_example_node_form_alter', 'a')->fields('a')->condition('nid', $node->nid, '=')->execute()->fetchAssoc()) {
db_update('ajax_example_node_form_alter')
->fields(array(
'example_1' => $node->ajax_example['example_1'],
'example_2' => $node->ajax_example['example_2'],
))
->condition('nid', $node->nid)
->execute();
}
else {
// Cleaner than doing it again.
ajax_example_node_insert($node);
}
}
/**
* Implements hook_node_delete().
* @see ajax_example_form_node_form_alter()
*/
function ajax_example_node_delete($node) {
db_delete('ajax_example_node_form_alter')
->condition('nid', $node->nid)
->execute();
}

View File

@@ -0,0 +1,116 @@
<?php
/**
* @file
* Progress bar example.
*/
/**
* Implements hook_FORMID().
*
* Build a landing-page form for the progress bar example.
*
* @see https://api.drupal.org/api/drupal/developer%21topics%21forms_api_reference.html/7#ajax_progress
*/
function ajax_example_progressbar_form($form, &$form_state) {
$form_state['time'] = REQUEST_TIME;
// We make a DIV which the progress bar can occupy. You can see this in use
// in ajax_example_progressbar_callback().
$form['status'] = array(
'#markup' => '<div id="progress-status"></div>',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
'#ajax' => array(
// Here we set up our AJAX callback handler.
'callback' => 'ajax_example_progressbar_callback',
// Tell FormAPI about our progress bar.
'progress' => array(
'type' => 'bar',
'message' => t('Execute..'),
// Have the progress bar access this URL path.
'url' => url('examples/ajax_example/progressbar/progress/' . $form_state['time']),
// The time interval for the progress bar to check for updates.
'interval' => 1000,
),
),
);
return $form;
}
/**
* Get the progress bar execution status, as JSON.
*
* This is the menu handler for
* examples/ajax_example/progressbar/progress/$time.
*
* This function is our wholly arbitrary job that we're checking the status for.
* In this case, we're reading a system variable that is being updated by
* ajax_example_progressbar_callback().
*
* We set up the AJAX progress bar to check the status every second, so this
* will execute about once every second.
*
* The progress bar JavaScript accepts two values: message and percentage. We
* set those in an array and in the end convert it JSON for sending back to the
* client-side JavaScript.
*
* @param int $time
* Timestamp.
*
* @see ajax_example_progressbar_callback()
*/
function ajax_example_progressbar_progress($time) {
$progress = array(
'message' => t('Starting execute...'),
'percentage' => -1,
);
$completed_percentage = variable_get('example_progressbar_' . $time, 0);
if ($completed_percentage) {
$progress['message'] = t('Executing...');
$progress['percentage'] = $completed_percentage;
}
drupal_json_output($progress);
}
/**
* Our submit handler.
*
* This handler spends some time changing a variable and sleeping, and then
* finally returns a form element which marks the #progress-status DIV as
* completed.
*
* While this is occurring, ajax_example_progressbar_progress() will be called
* a number of times by the client-sid JavaScript, which will poll the variable
* being set here.
*
* @see ajax_example_progressbar_progress()
*/
function ajax_example_progressbar_callback($form, &$form_state) {
$variable_name = 'example_progressbar_' . $form_state['time'];
$commands = array();
variable_set($variable_name, 10);
sleep(2);
variable_set($variable_name, 40);
sleep(2);
variable_set($variable_name, 70);
sleep(2);
variable_set($variable_name, 90);
sleep(2);
variable_del($variable_name);
$commands[] = ajax_command_html('#progress-status', t('Executed.'));
return array(
'#type' => 'ajax',
'#commands' => $commands,
);
}

View File

@@ -0,0 +1,12 @@
name = Batch example
description = An example outlining how a module can define batch operations.
package = Example modules
core = 7.x
files[] = batch_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,85 @@
<?php
/**
* @file
* Install, update, and uninstall functions for the batch_example module.
*/
/**
* Example of batch-driven update function.
*
* Because some update functions may require the batch API, the $sandbox
* provides a place to store state. When $sandbox['#finished'] == TRUE,
* calls to this update function are completed.
*
* The $sandbox param provides a way to store data during multiple invocations.
* When the $sandbox['#finished'] == 1, execution is complete.
*
* This dummy 'update' function changes no state in the system. It simply
* loads each node.
*
* To make this update function run again and again, execute the query
* "update system set schema_version = 0 where name = 'batch_example';"
* and then run /update.php.
*
* @ingroup batch_example
*/
function batch_example_update_7100(&$sandbox) {
$ret = array();
// Use the sandbox at your convenience to store the information needed
// to track progression between successive calls to the function.
if (!isset($sandbox['progress'])) {
// The count of nodes visited so far.
$sandbox['progress'] = 0;
// Total nodes that must be visited.
$sandbox['max'] = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
// A place to store messages during the run.
$sandbox['messages'] = array();
// Last node read via the query.
$sandbox['current_node'] = -1;
}
// Process nodes by groups of 10 (arbitrary value).
// When a group is processed, the batch update engine determines
// whether it should continue processing in the same request or provide
// progress feedback to the user and wait for the next request.
$limit = 10;
// Retrieve the next group of nids.
$result = db_select('node', 'n')
->fields('n', array('nid'))
->orderBy('n.nid', 'ASC')
->where('n.nid > :nid', array(':nid' => $sandbox['current_node']))
->extend('PagerDefault')
->limit($limit)
->execute();
foreach ($result as $row) {
// Here we actually perform a dummy 'update' on the current node.
$node = db_query('SELECT nid FROM {node} WHERE nid = :nid', array(':nid' => $row->nid))->fetchField();
// Update our progress information.
$sandbox['progress']++;
$sandbox['current_node'] = $row->nid;
}
// Set the "finished" status, to tell batch engine whether this function
// needs to run again. If you set a float, this will indicate the progress
// of the batch so the progress bar will update.
$sandbox['#finished'] = ($sandbox['progress'] >= $sandbox['max']) ? TRUE : ($sandbox['progress'] / $sandbox['max']);
// Set up a per-run message; Make a copy of $sandbox so we can change it.
// This is simply a debugging stanza to illustrate how to capture status
// from each pass through hook_update_N().
$sandbox_status = $sandbox;
// Don't want them in the output.
unset($sandbox_status['messages']);
$sandbox['messages'][] = t('$sandbox=') . print_r($sandbox_status, TRUE);
if ($sandbox['#finished']) {
// hook_update_N() may optionally return a string which will be displayed
// to the user.
$final_message = '<ul><li>' . implode('</li><li>', $sandbox['messages']) . "</li></ul>";
return t('The batch_example demonstration update did what it was supposed to do: @message', array('@message' => $final_message));
}
}

View File

@@ -0,0 +1,318 @@
<?php
/**
* @file
* Outlines how a module can use the Batch API.
*/
/**
* @defgroup batch_example Example: Batch API
* @ingroup examples
* @{
* Outlines how a module can use the Batch API.
*
* Batches allow heavy processing to be spread out over several page
* requests, ensuring that the processing does not get interrupted
* because of a PHP timeout, while allowing the user to receive feedback
* on the progress of the ongoing operations. It also can prevent out of memory
* situations.
*
* The @link batch_example.install .install file @endlink also shows how the
* Batch API can be used to handle long-running hook_update_N() functions.
*
* Two harmless batches are defined:
* - batch 1: Load the node with the lowest nid 100 times.
* - batch 2: Load all nodes, 20 times and uses a progressive op, loading nodes
* by groups of 5.
* @see batch
*/
/**
* Implements hook_menu().
*/
function batch_example_menu() {
$items = array();
$items['examples/batch_example'] = array(
'title' => 'Batch example',
'description' => 'Example of Drupal batch processing',
'page callback' => 'drupal_get_form',
'page arguments' => array('batch_example_simple_form'),
'access callback' => TRUE,
);
return $items;
}
/**
* Form builder function to allow choice of which batch to run.
*/
function batch_example_simple_form() {
$form['description'] = array(
'#type' => 'markup',
'#markup' => t('This example offers two different batches. The first does 1000 identical operations, each completed in on run; the second does 20 operations, but each takes more than one run to operate if there are more than 5 nodes.'),
);
$form['batch'] = array(
'#type' => 'select',
'#title' => 'Choose batch',
'#options' => array(
'batch_1' => t('batch 1 - 1000 operations, each loading the same node'),
'batch_2' => t('batch 2 - 20 operations. each one loads all nodes 5 at a time'),
),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Go',
);
// If no nodes, prevent submission.
// Find out if we have a node to work with. Otherwise it won't work.
$nid = batch_example_lowest_nid();
if (empty($nid)) {
drupal_set_message(t("You don't currently have any nodes, and this example requires a node to work with. As a result, this form is disabled."));
$form['submit']['#disabled'] = TRUE;
}
return $form;
}
/**
* Submit handler.
*
* @param array $form
* Form API form.
* @param array $form_state
* Form API form.
*/
function batch_example_simple_form_submit($form, &$form_state) {
$function = 'batch_example_' . $form_state['values']['batch'];
// Reset counter for debug information.
$_SESSION['http_request_count'] = 0;
// Execute the function named batch_example_1 or batch_example_2.
$batch = $function();
batch_set($batch);
}
/**
* Batch 1 definition: Load the node with the lowest nid 1000 times.
*
* This creates an operations array defining what batch 1 should do, including
* what it should do when it's finished. In this case, each operation is the
* same and by chance even has the same $nid to operate on, but we could have
* a mix of different types of operations in the operations array.
*/
function batch_example_batch_1() {
$nid = batch_example_lowest_nid();
$num_operations = 1000;
drupal_set_message(t('Creating an array of @num operations', array('@num' => $num_operations)));
$operations = array();
// Set up an operations array with 1000 elements, each doing function
// batch_example_op_1.
// Each operation in the operations array means at least one new HTTP request,
// running Drupal from scratch to accomplish the operation. If the operation
// returns with $context['finished'] != TRUE, then it will be called again.
// In this example, $context['finished'] is always TRUE.
for ($i = 0; $i < $num_operations; $i++) {
// Each operation is an array consisting of
// - The function to call.
// - An array of arguments to that function.
$operations[] = array(
'batch_example_op_1',
array(
$nid,
t('(Operation @operation)', array('@operation' => $i)),
),
);
}
$batch = array(
'operations' => $operations,
'finished' => 'batch_example_finished',
);
return $batch;
}
/**
* Batch operation for batch 1: load a node.
*
* This is the function that is called on each operation in batch 1.
*/
function batch_example_op_1($nid, $operation_details, &$context) {
$node = node_load($nid, NULL, TRUE);
// Store some results for post-processing in the 'finished' callback.
// The contents of 'results' will be available as $results in the
// 'finished' function (in this example, batch_example_finished()).
$context['results'][] = $node->nid . ' : ' . check_plain($node->title);
// Optional message displayed under the progressbar.
$context['message'] = t('Loading node "@title"', array('@title' => $node->title)) . ' ' . $operation_details;
_batch_example_update_http_requests();
}
/**
* Batch 2 : Prepare a batch definition that will load all nodes 20 times.
*/
function batch_example_batch_2() {
$num_operations = 20;
// Give helpful information about how many nodes are being operated on.
$node_count = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
drupal_set_message(
t('There are @node_count nodes so each of the @num operations will require @count HTTP requests.',
array(
'@node_count' => $node_count,
'@num' => $num_operations,
'@count' => ceil($node_count / 5),
)
)
);
$operations = array();
// 20 operations, each one loads all nodes.
for ($i = 0; $i < $num_operations; $i++) {
$operations[] = array(
'batch_example_op_2',
array(t('(Operation @operation)', array('@operation' => $i))),
);
}
$batch = array(
'operations' => $operations,
'finished' => 'batch_example_finished',
// Message displayed while processing the batch. Available placeholders are:
// @current, @remaining, @total, @percentage, @estimate and @elapsed.
// These placeholders are replaced with actual values in _batch_process(),
// using strtr() instead of t(). The values are determined based on the
// number of operations in the 'operations' array (above), NOT by the number
// of nodes that will be processed. In this example, there are 20
// operations, so @total will always be 20, even though there are multiple
// nodes per operation.
// Defaults to t('Completed @current of @total.').
'title' => t('Processing batch 2'),
'init_message' => t('Batch 2 is starting.'),
'progress_message' => t('Processed @current out of @total.'),
'error_message' => t('Batch 2 has encountered an error.'),
);
return $batch;
}
/**
* Batch operation for batch 2 : load all nodes, 5 by five.
*
* After each group of 5 control is returned to the batch API for later
* continuation.
*/
function batch_example_op_2($operation_details, &$context) {
// Use the $context['sandbox'] at your convenience to store the
// information needed to track progression between successive calls.
if (empty($context['sandbox'])) {
$context['sandbox'] = array();
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_node'] = 0;
// Save node count for the termination message.
$context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField();
}
// Process nodes by groups of 5 (arbitrary value).
// When a group of five is processed, the batch update engine determines
// whether it should continue processing in the same request or provide
// progress feedback to the user and wait for the next request.
// That way even though we're already processing at the operation level
// the operation itself is interruptible.
$limit = 5;
// Retrieve the next group of nids.
$result = db_select('node', 'n')
->fields('n', array('nid'))
->orderBy('n.nid', 'ASC')
->where('n.nid > :nid', array(':nid' => $context['sandbox']['current_node']))
->extend('PagerDefault')
->limit($limit)
->execute();
foreach ($result as $row) {
// Here we actually perform our dummy 'processing' on the current node.
$node = node_load($row->nid, NULL, TRUE);
// Store some results for post-processing in the 'finished' callback.
// The contents of 'results' will be available as $results in the
// 'finished' function (in this example, batch_example_finished()).
$context['results'][] = $node->nid . ' : ' . check_plain($node->title) . ' ' . $operation_details;
// Update our progress information.
$context['sandbox']['progress']++;
$context['sandbox']['current_node'] = $node->nid;
$context['message'] = check_plain($node->title);
}
// Inform the batch engine that we are not finished,
// and provide an estimation of the completion level we reached.
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
$context['finished'] = ($context['sandbox']['progress'] >= $context['sandbox']['max']);
}
_batch_example_update_http_requests();
}
/**
* Batch 'finished' callback used by both batch 1 and batch 2.
*/
function batch_example_finished($success, $results, $operations) {
if ($success) {
// Here we could do something meaningful with the results.
// We just display the number of nodes we processed...
drupal_set_message(t('@count results processed in @requests HTTP requests.', array('@count' => count($results), '@requests' => _batch_example_get_http_requests())));
drupal_set_message(t('The final result was "%final"', array('%final' => end($results))));
}
else {
// An error occurred.
// $operations contains the operations that remained unprocessed.
$error_operation = reset($operations);
drupal_set_message(
t('An error occurred while processing @operation with arguments : @args',
array(
'@operation' => $error_operation[0],
'@args' => print_r($error_operation[0], TRUE),
)
),
'error'
);
}
}
/**
* Utility function - simply queries and loads the lowest nid.
*
* @return int|NULL
* A nid or NULL if there are no nodes.
*/
function batch_example_lowest_nid() {
$select = db_select('node', 'n')
->fields('n', array('nid'))
->orderBy('n.nid', 'ASC')
->extend('PagerDefault')
->limit(1);
$nid = $select->execute()->fetchField();
return $nid;
}
/**
* Utility function to increment HTTP requests in a session variable.
*/
function _batch_example_update_http_requests() {
$_SESSION['http_request_count']++;
}
/**
* Utility function to count the HTTP requests in a session variable.
*
* @return int
* Number of requests.
*/
function _batch_example_get_http_requests() {
return !empty($_SESSION['http_request_count']) ? $_SESSION['http_request_count'] : 0;
}
/**
* @} End of "defgroup batch_example".
*/

View File

@@ -0,0 +1,60 @@
<?php
/**
* @file
* Test case for Testing the batch example module.
*
* This file contains the test cases to check if module is performing as
* expected.
*/
/**
* Functional tests for the Batch Example module.
*
* @ingroup batch_example
*/
class BatchExampleTestCase extends DrupalWebTestCase {
protected $webUser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Batch example functionality',
'description' => 'Verify the defined batches.',
'group' => 'Examples',
);
}
/**
* Enable modules and create user with specific permissions.
*/
public function setUp() {
parent::setUp('batch_example');
// Create user.
$this->webUser = $this->drupalCreateUser();
}
/**
* Login user, create 30 nodes and test both batch examples.
*/
public function testBatchExampleBasic() {
// Login the admin user.
$this->drupalLogin($this->webUser);
// Create 30 nodes.
for ($count = 0; $count < 30; $count++) {
$node = $this->drupalCreateNode();
}
// Launch Batch 1
$result = $this->drupalPost('examples/batch_example', array('batch' => 'batch_1'), t('Go'));
// Check that 1000 operations were performed.
$this->assertText('1000 results processed');
// Launch Batch 2
$result = $this->drupalPost('examples/batch_example', array('batch' => 'batch_2'), t('Go'));
// Check that 600 operations were performed.
$this->assertText('600 results processed');
}
}

View File

@@ -0,0 +1,13 @@
name = Block Example
description = An example outlining how a module can define blocks.
package = Example modules
core = 7.x
dependencies[] = block
files[] = block_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,14 @@
<?php
/**
* @file
* Install, update and uninstall functions for the block_example module.
*/
/**
* Implements hook_uninstall().
*
* @ingroup block_example
*/
function block_example_uninstall() {
variable_del('block_example_string');
}

View File

@@ -0,0 +1,248 @@
<?php
/**
* @file
* Module file for block_example.
*/
/**
* @defgroup block_example Example: Block
* @ingroup examples
* @{
* Demonstrates code creation of blocks.
*
* This is an example outlining how a module can define blocks that can be
* displayed on various pages of a site, or how to alter blocks provided by
* other modules.
*/
/**
* Implements hook_menu().
*
* Provides a default page to explain what this module does.
*/
function block_example_menu() {
$items['examples/block_example'] = array(
'page callback' => 'block_example_page',
'access callback' => TRUE,
'title' => 'Block Example',
);
return $items;
}
/**
* Simple page function to explain what the block example is about.
*/
function block_example_page() {
$page = array(
'#type' => 'markup',
'#markup' => t('The Block Example provides three sample blocks which demonstrate the various block APIs. To experiment with the blocks, enable and configure them on <a href="@url">the block admin page</a>.', array('@url' => url('admin/structure/block'))),
);
return $page;
}
/**
* Implements hook_block_info().
*
* This hook declares what blocks are provided by the module.
*/
function block_example_block_info() {
// This hook returns an array, each component of which is an array of block
// information. The array keys are the 'delta' values used in other block
// hooks.
//
// The required block information is a block description, which is shown
// to the site administrator in the list of possible blocks. You can also
// provide initial settings for block weight, status, etc.
//
// Many options are defined in hook_block_info():
$blocks['example_configurable_text'] = array(
// info: The name of the block.
'info' => t('Example: configurable text string'),
// Block caching options (per role, per user, etc.)
// DRUPAL_CACHE_PER_ROLE is the default.
'cache' => DRUPAL_CACHE_PER_ROLE,
);
// This sample shows how to provide default settings. In this case we'll
// enable the block in the first sidebar and make it visible only on
// 'node/*' pages. See the hook_block_info() documentation for these.
$blocks['example_empty'] = array(
'info' => t('Example: empty block'),
'status' => TRUE,
'region' => 'sidebar_first',
'visibility' => BLOCK_VISIBILITY_LISTED,
'pages' => 'node/*',
);
$blocks['example_uppercase'] = array(
// info: The name of the block.
'info' => t('Example: uppercase this please'),
'status' => TRUE,
'region' => 'sidebar_first',
);
return $blocks;
}
/**
* Implements hook_block_configure().
*
* This hook declares configuration options for blocks provided by this module.
*/
function block_example_block_configure($delta = '') {
$form = array();
// The $delta parameter tells us which block is being configured.
// In this example, we'll allow the administrator to customize
// the text of the 'configurable text string' block defined in this module.
if ($delta == 'example_configurable_text') {
// All we need to provide is the specific configuration options for our
// block. Drupal will take care of the standard block configuration options
// (block title, page visibility, etc.) and the save button.
$form['block_example_string'] = array(
'#type' => 'textfield',
'#title' => t('Block contents'),
'#size' => 60,
'#description' => t('This text will appear in the example block.'),
'#default_value' => variable_get('block_example_string', t('Some example content.')),
);
}
return $form;
}
/**
* Implements hook_block_save().
*
* This hook declares how the configured options for a block
* provided by this module are saved.
*/
function block_example_block_save($delta = '', $edit = array()) {
// We need to save settings from the configuration form.
// We need to check $delta to make sure we are saving the right block.
if ($delta == 'example_configurable_text') {
// Have Drupal save the string to the database.
variable_set('block_example_string', $edit['block_example_string']);
}
}
/**
* Implements hook_block_view().
*
* This hook generates the contents of the blocks themselves.
*/
function block_example_block_view($delta = '') {
// The $delta parameter tells us which block is being requested.
switch ($delta) {
case 'example_configurable_text':
// The subject is displayed at the top of the block. Note that it
// should be passed through t() for translation. The title configured
// for the block using Drupal UI supercedes this one.
$block['subject'] = t('Title of first block (example_configurable_text)');
// The content of the block is typically generated by calling a custom
// function.
$block['content'] = block_example_contents($delta);
break;
case 'example_empty':
$block['subject'] = t('Title of second block (example_empty)');
$block['content'] = block_example_contents($delta);
break;
case 'example_uppercase':
$block['subject'] = t("uppercase this please");
$block['content'] = t("This block's title will be changed to uppercase. Any other block with 'uppercase' in the subject or title will also be altered. If you change this block's title through the UI to omit the word 'uppercase', it will still be altered to uppercase as the subject key has not been changed.");
break;
}
return $block;
}
/**
* A module-defined block content function.
*/
function block_example_contents($which_block) {
switch ($which_block) {
case 'example_configurable_text':
// Modules would typically perform some database queries to fetch the
// content for their blocks. Here, we'll just use the variable set in the
// block configuration or, if none has set, a default value.
// Block content can be returned in two formats: renderable arrays
// (as here) are preferred though a simple string will work as well.
// Block content created through the UI defaults to a string.
$result = array(
'#markup' => variable_get('block_example_string',
t('A default value. This block was created at %time',
array('%time' => date('c'))
)
),
);
return $result;
case 'example_empty':
// It is possible that a block not have any content, since it is
// probably dynamically constructed. In this case, Drupal will not display
// the block at all. This block will not be displayed.
return;
}
}
/*
* The following hooks can be used to alter blocks
* provided by your own or other modules.
*/
/**
* Implements hook_block_list_alter().
*
* This hook allows you to add, remove or modify blocks in the block list. The
* block list contains the block definitions. This example requires
* search module and the search block enabled
* to see how this hook implementation works.
*
* You may also be interested in hook_block_info_alter(), which allows changes
* to the behavior of blocks.
*/
function block_example_block_list_alter(&$blocks) {
// We are going to make the search block sticky on bottom of regions. For
// this example, we will modify the block list and append the search block at
// the end of the list, so even if the administrator configures the block to
// be on the top of the region, it will demote to bottom again.
foreach ($blocks as $bid => $block) {
if (($block->module == 'search') && ($block->delta == 'form')) {
// Remove the block from the list and append to the end.
unset($blocks[$bid]);
$blocks[$bid] = $block;
break;
}
}
}
/**
* Implements hook_block_view_alter().
*
* This hook allows you to modify the output of any block in the system.
*
* In addition, instead of hook_block_view_alter(), which is called for all
* blocks, you can also use hook_block_view_MODULE_DELTA_alter() to alter a
* specific block. To change only our block using
* hook_block_view_MODULE_DELTA_alter, we would use the function:
* block_example_block_view_block_example_example_configurable_text_alter()
*
* We are going to uppercase the subject (the title of the block as shown to the
* user) of any block if the string "uppercase" appears in the block title or
* subject. Default block titles are set programmatically in the subject key;
* titles created through the UI are saved in the title key. This module creates
* an example block to demonstrate this effect (default title set
* programmatically as subject). You can also demonstrate the effect of this
* hook by creating a new block whose title has the string 'uppercase' in it
* (set as title through the UI).
*/
function block_example_block_view_alter(&$data, $block) {
// We'll search for the string 'uppercase'.
if ((!empty($block->title) && stristr($block->title, 'uppercase')) || (!empty($data['subject']) && stristr($data['subject'], 'uppercase'))) {
// This will uppercase the default title.
$data['subject'] = isset($data['subject']) ? drupal_strtoupper($data['subject']) : '';
// This will uppercase a title set in the UI.
$block->title = isset($block->title) ? drupal_strtoupper($block->title) : '';
}
}
/**
* @} End of "defgroup block_example".
*/

View File

@@ -0,0 +1,114 @@
<?php
/**
* @file
* Test case for testing the block example module.
*/
/**
* Functional tests for the Block Example module.
*
* @ingroup block_example
*/
class BlockExampleTestCase extends DrupalWebTestCase {
protected $webUser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Block example functionality',
'description' => 'Test the configuration options and block created by Block Example module.',
'group' => 'Examples',
);
}
/**
* Enable modules and create user with specific permissions.
*/
public function setUp() {
parent::setUp('block_example', 'search');
// Create user. Search content permission granted for the search block to
// be shown.
$this->webUser = $this->drupalCreateUser(
array(
'administer blocks',
'search content',
'access contextual links',
)
);
}
/**
* Functional test for our block example.
*
* Login user, create an example node, and test block functionality through
* the admin and user interfaces.
*/
public function testBlockExampleBasic() {
// Login the admin user.
$this->drupalLogin($this->webUser);
// Find the blocks in the settings page.
$this->drupalGet('admin/structure/block');
$this->assertRaw(t('Example: configurable text string'), 'Block configurable-string found.');
$this->assertRaw(t('Example: empty block'), 'Block empty-block found.');
// Verify the default settings for block are processed.
$this->assertFieldByName('blocks[block_example_example_empty][region]', 'sidebar_first', 'Empty block is enabled in first sidebar successfully verified.');
$this->assertFieldByName('blocks[block_example_example_configurable_text][region]', -1, 'Configurable text block is disabled in first sidebar successfully verified.');
// Verify that blocks are not shown.
$this->drupalGet('/');
$this->assertNoRaw(t('Title of first block (example_configurable_text)'), 'Block configurable test not found.');
$this->assertNoRaw(t('Title of second block (example_empty)'), 'Block empty not found.');
// Enable the Configurable text block and verify.
$this->drupalPost('admin/structure/block', array('blocks[block_example_example_configurable_text][region]' => 'sidebar_first'), t('Save blocks'));
$this->assertFieldByName('blocks[block_example_example_configurable_text][region]', 'sidebar_first', 'Configurable text block is enabled in first sidebar successfully verified.');
// Verify that blocks are there. Empty block will not be shown, because it
// is empty.
$this->drupalGet('/');
$this->assertRaw(t('Title of first block (example_configurable_text)'), 'Block configurable text found.');
// Change content of configurable text block.
$string = $this->randomName();
$this->drupalPost('admin/structure/block/manage/block_example/example_configurable_text/configure', array('block_example_string' => $string), t('Save block'));
// Verify that new content is shown.
$this->drupalGet('/');
$this->assertRaw($string, 'Content of configurable text block successfully verified.');
// Make sure our example uppercased block is shown as altered by the
// hook_block_view_alter().
$this->assertRaw(t('UPPERCASE THIS PLEASE'));
// Create a new block and make sure it gets uppercased.
$post = array(
'title' => t('configurable block to be uppercased'),
'info' => t('configurable block to be uppercased'),
'body[value]' => t('body of new block'),
'regions[bartik]' => 'sidebar_first',
);
$this->drupalPost('admin/structure/block/add', $post, t('Save block'));
$this->drupalGet('/');
$this->assertRaw(('CONFIGURABLE BLOCK TO BE UPPERCASED'));
// Verify that search block is at the bottom of the region.
// Enable the search block on top of sidebar_first.
$block_options = array(
'blocks[search_form][region]' => 'sidebar_first',
'blocks[search_form][weight]' => -9,
);
$this->drupalPost('admin/structure/block', $block_options, t('Save blocks'));
// The first 'configure block' link should be from our configurable block,
// the second from the Navigation menu, and the fifth (#4) from
// search block if it was successfully pushed to the bottom.
$this->drupalGet('/');
$this->clickLink('Configure block', 4);
$this->assertText(t("'@search' block", array('@search' => t('Search form'))), 'hook_block_info_alter successfully verified.');
}
}

View File

@@ -0,0 +1,13 @@
name = Cache Example
description = An example outlining how to use Cache API.
package = Example modules
core = 7.x
files[] = cache_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,246 @@
<?php
/**
* @file
* Outlines how a module can use the Cache API.
*
* @todo: Demonstrate cache expiration.
*/
/**
* @defgroup cache_example Example: Cache API
* @ingroup examples
* @{
* Outlines how a module can use the Cache API.
*
* Cache API allows us to cache data that is heavy to calculate. As this can
* significantly speed up the Drupal site, it is recommended to use cache
* mechanism when it is appropriate.
*
* Cache in Drupal is very easy to use. This example will search entire Drupal
* folder and display all files. Since this operation includes filesystem it can
* take a while. This list will not change much on production
* websites, so we decide to cache it.
*
* @see cache_get()
* @see cache_set()
* @see cache_clear_all()
*/
/**
* Implements hook_menu().
*/
function cache_example_menu() {
$items = array();
$items['examples/cache_example'] = array(
'title' => 'Cache example',
'description' => 'Example of Drupal Cache API',
'page callback' => 'drupal_get_form',
'page arguments' => array('cache_example_page_form'),
'access callback' => TRUE,
);
return $items;
}
/**
* Main page for cache_example.
*
* Displays a page/form which outlines how Drupal's cache works.
*/
function cache_example_page_form($form, &$form_state) {
// Log execution time.
$start_time = microtime(TRUE);
// Try to load the files count from cache. This function will accept two
// arguments:
// - cache object name (cid)
// - cache bin, the (optional) cache bin (most often a database table) where
// the object is to be saved.
//
// cache_get() returns the cached object or FALSE if object does not exist.
if ($cache = cache_get('cache_example_files_count')) {
/*
* Get cached data. Complex data types will be unserialized automatically.
*/
$files_count = $cache->data;
}
else {
// If there was no cached data available we have to search filesystem.
// Recursively get all files from Drupal's folder.
$files_count = count(file_scan_directory('.', '/.*/'));
// Since we have recalculated, we now need to store the new data into cache.
// Complex data types will be automatically serialized before being saved
// into cache.
// Here we use the default setting and create an unexpiring cache item.
// See below for an example that creates an expiring cache item.
cache_set('cache_example_files_count', $files_count);
}
$end_time = microtime(TRUE);
$duration = $end_time - $start_time;
// Format intro message.
$intro_message = '<p>' . t('This example will search the entire drupal folder and display a count of the files in it.') . ' ';
$intro_message .= t('This can take a while, since there are a lot of files to be searched.') . ' ';
$intro_message .= t('We will search filesystem just once and save output to the cache. We will use cached data for later requests.') . '</p>';
$intro_message .= '<p>' . t('<a href="@url">Reload this page</a> to see cache in action.', array('@url' => request_uri())) . ' ';
$intro_message .= t('You can use the button below to remove cached data.') . '</p>';
$form['file_search'] = array(
'#type' => 'fieldset',
'#title' => t('File search caching'),
);
$form['file_search']['introduction'] = array(
'#markup' => $intro_message,
);
$color = empty($cache) ? 'red' : 'green';
$retrieval = empty($cache) ? t('calculated by traversing the filesystem') : t('retrieved from cache');
$form['file_search']['statistics'] = array(
'#type' => 'item',
'#markup' => t('%count files exist in this Drupal installation; @retrieval in @time ms. <br/>(Source: <span style="color:@color;">@source</span>)',
array(
'%count' => $files_count,
'@retrieval' => $retrieval,
'@time' => number_format($duration * 1000, 2),
'@color' => $color,
'@source' => empty($cache) ? t('actual file search') : t('cached'),
)
),
);
$form['file_search']['remove_file_count'] = array(
'#type' => 'submit',
'#submit' => array('cache_example_form_expire_files'),
'#value' => t('Explicitly remove cached file count'),
);
$form['expiration_demo'] = array(
'#type' => 'fieldset',
'#title' => t('Cache expiration settings'),
);
$form['expiration_demo']['explanation'] = array(
'#markup' => t('A cache item can be set as CACHE_PERMANENT, meaning that it will only be removed when explicitly cleared, or it can have an expiration time (a Unix timestamp).'),
);
$expiring_item = cache_get('cache_example_expiring_item');
$item_status = $expiring_item ?
t('Cache item exists and is set to expire at %time', array('%time' => $expiring_item->data)) :
t('Cache item does not exist');
$form['expiration_demo']['current_status'] = array(
'#type' => 'item',
'#title' => t('Current status of cache item "cache_example_expiring_item"'),
'#markup' => $item_status,
);
$form['expiration_demo']['expiration'] = array(
'#type' => 'select',
'#title' => t('Time before cache expiration'),
'#options' => array(
'never_remove' => t('CACHE_PERMANENT'),
-10 => t('Immediate expiration'),
10 => t('10 seconds from form submission'),
60 => t('1 minute from form submission'),
300 => t('5 minutes from form submission'),
),
'#default_value' => -10,
'#description' => t('Any cache item can be set to only expire when explicitly cleared, or to expire at a given time.'),
);
$form['expiration_demo']['create_cache_item'] = array(
'#type' => 'submit',
'#value' => t('Create a cache item with this expiration'),
'#submit' => array('cache_example_form_create_expiring_item'),
);
$form['cache_clearing'] = array(
'#type' => 'fieldset',
'#title' => t('Expire and remove options'),
'#description' => t("We have APIs to expire cached items and also to just remove them. Unfortunately, they're all the same API, cache_clear_all"),
);
$form['cache_clearing']['cache_clear_type'] = array(
'#type' => 'radios',
'#title' => t('Type of cache clearing to do'),
'#options' => array(
'expire' => t('Remove items from the "cache" bin that have expired'),
'remove_all' => t('Remove all items from the "cache" bin regardless of expiration (super-wildcard)'),
'remove_wildcard' => t('Remove all items from the "cache" bin that match the pattern "cache_example"'),
),
'#default_value' => 'expire',
);
// Submit button to clear cached data.
$form['cache_clearing']['clear_expired'] = array(
'#type' => 'submit',
'#value' => t('Clear or expire cache'),
'#submit' => array('cache_example_form_cache_clearing'),
'#access' => user_access('administer site configuration'),
);
return $form;
}
/**
* Submit handler that explicitly clears cache_example_files_count from cache.
*/
function cache_example_form_expire_files($form, &$form_state) {
// Clear cached data. This function will delete cached object from cache bin.
//
// The first argument is cache id to be deleted. Since we've provided it
// explicitly, it will be removed whether or not it has an associated
// expiration time. The second argument (required here) is the cache bin.
// Using cache_clear_all() explicitly in this way
// forces removal of the cached item.
cache_clear_all('cache_example_files_count', 'cache');
// Display message to the user.
drupal_set_message(t('Cached data key "cache_example_files_count" was cleared.'), 'status');
}
/**
* Submit handler to create a new cache item with specified expiration.
*/
function cache_example_form_create_expiring_item($form, &$form_state) {
$interval = $form_state['values']['expiration'];
if ($interval == 'never_remove') {
$expiration = CACHE_PERMANENT;
$expiration_friendly = t('Never expires');
}
else {
$expiration = time() + $interval;
$expiration_friendly = format_date($expiration);
}
// Set the expiration to the actual Unix timestamp of the end of the required
// interval.
cache_set('cache_example_expiring_item', $expiration_friendly, 'cache', $expiration);
drupal_set_message(t('cache_example_expiring_item was set to expire at %time', array('%time' => $expiration_friendly)));
}
/**
* Submit handler to demonstrate the various uses of cache_clear_all().
*/
function cache_example_form_cache_clearing($form, &$form_state) {
switch ($form_state['values']['cache_clear_type']) {
case 'expire':
// Here we'll remove all cache keys in the 'cache' bin that have expired.
cache_clear_all(NULL, 'cache');
drupal_set_message(t('cache_clear_all(NULL, "cache") was called, removing any expired cache items.'));
break;
case 'remove_all':
// This removes all keys in a bin using a super-wildcard. This
// has nothing to do with expiration. It's just brute-force removal.
cache_clear_all('*', 'cache', TRUE);
drupal_set_message(t('ALL entries in the "cache" bin were removed with cache_clear_all("*", "cache", TRUE).'));
break;
case 'remove_wildcard':
// We can also explicitly remove all cache items whose cid begins with
// 'cache_example' by using a wildcard. This again is brute-force
// removal, not expiration.
cache_clear_all('cache_example', 'cache', TRUE);
drupal_set_message(t('Cache entries whose cid began with "cache_example" in the "cache" bin were removed with cache_clear_all("cache_example", "cache", TRUE).'));
break;
}
}
/**
* @} End of "defgroup cache_example".
*/

View File

@@ -0,0 +1,81 @@
<?php
/**
* @file
* Test case for testing the cache example module.
*/
/**
* Functional tests for the Cache Example module.
*
* @ingroup cache_example
*/
class CacheExampleTestCase extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Cache example functionality',
'description' => 'Test the Cache Example module.',
'group' => 'Examples',
);
}
/**
* Enable module.
*/
public function setUp() {
parent::setUp('cache_example');
}
/**
* Functional tests for cache_example.
*
* Load cache example page and test if displaying uncached version. Reload
* once again and test if displaying cached version. Find reload link and
* click on it. Clear cache at the end and test if displaying uncached
* version again.
*/
public function testCacheExampleBasic() {
// We need administrative privileges to clear the cache.
$admin_user = $this->drupalCreateUser(array('administer site configuration'));
$this->drupalLogin($admin_user);
// Get uncached output of cache example page and assert some things to be
// sure.
$this->drupalGet('examples/cache_example');
$this->assertText('Source: actual file search');
// Reload the page; the number should be cached.
$this->drupalGet('examples/cache_example');
$this->assertText('Source: cached');
// Now push the button to remove the count.
$this->drupalPost('examples/cache_example', array(), t('Explicitly remove cached file count'));
$this->assertText('Source: actual file search');
// Create a cached item. First make sure it doesn't already exist.
$this->assertText('Cache item does not exist');
$this->drupalPost('examples/cache_example', array('expiration' => -10), t('Create a cache item with this expiration'));
// We should now have an already-expired item.
$this->assertText('Cache item exists and is set to expire');
// Now do the expiration operation.
$this->drupalPost('examples/cache_example', array('cache_clear_type' => 'expire'), t('Clear or expire cache'));
// And verify that it was removed.
$this->assertText('Cache item does not exist');
// Create a cached item. This time we'll make it not expire.
$this->drupalPost('examples/cache_example', array('expiration' => 'never_remove'), t('Create a cache item with this expiration'));
// We should now have an never-remove item.
$this->assertText('Cache item exists and is set to expire at Never expires');
// Now do the expiration operation.
$this->drupalPost('examples/cache_example', array('cache_clear_type' => 'expire'), t('Clear or expire cache'));
// And verify that it was not removed.
$this->assertText('Cache item exists and is set to expire at Never expires');
// Now do full removal.
$this->drupalPost('examples/cache_example', array('cache_clear_type' => 'remove_wildcard'), t('Clear or expire cache'));
// And verify that it was removed.
$this->assertText('Cache item does not exist');
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* @file
* Default theme implementation: Display a sample object with contextual links.
*
* Available variables:
* - $title: The sanitized title of the object.
* - $content: The sanitized content of the object.
* These are defined in template_preprocess_contextual_links_example_object()
* and represent whichever variables you might actually use to display the
* main content of your module's object.
*
* Standard variables (required for contextual links):
* - $classes: String of classes that can be used to style contextually through
* CSS.
* - $title_prefix (array): An array containing additional output populated by
* modules, intended to be displayed in front of the main title tag that
* appears in the template.
* - $title_suffix (array): An array containing additional output populated by
* modules, intended to be displayed after the main title tag that appears in
* the template.
* The above variables are a subset of those which Drupal provides to all
* templates, and they must be printed in your template file in order for
* contextual links to be properly attached. For example, the core Contextual
* Links module adds the renderable contextual links themselves inside
* $title_suffix, so they will appear immediately after the object's title in
* the HTML. (This placement is for accessibility reasons, among others.)
*/
?>
<div class="<?php print $classes; ?>">
<?php print render($title_prefix); ?>
<h2><?php print $title; ?></h2>
<?php print render($title_suffix); ?>
<?php print $content; ?>
</div>

View File

@@ -0,0 +1,12 @@
name = Contextual links example
description = Demonstrates how to use contextual links for enhancing the user experience.
package = Example modules
core = 7.x
files[] = contextual_links_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,388 @@
<?php
/**
* @file
* Shows how to use Drupal's contextual links functionality.
*
* @see http://drupal.org/node/1089922
*/
/**
* @defgroup contextual_links_example Example: Contextual Links
* @ingroup examples
* @{
* Example of implementing contextual links.
*/
/**
* Implements hook_menu().
*
* Drupal's menu system allows you to indicate that particular menu items
* should be displayed as contextual links. If you hover over a block or node
* while logged in as an administrator (and with the Contextual Links module
* enabled) you'll see a small gear icon appear. Click on this icon, and the
* list of items that appears in the exposed menu are what Drupal calls
* "contextual links".
*
* Contextual links allow site administrators to quickly perform actions
* related to elements on a page, without having to hunt through the
* administrative interface. As such, you should usually attach them to objects
* that appear on the main part of a Drupal site and limit them to a few common
* tasks that are frequently performed (for example, "edit" or "configure").
* Do not rely on contextual links being present for your module to work
* correctly, since they are a convenience feature only. Within Drupal core,
* the Contextual Links module must be enabled (and the user viewing the page
* must have the "access contextual links" permission) in order for the
* contextual links corresponding to actions that the user can perform to
* actually be injected into the page's HTML.
*
* Three examples of contextual links are provided here. Although none are
* difficult to implement, they are presented in order of increasing
* complexity:
* - Attaching contextual links to a node.
* - Attaching contextual links to a block.
* - Attaching contextual links to an arbitrary piece of content defined by
* your module.
*
* @see contextual_links_example_block_info()
* @see contextual_links_example_block_view()
* @see contextual_links_overview_page()
*/
function contextual_links_example_menu() {
// First example (attaching contextual links to a node):
//
// Many modules add tabs to nodes underneath the node/<nid> path. If the path
// you are adding corresponds to a commonly performed action on the node, you
// can choose to expose it as a contextual link. Since the Node module
// already has code to display all contextual links underneath the node/<nid>
// path (such as "Edit" and "Delete") when a node is being rendered outside
// of its own page (for example, when a teaser of the node is being displayed
// on the front page of the site), you only need to inform Drupal's menu
// system that your path is a contextual link also, and it will automatically
// appear with the others. In the example below, we add a contextual link
// named "Example action" to the list.
$items['node/%node/example-action'] = array(
'title' => 'Example action',
'page callback' => 'drupal_get_form',
'page arguments' => array('contextual_links_example_node_action_form', 1),
'access callback' => TRUE,
// To be displayed as a contextual link, a menu item should be defined as
// one of the node's local tasks.
'type' => MENU_LOCAL_TASK,
// To make the local task display as a contextual link, specify the
// optional 'context' argument. The most common method is to set both
// MENU_CONTEXT_PAGE and MENU_CONTEXT_INLINE (shown below), which causes
// the link to display as both a tab on the node page and as an entry in
// the contextual links dropdown. This is recommended for most cases
// because not all users who have permission to visit the "Example action"
// page will necessarily have access to contextual links, and they still
// need a way to get to the page via the user interface.
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
// If we give the item a large weight, we can make it display as the last
// tab on the page, as well as the last item inside the contextual links
// dropdown.
'weight' => 80,
);
// Second example (attaching contextual links to a block):
//
// If your module provides content that is displayed in a block, you can
// attach contextual links to the block that allow actions to be performed on
// it. This is useful for administrative pages that affect the content
// wherever it is displayed or used on the site. For configuration options
// that only affect the appearance of the content in the block itself, it is
// better to implement hook_block_configure() rather than creating a separate
// administrative page (this allows your options to appear when an
// administrator clicks the existing "Configure block" contextual link
// already provided by the Block module).
//
// In the code below, we assume that your module has a type of object
// ("contextual links example object") that will be displayed in a block. The
// code below defines menu items for this object using a standard pattern,
// with "View" and "Edit object" as the object's local tasks, and makes the
// "Edit object" item display as a contextual link in addition to a tab. Once
// the contextual links are defined here, additional steps are required to
// actually display the content in a block and attach the contextual links to
// the block itself. This occurs in contextual_links_example_block_info() and
// contextual_links_example_block_view().
$items['examples/contextual-links/%contextual_links_example_object'] = array(
'title' => 'Contextual links example object',
'page callback' => 'contextual_links_example_object_page',
'page arguments' => array(2),
'access callback' => TRUE,
);
$items['examples/contextual-links/%contextual_links_example_object/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['examples/contextual-links/%contextual_links_example_object/edit'] = array(
'title' => 'Edit object',
'page callback' => 'drupal_get_form',
'page arguments' => array('contextual_links_example_object_edit_form', 2),
'access callback' => TRUE,
'type' => MENU_LOCAL_TASK,
// As in our first example, this is the line of code that makes "Edit
// "object" display as a contextual link in addition to as a tab.
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
// Third example (attaching contextual links directly to your module's
// content):
//
// Sometimes your module may want to display its content in an arbitrary
// location and attach contextual links there. For example, you might
// display your content in a listing on its own page and then attach the
// contextual links directly to each piece of content in the listing. Here,
// we will reuse the menu items and contextual links that were defined for
// our example object above, and display them in a listing in
// contextual_links_overview_page().
$items['examples/contextual-links'] = array(
'title' => 'Contextual Links Example',
'page callback' => 'contextual_links_overview_page',
'access callback' => TRUE,
);
return $items;
}
/**
* Menu loader callback for the object defined by this module.
*
* @param int $id
* The ID of the object to load.
*
* @return object|FALSE
* A fully loaded object, or FALSE if the object does not exist.
*/
function contextual_links_example_object_load($id) {
// In a real use case, this function might load an object from the database.
// For the sake of this example, we just define a stub object with a basic
// title and content for any numeric ID that is passed in.
if (is_numeric($id)) {
$object = new stdClass();
$object->id = $id;
$object->title = t('Title for example object @id', array('@id' => $id));
$object->content = t('This is the content of example object @id.', array('@id' => $id));
return $object;
}
else {
return FALSE;
}
}
/**
* Implements hook_block_info().
*/
function contextual_links_example_block_info() {
// Define the block that will display our module's content.
$blocks['example']['info'] = t('Contextual links example block');
return $blocks;
}
/**
* Implements hook_block_view().
*/
function contextual_links_example_block_view($delta = '') {
if ($delta == 'example') {
// Display our module's content inside a block. In a real use case, we
// might define a new block for each object that exists. For the sake of
// this example, though, we only define one block and hardcode it to always
// display object #1.
$id = 1;
$object = contextual_links_example_object_load($id);
$block['subject'] = t('Contextual links example block for object @id', array('@id' => $id));
$block['content'] = array(
// In order to attach contextual links, the block's content must be a
// renderable array. (Normally this would involve themed output using
// #theme, but for simplicity we just use HTML markup directly here.)
'#type' => 'markup',
'#markup' => filter_xss($object->content),
// Contextual links are attached to the block array using the special
// #contextual_links property. The #contextual_links property contains an
// array, keyed by the name of each module that is attaching contextual
// links to it.
'#contextual_links' => array(
'contextual_links_example' => array(
// Each element is itself an array, containing two elements which are
// combined together to form the base path whose contextual links
// should be attached. The two elements are split such that the first
// is the static part of the path and the second is the dynamic part.
// (This split is for performance reasons.) For example, the code
// below tells Drupal to load the menu item corresponding to the path
// "examples/contextual-links/$id" and attach all this item's
// contextual links (which were defined in hook_menu()) to the object
// when it is rendered. If the contextual links you are attaching
// don't have any dynamic elements in their path, you can pass an
// empty array as the second element.
'examples/contextual-links',
array($id),
),
),
);
// Since we are attaching our contextual links to a block, and the Block
// module takes care of rendering the block in such a way that contextual
// links are supported, we do not need to do anything else here. When the
// appropriate conditions are met, the contextual links we have defined
// will automatically appear attached to the block, next to the "Configure
// block" link that the Block module itself provides.
return $block;
}
}
/**
* Menu callback; displays a listing of objects defined by this module.
*
* @see contextual_links_example_theme()
* @see contextual-links-example-object.tpl.php
* @see contextual_links_example_block_view()
*/
function contextual_links_overview_page() {
$build = array();
// For simplicity, we will hardcode this example page to list five of our
// module's objects.
for ($id = 1; $id <= 5; $id++) {
$object = contextual_links_example_object_load($id);
$build[$id] = array(
// To support attaching contextual links to an object that we are
// displaying on our own, the object must be themed in a particular way.
// See contextual_links_example_theme() and
// contextual-links-example-object.tpl.php for more discussion.
'#theme' => 'contextual_links_example_object',
'#object' => $object,
// Contextual links are attached to the block using the special
// #contextual_links property. See contextual_links_example_block_view()
// for discussion of the syntax used here.
'#contextual_links' => array(
'contextual_links_example' => array(
'examples/contextual-links',
array($id),
),
),
);
}
return $build;
}
/**
* Implements hook_theme().
*
* @see template_preprocess_contextual_links_example_object()
*/
function contextual_links_example_theme() {
// The core Contextual Links module imposes two restrictions on how an object
// must be themed in order for it to display the object's contextual links in
// the user interface:
// - The object must use a template file rather than a theme function. See
// contextual-links-example-object.tpl.php for more information on how the
// template file should be structured.
// - The first variable passed to the template must be a renderable array. In
// this case, we accomplish that via the most common method, by passing a
// single renderable element.
return array(
'contextual_links_example_object' => array(
'template' => 'contextual-links-example-object',
'render element' => 'element',
),
);
}
/**
* Process variables for contextual-links-example-object.tpl.php.
*
* @see contextual_links_overview_page()
*/
function template_preprocess_contextual_links_example_object(&$variables) {
// Here we take the object that is being themed and define some useful
// variables that we will print in the template file.
$variables['title'] = filter_xss($variables['element']['#object']->title);
$variables['content'] = filter_xss($variables['element']['#object']->content);
}
/**
* Menu callback; displays an object defined by this module on its own page.
*
* @see contextual_links_overview_page()
*/
function contextual_links_example_object_page($object) {
// Here we render the object but without the #contextual_links property,
// since we don't want contextual links to appear when the object is already
// being displayed on its own page.
$build = array(
'#theme' => 'contextual_links_example_object',
'#object' => $object,
);
return $build;
}
/**
* Form callback; display the form for editing our module's content.
*
* @ingroup forms
* @see contextual_links_example_object_edit_form_submit()
*/
function contextual_links_example_object_edit_form($form, &$form_state, $object) {
$form['text'] = array(
'#markup' => t('This is the page that would allow you to edit object @id.', array('@id' => $object->id)),
'#prefix' => '<p>',
'#suffix' => '</p>',
);
$form['object_id'] = array(
'#type' => 'value',
'#value' => $object->id,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Submit handler for contextual_links_example_object_edit_form().
*/
function contextual_links_example_object_edit_form_submit($form, &$form_state) {
drupal_set_message(t('Object @id was edited.', array('@id' => $form_state['values']['object_id'])));
}
/**
* Form callback; display the form for performing an example action on a node.
*
* @ingroup forms
* @see contextual_links_example_node_action_form_submit()
*/
function contextual_links_example_node_action_form($form, &$form_state, $node) {
$form['text'] = array(
'#markup' => t('This is the page that would allow you to perform an example action on node @nid.', array('@nid' => $node->nid)),
'#prefix' => '<p>',
'#suffix' => '</p>',
);
$form['nid'] = array(
'#type' => 'value',
'#value' => $node->nid,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Submit handler for contextual_links_example_node_action_form().
*/
function contextual_links_example_node_action_form_submit($form, &$form_state) {
drupal_set_message(t('The example action was performed on node @nid.', array('@nid' => $form_state['values']['nid'])));
}
/**
* @} End of "defgroup contextual_links_example".
*/

View File

@@ -0,0 +1,62 @@
<?php
/**
* @file
* Functional tests for the contextual_links_example module.
*/
/**
* Functional tests for the contextual_links_example module.
*
* @ingroup contextual_links_example
*/
class ContextualLinksExampleTestCase extends DrupalWebTestCase {
protected $webUser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Contextual links example functionality',
'description' => 'Tests the behavior of the contextual links provided by the Contextual links example module.',
'group' => 'Examples',
);
}
/**
* Enable modules and create user with specific permissions.
*/
public function setUp() {
parent::setUp('contextual', 'contextual_links_example');
$this->webUser = $this->drupalCreateUser(array('access contextual links', 'administer blocks'));
$this->drupalLogin($this->webUser);
}
/**
* Test the various contextual links that this module defines and displays.
*/
public function testContextualLinksExample() {
// Create a node and promote it to the front page. Then view the front page
// and verify that the "Example action" contextual link works.
$node = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1));
$this->drupalGet('');
$this->clickLink(t('Example action'));
$this->assertUrl('node/' . $node->nid . '/example-action', array('query' => array('destination' => 'node')));
// Visit our example overview page and click the third contextual link.
// This should take us to a page for editing the third object we defined.
$this->drupalGet('examples/contextual-links');
$this->clickLink('Edit object', 2);
$this->assertUrl('examples/contextual-links/3/edit', array('query' => array('destination' => 'examples/contextual-links')));
// Enable our module's block, go back to the front page, and click the
// "Edit object" contextual link that we expect to be there.
$edit['blocks[contextual_links_example_example][region]'] = 'sidebar_first';
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
$this->drupalGet('');
$this->clickLink('Edit object');
$this->assertUrl('examples/contextual-links/1/edit', array('query' => array('destination' => 'node')));
}
}

View File

@@ -0,0 +1,12 @@
name = Cron example
description = Demonstrates hook_cron() and related features
package = Example modules
core = 7.x
files[] = cron_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,266 @@
<?php
/**
* @file
* Demonstrates use of the Cron API in Drupal - hook_cron()
*/
/**
* @defgroup cron_example Example: Cron
* @ingroup examples
* @{
* Example using Cron API, including hook_cron() and hook_cron_queue_info().
*
* This example is part of the Examples for Developers Project
* which you can download and experiment with at
* http://drupal.org/project/examples
*/
/**
* Implements hook_menu().
*/
function cron_example_menu() {
$items['examples/cron_example'] = array(
'title' => 'Cron Example',
'page callback' => 'drupal_get_form',
'page arguments' => array('cron_example_form'),
'access callback' => TRUE,
);
return $items;
}
/**
* The form to provide a link to cron.php.
*/
function cron_example_form($form, &$form_state) {
$form['status'] = array(
'#type' => 'fieldset',
'#title' => t('Cron status information'),
);
$form['status']['intro'] = array(
'#markup' => '<div>' . t('The cron example demonstrates hook_cron() and hook_cron_queue_info() processing. If you have administrative privileges you can run cron from this page and see the results.') . '</div>',
);
$form['status']['last'] = array(
'#markup' => '<div>' . t('cron_example_cron() will next execute the first time cron runs after %time (%seconds seconds from now)',
array(
'%time' => date_iso8601(variable_get('cron_example_next_execution', time())),
'%seconds' => variable_get('cron_example_next_execution', time()) - time(),
)
) . '</div>',
);
if (user_access('administer site configuration')) {
$form['cron_run'] = array(
'#type' => 'fieldset',
'#title' => t('Run cron manually'),
);
$form['cron_run']['cron_reset'] = array(
'#type' => 'checkbox',
'#title' => t("Run cron_example's cron regardless of whether interval has expired."),
'#default_value' => FALSE,
);
$form['cron_run']['cron_trigger'] = array(
'#type' => 'submit',
'#value' => t('Run cron now'),
'#submit' => array('cron_example_form_cron_run_submit'),
);
}
$form['cron_queue_setup'] = array(
'#type' => 'fieldset',
'#title' => t('Cron queue setup (for hook_cron_queue_info(), etc.)'),
);
$queue_1 = DrupalQueue::get('cron_example_queue_1');
$queue_2 = DrupalQueue::get('cron_example_queue_2');
$form['cron_queue_setup']['current_cron_queue_status'] = array(
'#markup' => '<div>' . t('There are currently %queue_1 items in queue 1 and %queue_2 items in queue 2',
array(
'%queue_1' => $queue_1->numberOfItems(),
'%queue_2' => $queue_2->numberOfItems(),
)) . '</div>',
);
$form['cron_queue_setup']['num_items'] = array(
'#type' => 'select',
'#title' => t('Number of items to add to queue'),
'#options' => drupal_map_assoc(array(1, 5, 10, 100, 1000)),
'#default_value' => 5,
);
$form['cron_queue_setup']['queue'] = array(
'#type' => 'radios',
'#title' => t('Queue to add items to'),
'#options' => array(
'cron_example_queue_1' => t('Queue 1'),
'cron_example_queue_2' => t('Queue 2'),
),
'#default_value' => 'cron_example_queue_1',
);
$form['cron_queue_setup']['submit'] = array(
'#type' => 'submit',
'#value' => t('Add jobs to queue'),
'#submit' => array('cron_example_add_jobs_to_queue'),
);
$form['configuration'] = array(
'#type' => 'fieldset',
'#title' => t('Configuration of cron_example_cron()'),
);
$form['configuration']['cron_example_interval'] = array(
'#type' => 'select',
'#title' => t('Cron interval'),
'#description' => t('Time after which cron_example_cron will respond to a processing request.'),
'#default_value' => variable_get('cron_example_interval', 60 * 60),
'#options' => array(
60 => t('1 minute'),
300 => t('5 minutes'),
3600 => t('1 hour'),
60 * 60 * 24 => t('1 day'),
),
);
return system_settings_form($form);
}
/**
* Allow user to directly execute cron, optionally forcing it.
*/
function cron_example_form_cron_run_submit($form, &$form_state) {
if (!empty($form_state['values']['cron_reset'])) {
variable_set('cron_example_next_execution', 0);
}
// We don't usually use globals in this way. This is used here only to
// make it easy to tell if cron was run by this form.
$GLOBALS['cron_example_show_status_message'] = TRUE;
if (drupal_cron_run()) {
drupal_set_message(t('Cron ran successfully.'));
}
else {
drupal_set_message(t('Cron run failed.'), 'error');
}
}
/**
* Submit function used to add the items to the queue.
*/
function cron_example_add_jobs_to_queue($form, &$form_state) {
$queue = $form_state['values']['queue'];
$num_items = $form_state['values']['num_items'];
$queue = DrupalQueue::get($queue);
for ($i = 1; $i <= $num_items; $i++) {
$item = new stdClass();
$item->created = time();
$item->sequence = $i;
$queue->createItem($item);
}
}
/**
* Implements hook_cron().
*
* hook_cron() is the traditional (pre-Drupal 7) hook for doing "background"
* processing. It gets called every time the Drupal cron runs and must decide
* what it will do.
*
* In this example, it does a watchdog() call after the time named in
* the variable 'cron_example_next_execution' has arrived, and then it
* resets that variable to a time in the future.
*/
function cron_example_cron() {
// Default to an hourly interval. Of course, cron has to be running at least
// hourly for this to work.
$interval = variable_get('cron_example_interval', 60 * 60);
// We usually don't want to act every time cron runs (which could be every
// minute) so keep a time for the next run in a variable.
if (time() >= variable_get('cron_example_next_execution', 0)) {
// This is a silly example of a cron job.
// It just makes it obvious that the job has run without
// making any changes to your database.
watchdog('cron_example', 'cron_example ran');
if (!empty($GLOBALS['cron_example_show_status_message'])) {
drupal_set_message(t('cron_example executed at %time', array('%time' => date_iso8601(time(0)))));
}
variable_set('cron_example_next_execution', time() + $interval);
}
}
/**
* Implements hook_cron_queue_info().
*
* hook_cron_queue_info() and family are new since Drupal 7, and allow any
* process to add work to the queue to be acted on when cron runs. Queues are
* described and worker callbacks are provided, and then only the worker
* callback needs to be implemented.
*
* All the details of queue use are done by the cron_queue implementation, so
* one doesn't need to know much about DrupalQueue().
*
* @see queue_example.module
*/
function cron_example_cron_queue_info() {
$queues['cron_example_queue_1'] = array(
'worker callback' => 'cron_example_queue_1_worker',
// One second max runtime per cron run.
'time' => 1,
);
$queues['cron_example_queue_2'] = array(
'worker callback' => 'cron_example_queue_2_worker',
'time' => 10,
);
return $queues;
}
/**
* Simple worker for our queues.
*
* @param object $item
* Any object to be worked on.
*/
function cron_example_queue_1_worker($item) {
cron_example_queue_report_work(1, $item);
}
/**
* Simple worker for our queues.
*
* @param object $item
* Any object to be worked on.
*/
function cron_example_queue_2_worker($item) {
cron_example_queue_report_work(2, $item);
}
/**
* Simple reporter for the workers.
*
* @param int $worker
* Worker number.
* @param object $item
* The $item which was stored in the cron queue.
*/
function cron_example_queue_report_work($worker, $item) {
if (!empty($GLOBALS['cron_example_show_status_message'])) {
drupal_set_message(
t('Queue @worker worker processed item with sequence @sequence created at @time',
array(
'@worker' => $worker,
'@sequence' => $item->sequence,
'@time' => date_iso8601($item->created),
)
)
);
}
watchdog('cron_example', 'Queue @worker worker processed item with sequence @sequence created at @time',
array(
'@worker' => $worker,
'@sequence' => $item->sequence,
'@time' => date_iso8601($item->created),
)
);
}
/**
* @} End of "defgroup cron_example".
*/

View File

@@ -0,0 +1,84 @@
<?php
/**
* @file
* Test case for testing the cron example module.
*/
/**
* cron_example test class
*
* @ingroup cron_example
*/
class CronExampleTestCase extends DrupalWebTestCase {
protected $webUser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Cron example functionality',
'description' => 'Test the functionality of the Cron Example.',
'group' => 'Examples',
);
}
/**
* Enable modules and create user with specific permissions.
*/
public function setUp() {
parent::setUp('cron_example');
// Create user. Search content permission granted for the search block to
// be shown.
$this->webUser = $this->drupalCreateUser(array('administer site configuration'));
$this->drupalLogin($this->webUser);
}
/**
* Test running cron through the user interface.
*/
public function testCronExampleBasic() {
// Pretend that cron has never been run (even though simpletest seems to
// run it once...)
variable_set('cron_example_next_execution', 0);
$this->drupalGet('examples/cron_example');
// Initial run should cause cron_example_cron() to fire.
$post = array();
$this->drupalPost('examples/cron_example', $post, t('Run cron now'));
$this->assertText(t('cron_example executed at'));
// Forcing should also cause cron_example_cron() to fire.
$post['cron_reset'] = TRUE;
$this->drupalPost(NULL, $post, t('Run cron now'));
$this->assertText(t('cron_example executed at'));
// But if followed immediately and not forced, it should not fire.
$post['cron_reset'] = FALSE;
$this->drupalPost(NULL, $post, t('Run cron now'));
$this->assertNoText(t('cron_example executed at'));
$this->assertText(t('There are currently 0 items in queue 1 and 0 items in queue 2'));
$post = array(
'num_items' => 5,
'queue' => 'cron_example_queue_1',
);
$this->drupalPost(NULL, $post, t('Add jobs to queue'));
$this->assertText('There are currently 5 items in queue 1 and 0 items in queue 2');
$post = array(
'num_items' => 100,
'queue' => 'cron_example_queue_2',
);
$this->drupalPost(NULL, $post, t('Add jobs to queue'));
$this->assertText('There are currently 5 items in queue 1 and 100 items in queue 2');
$post = array();
$this->drupalPost('examples/cron_example', $post, t('Run cron now'));
$this->assertPattern('/Queue 1 worker processed item with sequence 5 /');
$this->assertPattern('/Queue 2 worker processed item with sequence 100 /');
}
}
/**
* @} End of "addtogroup cron_example".
*/

View File

@@ -0,0 +1,12 @@
name = DBTNG example
description = An example module showing how use the database API: DBTNG.
package = Example modules
core = 7.x
files[] = dbtng_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,102 @@
<?php
/**
* @file
* Install, update and uninstall functions for the dbtng_example module.
*/
/**
* Implements hook_install().
*
* In Drupal 7, there is no need to install schema using this hook, the schema
* is already installed before this hook is called.
*
* We will create a default entry in the database.
*
* Outside of the .install file we would use drupal_write_record() to populate
* the database, but it cannot be used here, so we'll use db_insert().
*
* @see hook_install()
*
* @ingroup dbtng_example
*/
function dbtng_example_install() {
// Add a default entry.
$fields = array(
'name' => 'John',
'surname' => 'Doe',
'age' => 0,
);
db_insert('dbtng_example')
->fields($fields)
->execute();
// Add another entry.
$fields = array(
'name' => 'John',
'surname' => 'Roe',
'age' => 100,
'uid' => 1,
);
db_insert('dbtng_example')
->fields($fields)
->execute();
}
/**
* Implements hook_schema().
*
* Defines the database tables used by this module.
* Remember that the easiest way to create the code for hook_schema is with
* the @link http://drupal.org/project/schema schema module @endlink
*
* @see hook_schema()
* @ingroup dbtng_example
*/
function dbtng_example_schema() {
$schema['dbtng_example'] = array(
'description' => 'Stores example person entries for demonstration purposes.',
'fields' => array(
'pid' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique person ID.',
),
'uid' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => "Creator user's {users}.uid",
),
'name' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the person.',
),
'surname' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Surname of the person.',
),
'age' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
'description' => 'The age of the person in years.',
),
),
'primary key' => array('pid'),
'indexes' => array(
'name' => array('name'),
'surname' => array('surname'),
'age' => array('age'),
),
);
return $schema;
}

View File

@@ -0,0 +1,579 @@
<?php
/**
* @file
* This is an example outlining how a module can make use of the new DBTNG
* database API in Drupal 7.
*
* @todo Demonstrate transaction usage.
*
* General documentation is available at
* @link database Database abstraction layer documentation @endlink and
* at @link http://drupal.org/node/310069 @endlink.
*/
/**
* @defgroup dbtng_example Example: Database (DBTNG)
* @ingroup examples
* @{
* Database examples, including DBTNG.
*
* 'DBTNG' means 'Database: The Next Generation.' Yes, Drupallers are nerds.
*
* General documentation is available at
* @link database.inc database abstraction layer documentation @endlink and
* at @link http://drupal.org/node/310069 Database API @endlink.
*
* The several examples here demonstrate basic database usage.
*
* In Drupal 6, the recommended method to save or update an entry in the
* database was drupal_write_record() or db_query().
*
* In Drupal 7 and forward, the usage of db_query()
* for INSERT, UPDATE, or DELETE is deprecated, because it is
* database-dependent. Instead specific functions are provided to perform these
* operations: db_insert(), db_update(), and db_delete() do the job now.
* (Note that drupal_write_record() is also deprecated.)
*
* db_insert() example:
* @code
* // INSERT INTO {dbtng_example} (name, surname) VALUES('John, 'Doe')
* db_insert('dbtng_example')
* ->fields(array('name' => 'John', 'surname' => 'Doe'))
* ->execute();
* @endcode
*
* db_update() example:
* @code
* // UPDATE {dbtng_example} SET name = 'Jane' WHERE name = 'John'
* db_update('dbtng_example')
* ->fields(array('name' => 'Jane'))
* ->condition('name', 'John')
* ->execute();
* @endcode
*
* db_delete() example:
* @code
* // DELETE FROM {dbtng_example} WHERE name = 'Jane'
* db_delete('dbtng_example')
* ->condition('name', 'Jane')
* ->execute();
* @endcode
*
* See @link database Database Abstraction Layer @endlink
* @see db_insert()
* @see db_update()
* @see db_delete()
* @see drupal_write_record()
*/
/**
* Save an entry in the database.
*
* The underlying DBTNG function is db_insert().
*
* In Drupal 6, this would have been:
* @code
* db_query(
* "INSERT INTO {dbtng_example} (name, surname, age)
* VALUES ('%s', '%s', '%d')",
* $entry['name'],
* $entry['surname'],
* $entry['age']
* );
* @endcode
*
* Exception handling is shown in this example. It could be simplified
* without the try/catch blocks, but since an insert will throw an exception
* and terminate your application if the exception is not handled, it is best
* to employ try/catch.
*
* @param array $entry
* An array containing all the fields of the database record.
*
* @see db_insert()
*/
function dbtng_example_entry_insert($entry) {
$return_value = NULL;
try {
$return_value = db_insert('dbtng_example')
->fields($entry)
->execute();
}
catch (Exception $e) {
drupal_set_message(t('db_insert failed. Message = %message, query= %query',
array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error');
}
return $return_value;
}
/**
* Update an entry in the database.
*
* The former, deprecated techniques used db_query() or drupal_write_record():
* @code
* drupal_write_record('dbtng_example', $entry, $entry['pid']);
* @endcode
*
* @code
* db_query(
* "UPDATE {dbtng_example}
* SET name = '%s', surname = '%s', age = '%d'
* WHERE pid = %d",
* $entry['pid']
* );
* @endcode
*
* @param array $entry
* An array containing all the fields of the item to be updated.
*
* @see db_update()
*/
function dbtng_example_entry_update($entry) {
try {
// db_update()...->execute() returns the number of rows updated.
$count = db_update('dbtng_example')
->fields($entry)
->condition('pid', $entry['pid'])
->execute();
}
catch (Exception $e) {
drupal_set_message(t('db_update failed. Message = %message, query= %query',
array('%message' => $e->getMessage(), '%query' => $e->query_string)), 'error');
}
return $count;
}
/**
* Delete an entry from the database.
*
* The usage of db_query is deprecated except for static queries.
* Formerly, a deletion might have been accomplished like this:
* @code
* db_query("DELETE FROM {dbtng_example} WHERE pid = %d", $entry['pid]);
* @endcode
*
* @param array $entry
* An array containing at least the person identifier 'pid' element of the
* entry to delete.
*
* @see db_delete()
*/
function dbtng_example_entry_delete($entry) {
db_delete('dbtng_example')
->condition('pid', $entry['pid'])
->execute();
}
/**
* Read from the database using a filter array.
*
* In Drupal 6, the standard function to perform reads was db_query(), and
* for static queries, it still is.
*
* db_query() used an SQL query with placeholders and arguments as parameters.
*
* @code
* // Old way
* $query = "SELECT * FROM {dbtng_example} n WHERE n.uid = %d AND name = '%s'";
* $result = db_query($query, $uid, $name);
* @endcode
*
* Drupal 7 DBTNG provides an abstracted interface that will work with a wide
* variety of database engines.
*
* db_query() is deprecated except when doing a static query. The following is
* perfectly acceptable in Drupal 7. See
* @link http://drupal.org/node/310072 the handbook page on static queries @endlink
*
* @code
* // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
* db_query(
* "SELECT * FROM {dbtng_example} WHERE uid = :uid and name = :name",
* array(':uid' => 0, ':name' => 'John')
* )->execute();
* @endcode
*
* But for more dynamic queries, Drupal provides the db_select() API method, so
* there are several ways to perform the same SQL query. See the
* @link http://drupal.org/node/310075 handbook page on dynamic queries. @endlink
*
* @code
* // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
* db_select('dbtng_example')
* ->fields('dbtng_example')
* ->condition('uid', 0)
* ->condition('name', 'John')
* ->execute();
* @endcode
*
* Here is db_select with named placeholders:
* @code
* // SELECT * FROM {dbtng_example} WHERE uid = 0 AND name = 'John'
* $arguments = array(':name' => 'John', ':uid' => 0);
* db_select('dbtng_example')
* ->fields('dbtng_example')
* ->where('uid = :uid AND name = :name', $arguments)
* ->execute();
* @endcode
*
* Conditions are stacked and evaluated as AND and OR depending on the type of
* query. For more information, read the conditional queries handbook page at:
* http://drupal.org/node/310086
*
* The condition argument is an 'equal' evaluation by default, but this can be
* altered:
* @code
* // SELECT * FROM {dbtng_example} WHERE age > 18
* db_select('dbtng_example')
* ->fields('dbtng_example')
* ->condition('age', 18, '>')
* ->execute();
* @endcode
*
* @param array $entry
* An array containing all the fields used to search the entries in the table.
*
* @return object
* An object containing the loaded entries if found.
*
* @see db_select()
* @see db_query()
* @see http://drupal.org/node/310072
* @see http://drupal.org/node/310075
*/
function dbtng_example_entry_load($entry = array()) {
// Read all fields from the dbtng_example table.
$select = db_select('dbtng_example', 'example');
$select->fields('example');
// Add each field and value as a condition to this query.
foreach ($entry as $field => $value) {
$select->condition($field, $value);
}
// Return the result in object format.
return $select->execute()->fetchAll();
}
/**
* Render a filtered list of entries in the database.
*
* DBTNG also helps processing queries that return several rows, providing the
* found objects in the same query execution call.
*
* This function queries the database using a JOIN between users table and the
* example entries, to provide the username that created the entry, and creates
* a table with the results, processing each row.
*
* SELECT
* e.pid as pid, e.name as name, e.surname as surname, e.age as age
* u.name as username
* FROM
* {dbtng_example} e
* JOIN
* users u ON e.uid = u.uid
* WHERE
* e.name = 'John' AND e.age > 18
*
* @see db_select()
* @see http://drupal.org/node/310075
*/
function dbtng_example_advanced_list() {
$output = '';
$select = db_select('dbtng_example', 'e');
// Join the users table, so we can get the entry creator's username.
$select->join('users', 'u', 'e.uid = u.uid');
// Select these specific fields for the output.
$select->addField('e', 'pid');
$select->addField('u', 'name', 'username');
$select->addField('e', 'name');
$select->addField('e', 'surname');
$select->addField('e', 'age');
// Filter only persons named "John".
$select->condition('e.name', 'John');
// Filter only persons older than 18 years.
$select->condition('e.age', 18, '>');
// Make sure we only get items 0-49, for scalability reasons.
$select->range(0, 50);
// Now, loop all these entries and show them in a table. Note that there is no
// db_fetch_* object or array function being called here. Also note that the
// following line could have been written as
// $entries = $select->execute()->fetchAll() which would return each selected
// record as an object instead of an array.
$entries = $select->execute()->fetchAll(PDO::FETCH_ASSOC);
if (!empty($entries)) {
$rows = array();
foreach ($entries as $entry) {
// Sanitize the data before handing it off to the theme layer.
$rows[] = array_map('check_plain', $entry);
}
// Make a table for them.
$header = array(t('Id'), t('Created by'), t('Name'), t('Surname'), t('Age'));
$output .= theme('table', array('header' => $header, 'rows' => $rows));
}
else {
drupal_set_message(t('No entries meet the filter criteria (Name = "John" and Age > 18).'));
}
return $output;
}
/**
* Implements hook_help().
*
* Show some help on each form provided by this module.
*/
function dbtng_example_help($path) {
$output = '';
switch ($path) {
case 'examples/dbtng':
$output = t('Generate a list of all entries in the database. There is no filter in the query.');
break;
case 'examples/dbtng/advanced':
$output = t('A more complex list of entries in the database.') . ' ';
$output .= t('Only the entries with name = "John" and age older than 18 years are shown, the username of the person who created the entry is also shown.');
break;
case 'examples/dbtng/update':
$output = t('Demonstrates a database update operation.');
break;
case 'examples/dbtng/add':
$output = t('Add an entry to the dbtng_example table.');
break;
}
return $output;
}
/**
* Implements hook_menu().
*
* Set up calls to drupal_get_form() for all our example cases.
*/
function dbtng_example_menu() {
$items = array();
$items['examples/dbtng'] = array(
'title' => 'DBTNG Example',
'page callback' => 'dbtng_example_list',
'access callback' => TRUE,
);
$items['examples/dbtng/list'] = array(
'title' => 'List',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['examples/dbtng/add'] = array(
'title' => 'Add entry',
'page callback' => 'drupal_get_form',
'page arguments' => array('dbtng_example_form_add'),
'access callback' => TRUE,
'type' => MENU_LOCAL_TASK,
'weight' => -9,
);
$items['examples/dbtng/update'] = array(
'title' => 'Update entry',
'page callback' => 'drupal_get_form',
'page arguments' => array('dbtng_example_form_update'),
'type' => MENU_LOCAL_TASK,
'access callback' => TRUE,
'weight' => -5,
);
$items['examples/dbtng/advanced'] = array(
'title' => 'Advanced list',
'page callback' => 'dbtng_example_advanced_list',
'access callback' => TRUE,
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Render a list of entries in the database.
*/
function dbtng_example_list() {
$output = '';
// Get all entries in the dbtng_example table.
if ($entries = dbtng_example_entry_load()) {
$rows = array();
foreach ($entries as $entry) {
// Sanitize the data before handing it off to the theme layer.
$rows[] = array_map('check_plain', (array) $entry);
}
// Make a table for them.
$header = array(t('Id'), t('uid'), t('Name'), t('Surname'), t('Age'));
$output .= theme('table', array('header' => $header, 'rows' => $rows));
}
else {
drupal_set_message(t('No entries have been added yet.'));
}
return $output;
}
/**
* Prepare a simple form to add an entry, with all the interesting fields.
*/
function dbtng_example_form_add($form, &$form_state) {
$form = array();
$form['add'] = array(
'#type' => 'fieldset',
'#title' => t('Add a person entry'),
);
$form['add']['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#size' => 15,
);
$form['add']['surname'] = array(
'#type' => 'textfield',
'#title' => t('Surname'),
'#size' => 15,
);
$form['add']['age'] = array(
'#type' => 'textfield',
'#title' => t('Age'),
'#size' => 5,
'#description' => t("Values greater than 127 will cause an exception. Try it - it's a great example why exception handling is needed with DTBNG."),
);
$form['add']['submit'] = array(
'#type' => 'submit',
'#value' => t('Add'),
);
return $form;
}
/**
* Submit handler for 'add entry' form.
*/
function dbtng_example_form_add_submit($form, &$form_state) {
global $user;
// Save the submitted entry.
$entry = array(
'name' => $form_state['values']['name'],
'surname' => $form_state['values']['surname'],
'age' => $form_state['values']['age'],
'uid' => $user->uid,
);
$return = dbtng_example_entry_insert($entry);
if ($return) {
drupal_set_message(t("Created entry @entry", array('@entry' => print_r($entry, TRUE))));
}
}
/**
* Sample UI to update a record.
*/
function dbtng_example_form_update($form, &$form_state) {
$form = array(
'#prefix' => '<div id="updateform">',
'#suffix' => '</div>',
);
$entries = dbtng_example_entry_load();
$keyed_entries = array();
if (empty($entries)) {
$form['no_values'] = array(
'#value' => t("No entries exist in the table dbtng_example table."),
);
return $form;
}
foreach ($entries as $entry) {
$options[$entry->pid] = t("@pid: @name @surname (@age)",
array(
'@pid' => $entry->pid,
'@name' => $entry->name,
'@surname' => $entry->surname,
'@age' => $entry->age,
)
);
$keyed_entries[$entry->pid] = $entry;
}
$default_entry = !empty($form_state['values']['pid']) ? $keyed_entries[$form_state['values']['pid']] : $entries[0];
$form_state['entries'] = $keyed_entries;
$form['pid'] = array(
'#type' => 'select',
'#options' => $options,
'#title' => t('Choose entry to update'),
'#default_value' => $default_entry->pid,
'#ajax' => array(
'wrapper' => 'updateform',
'callback' => 'dbtng_example_form_update_callback',
),
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Updated first name'),
'#size' => 15,
'#default_value' => $default_entry->name,
);
$form['surname'] = array(
'#type' => 'textfield',
'#title' => t('Updated last name'),
'#size' => 15,
'#default_value' => $default_entry->surname,
);
$form['age'] = array(
'#type' => 'textfield',
'#title' => t('Updated age'),
'#size' => 4,
'#default_value' => $default_entry->age,
'#description' => t("Values greater than 127 will cause an exception"),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Update'),
);
return $form;
}
/**
* AJAX callback handler for the pid select.
*
* When the pid changes, populates the defaults from the database in the form.
*/
function dbtng_example_form_update_callback($form, $form_state) {
$entry = $form_state['entries'][$form_state['values']['pid']];
// Setting the #value of items is the only way I was able to figure out
// to get replaced defaults on these items. #default_value will not do it
// and shouldn't.
foreach (array('name', 'surname', 'age') as $item) {
$form[$item]['#value'] = $entry->$item;
}
return $form;
}
/**
* Submit handler for 'update entry' form.
*/
function dbtng_example_form_update_submit($form, &$form_state) {
global $user;
// Save the submitted entry.
$entry = array(
'pid' => $form_state['values']['pid'],
'name' => $form_state['values']['name'],
'surname' => $form_state['values']['surname'],
'age' => $form_state['values']['age'],
'uid' => $user->uid,
);
$count = dbtng_example_entry_update($entry);
drupal_set_message(t("Updated entry @entry (@count row updated)",
array('@count' => $count, '@entry' => print_r($entry, TRUE))));
}
/**
* @} End of "defgroup dbtng_example".
*/

View File

@@ -0,0 +1,191 @@
<?php
/**
* @file
* SimpleTests for dbtng_example module.
*/
/**
* Default test case for the dbtng_example module.
*
* @ingroup dbtng_example
*/
class DBTNGExampleUnitTestCase extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'DBTNG example unit and UI tests',
'description' => 'Various unit tests on the dbtng example module.' ,
'group' => 'Examples',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp('dbtng_example');
}
/**
* Test default module installation, two entries in the database table.
*/
public function testInstall() {
$result = dbtng_example_entry_load();
$this->assertEqual(
count($result),
2,
'Found two entries in the table after installing the module.'
);
}
/**
* Test the UI.
*/
public function testUI() {
// Test the basic list.
$this->drupalGet('examples/dbtng');
$this->assertPattern("/John[td\/<>\w]+Doe/", "Text 'John Doe' found in table");
// Test the add tab.
// Add the new entry.
$this->drupalPost('examples/dbtng/add',
array(
'name' => 'Some',
'surname' => 'Anonymous',
'age' => 33,
),
t('Add')
);
// Now find the new entry.
$this->drupalGet('examples/dbtng');
$this->assertPattern("/Some[td\/<>\w]+Anonymous/", "Text 'Some Anonymous' found in table");
// Try the update tab.
// Find out the pid of our "anonymous" guy.
$result = dbtng_example_entry_load(array('surname' => 'Anonymous'));
$this->drupalGet("examples/dbtng");
$this->assertEqual(
count($result),
1,
'Found one entry in the table with surname = "Anonymous".'
);
$entry = $result[0];
unset($entry->uid);
$entry->name = 'NewFirstName';
$this->drupalPost('examples/dbtng/update', (array) $entry, t('Update'));
// Now find the new entry.
$this->drupalGet('examples/dbtng');
$this->assertPattern("/NewFirstName[td\/<>\w]+Anonymous/", "Text 'NewFirstName Anonymous' found in table");
// Try the advanced tab.
$this->drupalGet('examples/dbtng/advanced');
$rows = $this->xpath("//*[@id='block-system-main']/div/table[1]/tbody/tr");
$this->assertEqual(count($rows), 1, "One row found in advanced view");
$this->assertFieldByXPath("//*[@id='block-system-main']/div/table[1]/tbody/tr/td[4]", "Roe", "Name 'Roe' Exists in advanced list");
}
/**
* Test several combinations, adding entries, updating and deleting.
*/
public function testAPIExamples() {
// Create a new entry.
$entry = array(
'name' => 'James',
'surname' => 'Doe',
'age' => 23,
);
dbtng_example_entry_insert($entry);
// Save another entry.
$entry = array(
'name' => 'Jane',
'surname' => 'NotDoe',
'age' => 19,
);
dbtng_example_entry_insert($entry);
// Verify that 4 records are found in the database.
$result = dbtng_example_entry_load();
$this->assertEqual(
count($result),
4,
'Found a total of four entries in the table after creating two additional entries.'
);
// Verify 2 of these records have 'Doe' as surname.
$result = dbtng_example_entry_load(array('surname' => 'Doe'));
$this->assertEqual(
count($result),
2,
'Found two entries in the table with surname = "Doe".'
);
// Now find our not-Doe entry.
$result = dbtng_example_entry_load(array('surname' => 'NotDoe'));
$this->assertEqual(
count($result),
1,
'Found one entry in the table with surname "NotDoe');
// Our NotDoe will be changed to "NowDoe".
$entry = $result[0];
$entry->surname = "NowDoe";
dbtng_example_entry_update((array) $entry);
$result = dbtng_example_entry_load(array('surname' => 'NowDoe'));
$this->assertEqual(
count($result),
1,
"Found renamed 'NowDoe' surname");
// Read only John Doe entry.
$result = dbtng_example_entry_load(array('name' => 'John', 'surname' => 'Doe'));
$this->assertEqual(
count($result),
1,
'Found one entry for John Doe.'
);
// Get the entry.
$entry = (array) end($result);
// Change age to 45
$entry['age'] = 45;
// Update entry in database.
dbtng_example_entry_update((array) $entry);
// Find entries with age = 45
// Read only John Doe entry.
$result = dbtng_example_entry_load(array('surname' => 'NowDoe'));
$this->assertEqual(
count($result),
1,
'Found one entry with surname = Nowdoe.'
);
// Verify it is Jane NowDoe.
$entry = (array) end($result);
$this->assertEqual(
$entry['name'],
'Jane',
'The name Jane is found in the entry'
);
$this->assertEqual(
$entry['surname'],
'NowDoe',
'The surname NowDoe is found in the entry'
);
// Delete the entry.
dbtng_example_entry_delete($entry);
// Verify that now there are only 3 records.
$result = dbtng_example_entry_load();
$this->assertEqual(
count($result),
3,
'Found only three records, a record was deleted.'
);
}
}

View File

@@ -0,0 +1,12 @@
name = E-mail Example
description = Demonstrate Drupal's e-mail APIs.
package = Example modules
core = 7.x
files[] = email_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,212 @@
<?php
/**
* @file
* Example of how to use Drupal's mail API.
*/
/**
* @defgroup email_example Example: Email
* @ingroup examples
* @{
* Example of how to use Drupal's mail API.
*
* This example module provides two different examples of the Drupal email API:
* - Defines a simple contact form and shows how to use drupal_mail()
* to send an e-mail (defined in hook_mail()) when the form is submitted.
* - Shows how modules can alter emails defined by other Drupal modules or
* Core using hook_mail_alter by attaching a custom signature before
* they are sent.
*/
/**
* Implements hook_mail().
*
* This hook defines a list of possible e-mail templates that this module can
* send. Each e-mail is given a unique identifier, or 'key'.
*
* $message comes in with some standard properties already set: 'to' address,
* 'from' address, and a set of default 'headers' from drupal_mail(). The goal
* of hook_mail() is to set the message's 'subject' and 'body' properties, as
* well as make any adjustments to the headers that are necessary.
*
* The $params argument is an array which can hold any additional data required
* to build the mail subject and body; for example, user-entered form data, or
* some context information as to where the mail request came from.
*
* Note that hook_mail() is not actually a hook. It is only called for a single
* module, the module named in the first argument of drupal_mail(). So it's
* a callback of a type, but not a hook.
*/
function email_example_mail($key, &$message, $params) {
global $user;
// Each message is associated with a language, which may or may not be the
// current user's selected language, depending on the type of e-mail being
// sent. This $options array is used later in the t() calls for subject
// and body to ensure the proper translation takes effect.
$options = array(
'langcode' => $message['language']->language,
);
switch ($key) {
// Send a simple message from the contact form.
case 'contact_message':
$message['subject'] = t('E-mail sent from @site-name', array('@site-name' => variable_get('site_name', 'Drupal')), $options);
// Note that the message body is an array, not a string.
$message['body'][] = t('@name sent you the following message:', array('@name' => $user->name), $options);
// Because this is just user-entered text, we do not need to translate it.
// Since user-entered text may have unintentional HTML entities in it like
// '<' or '>', we need to make sure these entities are properly escaped,
// as the body will later be transformed from HTML to text, meaning
// that a normal use of '<' will result in truncation of the message.
$message['body'][] = check_plain($params['message']);
break;
}
}
/**
* Sends an e-mail.
*
* @param array $form_values
* An array of values from the contact form fields that were submitted.
* There are just two relevant items: $form_values['email'] and
* $form_values['message'].
*/
function email_example_mail_send($form_values) {
// All system mails need to specify the module and template key (mirrored from
// hook_mail()) that the message they want to send comes from.
$module = 'email_example';
$key = 'contact_message';
// Specify 'to' and 'from' addresses.
$to = $form_values['email'];
$from = variable_get('site_mail', 'admin@example.com');
// "params" loads in additional context for email content completion in
// hook_mail(). In this case, we want to pass in the values the user entered
// into the form, which include the message body in $form_values['message'].
$params = $form_values;
// The language of the e-mail. This will one of three values:
// - user_preferred_language(): Used for sending mail to a particular website
// user, so that the mail appears in their preferred language.
// - global $language: Used when sending a mail back to the user currently
// viewing the site. This will send it in the language they're currently
// using.
// - language_default(): Used when sending mail to a pre-existing, 'neutral'
// address, such as the system e-mail address, or when you're unsure of the
// language preferences of the intended recipient.
//
// Since in our case, we are sending a message to a random e-mail address that
// is not necessarily tied to a user account, we will use the site's default
// language.
$language = language_default();
// Whether or not to automatically send the mail when drupal_mail() is
// called. This defaults to TRUE, and is normally what you want unless you
// need to do additional processing before drupal_mail_send() is called.
$send = TRUE;
// Send the mail, and check for success. Note that this does not guarantee
// message delivery; only that there were no PHP-related issues encountered
// while sending.
$result = drupal_mail($module, $key, $to, $language, $params, $from, $send);
if ($result['result'] == TRUE) {
drupal_set_message(t('Your message has been sent.'));
}
else {
drupal_set_message(t('There was a problem sending your message and it was not sent.'), 'error');
}
}
/**
* Implements hook_mail_alter().
*
* This function is not required to send an email using Drupal's mail system.
*
* Hook_mail_alter() provides an interface to alter any aspect of email sent by
* Drupal. You can use this hook to add a common site footer to all outgoing
* email, add extra header fields, and/or modify the email in anyway. HTML-izing
* the outgoing email is one possibility.
*/
function email_example_mail_alter(&$message) {
// For the purpose of this example, modify all the outgoing messages and
// attach a site signature. The signature will be translated to the language
// in which message was built.
$options = array(
'langcode' => $message['language']->language,
);
$signature = t("\n--\nMail altered by email_example module.", array(), $options);
if (is_array($message['body'])) {
$message['body'][] = $signature;
}
else {
// Some modules use the body as a string, erroneously.
$message['body'] .= $signature;
}
}
/**
* Supporting functions.
*/
/**
* Implements hook_menu().
*
* Set up a page with an e-mail contact form on it.
*/
function email_example_menu() {
$items['example/email_example'] = array(
'title' => 'E-mail Example: contact form',
'page callback' => 'drupal_get_form',
'page arguments' => array('email_example_form'),
'access arguments' => array('access content'),
);
return $items;
}
/**
* The contact form.
*/
function email_example_form() {
$form['intro'] = array(
'#markup' => t('Use this form to send a message to an e-mail address. No spamming!'),
);
$form['email'] = array(
'#type' => 'textfield',
'#title' => t('E-mail address'),
'#required' => TRUE,
);
$form['message'] = array(
'#type' => 'textarea',
'#title' => t('Message'),
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Form validation logic for the contact form.
*/
function email_example_form_validate($form, &$form_state) {
if (!valid_email_address($form_state['values']['email'])) {
form_set_error('email', t('That e-mail address is not valid.'));
}
}
/**
* Form submission logic for the contact form.
*/
function email_example_form_submit($form, &$form_state) {
email_example_mail_send($form_state['values']);
}
/**
* @} End of "defgroup email_example".
*/

View File

@@ -0,0 +1,103 @@
<?php
/**
* @file
* Simpletest case for email_example module.
*
* Verify example module functionality.
*/
/**
* Functionality tests for email example module.
*
* @ingroup email_example
*/
class EmailExampleTestCase extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Email example',
'description' => 'Verify the email submission using the contact form.',
'group' => 'Examples',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
// Enable the email_example module.
parent::setUp('email_example');
}
/**
* Verify the functionality of the example module.
*/
public function testContactForm() {
// Create and login user.
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
// Set default language for t() translations.
$t_options = array(
'langcode' => language_default()->language,
);
// First try to send to an invalid email address.
$email_options = array(
'email' => $this->randomName(),
'message' => $this->randomName(128),
);
$result = $this->drupalPost('example/email_example', $email_options, t('Submit'));
// Verify that email address is invalid and email was not sent.
$this->assertText(t('That e-mail address is not valid.'), 'Options were validated and form submitted.');
$this->assertTrue(!count($this->drupalGetMails()), 'No email was sent.');
// Now try with a valid email address.
$email_options['email'] = $this->randomName() . '@' . $this->randomName() . '.drupal';
$result = $this->drupalPost('example/email_example', $email_options, t('Submit'));
// Verify that email address is valid and email was sent.
$this->assertTrue(count($this->drupalGetMails()), 'An email has been sent.');
// Validate sent email.
$email = $this->drupalGetMails();
// Grab the first entry.
$email = $email[0];
// Verify email recipient.
$this->assertEqual(
$email['to'],
$email_options['email'],
'Email recipient successfully verified.'
);
// Verify email subject.
$this->assertEqual(
$email['subject'],
t('E-mail sent from @site-name', array('@site-name' => variable_get('site_name', 'Drupal')), $t_options),
'Email subject successfully verified.'
);
// Verify email body.
$this->assertTrue(
strstr(
$email['body'],
t('@name sent you the following message:', array('@name' => $account->name), $t_options)
),
'Email body successfully verified.'
);
// Verify that signature is attached.
$this->assertTrue(
strstr(
$email['body'],
t("--\nMail altered by email_example module.", array(), $t_options)
),
'Email signature successfully verified.'
);
}
}

View File

@@ -0,0 +1,14 @@
name = Entity Example
description = A simple entity example showing the main steps required to set up your own entity.
core = 7.x
package = Example modules
dependencies[] = field
files[] = entity_example.test
configure = admin/structure/entity_example_basic/manage
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,71 @@
<?php
/**
* @file
* Install for a basic entity - need to create the base table for our entity.
* This table can have as many columns as you need to keep track of
* entity-specific data that will not be added via attached fields.
* The minimum information for the entity to work is an id and an entity name.
*/
/**
* Implements hook_schema().
*
* @ingroup entity_example
*/
function entity_example_schema() {
$schema = array();
// The name of the table can be any name we choose. However, namespacing the
// table with the module name is best practice.
$schema['entity_example_basic'] = array(
'description' => 'The base table for our basic entity.',
'fields' => array(
'basic_id' => array(
'description' => 'Primary key of the basic entity.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
// If we allow multiple bundles, then the schema must handle that;
// We'll put it in the 'bundle_type' field to avoid confusion with the
// entity type.
'bundle_type' => array(
'description' => 'The bundle type',
'type' => 'text',
'size' => 'medium',
'not null' => TRUE,
),
// Additional properties are just things that are common to all
// entities and don't require field storage.
'item_description' => array(
'description' => 'A description of the item',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'created' => array(
'description' => 'The Unix timestamp of the entity creation time.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('basic_id'),
);
return $schema;
}
/**
* Implements hook_uninstall().
*
* At uninstall time we'll notify field.module that the entity was deleted
* so that attached fields can be cleaned up.
*
* @ingroup entity_example
*/
function entity_example_uninstall() {
field_attach_delete_bundle('entity_example_basic', 'first_example_bundle');
}

View File

@@ -0,0 +1,635 @@
<?php
/**
* @file
* Implements the basic functionality required to create and display an entity.
*
* This example does not use the
* @link http://drupal.org/project/entity Entity API module @endlink, which is
* used by many entity implementations and is recommended by many.
*
* An example of use of creating and managing entities using the Entity API
* module is provided in the
* @link http://drupal.org/project/model Model Entity module @endlink.
*
* @todo: Reference the ronald_istos article series
* @todo: Reference the Drupal module development book
* @todo: Add a single field
*/
/**
* @defgroup entity_example Example: Entity
* @ingroup examples
* @{
* Example creating a core Entity API entity.
*
* Note that this example does not use or demonstrate the contrib Entity API,
* which you can find here: http://drupal.org/project/entity
*/
/**
* Implements hook_entity_info().
*
* This is the fundamental description of the entity.
*
* It provides a single entity with a single bundle and without revision
* support.
*/
function entity_example_entity_info() {
$info['entity_example_basic'] = array(
// A human readable label to identify our entity.
'label' => t('Example Basic Entity'),
// The controller for our Entity, extending the Drupal core controller.
'controller class' => 'EntityExampleBasicController',
// The table for this entity defined in hook_schema()
'base table' => 'entity_example_basic',
// Returns the uri elements of an entity.
'uri callback' => 'entity_example_basic_uri',
// IF fieldable == FALSE, we can't attach fields.
'fieldable' => TRUE,
// entity_keys tells the controller what database fields are used for key
// functions. It is not required if we don't have bundles or revisions.
// Here we do not support a revision, so that entity key is omitted.
'entity keys' => array(
// The 'id' (basic_id here) is the unique id.
'id' => 'basic_id' ,
// Bundle will be determined by the 'bundle_type' field.
'bundle' => 'bundle_type',
),
'bundle keys' => array(
'bundle' => 'bundle_type',
),
// FALSE disables caching. Caching functionality is handled by Drupal core.
'static cache' => TRUE,
// Bundles are alternative groups of fields or configuration
// associated with a base entity type.
'bundles' => array(
'first_example_bundle' => array(
'label' => 'First example bundle',
// 'admin' key is used by the Field UI to provide field and
// display UI pages.
'admin' => array(
'path' => 'admin/structure/entity_example_basic/manage',
'access arguments' => array('administer entity_example_basic entities'),
),
),
),
// View modes allow entities to be displayed differently based on context.
// As a demonstration we'll support "Tweaky", but we could have and support
// multiple display modes.
'view modes' => array(
'tweaky' => array(
'label' => t('Tweaky'),
'custom settings' => FALSE,
),
),
);
return $info;
}
/**
* Fetch a basic object.
*
* This function ends up being a shim between the menu system and
* entity_example_basic_load_multiple().
*
* This function gets its name from the menu system's wildcard
* naming conventions. For example, /path/%wildcard would end
* up calling wildcard_load(%wildcard value). In our case defining
* the path: examples/entity_example/basic/%entity_example_basic in
* hook_menu() tells Drupal to call entity_example_basic_load().
*
* @param int $basic_id
* Integer specifying the basic entity id.
* @param bool $reset
* A boolean indicating that the internal cache should be reset.
*
* @return object
* A fully-loaded $basic object or FALSE if it cannot be loaded.
*
* @see entity_example_basic_load_multiple()
* @see entity_example_menu()
*/
function entity_example_basic_load($basic_id = NULL, $reset = FALSE) {
$basic_ids = (isset($basic_id) ? array($basic_id) : array());
$basic = entity_example_basic_load_multiple($basic_ids, array(), $reset);
return $basic ? reset($basic) : FALSE;
}
/**
* Loads multiple basic entities.
*
* We only need to pass this request along to entity_load(), which
* will in turn call the load() method of our entity controller class.
*/
function entity_example_basic_load_multiple($basic_ids = FALSE, $conditions = array(), $reset = FALSE) {
return entity_load('entity_example_basic', $basic_ids, $conditions, $reset);
}
/**
* Implements the uri callback.
*/
function entity_example_basic_uri($basic) {
return array(
'path' => 'examples/entity_example/basic/' . $basic->basic_id,
);
}
/**
* Implements hook_menu().
*/
function entity_example_menu() {
$items['examples/entity_example'] = array(
'title' => 'Entity Example',
'page callback' => 'entity_example_info_page',
'access arguments' => array('view any entity_example_basic entity'),
);
// This provides a place for Field API to hang its own
// interface and has to be the same as what was defined
// in basic_entity_info() above.
$items['admin/structure/entity_example_basic/manage'] = array(
'title' => 'Administer entity_example_basic entity type',
'page callback' => 'entity_example_basic_list_entities',
'access arguments' => array('administer entity_example_basic entities'),
);
// Add example entities.
$items['admin/structure/entity_example_basic/manage/add'] = array(
'title' => 'Add an Entity Example Basic Entity',
'page callback' => 'entity_example_basic_add',
'access arguments' => array('create entity_example_basic entities'),
'type' => MENU_LOCAL_ACTION,
);
// List of all entity_example_basic entities.
$items['admin/structure/entity_example_basic/manage/list'] = array(
'title' => 'List',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
// The page to view our entities - needs to follow what
// is defined in basic_uri and will use load_basic to retrieve
// the necessary entity info.
$items['examples/entity_example/basic/%entity_example_basic'] = array(
'title callback' => 'entity_example_basic_title',
'title arguments' => array(3),
'page callback' => 'entity_example_basic_view',
'page arguments' => array(3),
'access arguments' => array('view any entity_example_basic entity'),
);
// 'View' tab for an individual entity page.
$items['examples/entity_example/basic/%entity_example_basic/view'] = array(
'title' => 'View',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
// 'Edit' tab for an individual entity page.
$items['examples/entity_example/basic/%entity_example_basic/edit'] = array(
'title' => 'Edit',
'page callback' => 'drupal_get_form',
'page arguments' => array('entity_example_basic_form', 3),
'access arguments' => array('edit any entity_example_basic entity'),
'type' => MENU_LOCAL_TASK,
);
// Add example entities.
$items['examples/entity_example/basic/add'] = array(
'title' => 'Add an Entity Example Basic Entity',
'page callback' => 'entity_example_basic_add',
'access arguments' => array('create entity_example_basic entities'),
);
return $items;
}
/**
* Basic information for the page.
*/
function entity_example_info_page() {
$content['preface'] = array(
'#type' => 'item',
'#markup' => t('The entity example provides a simple example entity.'),
);
if (user_access('administer entity_example_basic entities')) {
$content['preface']['#markup'] = t('You can administer these and add fields and change the view !link.',
array('!link' => l(t('here'), 'admin/structure/entity_example_basic/manage'))
);
}
$content['table'] = entity_example_basic_list_entities();
return $content;
}
/**
* Implements hook_permission().
*/
function entity_example_permission() {
$permissions = array(
'administer entity_example_basic entities' => array(
'title' => t('Administer entity_example_basic entities'),
),
'view any entity_example_basic entity' => array(
'title' => t('View any Entity Example Basic entity'),
),
'edit any entity_example_basic entity' => array(
'title' => t('Edit any Entity Example Basic entity'),
),
'create entity_example_basic entities' => array(
'title' => t('Create Entity Example Basic Entities'),
),
);
return $permissions;
}
/**
* Returns a render array with all entity_example_basic entities.
*
* In this basic example we know that there won't be many entities,
* so we'll just load them all for display. See pager_example.module
* to implement a pager. Most implementations would probably do this
* with the contrib Entity API module, or a view using views module,
* but we avoid using non-core features in the Examples project.
*
* @see pager_example.module
*/
function entity_example_basic_list_entities() {
$content = array();
// Load all of our entities.
$entities = entity_example_basic_load_multiple();
if (!empty($entities)) {
foreach ($entities as $entity) {
// Create tabular rows for our entities.
$rows[] = array(
'data' => array(
'id' => $entity->basic_id,
'item_description' => l($entity->item_description, 'examples/entity_example/basic/' . $entity->basic_id),
'bundle' => $entity->bundle_type,
),
);
}
// Put our entities into a themed table. See theme_table() for details.
$content['entity_table'] = array(
'#theme' => 'table',
'#rows' => $rows,
'#header' => array(t('ID'), t('Item Description'), t('Bundle')),
);
}
else {
// There were no entities. Tell the user.
$content[] = array(
'#type' => 'item',
'#markup' => t('No entity_example_basic entities currently exist.'),
);
}
return $content;
}
/**
* Callback for a page title when this entity is displayed.
*/
function entity_example_basic_title($entity) {
return t('Entity Example Basic (item_description=@item_description)', array('@item_description' => $entity->item_description));
}
/**
* Menu callback to display an entity.
*
* As we load the entity for display, we're responsible for invoking a number
* of hooks in their proper order.
*
* @see hook_entity_prepare_view()
* @see hook_entity_view()
* @see hook_entity_view_alter()
*/
function entity_example_basic_view($entity, $view_mode = 'tweaky') {
// Our entity type, for convenience.
$entity_type = 'entity_example_basic';
// Start setting up the content.
$entity->content = array(
'#view_mode' => $view_mode,
);
// Build fields content - this is where the Field API really comes in to play.
// The task has very little code here because it all gets taken care of by
// field module.
// field_attach_prepare_view() lets the fields load any data they need
// before viewing.
field_attach_prepare_view($entity_type, array($entity->basic_id => $entity),
$view_mode);
// We call entity_prepare_view() so it can invoke hook_entity_prepare_view()
// for us.
entity_prepare_view($entity_type, array($entity->basic_id => $entity));
// Now field_attach_view() generates the content for the fields.
$entity->content += field_attach_view($entity_type, $entity, $view_mode);
// OK, Field API done, now we can set up some of our own data.
$entity->content['created'] = array(
'#type' => 'item',
'#title' => t('Created date'),
'#markup' => format_date($entity->created),
);
$entity->content['item_description'] = array(
'#type' => 'item',
'#title' => t('Item Description'),
'#markup' => $entity->item_description,
);
// Now to invoke some hooks. We need the language code for
// hook_entity_view(), so let's get that.
global $language;
$langcode = $language->language;
// And now invoke hook_entity_view().
module_invoke_all('entity_view', $entity, $entity_type, $view_mode,
$langcode);
// Now invoke hook_entity_view_alter().
drupal_alter(array('entity_example_basic_view', 'entity_view'),
$entity->content, $entity_type);
// And finally return the content.
return $entity->content;
}
/**
* Implements hook_field_extra_fields().
*
* This exposes the "extra fields" (usually properties that can be configured
* as if they were fields) of the entity as pseudo-fields
* so that they get handled by the Entity and Field core functionality.
* Node titles get treated in a similar manner.
*/
function entity_example_field_extra_fields() {
$form_elements['item_description'] = array(
'label' => t('Item Description'),
'description' => t('Item Description (an extra form field)'),
'weight' => -5,
);
$display_elements['created'] = array(
'label' => t('Creation date'),
'description' => t('Creation date (an extra display field)'),
'weight' => 0,
);
$display_elements['item_description'] = array(
'label' => t('Item Description'),
'description' => t('Just like title, but trying to point out that it is a separate property'),
'weight' => 0,
);
// Since we have only one bundle type, we'll just provide the extra_fields
// for it here.
$extra_fields['entity_example_basic']['first_example_bundle']['form'] = $form_elements;
$extra_fields['entity_example_basic']['first_example_bundle']['display'] = $display_elements;
return $extra_fields;
}
/**
* Provides a wrapper on the edit form to add a new entity.
*/
function entity_example_basic_add() {
// Create a basic entity structure to be used and passed to the validation
// and submission functions.
$entity = entity_get_controller('entity_example_basic')->create();
return drupal_get_form('entity_example_basic_form', $entity);
}
/**
* Form function to create an entity_example_basic entity.
*
* The pattern is:
* - Set up the form for the data that is specific to your
* entity: the columns of your base table.
* - Call on the Field API to pull in the form elements
* for fields attached to the entity.
*/
function entity_example_basic_form($form, &$form_state, $entity) {
$form['item_description'] = array(
'#type' => 'textfield',
'#title' => t('Item Description'),
'#required' => TRUE,
'#default_value' => $entity->item_description,
);
$form['basic_entity'] = array(
'#type' => 'value',
'#value' => $entity,
);
field_attach_form('entity_example_basic', $entity, $form, $form_state);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#weight' => 100,
);
$form['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#submit' => array('entity_example_basic_edit_delete'),
'#weight' => 200,
);
return $form;
}
/**
* Validation handler for entity_example_basic_add_form form.
*
* We pass things straight through to the Field API to handle validation
* of the attached fields.
*/
function entity_example_basic_form_validate($form, &$form_state) {
field_attach_form_validate('entity_example_basic', $form_state['values']['basic_entity'], $form, $form_state);
}
/**
* Form submit handler: Submits basic_add_form information.
*/
function entity_example_basic_form_submit($form, &$form_state) {
$entity = $form_state['values']['basic_entity'];
$entity->item_description = $form_state['values']['item_description'];
field_attach_submit('entity_example_basic', $entity, $form, $form_state);
$entity = entity_example_basic_save($entity);
$form_state['redirect'] = 'examples/entity_example/basic/' . $entity->basic_id;
}
/**
* Form deletion handler.
*
* @todo: 'Are you sure?' message.
*/
function entity_example_basic_edit_delete($form, &$form_state) {
$entity = $form_state['values']['basic_entity'];
entity_example_basic_delete($entity);
drupal_set_message(t('The entity %item_description (ID %id) has been deleted',
array('%item_description' => $entity->item_description, '%id' => $entity->basic_id))
);
$form_state['redirect'] = 'examples/entity_example';
}
/**
* We save the entity by calling the controller.
*/
function entity_example_basic_save(&$entity) {
return entity_get_controller('entity_example_basic')->save($entity);
}
/**
* Use the controller to delete the entity.
*/
function entity_example_basic_delete($entity) {
entity_get_controller('entity_example_basic')->delete($entity);
}
/**
* EntityExampleBasicControllerInterface definition.
*
* We create an interface here because anyone could come along and
* use hook_entity_info_alter() to change our controller class.
* We want to let them know what methods our class needs in order
* to function with the rest of the module, so here's a handy list.
*
* @see hook_entity_info_alter()
*/
interface EntityExampleBasicControllerInterface
extends DrupalEntityControllerInterface {
/**
* Create an entity.
*/
public function create();
/**
* Save an entity.
*
* @param object $entity
* The entity to save.
*/
public function save($entity);
/**
* Delete an entity.
*
* @param object $entity
* The entity to delete.
*/
public function delete($entity);
}
/**
* EntityExampleBasicController extends DrupalDefaultEntityController.
*
* Our subclass of DrupalDefaultEntityController lets us add a few
* important create, update, and delete methods.
*/
class EntityExampleBasicController
extends DrupalDefaultEntityController
implements EntityExampleBasicControllerInterface {
/**
* Create and return a new entity_example_basic entity.
*/
public function create() {
$entity = new stdClass();
$entity->type = 'entity_example_basic';
$entity->basic_id = 0;
$entity->bundle_type = 'first_example_bundle';
$entity->item_description = '';
return $entity;
}
/**
* Saves the custom fields using drupal_write_record().
*/
public function save($entity) {
// If our entity has no basic_id, then we need to give it a
// time of creation.
if (empty($entity->basic_id)) {
$entity->created = time();
}
// Invoke hook_entity_presave().
module_invoke_all('entity_presave', $entity, 'entity_example_basic');
// The 'primary_keys' argument determines whether this will be an insert
// or an update. So if the entity already has an ID, we'll specify
// basic_id as the key.
$primary_keys = $entity->basic_id ? 'basic_id' : array();
// Write out the entity record.
drupal_write_record('entity_example_basic', $entity, $primary_keys);
// We're going to invoke either hook_entity_update() or
// hook_entity_insert(), depending on whether or not this is a
// new entity. We'll just store the name of hook_entity_insert()
// and change it if we need to.
$invocation = 'entity_insert';
// Now we need to either insert or update the fields which are
// attached to this entity. We use the same primary_keys logic
// to determine whether to update or insert, and which hook we
// need to invoke.
if (empty($primary_keys)) {
field_attach_insert('entity_example_basic', $entity);
}
else {
field_attach_update('entity_example_basic', $entity);
$invocation = 'entity_update';
}
// Invoke either hook_entity_update() or hook_entity_insert().
module_invoke_all($invocation, $entity, 'entity_example_basic');
return $entity;
}
/**
* Delete a single entity.
*
* Really a convenience function for deleteMultiple().
*/
public function delete($entity) {
$this->deleteMultiple(array($entity));
}
/**
* Delete one or more entity_example_basic entities.
*
* Deletion is unfortunately not supported in the base
* DrupalDefaultEntityController class.
*
* @param array $entities
* An array of entity IDs or a single numeric ID.
*/
public function deleteMultiple($entities) {
$basic_ids = array();
if (!empty($entities)) {
$transaction = db_transaction();
try {
foreach ($entities as $entity) {
// Invoke hook_entity_delete().
module_invoke_all('entity_delete', $entity, 'entity_example_basic');
field_attach_delete('entity_example_basic', $entity);
$basic_ids[] = $entity->basic_id;
}
db_delete('entity_example_basic')
->condition('basic_id', $basic_ids, 'IN')
->execute();
}
catch (Exception $e) {
$transaction->rollback();
watchdog_exception('entity_example', $e);
throw $e;
}
}
}
}
/**
* @} End of "defgroup entity_example".
*/

View File

@@ -0,0 +1,162 @@
<?php
/**
* @file
* Tests for entity_example module.
*
* Verify example module functionality.
*/
/**
* Functionality tests for entity example module.
*
* @ingroup entity_example
*/
class EntityExampleTestCase extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Entity example',
'description' => 'Basic entity example tests',
'group' => 'Examples',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
// Enable the module.
parent::setUp('entity_example');
// Create and login user with access.
$permissions = array(
'access content',
'view any entity_example_basic entity',
'edit any entity_example_basic entity',
'create entity_example_basic entities',
'administer entity_example_basic entities',
'administer site configuration',
'administer fields',
);
$account = $this->drupalCreateUser($permissions);
$this->drupalLogin($account);
// Attach a field.
$field = array(
'field_name' => 'entity_example_test_text' ,
'type' => 'text',
);
field_create_field($field);
$instance = array(
'label' => 'Subject',
'field_name' => 'entity_example_test_text',
'entity_type' => 'entity_example_basic',
'bundle' => 'first_example_bundle',
);
field_create_instance($instance);
}
/**
* Test Entity Example features.
*
* - CRUD
* - Table display
* - User access
* - Field management
* - Display management
*/
public function testEntityExampleBasic() {
// Create 10 entities.
for ($i = 1; $i <= 10; $i++) {
$edit[$i]['item_description'] = $this->randomName();
$edit[$i]['entity_example_test_text[und][0][value]'] = $this->randomName(32);
$this->drupalPost('examples/entity_example/basic/add', $edit[$i], 'Save');
$this->assertText('item_description=' . $edit[$i]['item_description']);
$this->drupalGet('examples/entity_example/basic/' . $i);
$this->assertText('item_description=' . $edit[$i]['item_description']);
$this->assertText($edit[$i]['entity_example_test_text[und][0][value]']);
}
// Delete entity 5.
$this->drupalPost('examples/entity_example/basic/5/edit', $edit[5], 'Delete');
$this->drupalGet('examples/entity_example/basic/5');
$this->assertResponse(404, 'Deleted entity 5 no longer exists');
unset($edit[5]);
// Update entity 2 and verify the update.
$edit[2] = array(
'item_description' => 'updated entity 2 ',
'entity_example_test_text[und][0][value]' => 'updated entity 2 test text',
);
$this->drupalPost('examples/entity_example/basic/2/edit', $edit[2], 'Save');
$this->assertText('item_description=' . $edit[2]['item_description']);
$this->assertText('updated entity 2 test text');
// View the entity list page and verify that the items which still exist
// are there, and that the deleted #5 no longer is there.
$this->drupalGet('admin/structure/entity_example_basic/manage');
foreach ($edit as $id => $item) {
$this->assertRaw('examples/entity_example/basic/' . $id . '">' . $item['item_description'] . '</a>');
}
$this->assertNoRaw('examples/entity_example/basic/5">');
// Add a field through the field UI and verify that it behaves correctly.
$field_edit = array(
'fields[_add_new_field][label]' => 'New junk field',
'fields[_add_new_field][field_name]' => 'new_junk_field',
'fields[_add_new_field][type]' => 'text',
'fields[_add_new_field][widget_type]' => 'text_textfield',
);
$this->drupalPost('admin/structure/entity_example_basic/manage/fields', $field_edit, t('Save'));
$this->drupalPost(NULL, array(), t('Save field settings'));
$this->drupalPost(NULL, array(), t('Save settings'));
$this->assertResponse(200);
// Now verify that we can edit and view this entity with fields.
$edit[10]['field_new_junk_field[und][0][value]'] = $this->randomName();
$this->drupalPost('examples/entity_example/basic/10/edit', $edit[10], 'Save');
$this->assertResponse(200);
$this->assertText('item_description=' . $edit[10]['item_description']);
$this->assertText($edit[10]['field_new_junk_field[und][0][value]'], 'Custom field updated successfully');
// Create and login user without view access.
$account = $this->drupalCreateUser(array('access content'));
$this->drupalLogin($account);
$this->drupalGet('admin/structure/entity_example_basic/manage');
$this->assertResponse(403);
$this->drupalGet('examples/entity_example/basic/2');
$this->assertResponse(403, 'User does not have permission to view entity');
// Create and login user with view access but no edit access.
$account = $this->drupalCreateUser(array('access content', 'view any entity_example_basic entity'));
$this->drupalLogin($account);
$this->drupalGet('admin/structure/entity_example_basic/manage');
$this->assertResponse(403, 'Denied access to admin manage page');
$this->drupalGet('examples/entity_example/basic/2');
$this->assertResponse(200, 'User has permission to view entity');
$this->drupalGet('examples/entity_example/basic/2/edit');
$this->assertResponse(403, 'User is denied edit privileges');
// Create and login user with view and edit but no manage privs.
$account = $this->drupalCreateUser(
array(
'access content',
'view any entity_example_basic entity',
'edit any entity_example_basic entity',
)
);
$this->drupalLogin($account);
$this->drupalGet('admin/structure/entity_example_basic/manage');
$this->assertResponse(403, 'Denied access to admin manage page');
$this->drupalGet('examples/entity_example/basic/2');
$this->assertResponse(200, 'User has permission to view entity');
$this->drupalGet('examples/entity_example/basic/2/edit');
$this->assertResponse(200, 'User has edit privileges');
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* @file
* Example modules
*
* @link action_example.module Creating actions @endlink
* @link ajax_example.module Using AJAX forms @endlink
* @link batch_example.module Using the batch API @endlink
* @link block_example.module Defining blocks @endlink
* @link cache_example.module Demonstrate Drupal caching @endlink
* @link contextual_links_example.module Defining contextual links @endlink
* @link cron_example.module Using hook_cron() and hook_cron_queue_info() @endlink
* @link dbtng_example.module Database examples (DBTNG) @endlink
* @link email_example.module Sending e-mail @endlink
* @link entity_example.module Creating and managing a custom entity @endlink
* @link field_example.module Defining fields in the field API @endlink
* @link file_example.module Demonstrates file handling @endlink
* @link filter_example.module Defining an input filter @endlink
* @link form_example.module Form API examples, including multistep forms @endlink
* @link image_example.module Demonstrates image handling with styles and effects @endlink
* @link js_example.module Javascript examples @endlink
* @link menu_example.module Menu API examples @endlink
* @link nodeapi_example.module Node API demonstrations showing how a separate module can change the behavior of a node @endlink
* @link node_access_example.module Define custom node access fules using node access hooks @endlink
* @link node_example.module Creating custom node types, with fields @endlink
* @link page_example.module Creating a custom page @endlink
* @link pager_example.module Using a table with a pager @endlink
* @link queue_example.module Using the Queue API @endlink
* @link rdf_example.module Implementing RDF in Drupal @endlink
* @link render_example.module Demonstrates the render API @endlink
* @link simpletest_example.module Writing tests for Drupal @endlink
* @link tablesort_example.module Use a render array to present a table with automatic sorting @endlink
* @link tabledrag_example.module Demonstrates tabledrag forms @endlink
* @link theming_example.module Demonstrate how modules shoule use theme functions and provide tpl.php files @endlink
* @link token_example.module Using tokens @endlink
* @link trigger_example.module Implementing triggers and actions @endlink
* @link vertical_tabs_example.module Using vertical tabs @endlink
* @link xmlrpc_example.module XML-RPC example @endlink
*/

View File

@@ -0,0 +1,11 @@
name = Examples For Developers
description = A variety of example code for you to learn from and hack upon.
package = Example modules
core = 7.x
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,42 @@
<?php
/**
* @file
* This file serves as a stub file for the many Examples modules in the
* @link http://drupal.org/project/examples Examples for Developers Project @endlink
* which you can download and experiment with.
*
* One might say that examples.module is an example of documentation. However,
* note that the example submodules define many doxygen groups, which may or
* may not be a good strategy for other modules.
*/
/**
* @defgroup examples Examples
* @{
* Well-documented API examples for a broad range of Drupal 7 core
* functionality.
*
* Developers can learn how to use a particular API quickly by experimenting
* with the examples, and adapt them for their own use.
*
* Download the Examples for Developers Project (and participate with
* submissions, bug reports, patches, and documentation) at
* http://drupal.org/project/examples
*/
/**
* Implements hook_help().
*/
function examples_help($path, $arg) {
// re: http://drupal.org/node/767204
// 5. We need a master group (Examples) that will be in a main
// examples.module.
// The examples.module should be mostly doxy comments that point to the other
// examples. It will also have a hook_help() explaining its purpose and how
// to access the other examples.
}
/**
* @} End of 'defgroup examples'.
*/

View File

@@ -0,0 +1,8 @@
/**
* @file
* CSS for Field Example.
*/
div.form-item table .form-type-textfield,
div.form-item table .form-type-textfield * {
display: inline-block;
}

View File

@@ -0,0 +1,12 @@
name = Field Example
description = An implementation of a field to show the Field API
package = Example modules
core = 7.x
files[] = field_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,35 @@
<?php
/**
* @file
* Install, update, and uninstall functions for the field_example module.
*/
/**
* Implements hook_field_schema().
*
* Defines the database schema of the field, using the format used by the
* Schema API.
*
* The data we will store here is just one 7-character element, even
* though the widget presents the three portions separately.
*
* All implementations of hook_field_schema() must be in the module's
* .install file.
*
* @see http://drupal.org/node/146939
* @see schemaapi
* @see hook_field_schema()
* @ingroup field_example
*/
function field_example_field_schema($field) {
$columns = array(
'rgb' => array('type' => 'varchar', 'length' => 7, 'not null' => FALSE),
);
$indexes = array(
'rgb' => array('rgb'),
);
return array(
'columns' => $columns,
'indexes' => $indexes,
);
}

View File

@@ -0,0 +1,25 @@
/**
* @file
* Javascript for Field Example.
*/
/**
* Provides a farbtastic colorpicker for the fancier widget.
*/
(function ($) {
Drupal.behaviors.field_example_colorpicker = {
attach: function(context) {
$(".edit-field-example-colorpicker").live("focus", function(event) {
var edit_field = this;
var picker = $(this).closest('div').parent().find(".field-example-colorpicker");
// Hide all color pickers except this one.
$(".field-example-colorpicker").hide();
$(picker).show();
$.farbtastic(picker, function(color) {
edit_field.value = color;
}).setColor(edit_field.value);
});
}
}
})(jQuery);

View File

@@ -0,0 +1,389 @@
<?php
/**
* @file
* An example field using the Field Types API.
*/
/**
* @defgroup field_example Example: Field Types API
* @ingroup examples
* @{
* Examples using Field Types API.
*
* This is updated from Barry Jaspan's presentation at Drupalcon Paris,
* @link http://acquia.com/community/resources/acquia-tv/intro-field-api-module-developers Video Presentation @endlink
*
* Providing a field requires:
* - Defining a field:
* - hook_field_info()
* - hook_field_schema()
* - hook_field_validate()
* - hook_field_is_empty()
*
* - Defining a formatter for the field (the portion that outputs the field for
* display):
* - hook_field_formatter_info()
* - hook_field_formatter_view()
*
* - Defining a widget for the edit form:
* - hook_field_widget_info()
* - hook_field_widget_form()
*
* Our module defines the field in field_example_field_info(),
* field_example_field_validate() and field_example_field_is_empty().
* field_example_field_schema() is implemented in field_example.install.
*
* Our module sets up a formatter in field_example_field_formatter_info() and
* field_example_field_formatter_view(). These are the API hooks that present
* formatted and themed output to the user.
*
* And finally, our module defines the widget in
* field_example_field_widget_info() and field_example_field_widget_form().
* The widget is the form element used to receive input from the user
* when the field is being populated.
*
* @see field_types
* @see field
*/
/***************************************************************
* Field Type API hooks
***************************************************************/
/**
* Implements hook_field_info().
*
* Provides the description of the field.
*/
function field_example_field_info() {
return array(
// We name our field as the associative name of the array.
'field_example_rgb' => array(
'label' => t('Example Color RGB'),
'description' => t('Demonstrates a field composed of an RGB color.'),
'default_widget' => 'field_example_3text',
'default_formatter' => 'field_example_simple_text',
),
);
}
/**
* Implements hook_field_validate().
*
* This hook gives us a chance to validate content that's in our
* field. We're really only interested in the $items parameter, since
* it holds arrays representing content in the field we've defined.
* We want to verify that the items only contain RGB hex values like
* this: #RRGGBB. If the item validates, we do nothing. If it doesn't
* validate, we add our own error notification to the $errors parameter.
*
* @see field_example_field_widget_error()
*/
function field_example_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
foreach ($items as $delta => $item) {
if (!empty($item['rgb'])) {
if (!preg_match('@^#[0-9a-f]{6}$@', $item['rgb'])) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'field_example_invalid',
'message' => t('Color must be in the HTML format #abcdef.'),
);
}
}
}
}
/**
* Implements hook_field_is_empty().
*
* hook_field_is_empty() is where Drupal asks us if this field is empty.
* Return TRUE if it does not contain data, FALSE if it does. This lets
* the form API flag an error when required fields are empty.
*/
function field_example_field_is_empty($item, $field) {
return empty($item['rgb']);
}
/**
* Implements hook_field_formatter_info().
*
* We need to tell Drupal that we have two different types of formatters
* for this field. One will change the text color, and the other will
* change the background color.
*
* @see field_example_field_formatter_view()
*/
function field_example_field_formatter_info() {
return array(
// This formatter just displays the hex value in the color indicated.
'field_example_simple_text' => array(
'label' => t('Simple text-based formatter'),
'field types' => array('field_example_rgb'),
),
// This formatter changes the background color of the content region.
'field_example_color_background' => array(
'label' => t('Change the background of the output text'),
'field types' => array('field_example_rgb'),
),
);
}
/**
* Implements hook_field_formatter_view().
*
* Two formatters are implemented.
* - field_example_simple_text just outputs markup indicating the color that
* was entered and uses an inline style to set the text color to that value.
* - field_example_color_background does the same but also changes the
* background color of div.region-content.
*
* @see field_example_field_formatter_info()
*/
function field_example_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
switch ($display['type']) {
// This formatter simply outputs the field as text and with a color.
case 'field_example_simple_text':
foreach ($items as $delta => $item) {
$element[$delta] = array(
// We create a render array to produce the desired markup,
// "<p style="color: #hexcolor">The color code ... #hexcolor</p>".
// See theme_html_tag().
'#type' => 'html_tag',
'#tag' => 'p',
'#attributes' => array(
'style' => 'color: ' . $item['rgb'],
),
'#value' => t('The color code in this field is @code', array('@code' => $item['rgb'])),
);
}
break;
// This formatter adds css to the page changing the '.region-content' area's
// background color. If there are many fields, the last one will win.
case 'field_example_color_background':
foreach ($items as $delta => $item) {
$element[$delta] = array(
'#type' => 'html_tag',
'#tag' => 'p',
'#value' => t('The content area color has been changed to @code', array('@code' => $item['rgb'])),
'#attached' => array(
'css' => array(
array(
'data' => 'div.region-content { background-color:' . $item['rgb'] . ';}',
'type' => 'inline',
),
),
),
);
}
break;
}
return $element;
}
/**
* Implements hook_field_widget_info().
*
* Three widgets are provided.
* - A simple text-only widget where the user enters the '#ffffff'.
* - A 3-textfield widget that gathers the red, green, and blue values
* separately.
* - A farbtastic colorpicker widget that chooses the value graphically.
*
* These widget types will eventually show up in hook_field_widget_form,
* where we will have to flesh them out.
*
* @see field_example_field_widget_form()
*/
function field_example_field_widget_info() {
return array(
'field_example_text' => array(
'label' => t('RGB value as #ffffff'),
'field types' => array('field_example_rgb'),
),
'field_example_3text' => array(
'label' => t('RGB text field'),
'field types' => array('field_example_rgb'),
),
'field_example_colorpicker' => array(
'label' => t('Color Picker'),
'field types' => array('field_example_rgb'),
),
);
}
/**
* Implements hook_field_widget_form().
*
* hook_widget_form() is where Drupal tells us to create form elements for
* our field's widget.
*
* We provide one of three different forms, depending on the widget type of
* the Form API item provided.
*
* The 'field_example_colorpicker' and 'field_example_text' are essentially
* the same, but field_example_colorpicker adds a javascript colorpicker
* helper.
*
* field_example_3text displays three text fields, one each for red, green,
* and blue. However, the field type defines a single text column,
* rgb, which needs an HTML color spec. Define an element validate
* handler that converts our r, g, and b fields into a simulated single
* 'rgb' form element.
*/
function field_example_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$value = isset($items[$delta]['rgb']) ? $items[$delta]['rgb'] : '';
$widget = $element;
$widget['#delta'] = $delta;
switch ($instance['widget']['type']) {
case 'field_example_colorpicker':
$widget += array(
'#suffix' => '<div class="field-example-colorpicker"></div>',
'#attributes' => array('class' => array('edit-field-example-colorpicker')),
'#attached' => array(
// Add Farbtastic color picker.
'library' => array(
array('system', 'farbtastic'),
),
// Add javascript to trigger the colorpicker.
'js' => array(drupal_get_path('module', 'field_example') . '/field_example.js'),
),
);
// DELIBERATE fall-through: From here on the field_example_text and
// field_example_colorpicker are exactly the same.
case 'field_example_text':
$widget += array(
'#type' => 'textfield',
'#default_value' => $value,
// Allow a slightly larger size that the field length to allow for some
// configurations where all characters won't fit in input field.
'#size' => 7,
'#maxlength' => 7,
);
break;
case 'field_example_3text':
// Convert rgb value into r, g, and b for #default_value.
if (!empty($value)) {
preg_match_all('@..@', substr($value, 1), $match);
}
else {
$match = array(array());
}
// Make this a fieldset with the three text fields.
$widget += array(
'#type' => 'fieldset',
'#element_validate' => array('field_example_3text_validate'),
// #delta is set so that the validation function will be able
// to access external value information which otherwise would be
// unavailable.
'#delta' => $delta,
'#attached' => array(
'css' => array(drupal_get_path('module', 'field_example') . '/field_example.css'),
),
);
// Create a textfield for saturation values for Red, Green, and Blue.
foreach (array('r' => t('Red'), 'g' => t('Green'), 'b' => t('Blue')) as $key => $title) {
$widget[$key] = array(
'#type' => 'textfield',
'#title' => $title,
'#size' => 2,
'#default_value' => array_shift($match[0]),
'#attributes' => array('class' => array('rgb-entry')),
'#description' => t('The 2-digit hexadecimal representation of @color saturation, like "a1" or "ff"', array('@color' => $title)),
);
// Since Form API doesn't allow a fieldset to be required, we
// have to require each field element individually.
if ($instance['required'] == 1) {
$widget[$key]['#required'] = 1;
}
}
break;
}
$element['rgb'] = $widget;
return $element;
}
/**
* Validate the individual fields and then convert to RGB string.
*/
function field_example_3text_validate($element, &$form_state) {
// @todo: Isn't there a better way to find out which element?
$delta = $element['#delta'];
$field = $form_state['field'][$element['#field_name']][$element['#language']]['field'];
$field_name = $field['field_name'];
if (isset($form_state['values'][$field_name][$element['#language']][$delta]['rgb'])) {
$values = $form_state['values'][$field_name][$element['#language']][$delta]['rgb'];
foreach (array('r', 'g', 'b') as $colorfield) {
$colorfield_value = hexdec($values[$colorfield]);
// If they left any empty, we'll set the value empty and quit.
if (strlen($values[$colorfield]) == 0) {
form_set_value($element, '', $form_state);
return;
}
// If they gave us anything that's not hex, reject it.
if ((strlen($values[$colorfield]) != 2) || $colorfield_value < 0 || $colorfield_value > 255) {
form_error($element[$colorfield], t("Saturation value must be a 2-digit hexadecimal value between 00 and ff."));
}
}
$value = sprintf('#%02s%02s%02s', $values['r'], $values['g'], $values['b']);
form_set_value($element, $value, $form_state);
}
}
/**
* Implements hook_field_widget_error().
*
* hook_field_widget_error() lets us figure out what to do with errors
* we might have generated in hook_field_validate(). Generally, we'll just
* call form_error().
*
* @see field_example_field_validate()
* @see form_error()
*/
function field_example_field_widget_error($element, $error, $form, &$form_state) {
switch ($error['error']) {
case 'field_example_invalid':
form_error($element, $error['message']);
break;
}
}
/**
* Implements hook_menu().
*
* Provides a simple user interface that tells the developer where to go.
*/
function field_example_menu() {
$items['examples/field_example'] = array(
'title' => 'Field Example',
'page callback' => '_field_example_page',
'access callback' => TRUE,
);
return $items;
}
/**
* A simple page to explain to the developer what to do.
*/
function _field_example_page() {
return t("The Field Example provides a field composed of an HTML RGB value, like #ff00ff. To use it, add the field to a content type.");
}
/**
* @} End of "defgroup field_example".
*/

View File

@@ -0,0 +1,184 @@
<?php
/**
* @file
* Tests for Field Example.
*/
/**
* Functional tests for the Field Example module.
*
* @ingroup field_example
*/
class FieldExampleTest extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Field Example',
'description' => 'Create a content type with example_field_rgb fields, create a node, check for correct values.',
'group' => 'Examples',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
// Enable the email_example module.
parent::setUp(array('field_ui', 'field_example'));
}
/**
* Test basic functionality of the example field.
*
* - Creates a content type.
* - Adds a single-valued field_example_rgb to it.
* - Adds a multivalued field_example_rgb to it.
* - Creates a node of the new type.
* - Populates the single-valued field.
* - Populates the multivalued field with two items.
* - Tests the result.
*/
public function testExampleFieldBasic() {
$content_type_machine = strtolower($this->randomName(10));
$title = $this->randomName(20);
// Create and login user.
$account = $this->drupalCreateUser(array('administer content types', 'administer fields'));
$this->drupalLogin($account);
$this->drupalGet('admin/structure/types');
// Create the content type.
$this->clickLink(t('Add content type'));
$edit = array(
'name' => $content_type_machine,
'type' => $content_type_machine,
);
$this->drupalPost(NULL, $edit, t('Save and add fields'));
$this->assertText(t('The content type @name has been added.', array('@name' => $content_type_machine)));
$single_text_field = strtolower($this->randomName(10));
$single_colorpicker_field = strtolower($this->randomName(10));
$single_3text_field = strtolower($this->randomName(10));
$multivalue_3text_field = strtolower($this->randomName(10));
// Description of fields to be created;
$fields[$single_text_field] = array(
'widget' => 'field_example_text',
'cardinality' => '1',
);
$fields[$single_colorpicker_field] = array(
'widget' => 'field_example_colorpicker',
'cardinality' => 1,
);
$fields[$single_3text_field] = array(
'widget' => 'field_example_3text',
'cardinality' => 1,
);
$fields[$multivalue_3text_field] = array(
'widget' => 'field_example_3text',
'cardinality' => -1,
);
foreach ($fields as $fieldname => $details) {
$this->createField($fieldname, $details['widget'], $details['cardinality']);
}
// Somehow clicking "save" isn't enough, and we have to do a
// node_types_rebuild().
node_types_rebuild();
menu_rebuild();
$type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $content_type_machine))->fetchField();
$this->assertTrue($type_exists, 'The new content type has been created in the database.');
$permission = 'create ' . $content_type_machine . ' content';
// Reset the permissions cache.
$this->checkPermissions(array($permission), TRUE);
// Now that we have a new content type, create a user that has privileges
// on the content type.
$account = $this->drupalCreateUser(array($permission));
$this->drupalLogin($account);
$this->drupalGet('node/add/' . $content_type_machine);
// Add a node.
$edit = array(
'title' => $title,
'field_' . $single_text_field . '[und][0][rgb]' => '#000001',
'field_' . $single_colorpicker_field . '[und][0][rgb]' => '#000002',
'field_' . $single_3text_field . '[und][0][rgb][r]' => '00',
'field_' . $single_3text_field . '[und][0][rgb][g]' => '00',
'field_' . $single_3text_field . '[und][0][rgb][b]' => '03',
'field_' . $multivalue_3text_field . '[und][0][rgb][r]' => '00',
'field_' . $multivalue_3text_field . '[und][0][rgb][g]' => '00',
'field_' . $multivalue_3text_field . '[und][0][rgb][b]' => '04',
);
// We want to add a 2nd item to the multivalue field, so hit "add another".
$this->drupalPost(NULL, $edit, t('Add another item'));
$edit = array(
'field_' . $multivalue_3text_field . '[und][1][rgb][r]' => '00',
'field_' . $multivalue_3text_field . '[und][1][rgb][g]' => '00',
'field_' . $multivalue_3text_field . '[und][1][rgb][b]' => '05',
);
// Now we can fill in the second item in the multivalue field and save.
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertText(t('@content_type_machine @title has been created', array('@content_type_machine' => $content_type_machine, '@title' => $title)));
$output_strings = $this->xpath("//div[contains(@class,'field-type-field-example-rgb')]/div/div/p/text()");
$this->assertEqual((string) $output_strings[0], "The color code in this field is #000001");
$this->assertEqual((string) $output_strings[1], "The color code in this field is #000002");
$this->assertEqual((string) $output_strings[2], "The color code in this field is #000003");
$this->assertEqual((string) $output_strings[3], "The color code in this field is #000004");
$this->assertEqual((string) $output_strings[4], "The color code in this field is #000005");
}
/**
* Utility function to create fields on a content type.
*
* @param string $field_name
* Name of the field, like field_something
* @param string $widget_type
* Widget type, like field_example_3text
* @param int $cardinality
* Cardinality
*/
protected function createField($field_name, $widget_type, $cardinality) {
// Add a singleton field_example_text field.
$edit = array(
'fields[_add_new_field][label]' => $field_name,
'fields[_add_new_field][field_name]' => $field_name,
'fields[_add_new_field][type]' => 'field_example_rgb',
'fields[_add_new_field][widget_type]' => $widget_type,
);
$this->drupalPost(NULL, $edit, t('Save'));
// There are no settings for this, so just press the button.
$this->drupalPost(NULL, array(), t('Save field settings'));
$edit = array('field[cardinality]' => (string) $cardinality);
// Using all the default settings, so press the button.
$this->drupalPost(NULL, $edit, t('Save settings'));
debug(
t('Saved settings for field %field_name with widget %widget_type and cardinality %cardinality',
array(
'%field_name' => $field_name,
'%widget_type' => $widget_type,
'%cardinality' => $cardinality,
)
)
);
$this->assertText(t('Saved @name configuration.', array('@name' => $field_name)));
}
}

View File

@@ -0,0 +1,22 @@
/**
* @file
* CSS for Field Example.
*/
.stickynote {
background:#fefabc;
padding:0.8em;
font-family:cursive;
font-size:1.1em;
color: #000;
width:15em;
-moz-transform: rotate(2deg);
-webkit-transform: rotate(2deg);
-o-transform: rotate(2deg);
-ms-transform: rotate(2deg);
transform: rotate(2deg);
box-shadow: 0px 4px 6px #333;
-moz-box-shadow: 0px 4px 6px #333;
-webkit-box-shadow: 0px 4px 6px #333;
}

View File

@@ -0,0 +1,12 @@
name = Field Permission Example
description = A Field API Example: Fieldnote with Permissions
package = Example modules
core = 7.x
files[] = tests/field_permission_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,32 @@
<?php
/**
* @file
* Install function for the field_permission_example module.
*/
/**
* Implements hook_field_schema().
*
* Defines the database schema of the field, using the format used by the
* Schema API.
*
* We only want a simple text field, similar to the body of a node.
*
* Note that this field has only a normal text (which translates to
* 16k of text in MySQL), and so therefore doesn't have an index.
* More complex schema would probably have at least one indexed
* column.
*
* @see http://drupal.org/node/146939
* @see schemaapi
* @see hook_field_schema()
* @ingroup field_permission_example
*/
function field_permission_example_field_schema($field) {
$columns = array(
'notes' => array('type' => 'text', 'size' => 'normal', 'not null' => FALSE),
);
return array(
'columns' => $columns,
);
}

View File

@@ -0,0 +1,329 @@
<?php
/**
* @file
* An example field using the Field Types API.
*/
/**
* @defgroup field_permission_example Example: Field Permissions
* @ingroup examples
* @{
* Example using permissions on a Field API field.
*
* This example is a relatively simple text field you can attach to
* any fieldable entity.
*
* In this module we demonstrate how to limit access to a field.
* Drupal's Field API gives you two operations to permit or restrict:
* view and edit. So you can then decide who gets to see fields,
* who can edit them, and who can manage them.
*
* Our field is called field_permission_example_fieldnote. It has a
* simple default widget of a text area, and a default formatter
* that applies a CSS style to make it look like a sticky note.
*
* In addition to demonstrating how to set up permissions-based
* access to a field, this module also demonstrates the absolute
* minimum required to implement a field, since it doesn't have
* any field settings. The tests also have a generalized
* field testing class, called FieldTestGeneric, which can be easily
* subclassed and reused for other fields.
*
* If you wish to use this code as skeleton code for a field without
* permissions, you can simply remove field_permission_exampe_permission()
* and field_permission_field_access(). Also field_permission_example_menu()
* and _field_permission_example_page() are vestigial to the Examples
* project.
*
* How does it work?
*
* You can install this module and go to path /examples/field_permission_example
* for an introduction on how to use this field. Or see the same content at
* _field_permission_example_page().
*
* OK, how does the code work?
*
* As with any permission system, we implement hook_permission() in
* order to define a few permissions. In our case, users will want
* to either view or edit fieldnote fields. And, similar to the way
* node permissions work, we'll also include a context of either
* their own content or any content. So that gives us 4 permissions
* which administrators can assign to various roles. See
* field_permission_example_permission() for the list.
*
* With our permissions defined in hook_permission(), we can now
* handle requests for access. Those come in through
* hook_field_access(), which we've implemented as
* field_permission_example_field_access(). This function determines
* whether the user has the ability to view or edit the field
* in question by calling user_access(). We also give special edit
* access to users with the 'bypass node access' and
* 'administer content types' permissions, defined by the node module.
*
* One tricky part is that our field won't always be attached to
* nodes. It could be attached to any type of entity. This means
* that there's no general way to figure out if the user 'owns'
* the entity or not. Since this is a simple example, we'll just
* check for 'any' access to unknown entity types. We'll also
* limit our known entity types to node and user, since those
* are easy to demonstrate.
*
* In a real application, we'd have use-case specific permissions
* which might be more complex than these. Or perhaps simpler.
*
* You can see a more complex field implementation in
* field_example.module.
*
* @see field_example
* @see field_example.module
* @see field_types
* @see field
*/
/**
* Implements hook_permission().
*
* We want to let site administrators figure out who should
* be able to view, edit, and administer our field.
*
* Field permission operations can only be view or edit, in the
* context of one's own content or that of others. Contrast
* with content types where we also have to worry about who can
* create or delete content.
*/
function field_permission_example_permission() {
// Note: This would be very easy to generate programmatically,
// but it's all typed out here for clarity.
// The key text is the machine name of the permission.
$perms['view own fieldnote'] = array('title' => t('View own fieldnote'));
$perms['edit own fieldnote'] = array('title' => t('Edit own fieldnote'));
$perms['view any fieldnote'] = array('title' => t('View any fieldnote'));
$perms['edit any fieldnote'] = array('title' => t('Edit any fieldnote'));
return $perms;
}
/**
* Implements hook_field_access().
*
* We want to make sure that fields aren't being seen or edited
* by those who shouldn't.
*
* We have to build a permission string similar to those in
* hook_permission() in order to ask Drupal whether the user
* has that permission. Permission strings will end up being
* like 'view any fieldnote' or 'edit own fieldnote'.
*
* The tricky thing here is that a field can be attached to any type
* of entity, so it's not always trivial to figure out whether
* $account 'owns' the entity. We'll support access restrictions for
* user and node entity types, and be permissive with others,
* since that's easy to demonstrate.
*
* @see field_permission_example_permissions()
*/
function field_permission_example_field_access($op, $field, $entity_type, $entity, $account) {
// This hook will be invoked for every field type, so we have to
// check that it's the one we're interested in.
if ($field['type'] == 'field_permission_example_fieldnote') {
// First we'll check if the user has the 'superuser'
// permissions that node provides. This way administrators
// will be able to administer the content types.
if (user_access('bypass node access', $account)) {
drupal_set_message(t('User can bypass node access.'));
return TRUE;
}
if (user_access('administer content types', $account)) {
drupal_set_message(t('User can administer content types.'));
return TRUE;
}
// Now check for our own permissions.
// $context will end up being either 'any' or 'own.'
$context = 'any';
switch ($entity_type) {
case 'user':
case 'node':
// While administering the field itself, $entity will be
// NULL, so we have to check it.
if ($entity) {
if ($entity->uid == $account->uid) {
$context = 'own';
}
}
}
// Assemble a permission string, such as
// 'view any fieldnote'
$permission = $op . ' ' . $context . ' fieldnote';
// Finally, ask Drupal if this account has that permission.
$access = user_access($permission, $account);
$status = 'FALSE';
if ($access) {
$status = 'TRUE';
}
drupal_set_message($permission . ': ' . $status);
return $access;
}
// We have no opinion on field types other than our own.
return TRUE;
}
/**
* Implements hook_field_info().
*
* Provides the description of the field.
*/
function field_permission_example_field_info() {
return array(
// We name our field as the associative name of the array.
'field_permission_example_fieldnote' => array(
'label' => t('Fieldnote'),
'description' => t('Place a note-taking field on entities, with granular permissions.'),
'default_widget' => 'field_permission_example_widget',
'default_formatter' => 'field_permission_example_formatter',
),
);
}
/**
* Implements hook_field_is_empty().
*
* hook_field_is_empty() is where Drupal asks us if this field is empty.
* Return TRUE if it does not contain data, FALSE if it does. This lets
* the form API flag an error when required fields are empty.
*/
function field_permission_example_field_is_empty($item, $field) {
return empty($item['notes']);
}
/**
* Implements hook_field_formatter_info().
*
* We need to tell Drupal about our excellent field formatter.
*
* It's some text in a div, styled to look like a sticky note.
*
* @see field_permission_example_field_formatter_view()
*/
function field_permission_example_field_formatter_info() {
return array(
// This formatter simply displays the text in a text field.
'field_permission_example_formatter' => array(
'label' => t('Simple text-based formatter'),
'field types' => array('field_permission_example_fieldnote'),
),
);
}
/**
* Implements hook_field_formatter_view().
*
* Here we output the field for general consumption.
*
* The field will have a sticky note appearance, thanks to some
* simple CSS.
*
* Note that all of the permissions and access logic happens
* in hook_field_access(), and none of it is here.
*/
function field_permission_example_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
switch ($display['type']) {
case 'field_permission_example_formatter':
foreach ($items as $delta => $item) {
$element[$delta] = array(
// We wrap the fieldnote content up in a div tag.
'#type' => 'html_tag',
'#tag' => 'div',
'#value' => check_plain($item['notes']),
// Let's give the note a nice sticky-note CSS appearance.
'#attributes' => array(
'class' => 'stickynote',
),
// ..And this is the CSS for the stickynote.
'#attached' => array(
'css' => array(drupal_get_path('module', 'field_permission_example') .
'/field_permission_example.css'),
),
);
}
break;
}
return $element;
}
/**
* Implements hook_field_widget_info().
*
* We're implementing just one widget: A basic textarea.
*
* @see field_permission_example_field_widget_form()
*/
function field_permission_example_field_widget_info() {
return array(
'field_permission_example_widget' => array(
'label' => t('Field note textarea'),
'field types' => array('field_permission_example_fieldnote'),
),
);
}
/**
* Implements hook_field_widget_form().
*
* Drupal wants us to create a form for our field. We'll use
* something very basic like a default textarea.
*
* @see field_permission_example_field_widget_info()
*/
function field_permission_example_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
// Grab the existing value for the field.
$value = isset($items[$delta]['notes']) ? $items[$delta]['notes'] : '';
// Grab a reference to the form element.
$widget = $element;
// Set up the delta for our return element.
$widget['#delta'] = $delta;
// Figure out which widget we need to generate.
// In our case, there's only one type.
switch ($instance['widget']['type']) {
case 'field_permission_example_widget':
$widget += array(
'#type' => 'textarea',
'#default_value' => $value,
);
break;
}
$element['notes'] = $widget;
return $element;
}
/**
* Implements hook_menu().
*
* Provides a simple user interface that gives the developer some clues.
*/
function field_permission_example_menu() {
$items['examples/field_permission_example'] = array(
'title' => 'Field Permission Example',
'page callback' => '_field_permission_example_page',
'access callback' => TRUE,
);
return $items;
}
/**
* A simple page to explain to the developer what to do.
*
* @see field_permission_example.module
*/
function _field_permission_example_page() {
$page = t("<p>The Field Permission Example module shows how you can restrict view and edit permissions within your field implementation. It adds a new field type called Fieldnote. Fieldnotes appear as simple text boxes on the create/edit form, and as sticky notes when viewed. By 'sticky note' we mean 'Post-It Note' but that's a trademarked term.</p><p>To see this field in action, add it to a content type or user profile. Go to the permissions page (");
$page .= l(t('admin/people/permissions'), 'admin/people/permissions');
$page .= t(") and look at the 'Field Permission Example' section. This allows you to change which roles can see and edit Fieldnote fields.</p><p>Creating different users with different capabilities will let you see these behaviors in action. Fieldnote helpfully displays a message telling you which permissions it is trying to resolve for the current field/user combination.</p><p>Definitely look through the code to see various implementation details.</p>");
return $page;
}
/**
* @} End of "defgroup field_permission_example".
*/

View File

@@ -0,0 +1,572 @@
<?php
/**
* @file
* Tests for Field Permission Example.
*
* @ingroup field_permission_example
*/
/**
* A generic field testing class.
*
* Subclass this one to test your specific field type
* and get some basic unit testing for free.
*
* Since Simpletest only looks through one class definition
* to find test functions, we define generic tests as
* 'code_testWhatever' or 'form_testWhatever'. Subclasses
* can then implement shim test methods that just call the
* generic tests.
*
* 'code_' and 'form_' prefixes denote the type of test:
* using code only, or through Drupal page forms.
*
* @ingroup field_permission_example
*/
class GenericFieldTest extends DrupalWebTestCase {
// Our tests will generate some random field instance
// names. We store them here so many functions can act on them.
protected $instanceNames;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Generic Field Test',
'description' => 'Someone neglected to override GenericFieldTest::getInfo().',
'group' => 'Examples',
);
}
/**
* Supply the field types we wish to test.
*
* Return an array of field types to instantiate and test.
*
* @return array
* The field types we wish to use.
*/
protected function getFieldTypes() {
return array('these_are_not', 'valid_field_types', 'please_override');
}
/**
* The module to enable.
*
* @return string
* Module machine name.
*/
protected function getModule() {
return 'this-is-not-a-module-name-please-override';
}
/**
* Simpletest's setUp().
*
* We want to be able to subclass this class, so we jump
* through a few hoops in order to get the modules from args
* and add our own.
*/
public function setUp() {
$this->instanceNames = array();
$modules = func_get_args();
if (isset($modules[0]) && is_array($modules[0])) {
$modules = $modules[0];
}
$modules[] = 'node';
$modules[] = 'field_ui';
parent::setUp($modules);
}
/**
* Verify that all required fields are specified in hook_field_info().
*
* The full list is label, description, settings, instance_settings,
* default_widget, default_formatter, no_ui.
*
* Some are optional, and we won't check for those.
*
* In a sane world, this would be a unit test, rather than a
* web test, but module_implements is unavailable to us
* in unit tests.
*
* @see hook_field_info()
*/
public function runTestGenericFieldInfo() {
$field_types = $this->getFieldTypes();
$module = $this->getModule();
$info_keys = array(
'label',
'description',
'default_widget',
'default_formatter',
);
// We don't want to use field_info_field_types()
// because there is a hook_field_info_alter().
// We're testing the module here, not the rest of
// the system. So invoke hook_field_info() ourselves.
$modules = module_implements('field_info');
$this->assertTrue(in_array($module, $modules),
'Module ' . $module . ' implements hook_field_info()');
foreach ($field_types as $field_type) {
$field_info = module_invoke($module, 'field_info');
$this->assertTrue(isset($field_info[$field_type]),
'Module ' . $module . ' defines field type ' . $field_type);
$field_info = $field_info[$field_type];
foreach ($info_keys as $key) {
$this->assertTrue(
isset($field_info[$key]),
$field_type . "'s " . $key . ' is set.'
);
}
}
}
/**
* Add all testable fields as instances to a content type.
*
* As a side-effect: Store the names of the instances created
* in $this->$instance_names.
*
* @param object $node_type
* A content type object. If none is provided, one will be generated.
*
* @return object
* The content type object that has the fields attached.
*/
public function codeTestGenericAddAllFields($node_type = NULL) {
$this->instanceNames = array();
if (!$node_type) {
$node_type = $this->drupalCreateContentType();
}
foreach ($this->getFieldTypes() as $field_type) {
$instance_name = drupal_strtolower($this->randomName(32));
$field = array(
'field_name' => $instance_name,
'type' => $field_type,
);
$field = field_create_field($field);
$instance = array(
'field_name' => $instance_name,
'entity_type' => 'node',
'bundle' => $node_type->name,
'label' => drupal_strtolower($this->randomName(20)),
);
// Finally create the instance.
$instance = field_create_instance($instance);
// Reset the caches...
_field_info_collate_fields(TRUE);
// Grab this instance.
$verify_instance = field_info_instance('node', $instance_name, $node_type->name);
$this->assertTrue($verify_instance, 'Instance object exists.');
$this->assertTrue(
$verify_instance != NULL,
'field_info_instance() says ' . $instance_name . ' (' . $node_type->name . ') was created.'
);
$this->instanceNames[] = $instance_name;
}
return $node_type;
}
/**
* Remove all fields in $this->field_names.
*
* @param mixed $node_type
* A content type object. If none is specified,
* the test fails.
*/
public function codeTestGenericRemoveAllFields($node_type = NULL) {
if (!$node_type) {
$this->fail('No node type.');
}
if (count($this->instanceNames) < 1) {
$this->fail('There are no instances to remove.');
return;
}
foreach ($this->instanceNames as $instance_name) {
$instance = field_info_instance('node', $instance_name, $node_type->name);
$this->assertTrue($instance, "Instance exists, now we'll delete it.");
field_delete_field($instance_name);
$instance = field_info_instance('node', $instance_name, $node_type->name);
$this->assertFalse($instance, 'Instance was deleted.');
}
$this->instanceNames = array();
}
/**
* Add and delete all field types through Form API.
*
* @access public
*/
public function formTestGenericFieldNodeAddDeleteForm() {
// Create and login user.
$account = $this->drupalCreateUser(array(
'administer content types',
'administer fields',
));
$this->drupalLogin($account);
// Add a content type.
$node_type = $this->drupalCreateContentType();
// Add all our testable fields.
$field_names = $this->formAddAllFields($node_type);
// Now let's delete all the fields.
foreach ($field_names as $field_name) {
// This is the path for the 'delete' link on field admin page.
$this->drupalGet('admin/structure/types/manage/' .
$node_type->name . '/fields/field_' . $field_name . '/delete');
// Click the 'delete' button.
$this->drupalPost(NULL, array(), t('Delete'));
$this->assertText(t('The field @field has been deleted from the @type content type.',
array('@field' => $field_name, '@type' => $node_type->name)));
}
}
/**
* Add all fields using Form API.
*
* @param mixed $node_type
* A content type object. If none is specified,
* the test fails.
*/
protected function formAddAllFields($node_type = NULL) {
if (!$node_type) {
$this->fail('No content type specified.');
}
// Get all our field types.
$field_types = $this->getFieldTypes();
// Keep a list of no_ui fields so we can tell the user.
$unsafe_field_types = array();
$field_names = array();
$manage_path = 'admin/structure/types/manage/' . $node_type->name . '/fields';
foreach ($field_types as $field_type) {
// Get the field info.
$field_info = field_info_field_types($field_type);
// Exclude no_ui field types.
if (isset($field_info['no_ui']) && $field_info['no_ui']) {
$unsafe_field_types[] = $field_type;
}
else {
// Generate a name for our field.
// 26 is max length for field name.
$field_name = drupal_strtolower($this->randomName(26));
$field_names[$field_type] = $field_name;
// Create the field through Form API.
$this->formCreateField($manage_path, $field_type, $field_name,
$field_info['default_widget'], 1);
}
}
// Tell the user which fields we couldn't test.
if (!empty($unsafe_field_types)) {
debug(
'Unable to attach these no_ui fields: ' .
implode(', ', $unsafe_field_types)
);
}
// Somehow clicking "save" isn't enough, and we have to
// rebuild a few caches.
node_types_rebuild();
menu_rebuild();
return $field_names;
}
/**
* Create a field using the content type management form.
*
* @param mixed $manage_path
* Path to our content type management form.
* @param mixed $field_type
* The type of field we're adding.
* @param mixed $field_name
* The name of the field instance we want.
* @param mixed $widget_type
* Which widget would we like?
* @param mixed $cardinality
* Cardinality for this field instance.
*/
protected function formCreateField($manage_path, $field_type, $field_name, $widget_type, $cardinality) {
// $manage_path is the field edit form for our content type.
$this->drupalGet($manage_path);
$edit = array(
'fields[_add_new_field][label]' => $field_name,
'fields[_add_new_field][field_name]' => $field_name,
'fields[_add_new_field][type]' => $field_type,
'fields[_add_new_field][widget_type]' => $widget_type,
);
$this->drupalPost(NULL, $edit, t('Save'));
// Assume there are no settings for this,
// so just press the button.
$this->drupalPost(NULL, array(), t('Save field settings'));
$edit = array('field[cardinality]' => (string) $cardinality);
$this->drupalPost(NULL, $edit, t('Save settings'));
debug(
t('Saved settings for field !field_name with widget !widget_type and cardinality !cardinality',
array(
'!field_name' => $field_name,
'!widget_type' => $widget_type,
'!cardinality' => $cardinality,
)
)
);
$this->assertText(t('Saved @name configuration.', array('@name' => $field_name)));
}
/**
* Create a node with some field content.
*
* @return object
* Node object for the created node.
*/
public function createFieldContentForUser(
$account = NULL,
$content = 'testable_content',
$node_type = NULL,
$instance_name = '',
$column = NULL
) {
if (!$column) {
$this->fail('No column name given.');
return NULL;
}
if (!$account) {
$account = $this->drupalCreateUser(array(
'bypass node access',
'administer content types',
));
}
$this->drupalLogin($account);
if (!$node_type) {
$node_type = $this->codeTestGenericAddAllFields();
}
if (!$instance_name) {
$instance_name = reset($this->instanceNames);
}
$field = array();
$field[LANGUAGE_NONE][0][$column] = $content;
$settings = array(
'type' => $node_type->name,
$instance_name => $field,
);
$node = $this->drupalCreateNode($settings);
$this->assertTrue($node, 'Node of type ' . $node->type . ' allegedly created.');
$node = node_load($node->nid);
debug('Loaded node id: ' . $node->nid);
$this->assertTrue($node->$instance_name, 'Field actually created.');
$field = $node->$instance_name;
$this->assertTrue($field[LANGUAGE_NONE][0][$column] == $content,
'Content was stored properly on the field.');
return $node;
}
}
class FieldTestPermissionsExample extends GenericFieldTest {
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp(array('field_permission_example'));
}
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Field Permission Example',
'description' => 'Various tests on the functionality of the Fieldnote field.',
'group' => 'Examples',
);
}
/**
* {@inheritdoc}
*/
protected function getFieldTypes() {
return array('field_permission_example_fieldnote');
}
/**
* {@inheritdoc}
*/
protected function getModule() {
return 'field_permission_example';
}
/**
* Override createFieldContentForUser().
*
* We override so we can make sure $column is set to 'notes'.
*/
public function createFieldContentForUser(
$account = NULL,
$content = 'fieldnote_testable_content',
$node_type = NULL,
$instance_name = '',
$column = 'notes'
) {
return parent::createFieldContentForUser($account, $content, $node_type, $instance_name, $column);
}
/**
* Test of hook_field_info() and other implementation requirements.
*
* @see GenericFieldTest::runTestGenericFieldInfo()
*/
public function testFieldnoteInfo() {
$this->runTestGenericFieldInfo();
}
/**
* Add and remove the field through Form API.
*/
public function testAddRemoveFieldnoteForm() {
$this->formTestGenericFieldNodeAddDeleteForm();
}
/**
* Add and remove the field through code.
*/
public function testAddRemoveFieldnoteCode() {
$node_type = $this->codeTestGenericAddAllFields();
$this->codeTestGenericRemoveAllFields($node_type);
}
/**
* Test view permissions.
*/
public function testFieldnoteViewPerms() {
// We create two sets of content so we can get a few
// test cases out of the way.
$view_own_content = $this->randomName(23);
$view_any_content = $this->randomName(23);
$view_own_node = $this->createFieldContentForUser(NULL, $view_own_content);
// Get the type of the node so we can create another one.
$node_type = node_type_load($view_own_node->type);
$view_any_node = $this->createFieldContentForUser(NULL, $view_any_content, $node_type);
// There should be a node now, with some lovely content, but it's the wrong
// user for the view-own test.
$view_own_account = $this->drupalCreateUser(array(
'view own fieldnote',
));
debug("Created user with 'view own fieldnote' permission.");
// Now change the user id for the test node.
$view_own_node = node_load($view_own_node->nid);
$view_own_node->uid = $view_own_account->uid;
node_save($view_own_node);
$view_own_node = node_load($view_own_node->nid);
$this->assertTrue($view_own_node->uid == $view_own_account->uid, 'New user assigned to node.');
// Now we want to look at the page with the field and
// check that we can see it.
$this->drupalLogin($view_own_account);
$this->drupalGet('node/' . $view_own_node->nid);
// Check that the field content is present.
$output_strings = $this->xpath("//div[contains(@class,'stickynote')]/text()");
$this->assertEqual((string) reset($output_strings), $view_own_content);
debug("'view own fieldnote' can view own field.");
// This account shouldn't be able to see the field on the
// 'view any' node.
$this->drupalGet('node/' . $view_any_node->nid);
// Check that the field content is not present.
$output_strings = $this->xpath("//div[contains(@class,'stickynote')]/text()");
$this->assertNotEqual((string) reset($output_strings), $view_any_content);
debug("'view own fieldnote' cannot view other field.");
// Now, to test for 'view any fieldnote' we create another user
// with that permission, and try to look at the same node.
$view_any_account = $this->drupalCreateUser(array(
'view any fieldnote',
));
debug("Created user with 'view any fieldnote' permission.");
$this->drupalLogin($view_any_account);
// This account should be able to see the field on the
// 'view any' node.
$this->drupalGet('node/' . $view_any_node->nid);
// Check that the field content is present.
$output_strings = $this->xpath("//div[contains(@class,'stickynote')]/text()");
$this->assertEqual((string) reset($output_strings), $view_any_content);
debug("'view any fieldnote' can view other field.");
}
/**
* Test edit permissions.
*
* Note that this is mostly identical to testFieldnoteViewPerms() and could
* probably be refactored.
*/
public function testFieldnoteEditPerms() {
// We create two sets of content so we can get a few
// test cases out of the way.
$edit_own_content = $this->randomName(23);
$edit_any_content = $this->randomName(23);
$edit_own_node = $this->createFieldContentForUser(NULL, $edit_own_content);
// Get the type of the node so we can create another one.
$node_type = node_type_load($edit_own_node->type);
$edit_any_node = $this->createFieldContentForUser(NULL, $edit_any_content, $node_type);
$edit_own_account = $this->drupalCreateUser(array(
'edit own ' . $node_type->name . ' content',
'edit own fieldnote',
));
debug("Created user with 'edit own fieldnote' permission.");
// Now change the user id for the test node.
$edit_own_node = node_load($edit_own_node->nid);
$edit_own_node->uid = $edit_own_account->uid;
node_save($edit_own_node);
$edit_own_node = node_load($edit_own_node->nid);
$this->assertTrue($edit_own_node->uid == $edit_own_account->uid, 'New edit test user assigned to node.');
// Now we want to look at the page with the field and
// check that we can see it.
$this->drupalLogin($edit_own_account);
$this->drupalGet('node/' . $edit_own_node->nid . '/edit');
$this->assertText($edit_own_content, "'edit own fieldnote' can edit own fieldnote.");
// This account shouldn't be able to edit the field on the
// 'edit any' node.
$this->drupalGet('node/' . $edit_any_node->nid . '/edit');
$this->assertNoText($edit_any_content, "'edit own fieldnote' can not edit any fieldnote.");
// Now, to test for 'edit any fieldnote' we create another user
// with that permission, and try to edit at the same node.
// We have to add the ability to edit any node content, as well
// or Drupal will deny us access to the page.
$edit_any_account = $this->drupalCreateUser(array(
'edit any ' . $node_type->name . ' content',
'edit any fieldnote',
));
debug("Created user with 'edit any fieldnote' permission.");
$this->drupalLogin($edit_any_account);
// This account should be able to see the field on the
// 'edit any' node.
$this->drupalGet('node/' . $edit_any_node->nid . '/edit');
$this->assertText($edit_any_content, "'edit any fieldnote' can edit any fieldnote.");
}
}

View File

@@ -0,0 +1,13 @@
name = File example
description = Examples of using the Drupal File API and Stream Wrappers.
package = Example modules
core = 7.x
files[] = file_example_session_streams.inc
files[] = file_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,570 @@
<?php
/**
* @file
* Examples demonstrating the Drupal File API (and Stream Wrappers).
*/
/**
* @defgroup file_example Example: Files
* @ingroup examples
* @{
* Examples demonstrating the Drupal File API (and Stream Wrappers).
*
* The File Example module is a part of the Examples for Developers Project
* and provides various Drupal File API Examples. You can download and
* experiment with this code at the
* @link http://drupal.org/project/examples Examples for Developers project page. @endlink
*
* See @link http://drupal.org/node/555118 Drupal File API @endlink for handbook
* documentation on the File API and
* @link file File summary on api.drupal.org @endlink for the function summary.
*/
/**
* Implements hook_menu().
*
* Set up the URLs (menu entries) for the file examples.
*/
function file_example_menu() {
$items = array();
$items['examples/file_example'] = array(
'title' => 'File Example',
'page callback' => 'file_example_intro',
'access callback' => TRUE,
'expanded' => TRUE,
);
$items['examples/file_example/fileapi'] = array(
'title' => 'Use File API to read/write a file',
'page callback' => 'drupal_get_form',
'access arguments' => array('use file example'),
'page arguments' => array('file_example_readwrite'),
);
$items['examples/file_example/access_session'] = array(
'page callback' => 'file_example_session_contents',
'access arguments' => array('use file example'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_permission().
*/
function file_example_permission() {
return array(
'use file example' => array(
'title' => t('Use the examples in the File Example module'),
),
);
}
/**
* A simple introduction to the workings of this module.
*/
function file_example_intro() {
$markup = t('The file example module provides a form and code to demonstrate the Drupal 7 file api. Experiment with the form, and then look at the submit handlers in the code to understand the file api.');
return array('#markup' => $markup);
}
/**
* Form builder function.
*
* A simple form that allows creation of a file, managed or unmanaged. It
* also allows reading/deleting a file and creation of a directory.
*/
function file_example_readwrite($form, &$form_state) {
if (empty($_SESSION['file_example_default_file'])) {
$_SESSION['file_example_default_file'] = 'session://drupal.txt';
}
$default_file = $_SESSION['file_example_default_file'];
if (empty($_SESSION['file_example_default_directory'])) {
$_SESSION['file_example_default_directory'] = 'session://directory1';
}
$default_directory = $_SESSION['file_example_default_directory'];
$form['write_file'] = array(
'#type' => 'fieldset',
'#title' => t('Write to a file'),
);
$form['write_file']['write_contents'] = array(
'#type' => 'textfield',
'#title' => t('Enter something you would like to write to a file') . ' ' . date('m'),
'#default_value' => t('Put some text here or just use this text'),
);
$form['write_file']['destination'] = array(
'#type' => 'textfield',
'#default_value' => $default_file,
'#title' => t('Optional: Enter the streamwrapper saying where it should be written'),
'#description' => t('This may be public://some_dir/test_file.txt or private://another_dir/some_file.txt, for example. If you include a directory, it must already exist. The default is "public://". Since this example supports session://, you can also use something like session://somefile.txt.'),
);
$form['write_file']['managed_submit'] = array(
'#type' => 'submit',
'#value' => t('Write managed file'),
'#submit' => array('file_example_managed_write_submit'),
);
$form['write_file']['unmanaged_submit'] = array(
'#type' => 'submit',
'#value' => t('Write unmanaged file'),
'#submit' => array('file_example_unmanaged_write_submit'),
);
$form['write_file']['unmanaged_php'] = array(
'#type' => 'submit',
'#value' => t('Unmanaged using PHP'),
'#submit' => array('file_example_unmanaged_php_submit'),
);
$form['fileops'] = array(
'#type' => 'fieldset',
'#title' => t('Read from a file'),
);
$form['fileops']['fileops_file'] = array(
'#type' => 'textfield',
'#default_value' => $default_file,
'#title' => t('Enter the URI of a file'),
'#description' => t('This must be a stream-type description like public://some_file.txt or http://drupal.org or private://another_file.txt or (for this example) session://yet_another_file.txt.'),
);
$form['fileops']['read_submit'] = array(
'#type' => 'submit',
'#value' => t('Read the file and store it locally'),
'#submit' => array('file_example_read_submit'),
);
$form['fileops']['delete_submit'] = array(
'#type' => 'submit',
'#value' => t('Delete file'),
'#submit' => array('file_example_delete_submit'),
);
$form['fileops']['check_submit'] = array(
'#type' => 'submit',
'#value' => t('Check to see if file exists'),
'#submit' => array('file_example_file_check_exists_submit'),
);
$form['directory'] = array(
'#type' => 'fieldset',
'#title' => t('Create or prepare a directory'),
);
$form['directory']['directory_name'] = array(
'#type' => 'textfield',
'#title' => t('Directory to create/prepare/delete'),
'#default_value' => $default_directory,
'#description' => t('This is a directory as in public://some/directory or private://another/dir.'),
);
$form['directory']['create_directory'] = array(
'#type' => 'submit',
'#value' => t('Create directory'),
'#submit' => array('file_example_create_directory_submit'),
);
$form['directory']['delete_directory'] = array(
'#type' => 'submit',
'#value' => t('Delete directory'),
'#submit' => array('file_example_delete_directory_submit'),
);
$form['directory']['check_directory'] = array(
'#type' => 'submit',
'#value' => t('Check to see if directory exists'),
'#submit' => array('file_example_check_directory_submit'),
);
$form['debug'] = array(
'#type' => 'fieldset',
'#title' => t('Debugging'),
);
$form['debug']['show_raw_session'] = array(
'#type' => 'submit',
'#value' => t('Show raw $_SESSION contents'),
'#submit' => array('file_example_show_session_contents_submit'),
);
return $form;
}
/**
* Submit handler to write a managed file.
*
* The key functions used here are:
* - file_save_data(), which takes a buffer and saves it to a named file and
* also creates a tracking record in the database and returns a file object.
* In this function we use FILE_EXISTS_RENAME (the default) as the argument,
* which means that if there's an existing file, create a new non-colliding
* filename and use it.
* - file_create_url(), which converts a URI in the form public://junk.txt or
* private://something/test.txt into a URL like
* http://example.com/sites/default/files/junk.txt.
*/
function file_example_managed_write_submit($form, &$form_state) {
$data = $form_state['values']['write_contents'];
$uri = !empty($form_state['values']['destination']) ? $form_state['values']['destination'] : NULL;
// Managed operations work with a file object.
$file_object = file_save_data($data, $uri, FILE_EXISTS_RENAME);
if (!empty($file_object)) {
$url = file_create_url($file_object->uri);
$_SESSION['file_example_default_file'] = $file_object->uri;
drupal_set_message(
t('Saved managed file: %file to destination %destination (accessible via !url, actual uri=<span id="uri">@uri</span>)',
array(
'%file' => print_r($file_object, TRUE),
'%destination' => $uri, '@uri' => $file_object->uri,
'!url' => l(t('this URL'), $url),
)
)
);
}
else {
drupal_set_message(t('Failed to save the managed file'), 'error');
}
}
/**
* Submit handler to write an unmanaged file.
*
* The key functions used here are:
* - file_unmanaged_save_data(), which takes a buffer and saves it to a named
* file, but does not create any kind of tracking record in the database.
* This example uses FILE_EXISTS_REPLACE for the third argument, meaning
* that if there's an existing file at this location, it should be replaced.
* - file_create_url(), which converts a URI in the form public://junk.txt or
* private://something/test.txt into a URL like
* http://example.com/sites/default/files/junk.txt.
*/
function file_example_unmanaged_write_submit($form, &$form_state) {
$data = $form_state['values']['write_contents'];
$destination = !empty($form_state['values']['destination']) ? $form_state['values']['destination'] : NULL;
// With the unmanaged file we just get a filename back.
$filename = file_unmanaged_save_data($data, $destination, FILE_EXISTS_REPLACE);
if ($filename) {
$url = file_create_url($filename);
$_SESSION['file_example_default_file'] = $filename;
drupal_set_message(
t('Saved file as %filename (accessible via !url, uri=<span id="uri">@uri</span>)',
array(
'%filename' => $filename,
'@uri' => $filename,
'!url' => l(t('this URL'), $url),
)
)
);
}
else {
drupal_set_message(t('Failed to save the file'), 'error');
}
}
/**
* Submit handler to write an unmanaged file using plain PHP functions.
*
* The key functions used here are:
* - file_unmanaged_save_data(), which takes a buffer and saves it to a named
* file, but does not create any kind of tracking record in the database.
* - file_create_url(), which converts a URI in the form public://junk.txt or
* private://something/test.txt into a URL like
* http://example.com/sites/default/files/junk.txt.
* - drupal_tempnam() generates a temporary filename for use.
*/
function file_example_unmanaged_php_submit($form, &$form_state) {
$data = $form_state['values']['write_contents'];
$destination = !empty($form_state['values']['destination']) ? $form_state['values']['destination'] : NULL;
if (empty($destination)) {
// If no destination has been provided, use a generated name.
$destination = drupal_tempnam('public://', 'file');
}
// With all traditional PHP functions we can use the stream wrapper notation
// for a file as well.
$fp = fopen($destination, 'w');
// To demonstrate the fact that everything is based on streams, we'll do
// multiple 5-character writes to put this to the file. We could easily
// (and far more conveniently) write it in a single statement with
// fwrite($fp, $data).
$length = strlen($data);
$write_size = 5;
for ($i = 0; $i < $length; $i += $write_size) {
$result = fwrite($fp, substr($data, $i, $write_size));
if ($result === FALSE) {
drupal_set_message(t('Failed writing to the file %file', array('%file' => $destination)), 'error');
fclose($fp);
return;
}
}
$url = file_create_url($destination);
$_SESSION['file_example_default_file'] = $destination;
drupal_set_message(
t('Saved file as %filename (accessible via !url, uri=<span id="uri">@uri</span>)',
array(
'%filename' => $destination,
'@uri' => $destination,
'!url' => l(t('this URL'), $url),
)
)
);
}
/**
* Submit handler for reading a stream wrapper.
*
* Drupal now has full support for PHP's stream wrappers, which means that
* instead of the traditional use of all the file functions
* ($fp = fopen("/tmp/some_file.txt");) far more sophisticated and generalized
* (and extensible) things can be opened as if they were files. Drupal itself
* provides the public:// and private:// schemes for handling public and
* private files. PHP provides file:// (the default) and http://, so that a
* URL can be read or written (as in a POST) as if it were a file. In addition,
* new schemes can be provided for custom applications, as will be demonstrated
* below.
*
* Here we take the stream wrapper provided in the form. We grab the
* contents with file_get_contents(). Notice that's it's as simple as that:
* file_get_contents("http://example.com") or
* file_get_contents("public://somefile.txt") just works. Although it's
* not necessary, we use file_unmanaged_save_data() to save this file locally
* and then find a local URL for it by using file_create_url().
*/
function file_example_read_submit($form, &$form_state) {
$uri = $form_state['values']['fileops_file'];
if (!is_file($uri)) {
drupal_set_message(t('The file %uri does not exist', array('%uri' => $uri)), 'error');
return;
}
// Make a working filename to save this by stripping off the (possible)
// file portion of the streamwrapper. If it's an evil file extension,
// file_munge_filename() will neuter it.
$filename = file_munge_filename(preg_replace('@^.*/@', '', $uri), '', TRUE);
$buffer = file_get_contents($uri);
if ($buffer) {
$sourcename = file_unmanaged_save_data($buffer, 'public://' . $filename);
if ($sourcename) {
$url = file_create_url($sourcename);
$_SESSION['file_example_default_file'] = $sourcename;
drupal_set_message(
t('The file was read and copied to %filename which is accessible at !url',
array(
'%filename' => $sourcename,
'!url' => l($url, $url),
)
)
);
}
else {
drupal_set_message(t('Failed to save the file'));
}
}
else {
// We failed to get the contents of the requested file.
drupal_set_message(t('Failed to retrieve the file %file', array('%file' => $uri)));
}
}
/**
* Submit handler to delete a file.
*/
function file_example_delete_submit($form, &$form_state) {
$uri = $form_state['values']['fileops_file'];
// Since we don't know if the file is managed or not, look in the database
// to see. Normally, code would be working with either managed or unmanaged
// files, so this is not a typical situation.
$file_object = file_example_get_managed_file($uri);
// If a managed file, use file_delete().
if (!empty($file_object)) {
$result = file_delete($file_object);
if ($result !== TRUE) {
drupal_set_message(t('Failed deleting managed file %uri. Result was %result',
array(
'%uri' => $uri,
'%result' => print_r($result, TRUE),
)
), 'error');
}
else {
drupal_set_message(t('Successfully deleted managed file %uri', array('%uri' => $uri)));
$_SESSION['file_example_default_file'] = $uri;
}
}
// Else use file_unmanaged_delete().
else {
$result = file_unmanaged_delete($uri);
if ($result !== TRUE) {
drupal_set_message(t('Failed deleting unmanaged file %uri', array('%uri' => $uri, 'error')));
}
else {
drupal_set_message(t('Successfully deleted unmanaged file %uri', array('%uri' => $uri)));
$_SESSION['file_example_default_file'] = $uri;
}
}
}
/**
* Submit handler to check existence of a file.
*/
function file_example_file_check_exists_submit($form, &$form_state) {
$uri = $form_state['values']['fileops_file'];
if (is_file($uri)) {
drupal_set_message(t('The file %uri exists.', array('%uri' => $uri)));
}
else {
drupal_set_message(t('The file %uri does not exist.', array('%uri' => $uri)));
}
}
/**
* Submit handler for directory creation.
*
* Here we create a directory and set proper permissions on it using
* file_prepare_directory().
*/
function file_example_create_directory_submit($form, &$form_state) {
$directory = $form_state['values']['directory_name'];
// The options passed to file_prepare_directory are a bitmask, so we can
// specify either FILE_MODIFY_PERMISSIONS (set permissions on the directory),
// FILE_CREATE_DIRECTORY, or both together:
// FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY.
// FILE_MODIFY_PERMISSIONS will set the permissions of the directory by
// by default to 0755, or to the value of the variable 'file_chmod_directory'.
if (!file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS | FILE_CREATE_DIRECTORY)) {
drupal_set_message(t('Failed to create %directory.', array('%directory' => $directory)), 'error');
}
else {
drupal_set_message(t('Directory %directory is ready for use.', array('%directory' => $directory)));
$_SESSION['file_example_default_directory'] = $directory;
}
}
/**
* Submit handler for directory deletion.
*
* @see file_unmanaged_delete_recursive()
*/
function file_example_delete_directory_submit($form, &$form_state) {
$directory = $form_state['values']['directory_name'];
$result = file_unmanaged_delete_recursive($directory);
if (!$result) {
drupal_set_message(t('Failed to delete %directory.', array('%directory' => $directory)), 'error');
}
else {
drupal_set_message(t('Recursively deleted directory %directory.', array('%directory' => $directory)));
$_SESSION['file_example_default_directory'] = $directory;
}
}
/**
* Submit handler to test directory existence.
*
* This actually just checks to see if the directory is writable
*
* @param array $form
* FormAPI form.
* @param array $form_state
* FormAPI form state.
*/
function file_example_check_directory_submit($form, &$form_state) {
$directory = $form_state['values']['directory_name'];
$result = is_dir($directory);
if (!$result) {
drupal_set_message(t('Directory %directory does not exist.', array('%directory' => $directory)));
}
else {
drupal_set_message(t('Directory %directory exists.', array('%directory' => $directory)));
}
}
/**
* Utility submit function to show the contents of $_SESSION.
*/
function file_example_show_session_contents_submit($form, &$form_state) {
// If the devel module is installed, use it's nicer message format.
if (module_exists('devel')) {
dsm($_SESSION['file_example'], t('Entire $_SESSION["file_example"]'));
}
else {
drupal_set_message('<pre>' . print_r($_SESSION['file_example'], TRUE) . '</pre>');
}
}
/**
* Utility function to check for and return a managed file.
*
* In this demonstration code we don't necessarily know if a file is managed
* or not, so often need to check to do the correct behavior. Normal code
* would not have to do this, as it would be working with either managed or
* unmanaged files.
*
* @param string $uri
* The URI of the file, like public://test.txt.
*/
function file_example_get_managed_file($uri) {
$fid = db_query('SELECT fid FROM {file_managed} WHERE uri = :uri', array(':uri' => $uri))->fetchField();
if (!empty($fid)) {
$file_object = file_load($fid);
return $file_object;
}
return FALSE;
}
/**
* Implements hook_stream_wrappers().
*
* hook_stream_wrappers() is Drupal's way of exposing the class that PHP will
* use to provide a new stream wrapper class. In this case, we'll expose the
* 'session' scheme, so a file reference like "session://example/example.txt"
* is readable and writable as a location in the $_SESSION variable.
*
* @see FileExampleSessionStreamWrapper
*/
function file_example_stream_wrappers() {
$wrappers = array(
'session' => array(
'name' => t('Example: $_SESSION variable storage'),
'class' => 'FileExampleSessionStreamWrapper',
'description' => t('Store files in the $_SESSION variable as an example.'),
),
);
return $wrappers;
}
/**
* Show the contents of a session file.
*
* This page callback function is called by the Menu API for the path
* examples/file_example/access_session. Any extra path elements
* beyond this are considered to be the session path. E.g.:
* examples/file_example/access_session/foo/bar.txt would be the
* equivalent of session://foo/bar.txt, which will map into
* $_SESSION as keys: $_SESSION['foo']['bar.txt']
*
* Menu API will pass in additional path elements as function arguments. You
* can obtain these with func_get_args().
*
* @return string
* A message containing the contents of the session file.
*
* @see file_get_contents()
*/
function file_example_session_contents() {
$path_components = func_get_args();
$session_path = 'session://' . implode('/', $path_components);
$content = file_get_contents($session_path);
if ($content !== FALSE) {
return t('Contents of @path :',
array('@path' => check_plain($session_path))) . ' ' .
print_r($content, TRUE);
}
return t('Unable to load contents of: @path',
array('@path' => check_plain($session_path)));
}
/**
* @} End of "defgroup file_example".
*/

View File

@@ -0,0 +1,149 @@
<?php
/**
* @file
* Tests for File Example.
*/
/**
* Functional tests for the File Example module.
*
* @ingroup file_example
*/
class FileExampleTest extends DrupalWebTestCase {
protected $priviledgedUser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'File Example Functionality',
'description' => 'Test File Example features and sample streamwrapper.',
'group' => 'Examples',
);
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp(array('file_example'));
$this->priviledgedUser = $this->drupalCreateUser(array('use file example'));
$this->drupalLogin($this->priviledgedUser);
}
/**
* Test the basic File Example UI.
*
* - Create a directory to work with
* - Foreach scheme create and read files using each of the three methods.
*/
public function testFileExampleBasic() {
$expected_text = array(
t('Write managed file') => t('Saved managed file'),
t('Write unmanaged file') => t('Saved file as'),
t('Unmanaged using PHP') => t('Saved file as'),
);
// For each of the three buttons == three write types.
$buttons = array(
t('Write managed file'),
t('Write unmanaged file'),
t('Unmanaged using PHP'),
);
foreach ($buttons as $button) {
// For each scheme supported by Drupal + the session:// wrapper.
$schemes = array('public', 'private', 'temporary', 'session');
foreach ($schemes as $scheme) {
// Create a directory for use.
$dirname = $scheme . '://' . $this->randomName(10);
// Directory does not yet exist; assert that.
$edit = array(
'directory_name' => $dirname,
);
$this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if directory exists'));
$this->assertRaw(t('Directory %dirname does not exist', array('%dirname' => $dirname)), 'Verify that directory does not exist.');
$this->drupalPost('examples/file_example/fileapi', $edit, t('Create directory'));
$this->assertRaw(t('Directory %dirname is ready for use', array('%dirname' => $dirname)));
$this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if directory exists'));
$this->assertRaw(t('Directory %dirname exists', array('%dirname' => $dirname)), 'Verify that directory now does exist.');
// Create a file in the directory we created.
$content = $this->randomName(30);
$filename = $dirname . '/' . $this->randomName(30) . '.txt';
// Assert that the file we're about to create does not yet exist.
$edit = array(
'fileops_file' => $filename,
);
$this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if file exists'));
$this->assertRaw(t('The file %filename does not exist', array('%filename' => $filename)), 'Verify that file does not yet exist.');
debug(
t('Processing button=%button, scheme=%scheme, dir=%dirname, file=%filename',
array(
'%button' => $button,
'%scheme' => $scheme,
'%filename' => $filename,
'%dirname' => $dirname,
)
)
);
$edit = array(
'write_contents' => $content,
'destination' => $filename,
);
$this->drupalPost('examples/file_example/fileapi', $edit, $button);
$this->assertText($expected_text[$button]);
// Capture the name of the output file, as it might have changed due
// to file renaming.
$element = $this->xpath('//span[@id="uri"]');
$output_filename = (string) $element[0];
debug($output_filename, 'Name of output file');
// Click the link provided that is an easy way to get the data for
// checking and make sure that the data we put in is what we get out.
if (!in_array($scheme, array('private', 'temporary'))) {
$this->clickLink(t('this URL'));
$this->assertText($content);
}
// Verify that the file exists.
$edit = array(
'fileops_file' => $filename,
);
$this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if file exists'));
$this->assertRaw(t('The file %filename exists', array('%filename' => $filename)), 'Verify that file now exists.');
// Now read the file that got written above and verify that we can use
// the writing tools.
$edit = array(
'fileops_file' => $output_filename,
);
$this->drupalPost('examples/file_example/fileapi', $edit, t('Read the file and store it locally'));
$this->assertText(t('The file was read and copied'));
$edit = array(
'fileops_file' => $filename,
);
$this->drupalPost('examples/file_example/fileapi', $edit, t('Delete file'));
$this->assertText(t('Successfully deleted'));
$this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if file exists'));
$this->assertRaw(t('The file %filename does not exist', array('%filename' => $filename)), 'Verify file has been deleted.');
$edit = array(
'directory_name' => $dirname,
);
$this->drupalPost('examples/file_example/fileapi', $edit, t('Delete directory'));
$this->drupalPost('examples/file_example/fileapi', $edit, t('Check to see if directory exists'));
$this->assertRaw(t('Directory %dirname does not exist', array('%dirname' => $dirname)), 'Verify that directory does not exist after deletion.');
}
}
}
}

View File

@@ -0,0 +1,698 @@
<?php
/**
* @file
* Provides a demonstration session:// streamwrapper.
*
* This example is nearly fully functional, but has no known
* practical use. It's an example and demonstration only.
*/
/**
* Example stream wrapper class to handle session:// streams.
*
* This is just an example, as it could have horrible results if much
* information were placed in the $_SESSION variable. However, it does
* demonstrate both the read and write implementation of a stream wrapper.
*
* A "stream" is an important Unix concept for the reading and writing of
* files and other devices. Reading or writing a "stream" just means that you
* open some device, file, internet site, or whatever, and you don't have to
* know at all what it is. All the functions that deal with it are the same.
* You can read/write more from/to the stream, seek a position in the stream,
* or anything else without the code that does it even knowing what kind
* of device it is talking to. This Unix idea is extended into PHP's
* mindset.
*
* The idea of "stream wrapper" is that this can be extended indefinitely.
* The classic example is HTTP: With PHP you can do a
* file_get_contents("http://drupal.org/projects") as if it were a file,
* because the scheme "http" is supported natively in PHP. So Drupal adds
* the public:// and private:// schemes, and contrib modules can add any
* scheme they want to. This example adds the session:// scheme, which allows
* reading and writing the $_SESSION['file_example'] key as if it were a file.
*
* Note that because this implementation uses simple PHP arrays ($_SESSION)
* it is limited to string values, so binary files will not work correctly.
* Only text files can be used.
*
* @ingroup file_example
*/
class FileExampleSessionStreamWrapper implements DrupalStreamWrapperInterface {
/**
* Stream context resource.
*
* @var Resource
*/
public $context;
/**
* Instance URI (stream).
*
* These streams will be references as 'session://example_target'
*
* @var String
*/
protected $uri;
/**
* The content of the stream.
*
* Since this trivial example just uses the $_SESSION variable, this is
* simply a reference to the contents of the related part of
* $_SESSION['file_example'].
*/
protected $sessionContent;
/**
* Pointer to where we are in a directory read.
*/
protected $directoryPointer;
/**
* List of keys in a given directory.
*/
protected $directoryKeys;
/**
* The pointer to the next read or write within the session variable.
*/
protected $streamPointer;
/**
* Constructor method.
*/
public function __construct() {
$_SESSION['file_example']['.isadir.txt'] = TRUE;
}
/**
* Implements setUri().
*/
public function setUri($uri) {
$this->uri = $uri;
}
/**
* Implements getUri().
*/
public function getUri() {
return $this->uri;
}
/**
* Implements getTarget().
*
* The "target" is the portion of the URI to the right of the scheme.
* So in session://example/test.txt, the target is 'example/test.txt'.
*/
public function getTarget($uri = NULL) {
if (!isset($uri)) {
$uri = $this->uri;
}
list($scheme, $target) = explode('://', $uri, 2);
// Remove erroneous leading or trailing, forward-slashes and backslashes.
// In the session:// scheme, there is never a leading slash on the target.
return trim($target, '\/');
}
/**
* Implements getMimeType().
*/
public static function getMimeType($uri, $mapping = NULL) {
if (!isset($mapping)) {
// The default file map, defined in file.mimetypes.inc is quite big.
// We only load it when necessary.
include_once DRUPAL_ROOT . '/includes/file.mimetypes.inc';
$mapping = file_mimetype_mapping();
}
$extension = '';
$file_parts = explode('.', basename($uri));
// Remove the first part: a full filename should not match an extension.
array_shift($file_parts);
// Iterate over the file parts, trying to find a match.
// For my.awesome.image.jpeg, we try:
// - jpeg
// - image.jpeg, and
// - awesome.image.jpeg
while ($additional_part = array_pop($file_parts)) {
$extension = drupal_strtolower($additional_part . ($extension ? '.' . $extension : ''));
if (isset($mapping['extensions'][$extension])) {
return $mapping['mimetypes'][$mapping['extensions'][$extension]];
}
}
return 'application/octet-stream';
}
/**
* Implements getDirectoryPath().
*
* In this case there is no directory string, so return an empty string.
*/
public function getDirectoryPath() {
return '';
}
/**
* Overrides getExternalUrl().
*
* We have set up a helper function and menu entry to provide access to this
* key via HTTP; normally it would be accessible some other way.
*/
public function getExternalUrl() {
$path = $this->getLocalPath();
$url = url('examples/file_example/access_session/' . $path, array('absolute' => TRUE));
return $url;
}
/**
* We have no concept of chmod, so just return TRUE.
*/
public function chmod($mode) {
return TRUE;
}
/**
* Implements realpath().
*/
public function realpath() {
return 'session://' . $this->getLocalPath();
}
/**
* Returns the local path.
*
* Here we aren't doing anything but stashing the "file" in a key in the
* $_SESSION variable, so there's not much to do but to create a "path"
* which is really just a key in the $_SESSION variable. So something
* like 'session://one/two/three.txt' becomes
* $_SESSION['file_example']['one']['two']['three.txt'] and the actual path
* is "one/two/three.txt".
*
* @param string $uri
* Optional URI, supplied when doing a move or rename.
*/
protected function getLocalPath($uri = NULL) {
if (!isset($uri)) {
$uri = $this->uri;
}
$path = str_replace('session://', '', $uri);
$path = trim($path, '/');
return $path;
}
/**
* Opens a stream, as for fopen(), file_get_contents(), file_put_contents().
*
* @param string $uri
* A string containing the URI to the file to open.
* @param string $mode
* The file mode ("r", "wb" etc.).
* @param int $options
* A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
* @param string &$opened_path
* A string containing the path actually opened.
*
* @return bool
* Returns TRUE if file was opened successfully. (Always returns TRUE).
*
* @see http://php.net/manual/en/streamwrapper.stream-open.php
*/
public function stream_open($uri, $mode, $options, &$opened_path) {
$this->uri = $uri;
// We make $session_content a reference to the appropriate key in the
// $_SESSION variable. So if the local path were
// /example/test.txt it $session_content would now be a
// reference to $_SESSION['file_example']['example']['test.txt'].
$this->sessionContent = &$this->uri_to_session_key($uri);
// Reset the stream pointer since this is an open.
$this->streamPointer = 0;
return TRUE;
}
/**
* Return a reference to the correct $_SESSION key.
*
* @param string $uri
* The uri: session://something
* @param bool $create
* If TRUE, create the key
*
* @return array|bool
* A reference to the array at the end of the key-path, or
* FALSE if the path doesn't map to a key-path (and $create is FALSE).
*/
protected function &uri_to_session_key($uri, $create = TRUE) {
// Since our uri_to_session_key() method returns a reference, we
// have to set up a failure flag variable.
$fail = FALSE;
$path = $this->getLocalPath($uri);
$path_components = explode('/', $path);
// Set up a reference to the root session:// 'directory.'
$var = &$_SESSION['file_example'];
// Handle case of just session://.
if (count($path_components) < 1) {
return $var;
}
// Walk through the path components and create keys in $_SESSION,
// unless we're told not to create them.
foreach ($path_components as $component) {
if ($create || isset($var[$component])) {
$var = &$var[$component];
}
else {
// This path doesn't exist as keys, either because the
// key doesn't exist, or because we're told not to create it.
return $fail;
}
}
return $var;
}
/**
* Support for flock().
*
* The $_SESSION variable has no locking capability, so return TRUE.
*
* @param int $operation
* One of the following:
* - LOCK_SH to acquire a shared lock (reader).
* - LOCK_EX to acquire an exclusive lock (writer).
* - LOCK_UN to release a lock (shared or exclusive).
* - LOCK_NB if you don't want flock() to block while locking (not
* supported on Windows).
*
* @return bool
* Always returns TRUE at the present time. (no support)
*
* @see http://php.net/manual/en/streamwrapper.stream-lock.php
*/
public function stream_lock($operation) {
return TRUE;
}
/**
* Support for fread(), file_get_contents() etc.
*
* @param int $count
* Maximum number of bytes to be read.
*
* @return string
* The string that was read, or FALSE in case of an error.
*
* @see http://php.net/manual/en/streamwrapper.stream-read.php
*/
public function stream_read($count) {
if (is_string($this->sessionContent)) {
$remaining_chars = drupal_strlen($this->sessionContent) - $this->streamPointer;
$number_to_read = min($count, $remaining_chars);
if ($remaining_chars > 0) {
$buffer = drupal_substr($this->sessionContent, $this->streamPointer, $number_to_read);
$this->streamPointer += $number_to_read;
return $buffer;
}
}
return FALSE;
}
/**
* Support for fwrite(), file_put_contents() etc.
*
* @param string $data
* The string to be written.
*
* @return int
* The number of bytes written (integer).
*
* @see http://php.net/manual/en/streamwrapper.stream-write.php
*/
public function stream_write($data) {
// Sanitize the data in a simple way since we're putting it into the
// session variable.
$data = check_plain($data);
$this->sessionContent = substr_replace($this->sessionContent, $data, $this->streamPointer);
$this->streamPointer += drupal_strlen($data);
return drupal_strlen($data);
}
/**
* Support for feof().
*
* @return bool
* TRUE if end-of-file has been reached.
*
* @see http://php.net/manual/en/streamwrapper.stream-eof.php
*/
public function stream_eof() {
return FALSE;
}
/**
* Support for fseek().
*
* @param int $offset
* The byte offset to got to.
* @param int $whence
* SEEK_SET, SEEK_CUR, or SEEK_END.
*
* @return bool
* TRUE on success.
*
* @see http://php.net/manual/en/streamwrapper.stream-seek.php
*/
public function stream_seek($offset, $whence) {
if (drupal_strlen($this->sessionContent) >= $offset) {
$this->streamPointer = $offset;
return TRUE;
}
return FALSE;
}
/**
* Support for fflush().
*
* @return bool
* TRUE if data was successfully stored (or there was no data to store).
* This always returns TRUE, as this example provides and needs no
* flush support.
*
* @see http://php.net/manual/en/streamwrapper.stream-flush.php
*/
public function stream_flush() {
return TRUE;
}
/**
* Support for ftell().
*
* @return int
* The current offset in bytes from the beginning of file.
*
* @see http://php.net/manual/en/streamwrapper.stream-tell.php
*/
public function stream_tell() {
return $this->streamPointer;
}
/**
* Support for fstat().
*
* @return array
* An array with file status, or FALSE in case of an error - see fstat()
* for a description of this array.
*
* @see http://php.net/manual/en/streamwrapper.stream-stat.php
*/
public function stream_stat() {
return array(
'size' => drupal_strlen($this->sessionContent),
);
}
/**
* Support for fclose().
*
* @return bool
* TRUE if stream was successfully closed.
*
* @see http://php.net/manual/en/streamwrapper.stream-close.php
*/
public function stream_close() {
$this->streamPointer = 0;
// Unassign the reference.
unset($this->sessionContent);
return TRUE;
}
/**
* Support for unlink().
*
* @param string $uri
* A string containing the uri to the resource to delete.
*
* @return bool
* TRUE if resource was successfully deleted.
*
* @see http://php.net/manual/en/streamwrapper.unlink.php
*/
public function unlink($uri) {
$path = $this->getLocalPath($uri);
$path_components = preg_split('/\//', $path);
$unset = '$_SESSION[\'file_example\']';
foreach ($path_components as $component) {
$unset .= '[\'' . $component . '\']';
}
// TODO: Is there a better way to delete from an array?
// drupal_array_get_nested_value() doesn't work because it only returns
// a reference; unsetting a reference only unsets the reference.
eval("unset($unset);");
return TRUE;
}
/**
* Support for rename().
*
* @param string $from_uri
* The uri to the file to rename.
* @param string $to_uri
* The new uri for file.
*
* @return bool
* TRUE if file was successfully renamed.
*
* @see http://php.net/manual/en/streamwrapper.rename.php
*/
public function rename($from_uri, $to_uri) {
$from_key = &$this->uri_to_session_key($from_uri);
$to_key = &$this->uri_to_session_key($to_uri);
if (is_dir($to_key) || is_file($to_key)) {
return FALSE;
}
$to_key = $from_key;
unset($from_key);
return TRUE;
}
/**
* Gets the name of the directory from a given path.
*
* @param string $uri
* A URI.
*
* @return string
* A string containing the directory name.
*
* @see drupal_dirname()
*/
public function dirname($uri = NULL) {
list($scheme, $target) = explode('://', $uri, 2);
$target = $this->getTarget($uri);
if (strpos($target, '/')) {
$dirname = preg_replace('@/[^/]*$@', '', $target);
}
else {
$dirname = '';
}
return $scheme . '://' . $dirname;
}
/**
* Support for mkdir().
*
* @param string $uri
* A string containing the URI to the directory to create.
* @param int $mode
* Permission flags - see mkdir().
* @param int $options
* A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE.
*
* @return bool
* TRUE if directory was successfully created.
*
* @see http://php.net/manual/en/streamwrapper.mkdir.php
*/
public function mkdir($uri, $mode, $options) {
// If this already exists, then we can't mkdir.
if (is_dir($uri) || is_file($uri)) {
return FALSE;
}
// Create the key in $_SESSION;
$this->uri_to_session_key($uri, TRUE);
// Place a magic file inside it to differentiate this from an empty file.
$marker_uri = $uri . '/.isadir.txt';
$this->uri_to_session_key($marker_uri, TRUE);
return TRUE;
}
/**
* Support for rmdir().
*
* @param string $uri
* A string containing the URI to the directory to delete.
* @param int $options
* A bit mask of STREAM_REPORT_ERRORS.
*
* @return bool
* TRUE if directory was successfully removed.
*
* @see http://php.net/manual/en/streamwrapper.rmdir.php
*/
public function rmdir($uri, $options) {
$path = $this->getLocalPath($uri);
$path_components = preg_split('/\//', $path);
$unset = '$_SESSION[\'file_example\']';
foreach ($path_components as $component) {
$unset .= '[\'' . $component . '\']';
}
// TODO: I really don't like this eval.
debug($unset, 'array element to be unset');
eval("unset($unset);");
return TRUE;
}
/**
* Support for stat().
*
* This important function goes back to the Unix way of doing things.
* In this example almost the entire stat array is irrelevant, but the
* mode is very important. It tells PHP whether we have a file or a
* directory and what the permissions are. All that is packed up in a
* bitmask. This is not normal PHP fodder.
*
* @param string $uri
* A string containing the URI to get information about.
* @param int $flags
* A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET.
*
* @return array|bool
* An array with file status, or FALSE in case of an error - see fstat()
* for a description of this array.
*
* @see http://php.net/manual/en/streamwrapper.url-stat.php
*/
public function url_stat($uri, $flags) {
// Get a reference to the $_SESSION key for this URI.
$key = $this->uri_to_session_key($uri, FALSE);
// Default to fail.
$return = FALSE;
$mode = 0;
// We will call an array a directory and the root is always an array.
if (is_array($key) && array_key_exists('.isadir.txt', $key)) {
// S_IFDIR means it's a directory.
$mode = 0040000;
}
elseif ($key !== FALSE) {
// S_IFREG, means it's a file.
$mode = 0100000;
}
if ($mode) {
$size = 0;
if ($mode == 0100000) {
$size = drupal_strlen($key);
}
// There are no protections on this, so all writable.
$mode |= 0777;
$return = array(
'dev' => 0,
'ino' => 0,
'mode' => $mode,
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $size,
'atime' => 0,
'mtime' => 0,
'ctime' => 0,
'blksize' => 0,
'blocks' => 0,
);
}
return $return;
}
/**
* Support for opendir().
*
* @param string $uri
* A string containing the URI to the directory to open.
* @param int $options
* Whether or not to enforce safe_mode (0x04).
*
* @return bool
* TRUE on success.
*
* @see http://php.net/manual/en/streamwrapper.dir-opendir.php
*/
public function dir_opendir($uri, $options) {
$var = &$this->uri_to_session_key($uri, FALSE);
if ($var === FALSE || !array_key_exists('.isadir.txt', $var)) {
return FALSE;
}
// We grab the list of key names, flip it so that .isadir.txt can easily
// be removed, then flip it back so we can easily walk it as a list.
$this->directoryKeys = array_flip(array_keys($var));
unset($this->directoryKeys['.isadir.txt']);
$this->directoryKeys = array_keys($this->directoryKeys);
$this->directoryPointer = 0;
return TRUE;
}
/**
* Support for readdir().
*
* @return string|bool
* The next filename, or FALSE if there are no more files in the directory.
*
* @see http://php.net/manual/en/streamwrapper.dir-readdir.php
*/
public function dir_readdir() {
if ($this->directoryPointer < count($this->directoryKeys)) {
$next = $this->directoryKeys[$this->directoryPointer];
$this->directoryPointer++;
return $next;
}
return FALSE;
}
/**
* Support for rewinddir().
*
* @return bool
* TRUE on success.
*
* @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php
*/
public function dir_rewinddir() {
$this->directoryPointer = 0;
}
/**
* Support for closedir().
*
* @return bool
* TRUE on success.
*
* @see http://php.net/manual/en/streamwrapper.dir-closedir.php
*/
public function dir_closedir() {
$this->directoryPointer = 0;
unset($this->directoryKeys);
return TRUE;
}
}

View File

@@ -0,0 +1,12 @@
name = Filter example
description = An example module showing how to define a custom filter.
package = Example modules
core = 7.x
files[] = filter_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,203 @@
<?php
/**
* @file
* Module file for filter_example.
*/
/**
* @defgroup filter_example Example: Filter
* @ingroup examples
* @{
* Demonstrates the creation of filters.
*
* This is an example outlining how a module can be used to define a filter
* to be run on user-submitted content before it is output to the browser.
*
* To show all the capabilities of the filter system, we will define two filters
* in this module. One will substitute the string "foo" with an
* administratively-defined replacement string. The other will find a custom
* XML tag, <time />, and replace it by the current time.
*
* Foo filter
*
* Drupal has several content formats (they are not filters), and in our example
* the foo replacement can be configured for each one of them, allowing an html
* or php replacement, so the module includes a settings callback, with options
* to configure that replacements. Also, a Tips callback will help showing the
* current replacement for the content type being edited.
*
* Time filter.
*
* This filter is a little trickier to implement than the previous one.
* Since the input involves special HTML characters (< and >) we have to
* run the filter before HTML is escaped/stripped by other filters. But
* we want to use HTML in our result as well, and so if we run this filter
* first our replacement string could be escaped or stripped. The solution
* is to use the "prepare" operation to escape the special characters, and
* to later replace our escaped version in the "process" step.
*/
/**
* Implements hook_menu().
*/
function filter_example_menu() {
$items['examples/filter_example'] = array(
'title' => 'Filter Example',
'page callback' => '_filter_example_information',
'access callback' => TRUE,
);
return $items;
}
/**
* Implements hook_help().
*/
function filter_example_help($path, $arg) {
switch ($path) {
case 'admin/help#filter_example':
return _filter_example_information();
}
}
/**
* Simply returns a little bit of information about the example.
*/
function _filter_example_information() {
return t("<p>This example provides two filters.</p><p>Foo Filter replaces
'foo' with a configurable replacement.</p><p>Time Tag replaces the string
'&lt;time /&gt;' with the current time.</p><p>To use these filters, go to !link and
configure an input format, or create a new one.</p>",
array('!link' => l(t('admin/config/content/formats'), 'admin/config/content/formats'))
);
}
/**
* Implements hook_filter_info().
*
* Here we define the different filters provided by the module. For this
* example, time_filter is a very static and simple replacement, but it requires
* some preparation of the string because of the special html tags < and >. The
* foo_filter is more complex, including its own settings and inline tips.
*/
function filter_example_filter_info() {
$filters['filter_foo'] = array(
'title' => t('Foo Filter (example)'),
'description' => t('Every instance of "foo" in the input text will be replaced with a preconfigured replacement.'),
'process callback' => '_filter_example_filter_foo_process',
'default settings' => array(
'filter_example_foo' => 'bar',
),
'settings callback' => '_filter_example_filter_foo_settings',
'tips callback' => '_filter_example_filter_foo_tips',
);
$filters['filter_time'] = array(
'title' => t('Time Tag (example)'),
'description' => t("Every instance of the special &lt;time /&gt; tag will be replaced with the current date and time in the user's specified time zone."),
'prepare callback' => '_filter_example_filter_time_prepare',
'process callback' => '_filter_example_filter_time_process',
'tips callback' => '_filter_example_filter_time_tips',
);
return $filters;
}
/*
* Foo filter
*
* Drupal has several text formats (they are not filters), and in our example
* the foo replacement can be configured for each one of them, so the module
* includes a settings callback, with options to configure those replacements.
* Also, a Tips callback will help showing the current replacement
* for the content type being edited.
*/
/**
* Settings callback for foo filter.
*
* Make use of $format to have different replacements for every input format.
* Since we allow the administrator to define the string that gets substituted
* when "foo" is encountered, we need to provide an interface for this kind of
* customization. The object format is also an argument of the callback.
*
* The settings defined in this form are stored in database by the filter
* module, and they will be available in the $filter argument.
*/
function _filter_example_filter_foo_settings($form, $form_state, $filter, $format, $defaults) {
$settings['filter_example_foo'] = array(
'#type' => 'textfield',
'#title' => t('Substitution string'),
'#default_value' => isset($filter->settings['filter_example_foo']) ? $filter->settings['filter_example_foo'] : $defaults['filter_example_foo'],
'#description' => t('The string to substitute for "foo" everywhere in the text.'),
);
return $settings;
}
/**
* Foo filter process callback.
*
* The actual filtering is performed here. The supplied text should be returned,
* once any necessary substitutions have taken place. The example just replaces
* foo with our custom defined string in the settings page.
*/
function _filter_example_filter_foo_process($text, $filter, $format) {
$replacement = isset($filter->settings['filter_example_foo']) ? $filter->settings['filter_example_foo'] : 'bar';
return str_replace('foo', $replacement, $text);
}
/**
* Filter tips callback for foo filter.
*
* The tips callback allows filters to provide help text to users during the
* content editing process. Short tips are provided on the content editing
* screen, while long tips are provided on a separate linked page. Short tips
* are optional, but long tips are highly recommended.
*/
function _filter_example_filter_foo_tips($filter, $format, $long = FALSE) {
$replacement = isset($filter->settings['filter_example_foo']) ? $filter->settings['filter_example_foo'] : 'bar';
if (!$long) {
// This string will be shown in the content add/edit form.
return t('<em>foo</em> replaced with %replacement.', array('%replacement' => $replacement));
}
else {
return t('Every instance of "foo" in the input text will be replaced with a configurable value. You can configure this value and put whatever you want there. The replacement value is "%replacement".', array('%replacement' => $replacement));
}
}
/**
* Time filter prepare callback.
*
* We'll use [filter-example-time] as a replacement for the time tag.
* Note that in a more complicated filter a closing tag may also be
* required. For more information, see "Temporary placeholders and
* delimiters" at http://drupal.org/node/209715.
*/
function _filter_example_filter_time_prepare($text, $filter) {
return preg_replace('!<time ?/>!', '[filter-example-time]', $text);
}
/**
* Time filter process callback.
*
* Now, in the "process" step, we'll search for our escaped time tags and
* do the real filtering: replace the xml tag with the date.
*/
function _filter_example_filter_time_process($text, $filter) {
return str_replace('[filter-example-time]', '<em>' . format_date(time()) . '</em>', $text);
}
/**
* Filter tips callback for time filter.
*
* The tips callback allows filters to provide help text to users during the
* content editing process. Short tips are provided on the content editing
* screen, while long tips are provided on a separate linked page. Short tips
* are optional, but long tips are highly recommended.
*/
function _filter_example_filter_time_tips($filter, $format, $long = FALSE) {
return t('<em>&lt;time /&gt;</em> is replaced with the current time.');
}
/**
* @} End of "defgroup filter_example".
*/

View File

@@ -0,0 +1,109 @@
<?php
/**
* @file
* Test case for Testing the filter example module.
*
* This file contains the test cases to check if module is performing as
* expected.
*/
/**
* Functional tests for the Filter Example module.
*
* @ingroup filter_example
*/
class FilterExampleTestCase extends DrupalWebTestCase {
protected $webUser;
protected $filteredHtml;
protected $fullHtml;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Filter example functionality',
'description' => 'Verify that content is processed by example filter.',
'group' => 'Examples',
);
}
/**
* Enable modules and create user with specific permissions.
*/
public function setUp() {
parent::setUp('filter_example');
// Load the used input formats.
$this->filteredHtml = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Filtered HTML'))->fetchObject();
$this->fullHtml = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchObject();
// Create user.
$this->webUser = $this->drupalCreateUser(array(
'administer filters',
filter_permission_name($this->filteredHtml),
filter_permission_name($this->fullHtml),
'bypass node access',
));
}
/**
* Functional test of the foo filter.
*
* Login user, create an example node, and test blog functionality through
* the admin and user interfaces.
*/
public function testFilterExampleBasic() {
// Login the admin user.
$this->drupalLogin($this->webUser);
// Enable both filters in format id 1 (default format).
$edit = array(
'filters[filter_time][status]' => TRUE,
'filters[filter_foo][status]' => TRUE,
);
$this->drupalPost('admin/config/content/formats/' . $this->filteredHtml->format, $edit, t('Save configuration'));
// Create a content type to test the filters (with default format).
$content_type = $this->drupalCreateContentType();
// Create a test node.
$langcode = LANGUAGE_NONE;
$edit = array(
"title" => $this->randomName(),
"body[$langcode][0][value]" => 'What foo is it? it is <time />',
);
$result = $this->drupalPost('node/add/' . $content_type->type, $edit, t('Save'));
$this->assertResponse(200);
$time = format_date(time());
$this->assertRaw('What bar is it? it is <em>' . $time . '</em>');
// Enable foo filter in other format id 2
$edit = array(
'filters[filter_foo][status]' => TRUE,
);
$this->drupalPost('admin/config/content/formats/' . $this->fullHtml->format, $edit, t('Save configuration'));
// Change foo filter replacement with a random string in format id 2
$replacement = $this->randomName();
$options = array(
'filters[filter_foo][settings][filter_example_foo]' => $replacement,
);
$this->drupalPost('admin/config/content/formats/' . $this->fullHtml->format, $options, t('Save configuration'));
// Create a test node with content format 2
$langcode = LANGUAGE_NONE;
$edit = array(
"title" => $this->randomName(),
"body[$langcode][0][value]" => 'What foo is it? it is <time />',
"body[$langcode][0][format]" => $this->fullHtml->format,
);
$result = $this->drupalPost('node/add/' . $content_type->type, $edit, t('Save'));
$this->assertResponse(200);
// Only foo filter is enabled.
$this->assertRaw("What " . $replacement . " is it", 'Foo filter successfully verified.');
}
}

View File

@@ -0,0 +1,12 @@
name = Form example
description = Examples of using the Drupal Form API.
package = Example modules
core = 7.x
files[] = form_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,234 @@
<?php
/**
* @file
* Examples demonstrating the Drupal Form API.
*/
/**
* @defgroup form_example Example: Form API
* @ingroup examples
* @{
* Examples demonstrating the Drupal Form API.
*
* The Form Example module is a part of the Examples for Developers Project
* and provides various Drupal Form API Examples. You can download and
* experiment with this code at the
* @link http://drupal.org/project/examples Examples for Developers project page. @endlink
*/
/**
* Implements hook_menu().
*
* Here we set up the URLs (menu entries) for the
* form examples. Note that most of the menu items
* have page callbacks and page arguments set, with
* page arguments set to be functions in external files.
*/
function form_example_menu() {
$items = array();
$items['examples/form_example'] = array(
'title' => 'Form Example',
'page callback' => 'form_example_intro',
'access callback' => TRUE,
'expanded' => TRUE,
);
$items['examples/form_example/tutorial'] = array(
'title' => 'Form Tutorial',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_1'),
'access callback' => TRUE,
'description' => 'A set of ten tutorials',
'file' => 'form_example_tutorial.inc',
'type' => MENU_NORMAL_ITEM,
);
$items['examples/form_example/tutorial/1'] = array(
'title' => '#1',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_1'),
'access callback' => TRUE,
'description' => 'Tutorial 1: Simplest form',
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/2'] = array(
'title' => '#2',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_2'),
'access callback' => TRUE,
'description' => 'Tutorial 2: Form with a submit button',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/3'] = array(
'title' => '#3',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_3'),
'access callback' => TRUE,
'description' => 'Tutorial 3: Fieldsets',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/4'] = array(
'title' => '#4',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_4'),
'access callback' => TRUE,
'description' => 'Tutorial 4: Required fields',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/5'] = array(
'title' => '#5',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_5'),
'access callback' => TRUE,
'description' => 'Tutorial 5: More element attributes',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/6'] = array(
'title' => '#6',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_6'),
'access callback' => TRUE,
'description' => 'Tutorial 6: Form with a validate handler',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/7'] = array(
'title' => '#7',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_7'),
'access callback' => TRUE,
'description' => 'Tutorial 7: Form with a submit handler',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/8'] = array(
'title' => '#8',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_8'),
'access callback' => TRUE,
'description' => 'Tutorial 8: Basic multistep form',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/tutorial/9'] = array(
'title' => '#9',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_9'),
'access callback' => TRUE,
'description' => 'Tutorial 9: Form with dynamically added new fields',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
'weight' => 9,
);
$items['examples/form_example/tutorial/10'] = array(
'title' => '#10',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_10'),
'access callback' => TRUE,
'description' => 'Tutorial 10: Form with file upload',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
'weight' => 10,
);
$items['examples/form_example/tutorial/11'] = array(
'title' => '#11',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_11'),
'access callback' => TRUE,
'description' => 'Tutorial 11: generating a confirmation form',
'type' => MENU_LOCAL_TASK,
'file' => 'form_example_tutorial.inc',
'weight' => 11,
);
$items['examples/form_example/tutorial/11/confirm/%'] = array(
'title' => 'Name Confirmation',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_tutorial_11_confirm_name', 5),
'access callback' => TRUE,
'description' => 'Confirmation form for tutorial 11. Generated using the confirm_form function',
'file' => 'form_example_tutorial.inc',
);
$items['examples/form_example/states'] = array(
'title' => '#states example',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_states_form'),
'access callback' => TRUE,
'description' => 'How to use the #states attribute in FAPI',
'file' => 'form_example_states.inc',
);
$items['examples/form_example/wizard'] = array(
'title' => 'Extensible wizard example',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_wizard'),
'access callback' => TRUE,
'description' => 'A general approach to a wizard multistep form.',
'file' => 'form_example_wizard.inc',
);
$items['examples/form_example/element_example'] = array(
'title' => 'Element example',
'page callback' => 'drupal_get_form',
'page arguments' => array('form_example_element_demo_form'),
'access callback' => TRUE,
'file' => 'form_example_elements.inc',
'weight' => 100,
);
return $items;
}
/**
* Page callback for our general info page.
*/
function form_example_intro() {
$markup = t('The form example module provides a tutorial, extensible multistep example, an element example, and a #states example');
return array('#markup' => $markup);
}
/**
* Implements hook_help().
*/
function form_example_help($path, $arg) {
switch ($path) {
case 'examples/form_example/tutorial':
// TODO: Update the URL.
$help = t('This form example tutorial for Drupal 7 is the code from the <a href="http://drupal.org/node/262422">Handbook 10-step tutorial</a>');
break;
case 'examples/form_example/element_example':
$help = t('The Element Example shows how modules can provide their own Form API element types. Four different element types are demonstrated.');
break;
}
if (!empty($help)) {
return '<p>' . $help . '</p>';
}
}
/**
* Implements hook_element_info().
*
* To keep the various pieces of the example together in external files,
* this just returns _form_example_elements().
*/
function form_example_element_info() {
require_once 'form_example_elements.inc';
return _form_example_element_info();
}
/**
* Implements hook_theme().
*
* The only theme implementation is by the element example. To keep the various
* parts of the example together, this actually returns
* _form_example_element_theme().
*/
function form_example_theme($existing, $type, $theme, $path) {
require_once 'form_example_elements.inc';
return _form_example_element_theme($existing, $type, $theme, $path);
}
/**
* @} End of "defgroup form_example".
*/

View File

@@ -0,0 +1,275 @@
<?php
/**
* @file
* Test file for form_example module.
*/
/**
* Default test case for the form_example module.
*
* @ingroup form_example
*/
class FormExampleTestCase extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Form Example',
'description' => 'Various tests on the form_example module.' ,
'group' => 'Examples',
);
}
/**
* Enable modules.
*/
public function setUp() {
parent::setUp('form_example');
}
/**
* Test each tutorial.
*/
public function testTutorials() {
// Tutorial #1
$this->drupalGet('examples/form_example/tutorial');
$this->assertText(t('#9'));
// #2
$this->drupalPost('examples/form_example/tutorial/2', array('name' => t('name')), t('Submit'));
// #4
$this->drupalPost('examples/form_example/tutorial/4',
array('first' => t('firstname'), 'last' => t('lastname')), t('Submit'));
$this->drupalPost('examples/form_example/tutorial/4', array(), t('Submit'));
$this->assertText(t('First name field is required'));
$this->assertText(t('Last name field is required'));
// #5
$this->drupalPost('examples/form_example/tutorial/5',
array('first' => t('firstname'), 'last' => t('lastname')), t('Submit'));
$this->assertText(t('Please enter your first name'));
$this->drupalPost('examples/form_example/tutorial/4', array(), t('Submit'));
$this->assertText(t('First name field is required'));
$this->assertText(t('Last name field is required'));
// #6
$this->drupalPost(
'examples/form_example/tutorial/6',
array(
'first' => t('firstname'),
'last' => t('lastname'),
'year_of_birth' => 1955,
),
t('Submit'));
$this->assertNoText(t('Enter a year between 1900 and 2000'));
$this->drupalPost(
'examples/form_example/tutorial/6',
array(
'first' => t('firstname'),
'last' => t('lastname'),
'year_of_birth' => 1855,
),
t('Submit')
);
$this->assertText(t('Enter a year between 1900 and 2000'));
// #7
$this->drupalPost(
'examples/form_example/tutorial/7',
array(
'first' => t('firstname'),
'last' => t('lastname'),
'year_of_birth' => 1955,
),
t('Submit')
);
$this->assertText(t('The form has been submitted. name="firstname lastname", year of birth=1955'));
$this->drupalPost(
'examples/form_example/tutorial/7',
array(
'first' => t('firstname'),
'last' => t('lastname'),
'year_of_birth' => 1855,
),
t('Submit')
);
$this->assertText(t('Enter a year between 1900 and 2000'));
// Test tutorial #8.
$this->drupalPost(
'examples/form_example/tutorial/8',
array(
'first' => t('firstname'),
'last' => t('lastname'),
'year_of_birth' => 1955,
),
t('Next >>')
);
$this->drupalPost(NULL, array('color' => t('green')), t('<< Back'));
$this->drupalPost(NULL, array(), t('Next >>'));
$this->drupalPost(NULL, array('color' => t('red')), t('Submit'));
$this->assertText(t('The form has been submitted. name="firstname lastname", year of birth=1955'));
$this->assertText(t('And the favorite color is red'));
// #9
$url = 'examples/form_example/tutorial/9';
for ($i = 1; $i <= 4; $i++) {
if ($i > 1) {
// Later steps of multistep form take NULL.
$url = NULL;
}
$this->drupalPost(
$url,
array(
"name[$i][first]" => "firstname $i",
"name[$i][last]" => "lastname $i",
"name[$i][year_of_birth]" => 1950 + $i,
),
t('Add another name')
);
$this->assertText(t('Name #@num', array('@num' => $i + 1)));
}
// Now remove the last name added (#5).
$this->drupalPost(NULL, array(), t('Remove latest name'));
$this->assertNoText("Name #5");
$this->drupalPost(NULL, array(), t('Submit'));
$this->assertText('Form 9 has been submitted');
for ($i = 1; $i <= 4; $i++) {
$this->assertText(t('@num: firstname @num lastname @num (@year)', array('@num' => $i, '@year' => 1950 + $i)));
}
// #10
$url = 'examples/form_example/tutorial/10';
$this->drupalPost($url, array(), t('Submit'));
$this->assertText(t('No file was uploaded.'));
// Get sample images.
$images = $this->drupalGetTestFiles('image');
foreach ($images as $image) {
$this->drupalPost($url, array('files[file]' => drupal_realpath($image->uri)), t('Submit'));
$this->assertText(t('The form has been submitted and the image has been saved, filename: @filename.', array('@filename' => $image->filename)));
}
// #11: Confirmation form.
// Try to submit without a name.
$url = 'examples/form_example/tutorial/11';
$this->drupalPost($url, array(), t('Submit'));
$this->assertText('Name field is required.');
// Verify that we can enter a name and get the confirmation form.
$this->drupalPost(
$url,
array('name' => t('name 1')), t('Submit')
);
$this->assertText(t('Is this really your name?'));
$this->assertFieldById('edit-name', 'name 1');
// Check the 'yes' button.
$confirmation_text = t("Confirmation form submission recieved. According to your submission your name is '@name'", array('@name' => 'name 1'));
$url = 'examples/form_example/tutorial/11/confirm/name%201';
$this->drupalPost($url, array(), t('This is my name'));
$this->assertText($confirmation_text);
// Check the 'no' button.
$this->drupalGet($url);
$this->clickLink(t('Nope, not my name'));
$this->assertNoText($confirmation_text);
}
/**
* Test Wizard tutorial.
*
* @TODO improve this using drupal_form_submit
*/
public function testWizard() {
// Check if the wizard is there.
$this->drupalGet('examples/form_example/wizard');
$this->assertText(t('Extensible wizard example'));
$first_name = $this->randomName(8);
$last_name = $this->randomName(8);
$city = $this->randomName(8);
$aunts_name = $this->randomName(8);
// Submit the first step of the wizard.
$options = array(
'first_name' => $first_name,
'last_name' => $last_name,
);
$this->drupalPost('examples/form_example/wizard', $options, t('Next'));
// A label city is created, and two buttons appear, Previous and Next.
$this->assertText(t('Hint: Do not enter "San Francisco", and do not leave this out.'));
// Go back to the beginning and verify that the value is there.
$this->drupalPost(NULL, array(), t('Previous'));
$this->assertFieldByName('first_name', $first_name);
$this->assertFieldByName('last_name', $last_name);
// Go next. We should keep our values.
$this->drupalPost(NULL, array(), t('Next'));
$this->assertText(t('Hint: Do not enter "San Francisco", and do not leave this out.'));
// Try "San Francisco".
$this->drupalPost(NULL, array('city' => 'San Francisco'), t('Next'));
$this->assertText(t('You were warned not to enter "San Francisco"'));
// Try the real city.
$this->drupalPost(NULL, array('city' => $city), t('Next'));
// Enter the Aunt's name, but then the previous button.
$this->drupalPost(NULL, array('aunts_name' => $aunts_name), t('Previous'));
$this->assertFieldByName('city', $city);
// Now go forward and then press finish; check for correct values.
$this->drupalPost(NULL, array(), t('Next'));
$this->drupalPost(NULL, array('aunts_name' => $aunts_name), t('Finish'));
$this->assertRaw(t('[first_name] =&gt; @first_name', array('@first_name' => $first_name)));
$this->assertRaw(t('[last_name] =&gt; @last_name', array('@last_name' => $last_name)));
$this->assertRaw(t('[city] =&gt; @city', array('@city' => $city)));
$this->assertRaw(t('[aunts_name] =&gt; @aunts_name', array('@aunts_name' => $aunts_name)));
}
/**
* Test the element_example form for correct behavior.
*/
public function testElementExample() {
// Make one basic POST with a set of values and check for correct responses.
$edit = array(
'a_form_example_textfield' => $this->randomName(),
'a_form_example_checkbox' => TRUE,
'a_form_example_element_discrete[areacode]' => sprintf('%03d', rand(0, 999)),
'a_form_example_element_discrete[prefix]' => sprintf('%03d', rand(0, 999)),
'a_form_example_element_discrete[extension]' => sprintf('%04d', rand(0, 9999)),
'a_form_example_element_combined[areacode]' => sprintf('%03d', rand(0, 999)),
'a_form_example_element_combined[prefix]' => sprintf('%03d', rand(0, 999)),
'a_form_example_element_combined[extension]' => sprintf('%04d', rand(0, 9999)),
);
$this->drupalPost('examples/form_example/element_example', $edit, t('Submit'));
$this->assertText(t('a_form_example_textfield has value @value', array('@value' => $edit['a_form_example_textfield'])));
$this->assertText(t('a_form_example_checkbox has value 1'));
$this->assertPattern(t('/areacode.*!areacode/', array('!areacode' => $edit['a_form_example_element_discrete[areacode]'])));
$this->assertPattern(t('/prefix.*!prefix/', array('!prefix' => $edit['a_form_example_element_discrete[prefix]'])));
$this->assertPattern(t('/extension.*!extension/', array('!extension' => $edit['a_form_example_element_discrete[extension]'])));
$this->assertText(t('a_form_example_element_combined has value @value', array('@value' => $edit['a_form_example_element_combined[areacode]'] . $edit['a_form_example_element_combined[prefix]'] . $edit['a_form_example_element_combined[extension]'])));
// Now flip the checkbox and check for correct behavior.
$edit['a_form_example_checkbox'] = FALSE;
$this->drupalPost('examples/form_example/element_example', $edit, t('Submit'));
$this->assertText(t('a_form_example_checkbox has value 0'));
}
}

View File

@@ -0,0 +1,531 @@
<?php
/**
* @file
* This is an example demonstrating how a module can define custom form and
* render elements.
*
* Form elements are already familiar to anyone who uses Form API. They share
* history with render elements, which are explained in the
* @link render_example.module Render Example @endlink. Examples
* of core form elements are 'textfield', 'checkbox' and 'fieldset'. Drupal
* utilizes hook_elements() to define these FAPI types, and this occurs in
* the core function system_elements().
*
* Each form element has a #type value that determines how it is treated by
* the Form API and how it is ultimately rendered into HTML.
* hook_element_info() allows modules to define new element types, and tells
* the Form API what default values they should automatically be populated with.
*
* By implementing hook_element_info() in your own module, you can create custom
* form (or render) elements with their own properties, validation and theming.
*
* In this example, we define a series of elements that range from trivial
* (a renamed textfield) to more advanced (a telephone number field with each
* portion separately validated).
*
* Since each element can use arbitrary properties (like #process or #dropthis)
* it can be quite complicated to figure out what all the properties actually
* mean. This example won't undertake the exhaustive task of explaining them
* all, as that would probably be impossible.
*/
/**
* @todo: Some additional magic things to explain:
* - #process and process callback (and naming) (in forms)
* - #value and value callback (and naming of the above)
* - #theme and #theme_wrappers
* - What is #return_value?
* - system module provides the standard default elements.
* - What are all the things that can be defined in hook_element_info() and
* where do the defaults come from?
* - Form elements that have a type that has a matching type in the element
* array created by hook_element_info() get those items merged with them.
* - #value_callback is called first by form_builder(). Its job is to figure
* out what the actual value of the element, using #default_value or whatever.
* - #process is then called to allow changes to the whole element (like adding
* child form elements.)
* - #return_value: chx: you need three different values for form API. You need
* the default value (#default_value), the value for the element if it gets
* checked )#return_value) and then #value which is either 0 or the
* #return_value
*/
/**
* Utility function providing data for form_example_element_info().
*
* This defines several new form element types.
*
* - form_example_textfield: This is actually just a textfield, but provides
* the new type. If more were to be done with it a theme function could be
* provided.
* - form_example_checkbox: Nothing more than a regular checkbox, but uses
* an alternate theme function provided by this module.
* - form_example_phonenumber_discrete: Provides a North-American style
* three-part phonenumber where the value of the phonenumber is managed
* as an array of three parts.
* - form_example_phonenumber_combined: Provides a North-American style
* three-part phonenumber where the actual value is managed as a 10-digit
* string and only broken up into three parts for the user interface.
*
* form_builder() has significant discussion of #process and #value_callback.
* See also hook_element_info().
*
* system_element_info() contains the Drupal default element types, which can
* also be used as examples.
*/
function _form_example_element_info() {
// form_example_textfield is a trivial element based on textfield that
// requires only a definition and a theme function. In this case we provide
// the theme function using the parent "textfield" theme function, but it
// would by default be provided in hook_theme(), by a "form_example_textfield"
// theme implementation, provided by default by the function
// theme_form_example_textfield(). Note that the 'form_example_textfield'
// element type is completely defined here. There is no further code required
// for it.
$types['form_example_textfield'] = array(
// #input = TRUE means that the incoming value will be used to figure out
// what #value will be.
'#input' => TRUE,
// Use theme('textfield') to format this element on output.
'#theme' => array('textfield'),
// Do not provide autocomplete.
'#autocomplete_path' => FALSE,
// Allow theme('form_element') to control the markup surrounding this
// value on output.
'#theme_wrappers' => array('form_element'),
);
// form_example_checkbox is mostly a copy of the system-defined checkbox
// element.
$types['form_example_checkbox'] = array(
// This is an HTML <input>.
'#input' => TRUE,
// @todo: Explain #return_value.
'#return_value' => TRUE,
// Our #process array will use the standard process functions used for a
// regular checkbox.
'#process' => array('form_process_checkbox', 'ajax_process_form'),
// Use theme('form_example_checkbox') to render this element on output.
'#theme' => 'form_example_checkbox',
// Use theme('form_element') to provide HTML wrappers for this element.
'#theme_wrappers' => array('form_element'),
// Place the title after the element (to the right of the checkbox).
// This attribute affects the behavior of theme_form_element().
'#title_display' => 'after',
// We use the default function name for the value callback, so it does not
// have to be listed explicitly. The pattern for the default function name
// is form_type_TYPENAME_value().
// '#value_callback' => 'form_type_form_example_checkbox_value',
);
// This discrete phonenumber element keeps its values as the separate elements
// area code, prefix, extension.
$types['form_example_phonenumber_discrete'] = array(
// #input == TRUE means that the form value here will be used to determine
// what #value will be.
'#input' => TRUE,
// #process is an array of callback functions executed when this element is
// processed. Here it provides the child form elements which define
// areacode, prefix, and extension.
'#process' => array('form_example_phonenumber_discrete_process'),
// Validation handlers for this element. These are in addition to any
// validation handlers that might.
'#element_validate' => array('form_example_phonenumber_discrete_validate'),
'#autocomplete_path' => FALSE,
'#theme_wrappers' => array('form_example_inline_form_element'),
);
// Define form_example_phonenumber_combined, which combines the phone
// number into a single validated text string.
$types['form_example_phonenumber_combined'] = array(
'#input' => TRUE ,
'#process' => array('form_example_phonenumber_combined_process'),
'#element_validate' => array('form_example_phonenumber_combined_validate'),
'#autocomplete_path' => FALSE,
'#value_callback' => 'form_example_phonenumber_combined_value',
'#default_value' => array(
'areacode' => '',
'prefix' => '',
'extension' => '',
),
'#theme_wrappers' => array('form_example_inline_form_element'),
);
return $types;
}
/**
* Value callback for form_example_phonenumber_combined.
*
* Builds the current combined value of the phone number only when the form
* builder is not processing the input.
*
* @param array $element
* Form element.
* @param array $input
* Input.
* @param array $form_state
* Form state.
*
* @return array
* The modified element.
*/
function form_example_phonenumber_combined_value(&$element, $input = FALSE, $form_state = NULL) {
if (!$form_state['process_input']) {
$matches = array();
$match = preg_match('/^(\d{3})(\d{3})(\d{4})$/', $element['#default_value'], $matches);
if ($match) {
// Get rid of the "all match" element.
array_shift($matches);
list($element['areacode'], $element['prefix'], $element['extension']) = $matches;
}
}
return $element;
}
/**
* Value callback for form_example_checkbox element type.
*
* Copied from form_type_checkbox_value().
*
* @param array $element
* The form element whose value is being populated.
* @param mixed $input
* The incoming input to populate the form element. If this is FALSE, meaning
* there is no input, the element's default value should be returned.
*
* @return int
* The value represented by the form element.
*/
function form_type_form_example_checkbox_value($element, $input = FALSE) {
if ($input === FALSE) {
return isset($element['#default_value']) ? $element['#default_value'] : 0;
}
else {
return isset($input) ? $element['#return_value'] : 0;
}
}
/**
* Process callback for the discrete version of phonenumber.
*/
function form_example_phonenumber_discrete_process($element, &$form_state, $complete_form) {
// #tree = TRUE means that the values in $form_state['values'] will be stored
// hierarchically. In this case, the parts of the element will appear in
// $form_state['values'] as
// $form_state['values']['<element_name>']['areacode'],
// $form_state['values']['<element_name>']['prefix'],
// etc. This technique is preferred when an element has member form
// elements.
$element['#tree'] = TRUE;
// Normal FAPI field definitions, except that #value is defined.
$element['areacode'] = array(
'#type' => 'textfield',
'#size' => 3,
'#maxlength' => 3,
'#value' => $element['#value']['areacode'],
'#required' => TRUE,
'#prefix' => '(',
'#suffix' => ')',
);
$element['prefix'] = array(
'#type' => 'textfield',
'#size' => 3,
'#maxlength' => 3,
'#required' => TRUE,
'#value' => $element['#value']['prefix'],
);
$element['extension'] = array(
'#type' => 'textfield',
'#size' => 4,
'#maxlength' => 4,
'#value' => $element['#value']['extension'],
);
return $element;
}
/**
* Validation handler for the discrete version of the phone number.
*
* Uses regular expressions to check that:
* - the area code is a three digit number.
* - the prefix is numeric 3-digit number.
* - the extension is a numeric 4-digit number.
*
* Any problems are shown on the form element using form_error().
*/
function form_example_phonenumber_discrete_validate($element, &$form_state) {
if (isset($element['#value']['areacode'])) {
if (0 == preg_match('/^\d{3}$/', $element['#value']['areacode'])) {
form_error($element['areacode'], t('The area code is invalid.'));
}
}
if (isset($element['#value']['prefix'])) {
if (0 == preg_match('/^\d{3}$/', $element['#value']['prefix'])) {
form_error($element['prefix'], t('The prefix is invalid.'));
}
}
if (isset($element['#value']['extension'])) {
if (0 == preg_match('/^\d{4}$/', $element['#value']['extension'])) {
form_error($element['extension'], t('The extension is invalid.'));
}
}
return $element;
}
/**
* Process callback for the combined version of the phonenumber element.
*/
function form_example_phonenumber_combined_process($element, &$form_state, $complete_form) {
// #tree = TRUE means that the values in $form_state['values'] will be stored
// hierarchically. In this case, the parts of the element will appear in
// $form_state['values'] as
// $form_state['values']['<element_name>']['areacode'],
// $form_state['values']['<element_name>']['prefix'],
// etc. This technique is preferred when an element has member form
// elements.
$element['#tree'] = TRUE;
// Normal FAPI field definitions, except that #value is defined.
$element['areacode'] = array(
'#type' => 'textfield',
'#size' => 3,
'#maxlength' => 3,
'#required' => TRUE,
'#prefix' => '(',
'#suffix' => ')',
);
$element['prefix'] = array(
'#type' => 'textfield',
'#size' => 3,
'#maxlength' => 3,
'#required' => TRUE,
);
$element['extension'] = array(
'#type' => 'textfield',
'#size' => 4,
'#maxlength' => 4,
'#required' => TRUE,
);
$matches = array();
$match = preg_match('/^(\d{3})(\d{3})(\d{4})$/', $element['#default_value'], $matches);
if ($match) {
// Get rid of the "all match" element.
array_shift($matches);
list($element['areacode']['#default_value'], $element['prefix']['#default_value'], $element['extension']['#default_value']) = $matches;
}
return $element;
}
/**
* Phone number validation function for the combined phonenumber.
*
* Uses regular expressions to check that:
* - the area code is a three digit number
* - the prefix is numeric 3-digit number
* - the extension is a numeric 4-digit number
*
* Any problems are shown on the form element using form_error().
*
* The combined value is then updated in the element.
*/
function form_example_phonenumber_combined_validate($element, &$form_state) {
$lengths = array(
'areacode' => 3,
'prefix' => 3,
'extension' => 4,
);
foreach ($lengths as $member => $length) {
$regex = '/^\d{' . $length . '}$/';
if (!empty($element['#value'][$member]) && 0 == preg_match($regex, $element['#value'][$member])) {
form_error($element[$member], t('@member is invalid', array('@member' => $member)));
}
}
// Consolidate into the three parts into one combined value.
$value = $element['areacode']['#value'] . $element['prefix']['#value'] . $element['extension']['#value'];
form_set_value($element, $value, $form_state);
return $element;
}
/**
* Called by form_example_theme() to provide hook_theme().
*
* This is kept in this file so it can be with the theme functions it presents.
* Otherwise it would get lonely.
*/
function _form_example_element_theme() {
return array(
'form_example_inline_form_element' => array(
'render element' => 'element',
'file' => 'form_example_elements.inc',
),
'form_example_checkbox' => array(
'render element' => 'element',
'file' => 'form_example_elements.inc',
),
);
}
/**
* Themes a custom checkbox.
*
* This doesn't actually do anything, but is here to show that theming can
* be done here.
*/
function theme_form_example_checkbox($variables) {
$element = $variables['element'];
return theme('checkbox', $element);
}
/**
* Formats child form elements as inline elements.
*/
function theme_form_example_inline_form_element($variables) {
$element = $variables['element'];
// Add element #id for #type 'item'.
if (isset($element['#markup']) && !empty($element['#id'])) {
$attributes['id'] = $element['#id'];
}
// Add element's #type and #name as class to aid with JS/CSS selectors.
$attributes['class'] = array('form-item');
if (!empty($element['#type'])) {
$attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-');
}
if (!empty($element['#name'])) {
$attributes['class'][] = 'form-item-' . strtr($element['#name'],
array(
' ' => '-',
'_' => '-',
'[' => '-',
']' => '',
)
);
}
// Add a class for disabled elements to facilitate cross-browser styling.
if (!empty($element['#attributes']['disabled'])) {
$attributes['class'][] = 'form-disabled';
}
$output = '<div' . drupal_attributes($attributes) . '>' . "\n";
// If #title is not set, we don't display any label or required marker.
if (!isset($element['#title'])) {
$element['#title_display'] = 'none';
}
$prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : '';
$suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : '';
switch ($element['#title_display']) {
case 'before':
$output .= ' ' . theme('form_element_label', $variables);
$output .= ' ' . '<div class="container-inline">' . $prefix . $element['#children'] . $suffix . "</div>\n";
break;
case 'invisible':
case 'after':
$output .= ' ' . $prefix . $element['#children'] . $suffix;
$output .= ' ' . theme('form_element_label', $variables) . "\n";
break;
case 'none':
case 'attribute':
// Output no label and no required marker, only the children.
$output .= ' ' . $prefix . $element['#children'] . $suffix . "\n";
break;
}
if (!empty($element['#description'])) {
$output .= ' <div class="description">' . $element['#description'] . "</div>\n";
}
$output .= "</div>\n";
return $output;
}
/**
* Form content for examples/form_example/element_example.
*
* Simple form to demonstrate how to use the various new FAPI elements
* we've defined.
*/
function form_example_element_demo_form($form, &$form_state) {
$form['a_form_example_textfield'] = array(
'#type' => 'form_example_textfield',
'#title' => t('Form Example textfield'),
'#default_value' => variable_get('form_example_textfield', ''),
'#description' => t('form_example_textfield is a new type, but it is actually uses the system-provided functions of textfield'),
);
$form['a_form_example_checkbox'] = array(
'#type' => 'form_example_checkbox',
'#title' => t('Form Example checkbox'),
'#default_value' => variable_get('form_example_checkbox', FALSE),
'#description' => t('Nothing more than a regular checkbox but with a theme provided by this module.'),
);
$form['a_form_example_element_discrete'] = array(
'#type' => 'form_example_phonenumber_discrete',
'#title' => t('Discrete phone number'),
'#default_value' => variable_get(
'form_example_element_discrete',
array(
'areacode' => '999',
'prefix' => '999',
'extension' => '9999',
)
),
'#description' => t('A phone number : areacode (XXX), prefix (XXX) and extension (XXXX). This one uses a "discrete" element type, one which stores the three parts of the telephone number separately.'),
);
$form['a_form_example_element_combined'] = array(
'#type' => 'form_example_phonenumber_combined',
'#title' => t('Combined phone number'),
'#default_value' => variable_get('form_example_element_combined', '0000000000'),
'#description' => t('form_example_element_combined one uses a "combined" element type, one with a single 10-digit value which is broken apart when needed.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Submit handler for form_example_element_demo_form().
*/
function form_example_element_demo_form_submit($form, &$form_state) {
// Exclude unnecessary elements.
unset($form_state['values']['submit'], $form_state['values']['form_id'], $form_state['values']['op'], $form_state['values']['form_token'], $form_state['values']['form_build_id']);
foreach ($form_state['values'] as $key => $value) {
variable_set($key, $value);
drupal_set_message(
t('%name has value %value',
array(
'%name' => $key,
'%value' => print_r($value, TRUE),
)
)
);
}
}

View File

@@ -0,0 +1,296 @@
<?php
/**
* @file
* An example of how to use the new #states Form API element, allowing
* dynamic form behavior with very simple setup.
*/
/**
* States demo form.
*
* This form shows off the #states system by dynamically showing parts of the
* form based on the state of other parts.
*
* @ingroup form_example
*
* The basic idea is that you add a #states property to the element which is
* to be changed based on some action elsewhere on the form. The #states
* property lists a change which is to be made, and under what conditions
* that change should be made.
*
* For example, in the 'tests_taken' form element below we have:
* @code
* '#states' => array(
* 'visible' => array(
* ':input[name="student_type"]' => array('value' => 'high_school'),
* ),
* ),
* @endcode
* Meaning that the element is to be made visible when the condition is met.
* The condition is a combination of a jQuery selector (which selects the
* element we want to test) and a condition for that element. In this case,
* the condition is whether the return value of the 'student_type' element is
* 'high_school'. If it is, this element will be visible.
*
* So the syntax is:
* @code
* '#states' => array(
* 'action_to_take_on_this_form_element' => array(
* 'jquery_selector_for_another_element' => array(
* 'condition_type' => value,
* ),
* ),
* ),
* @endcode
*
* If you need an action to take place only when two different conditions are
* true, then you add both of those conditions to the action. See the
* 'country_writein' element below for an example.
*
* Note that the easiest way to select a textfield, checkbox, or select is with
* the
* @link http://api.jquery.com/input-selector/ ':input' jquery shortcut @endlink,
* which selects any any of those.
*
* There are examples below of changing or hiding an element when a checkbox
* is checked, when a textarea is filled, when a select has a given value.
*
* See drupal_process_states() for full documentation.
*
* @see forms_api_reference.html
*/
function form_example_states_form($form, &$form_state) {
$form['student_type'] = array(
'#type' => 'radios',
'#options' => array(
'high_school' => t('High School'),
'undergraduate' => t('Undergraduate'),
'graduate' => t('Graduate'),
),
'#title' => t('What type of student are you?'),
);
$form['high_school'] = array(
'#type' => 'fieldset',
'#title' => t('High School Information'),
// This #states rule says that the "high school" fieldset should only
// be shown if the "student_type" form element is set to "High School".
'#states' => array(
'visible' => array(
':input[name="student_type"]' => array('value' => 'high_school'),
),
),
);
// High school information.
$form['high_school']['tests_taken'] = array(
'#type' => 'checkboxes',
'#options' => drupal_map_assoc(array(t('SAT'), t('ACT'))),
'#title' => t('What standardized tests did you take?'),
// This #states rule says that this checkboxes array will be visible only
// when $form['student_type'] is set to t('High School').
// It uses the jQuery selector :input[name=student_type] to choose the
// element which triggers the behavior, and then defines the "High School"
// value as the one that triggers visibility.
'#states' => array(
// Action to take.
'visible' => array(
':input[name="student_type"]' => array('value' => 'high_school'),
),
),
);
$form['high_school']['sat_score'] = array(
'#type' => 'textfield',
'#title' => t('Your SAT score:'),
'#size' => 4,
// This #states rule limits visibility to when the $form['tests_taken']
// 'SAT' checkbox is checked."
'#states' => array(
// Action to take.
'visible' => array(
':input[name="tests_taken[SAT]"]' => array('checked' => TRUE),
),
),
);
$form['high_school']['act_score'] = array(
'#type' => 'textfield',
'#title' => t('Your ACT score:'),
'#size' => 4,
// Set this element visible if the ACT checkbox above is checked.
'#states' => array(
// Action to take.
'visible' => array(
':input[name="tests_taken[ACT]"]' => array('checked' => TRUE),
),
),
);
// Undergrad information.
$form['undergraduate'] = array(
'#type' => 'fieldset',
'#title' => t('Undergraduate Information'),
// This #states rule says that the "undergraduate" fieldset should only
// be shown if the "student_type" form element is set to "Undergraduate".
'#states' => array(
'visible' => array(
':input[name="student_type"]' => array('value' => 'undergraduate'),
),
),
);
$form['undergraduate']['how_many_years'] = array(
'#type' => 'select',
'#title' => t('How many years have you completed?'),
// The options here are integers, but since all the action here happens
// using the DOM on the client, we will have to use strings to work with
// them.
'#options' => array(
1 => t('One'),
2 => t('Two'),
3 => t('Three'),
4 => t('Four'),
5 => t('Lots'),
),
);
$form['undergraduate']['comment'] = array(
'#type' => 'item',
'#description' => t("Wow, that's a long time."),
'#states' => array(
'visible' => array(
// Note that '5' must be used here instead of the integer 5.
// The information is coming from the DOM as a string.
':input[name="how_many_years"]' => array('value' => '5'),
),
),
);
$form['undergraduate']['school_name'] = array(
'#type' => 'textfield',
'#title' => t('Your college or university:'),
);
$form['undergraduate']['school_country'] = array(
'#type' => 'select',
'#options' => drupal_map_assoc(array(t('UK'), t('Other'))),
'#title' => t('In what country is your college or university located?'),
);
$form['undergraduate']['country_writein'] = array(
'#type' => 'textfield',
'#size' => 20,
'#title' => t('Please enter the name of the country where your college or university is located.'),
// Only show this field if school_country is set to 'Other'.
'#states' => array(
// Action to take: Make visible.
'visible' => array(
':input[name="school_country"]' => array('value' => t('Other')),
),
),
);
$form['undergraduate']['thanks'] = array(
'#type' => 'item',
'#description' => t('Thanks for providing both your school and your country.'),
'#states' => array(
// Here visibility requires that two separate conditions be true.
'visible' => array(
':input[name="school_country"]' => array('value' => t('Other')),
':input[name="country_writein"]' => array('filled' => TRUE),
),
),
);
$form['undergraduate']['go_away'] = array(
'#type' => 'submit',
'#value' => t('Done with form'),
'#states' => array(
// Here visibility requires that two separate conditions be true.
'visible' => array(
':input[name="school_country"]' => array('value' => t('Other')),
':input[name="country_writein"]' => array('filled' => TRUE),
),
),
);
// Graduate student information.
$form['graduate'] = array(
'#type' => 'fieldset',
'#title' => t('Graduate School Information'),
// This #states rule says that the "graduate" fieldset should only
// be shown if the "student_type" form element is set to "Graduate".
'#states' => array(
'visible' => array(
':input[name="student_type"]' => array('value' => 'graduate'),
),
),
);
$form['graduate']['more_info'] = array(
'#type' => 'textarea',
'#title' => t('Please describe your graduate studies'),
);
$form['graduate']['info_provide'] = array(
'#type' => 'checkbox',
'#title' => t('Check here if you have provided information above'),
'#disabled' => TRUE,
'#states' => array(
// Mark this checkbox checked if the "more_info" textarea has something
// in it, if it's 'filled'.
'checked' => array(
':input[name="more_info"]' => array('filled' => TRUE),
),
),
);
$form['average'] = array(
'#type' => 'textfield',
'#title' => t('Enter your average'),
// To trigger a state when the same controlling element can have more than
// one possible value, put all values in a higher-level array.
'#states' => array(
'visible' => array(
':input[name="student_type"]' => array(
array('value' => 'high_school'),
array('value' => 'undergraduate'),
),
),
),
);
$form['expand_more_info'] = array(
'#type' => 'checkbox',
'#title' => t('Check here if you want to add more information.'),
);
$form['more_info'] = array(
'#type' => 'fieldset',
'#title' => t('Additional Information'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
// Expand the expand_more_info fieldset if the box is checked.
'#states' => array(
'expanded' => array(
':input[name="expand_more_info"]' => array('checked' => TRUE),
),
),
);
$form['more_info']['feedback'] = array(
'#type' => 'textarea',
'#title' => t('What do you have to say?'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit your information'),
);
return $form;
}
/**
* Submit handler for form_example_states_form().
*/
function form_example_states_form_submit($form, &$form_state) {
drupal_set_message(t('Submitting values: @values', array('@values' => var_export($form_state['values'], TRUE))));
}

View File

@@ -0,0 +1,934 @@
<?php
/**
* @file
* This is the Form API Tutorial from the handbook.
*
* It goes through several form examples of increasing complexity to demonstrate
* Drupal 7 Form API.
*
* Links are provided inline for the related handbook pages.
*
* @see http://drupal.org/node/262422
*/
/**
* Main Form tutorial page.
*
* @see form_example_tutorial_1()
* @see form_example_tutorial_2()
* @see form_example_tutorial_3()
* @see form_example_tutorial_4()
* @see form_example_tutorial_5()
* @see form_example_tutorial_6()
* @see form_example_tutorial_7()
* @see form_example_tutorial_8()
* @see form_example_tutorial_9()
* @see form_example_tutorial_10()
*
* @ingroup form_example
*/
function form_example_tutorial() {
return t('This is a set of form tutorials tied to the <a href="http://drupal.org/node/262422">Drupal handbook</a>.');
}
/**
* Tutorial Example 1.
*
* This first form function is from the
* @link http://drupal.org/node/717722 Form Tutorial handbook page @endlink
*
* It just creates a very basic form with a textfield.
*
* This function is called the "form constructor function". It builds the form.
* It takes a two arguments, $form and $form_state, but if drupal_get_form()
* sends additional arguments, they will be provided after $form_state.
*
* @ingroup form_example
*/
function form_example_tutorial_1($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A form with nothing but a textfield'),
);
// This is the first form element. It's a textfield with a label, "Name"
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
);
return $form;
}
/**
* This is Example 2, a basic form with a submit button.
*
* @see http://drupal.org/node/717726
* @ingroup form_example
*/
function form_example_tutorial_2($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A simple form with a submit button'),
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
);
// Adds a simple submit button that refreshes the form and clears its
// contents. This is the default behavior for forms.
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* Example 3: A basic form with fieldsets.
*
* We establish a fieldset element and then place two text fields within
* it, one for a first name and one for a last name. This helps us group
* related content.
*
* Study the code below and you'll notice that we renamed the array of the first
* and last name fields by placing them under the $form['name']
* array. This tells Form API these fields belong to the $form['name'] fieldset.
*
* @ingroup form_example
*/
function form_example_tutorial_3($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A form with a fieldset'),
);
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* Example 4: Basic form with required fields.
*
* @ingroup form_example
*/
function form_example_tutorial_4($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A form with required fields'),
);
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
// Make the fieldset collapsible.
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
// Make these fields required.
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* Example 5: Basic form with additional element attributes.
*
* This demonstrates additional attributes of text form fields.
*
* See the
* @link http://api.drupal.org/api/file/developer/topics/forms_api.html complete form reference @endlink
*
* @ingroup form_example
*/
function form_example_tutorial_5($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A form with additional attributes'),
'#description' => t('This one adds #default_value and #description'),
);
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
'#default_value' => "First name",
'#description' => "Please enter your first name.",
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* Example 6: A basic form with a validate handler.
*
* From http://drupal.org/node/717736
* @see form_example_tutorial_6_validate()
*
* @ingroup form_example
*/
function form_example_tutorial_6($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A form with a validation handler'),
);
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
'#default_value' => "First name",
'#description' => "Please enter your first name.",
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
// New form field added to permit entry of year of birth.
// The data entered into this field will be validated with
// the default validation function.
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => "Year of birth",
'#description' => 'Format is "YYYY"',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* Validation handler for Tutorial 6.
*
* Now we add a handler/function to validate the data entered into the
* "year of birth" field to make sure it's between the values of 1900
* and 2000. If not, it displays an error. The value report is
* $form_state['values'] (see http://drupal.org/node/144132#form-state).
*
* Notice the name of the function. It is simply the name of the form
* followed by '_validate'. This is always the name of the default validation
* function. An alternate list of validation functions could have been provided
* in $form['#validate'].
*
* @see form_example_tutorial_6()
*
* @ingroup form_example
*/
function form_example_tutorial_6_validate($form, &$form_state) {
$year_of_birth = $form_state['values']['year_of_birth'];
if ($year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
}
/**
* Example 7: With a submit handler.
*
* From the handbook page:
* http://drupal.org/node/717740
*
* @see form_example_tutorial_7_validate()
* @see form_example_tutorial_7_submit()
*
* @ingroup form_example
*/
function form_example_tutorial_7($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A form with a submit handler'),
);
$form['name'] = array(
'#type' => 'fieldset',
'#title' => t('Name'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name']['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#required' => TRUE,
'#default_value' => "First name",
'#description' => "Please enter your first name.",
'#size' => 20,
'#maxlength' => 20,
);
$form['name']['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#required' => TRUE,
);
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => "Year of birth",
'#description' => 'Format is "YYYY"',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* Validation function for form_example_tutorial_7().
*
* @ingroup form_example
*/
function form_example_tutorial_7_validate($form, &$form_state) {
$year_of_birth = $form_state['values']['year_of_birth'];
if ($year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
}
/**
* Submit function for form_example_tutorial_7().
*
* Adds a submit handler/function to our form to send a successful
* completion message to the screen.
*
* @ingroup form_example
*/
function form_example_tutorial_7_submit($form, &$form_state) {
drupal_set_message(t('The form has been submitted. name="@first @last", year of birth=@year_of_birth',
array(
'@first' => $form_state['values']['first'],
'@last' => $form_state['values']['last'],
'@year_of_birth' => $form_state['values']['year_of_birth'],
)
));
}
/**
* Example 8: A simple multistep form with a Next and a Back button.
*
* Handbook page: http://drupal.org/node/717750.
*
* For more extensive multistep forms, see
* @link form_example_wizard.inc form_example_wizard.inc @endlink
*
*
* Adds logic to our form builder to give it two pages.
* The @link ajax_example_wizard AJAX Example's Wizard Example @endlink
* gives an AJAX version of this same idea.
*
* @see form_example_tutorial_8_page_two()
* @see form_example_tutorial_8_page_two_back()
* @see form_example_tutorial_8_page_two_submit()
* @see form_example_tutorial_8_next_submit()
* @see form_example_tutorial.inc
*
* @ingroup form_example
*/
function form_example_tutorial_8($form, &$form_state) {
// Display page 2 if $form_state['page_num'] == 2
if (!empty($form_state['page_num']) && $form_state['page_num'] == 2) {
return form_example_tutorial_8_page_two($form, $form_state);
}
// Otherwise we build page 1.
$form_state['page_num'] = 1;
$form['description'] = array(
'#type' => 'item',
'#title' => t('A basic multistep form (page 1)'),
);
$form['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#description' => "Please enter your first name.",
'#size' => 20,
'#maxlength' => 20,
'#required' => TRUE,
'#default_value' => !empty($form_state['values']['first']) ? $form_state['values']['first'] : '',
);
$form['last'] = array(
'#type' => 'textfield',
'#title' => t('Last name'),
'#default_value' => !empty($form_state['values']['last']) ? $form_state['values']['last'] : '',
);
$form['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => "Year of birth",
'#description' => 'Format is "YYYY"',
'#default_value' => !empty($form_state['values']['year_of_birth']) ? $form_state['values']['year_of_birth'] : '',
);
$form['next'] = array(
'#type' => 'submit',
'#value' => 'Next >>',
'#submit' => array('form_example_tutorial_8_next_submit'),
'#validate' => array('form_example_tutorial_8_next_validate'),
);
return $form;
}
/**
* Returns the form for the second page of form_example_tutorial_8().
*
* @ingroup form_example
*/
function form_example_tutorial_8_page_two($form, &$form_state) {
$form['description'] = array(
'#type' => 'item',
'#title' => t('A basic multistep form (page 2)'),
);
$form['color'] = array(
'#type' => 'textfield',
'#title' => t('Favorite color'),
'#required' => TRUE,
'#default_value' => !empty($form_state['values']['color']) ? $form_state['values']['color'] : '',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
'#submit' => array('form_example_tutorial_8_page_two_submit'),
);
$form['back'] = array(
'#type' => 'submit',
'#value' => t('<< Back'),
'#submit' => array('form_example_tutorial_8_page_two_back'),
// We won't bother validating the required 'color' field, since they
// have to come back to this page to submit anyway.
'#limit_validation_errors' => array(),
);
return $form;
}
/**
* Validate handler for the next button on first page.
*
* @ingroup form_example
*/
function form_example_tutorial_8_next_validate($form, &$form_state) {
$year_of_birth = $form_state['values']['year_of_birth'];
if ($year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error('year_of_birth', t('Enter a year between 1900 and 2000.'));
}
}
/**
* Submit handler for form_example_tutorial_8() next button.
*
* Capture the values from page one and store them away so they can be used
* at final submit time.
*
* @ingroup form_example
*/
function form_example_tutorial_8_next_submit($form, &$form_state) {
// Values are saved for each page.
// to carry forward to subsequent pages in the form.
// and we tell FAPI to rebuild the form.
$form_state['page_values'][1] = $form_state['values'];
if (!empty($form_state['page_values'][2])) {
$form_state['values'] = $form_state['page_values'][2];
}
// When form rebuilds, it will look at this to figure which page to build.
$form_state['page_num'] = 2;
$form_state['rebuild'] = TRUE;
}
/**
* Back button handler submit handler.
*
* Since #limit_validation_errors = array() is set, values from page 2
* will be discarded. We load the page 1 values instead.
*
* @ingroup form_example
*/
function form_example_tutorial_8_page_two_back($form, &$form_state) {
$form_state['values'] = $form_state['page_values'][1];
$form_state['page_num'] = 1;
$form_state['rebuild'] = TRUE;
}
/**
* The page 2 submit handler.
*
* This is the final submit handler. Gather all the data together and output
* it in a drupal_set_message().
*
* @ingroup form_example
*/
function form_example_tutorial_8_page_two_submit($form, &$form_state) {
// Normally, some code would go here to alter the database with the data
// collected from the form. Instead sets a message with drupal_set_message()
// to validate that the code worked.
$page_one_values = $form_state['page_values'][1];
drupal_set_message(t('The form has been submitted. name="@first @last", year of birth=@year_of_birth',
array(
'@first' => $page_one_values['first'],
'@last' => $page_one_values['last'],
'@year_of_birth' => $page_one_values['year_of_birth'],
)
));
if (!empty($page_one_values['first2'])) {
drupal_set_message(t('Second name: name="@first @last", year of birth=@year_of_birth',
array(
'@first' => $page_one_values['first2'],
'@last' => $page_one_values['last2'],
'@year_of_birth' => $page_one_values['year_of_birth2'],
)
));
}
drupal_set_message(t('And the favorite color is @color', array('@color' => $form_state['values']['color'])));
// If we wanted to redirect on submission, set $form_state['redirect']. For
// simple redirects, the value can be a string of the path to redirect to. For
// example, to redirect to /node, one would specify the following:
//
// $form_state['redirect'] = 'node';
//
// For more complex redirects, this value can be set to an array of options to
// pass to drupal_goto(). For example, to redirect to /foo?bar=1#baz, one
// would specify the following:
//
// @code
// $form_state['redirect'] = array(
// 'foo',
// array(
// 'query' => array('bar' => 1),
// 'fragment' => 'baz',
// ),
// );
// @endcode
//
// The first element in the array is the path to redirect to, and the second
// element in the array is the array of options. For more information on the
// available options, see http://api.drupal.org/url.
}
/**
* Example 9: A form with a dynamically added new fields.
*
* This example adds default values so that when the form is rebuilt,
* the form will by default have the previously-entered values.
*
* From handbook page http://drupal.org/node/717746.
*
* @see form_example_tutorial_9_add_name()
* @see form_example_tutorial_9_remove_name()
* @see form_example_tutorial_9_submit()
* @see form_example_tutorial_9_validate()
*
* @ingroup form_example
*/
function form_example_tutorial_9($form, &$form_state) {
// We will have many fields with the same name, so we need to be able to
// access the form hierarchically.
$form['#tree'] = TRUE;
$form['description'] = array(
'#type' => 'item',
'#title' => t('A form with dynamically added new fields'),
);
if (empty($form_state['num_names'])) {
$form_state['num_names'] = 1;
}
// Build the number of name fieldsets indicated by $form_state['num_names']
for ($i = 1; $i <= $form_state['num_names']; $i++) {
$form['name'][$i] = array(
'#type' => 'fieldset',
'#title' => t('Name #@num', array('@num' => $i)),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['name'][$i]['first'] = array(
'#type' => 'textfield',
'#title' => t('First name'),
'#description' => t("Enter first name."),
'#size' => 20,
'#maxlength' => 20,
'#required' => TRUE,
);
$form['name'][$i]['last'] = array(
'#type' => 'textfield',
'#title' => t('Enter Last name'),
'#required' => TRUE,
);
$form['name'][$i]['year_of_birth'] = array(
'#type' => 'textfield',
'#title' => t("Year of birth"),
'#description' => t('Format is "YYYY"'),
);
}
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
// Adds "Add another name" button.
$form['add_name'] = array(
'#type' => 'submit',
'#value' => t('Add another name'),
'#submit' => array('form_example_tutorial_9_add_name'),
);
// If we have more than one name, this button allows removal of the
// last name.
if ($form_state['num_names'] > 1) {
$form['remove_name'] = array(
'#type' => 'submit',
'#value' => t('Remove latest name'),
'#submit' => array('form_example_tutorial_9_remove_name'),
// Since we are removing a name, don't validate until later.
'#limit_validation_errors' => array(),
);
}
return $form;
}
/**
* Submit handler for "Add another name" button on form_example_tutorial_9().
*
* $form_state['num_names'] tells the form builder function how many name
* fieldsets to build, so here we increment it.
*
* All elements of $form_state are persisted, so there's no need to use a
* particular key, like the old $form_state['storage']. We can just use
* $form_state['num_names'].
*
* @ingroup form_example
*/
function form_example_tutorial_9_add_name($form, &$form_state) {
// Everything in $form_state is persistent, so we'll just use
// $form_state['add_name']
$form_state['num_names']++;
// Setting $form_state['rebuild'] = TRUE causes the form to be rebuilt again.
$form_state['rebuild'] = TRUE;
}
/**
* Submit handler for "Remove name" button on form_example_tutorial_9().
*
* @ingroup form_example
*/
function form_example_tutorial_9_remove_name($form, &$form_state) {
if ($form_state['num_names'] > 1) {
$form_state['num_names']--;
}
// Setting $form_state['rebuild'] = TRUE causes the form to be rebuilt again.
$form_state['rebuild'] = TRUE;
}
/**
* Validate function for form_example_tutorial_9().
*
* Adds logic to validate the form to check the validity of the new fields,
* if they exist.
*
* @ingroup form_example
*/
function form_example_tutorial_9_validate($form, &$form_state) {
for ($i = 1; $i <= $form_state['num_names']; $i++) {
$year_of_birth = $form_state['values']['name'][$i]['year_of_birth'];
if ($year_of_birth && ($year_of_birth < 1900 || $year_of_birth > 2000)) {
form_set_error("name][$i][year_of_birth", t('Enter a year between 1900 and 2000.'));
}
}
}
/**
* Submit function for form_example_tutorial_9().
*
* @ingroup form_example
*/
function form_example_tutorial_9_submit($form, &$form_state) {
$output = t("Form 9 has been submitted.");
for ($i = 1; $i <= $form_state['num_names']; $i++) {
$output .= t("@num: @first @last (@date)...",
array(
'@num' => $i,
'@first' => $form_state['values']['name'][$i]['first'],
'@last' => $form_state['values']['name'][$i]['last'],
'@date' => $form_state['values']['name'][$i]['year_of_birth'],
)
) . ' ';
}
drupal_set_message($output);
}
/**
* Example 10: A form with a file upload field.
*
* This example allows the user to upload a file to Drupal which is stored
* physically and with a reference in the database.
*
* @see form_example_tutorial_10_submit()
* @see form_example_tutorial_10_validate()
*
* @ingroup form_example
*/
function form_example_tutorial_10($form_state) {
// If you are familiar with how browsers handle files, you know that
// enctype="multipart/form-data" is required. Drupal takes care of that, so
// you don't need to include it yourself.
$form['file'] = array(
'#type' => 'file',
'#title' => t('Image'),
'#description' => t('Upload a file, allowed extensions: jpg, jpeg, png, gif'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Submit'),
);
return $form;
}
/**
* Validate handler for form_example_tutorial_10().
*
* @ingroup form_example
*/
function form_example_tutorial_10_validate($form, &$form_state) {
$file = file_save_upload('file', array(
// Validates file is really an image.
'file_validate_is_image' => array(),
// Validate extensions.
'file_validate_extensions' => array('png gif jpg jpeg'),
));
// If the file passed validation:
if ($file) {
// Move the file into the Drupal file system.
if ($file = file_move($file, 'public://')) {
// Save the file for use in the submit handler.
$form_state['storage']['file'] = $file;
}
else {
form_set_error('file', t("Failed to write the uploaded file to the site's file folder."));
}
}
else {
form_set_error('file', t('No file was uploaded.'));
}
}
/**
* Submit handler for form_example_tutorial_10().
*
* @ingroup form_example
*/
function form_example_tutorial_10_submit($form, &$form_state) {
$file = $form_state['storage']['file'];
// We are done with the file, remove it from storage.
unset($form_state['storage']['file']);
// Make the storage of the file permanent.
$file->status = FILE_STATUS_PERMANENT;
// Save file status.
file_save($file);
// Set a response to the user.
drupal_set_message(t('The form has been submitted and the image has been saved, filename: @filename.', array('@filename' => $file->filename)));
}
/**
* Example 11: adding a confirmation form.
*
* This example generates a simple form that, when submitted, directs
* the user to a confirmation form generated using the confirm_form function.
* It asks the user to verify that the name they input was correct
*
* @see form_example_tutorial_11_submit()
*
* @ingroup form_example
*/
function form_example_tutorial_11($form, &$form_state) {
// This form is identical to the one in example 2 except for one thing: We are
// adding an #action tag to direct the form submission to a confirmation page.
$form['description'] = array(
'#type' => 'item',
'#title' => t('A set of two forms that demonstrate the confirm_form function. This form has an explicit action to direct the form to a confirmation page'),
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name'),
'#required' => TRUE,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit',
);
return $form;
}
/**
* Submit function for form_example_tutorial_11().
*
* Adds a submit handler/function to our form to redirect
* the user to a confirmation page.
*
* @ingroup form_example
*/
function form_example_tutorial_11_submit($form, &$form_state) {
// Simple submit function that changes the redirect of the form based on the
// value of the name field.
$name = $form_state['values']['name'];
$form_state['redirect'] = 'examples/form_example/tutorial/11/confirm/' . urlencode($name);
}
/**
* Example 11: A form generated with confirm_form().
*
* This function generates the confirmation form using the confirm_form()
* function. If confirmed, it sets a drupal message to demonstrate it's success.
*
* @param string $name
* The urlencoded name entered by the user.
*
* @see form_example_tutorial_11_confirm_name_submit()
*
* @ingroup form_example
*/
function form_example_tutorial_11_confirm_name($form, $form_state, $name) {
// confirm_form() returns a complete form array for confirming an action.
// It has 7 arguments: $form, $question, $path, $description, $yes, $no, and
// $name.
// - $form: Additional elements to add to the form that will be available in
// the submit handler.
// - $question: What is the user confirming? This will be the title of the
// page.
// - $path: Where should the page go if the user hits cancel?
// - $description = NULL: Additional text to display.
// - $yes = NULL: Anchor text for the confirmation button. Defaults to
// t('Confirm').
// - $no = NULL: Anchor text for the cancel link. Defaults to t('Cancel').
// - $name = 'confirm': The internal name used to refer to the confirmation
// item.
// First we make a textfield for our user's name. confirm_form() allows us to
// Add form elements to the confirmation form, so we'll take advangage of
// that.
$user_name_text_field = array(
'name' => array(
'#type' => 'textfield',
// We don't want the user to be able to edit their name here.
'#disabled' => TRUE,
'#title' => t('Your name:'),
'#value' => urldecode($name),
),
);
// The question to ask the user.
$confirmation_question = t('Is this really your name?');
// If the user clicks 'no,' they're sent to this path.
$cancel_path = 'examples/form_example/tutorial/11';
// Some helpful descriptive text.
$description = t('Please verify whether or not you have input your name correctly. If you verify you will be sent back to the form and a message will be set. Otherwise you will be sent to the same page but with no message.');
// These are the text for our yes and no buttons.
$yes_button = t('This is my name');
$no_button = t('Nope, not my name');
// The name Form API will use to refer to our confirmation form.
$confirm_name = 'confirm_example';
// Finally, call confirm_form() with our information, and then return the form
// array it gives us.
return confirm_form(
$user_name_text_field,
$confirmation_question,
$cancel_path,
$description,
$yes_button,
$no_button,
$confirm_name
);
}
/**
* Submit function for form_example_tutorial_11_confirm_form().
*
* Adds a submit handler/function to the confirmation form
* if this point is reached the submission has been confirmed
* so we will set a message to demonstrate the success.
*
* @ingroup form_example
*/
function form_example_tutorial_11_confirm_name_submit($form, &$form_state) {
drupal_set_message(t("Confirmation form submission recieved. According to your submission your name is '@name'", array("@name" => $form_state['values']['name'])));
$form_state['redirect'] = 'examples/form_example/tutorial/11';
}

View File

@@ -0,0 +1,325 @@
<?php
/**
* @file
* Extensible wizard form example.
*/
/**
* Extensible wizard form example.
*
* This is an example of a multistep form using a wizard style. It will include
* the 'Previous' and 'Next' buttons when required, and a 'Finish' button at the
* last stage of the form submission.
*
* This example is an extensible skeleton that can include (even
* programmatically) more steps. The demonstration form includes three steps,
* each step having its own validation functions.
*
* How to extend this example:
* - Steps are defined in the _form_example_steps() function. Include or alter
* the steps as you require.
* - For each step, implement the corresponding 'form' function (see
* 'form_example_wizard_personal_info' for the first step in this example.)
* Each step is a regular form, and the wizard collects all the values of the
* included forms.
* - Optionally, you may include custom validation functions using the regular
* validation hook (formname_validate). The wizard uses these validation
* functions for each step.
* - The most important customization step is to change the submit handler and
* do whatever you want with the collected information. In this case, the
* example just shows the collected values in the various steps.
* @ingroup form_example
*/
/**
* Returns the list of steps and their associated forms.
*
* This has been separated to clarify and easy the understanding of this
* example. You should edit this function to include the steps your
* wizard/multistep form requires.
*
* @return array
* List of steps and their forms.
*
* @ingroup form_example
*/
function _form_example_steps() {
return array(
1 => array(
'form' => 'form_example_wizard_personal_info',
),
2 => array(
'form' => 'form_example_wizard_location_info',
),
3 => array(
'form' => 'form_example_wizard_other_info',
),
);
}
/**
* The primary formbuilder function for the wizard form.
*
* This is the form that you should call with drupal_get_form() from your code,
* and it will include the rest of the step forms defined. You are not required
* to change this function, as this will handle all the step actions for you.
*
* This form has two defined submit handlers to process the different steps:
* - Previous: handles the way to get back one step in the wizard.
* - Next: handles each step form submission,
*
* The third handler, the finish button handler, is the default form_submit
* handler used to process the information.
*
* You are not required to change the next or previous handlers, but you must
* change the form_example_wizard_submit handler to perform the operations you
* need on the collected information.
*
* @ingroup form_example
*/
function form_example_wizard($form, &$form_state) {
// Initialize a description of the steps for the wizard.
if (empty($form_state['step'])) {
$form_state['step'] = 1;
// This array contains the function to be called at each step to get the
// relevant form elements. It will also store state information for each
// step.
$form_state['step_information'] = _form_example_steps();
}
$step = &$form_state['step'];
drupal_set_title(t('Extensible Wizard: Step @step', array('@step' => $step)));
// Call the function named in $form_state['step_information'] to get the
// form elements to display for this step.
$form = $form_state['step_information'][$step]['form']($form, $form_state);
// Show the 'previous' button if appropriate. Note that #submit is set to
// a special submit handler, and that we use #limit_validation_errors to
// skip all complaints about validation when using the back button. The
// values entered will be discarded, but they will not be validated, which
// would be annoying in a "back" button.
if ($step > 1) {
$form['prev'] = array(
'#type' => 'submit',
'#value' => t('Previous'),
'#name' => 'prev',
'#submit' => array('form_example_wizard_previous_submit'),
'#limit_validation_errors' => array(),
);
}
// Show the Next button only if there are more steps defined.
if ($step < count($form_state['step_information'])) {
// The Next button should be included on every step.
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next'),
'#name' => 'next',
'#submit' => array('form_example_wizard_next_submit'),
);
}
else {
// Just in case there are no more steps, we use the default submit handler
// of the form wizard. Call this button Finish, Submit, or whatever you
// want to show. When this button is clicked, the
// form_example_wizard_submit handler will be called.
$form['finish'] = array(
'#type' => 'submit',
'#value' => t('Finish'),
);
}
// Include each validation function defined for the different steps.
if (function_exists($form_state['step_information'][$step]['form'] . '_validate')) {
$form['next']['#validate'] = array($form_state['step_information'][$step]['form'] . '_validate');
}
return $form;
}
/**
* Submit handler for the "previous" button.
*
* This function:
* - Stores away $form_state['values']
* - Decrements the step counter
* - Replaces $form_state['values'] with the values from the previous state.
* - Forces form rebuild.
*
* You are not required to change this function.
*
* @ingroup form_example
*/
function form_example_wizard_previous_submit($form, &$form_state) {
$current_step = &$form_state['step'];
$form_state['step_information'][$current_step]['stored_values'] = $form_state['values'];
if ($current_step > 1) {
$current_step--;
$form_state['values'] = $form_state['step_information'][$current_step]['stored_values'];
}
$form_state['rebuild'] = TRUE;
}
/**
* Submit handler for the 'next' button.
*
* This function:
* - Saves away $form_state['values']
* - Increments the step count.
* - Replace $form_state['values'] from the last time we were at this page
* or with array() if we haven't been here before.
* - Force form rebuild.
*
* You are not required to change this function.
*
* @ingroup form_example
*/
function form_example_wizard_next_submit($form, &$form_state) {
$current_step = &$form_state['step'];
$form_state['step_information'][$current_step]['stored_values'] = $form_state['values'];
if ($current_step < count($form_state['step_information'])) {
$current_step++;
if (!empty($form_state['step_information'][$current_step]['stored_values'])) {
$form_state['values'] = $form_state['step_information'][$current_step]['stored_values'];
}
else {
$form_state['values'] = array();
}
// Force rebuild with next step.
$form_state['rebuild'] = TRUE;
return;
}
}
/**
* The previous code was a 'skeleton' of a multistep wizard form. You are not
* required to change a line on the previous code (apart from defining your own
* steps in the _form_example_steps() function.
*
* All the code included from here is the content of the wizard, the steps of
* the form.
*
* First, let's show the defined steps for the wizard example.
* @ingroup form_example
*/
/**
* Returns form elements for the 'personal info' page of the wizard.
*
* This is the first step of the wizard, asking for two textfields: first name
* and last name.
*
* @ingroup form_example
*/
function form_example_wizard_personal_info($form, &$form_state) {
$form = array();
$form['first_name'] = array(
'#type' => 'textfield',
'#title' => t('First Name'),
'#default_value' => !empty($form_state['values']['first_name']) ? $form_state['values']['first_name'] : '',
);
$form['last_name'] = array(
'#type' => 'textfield',
'#title' => t('Last Name'),
'#default_value' => !empty($form_state['values']['last_name']) ? $form_state['values']['last_name'] : '',
);
return $form;
}
/**
* Returns form elements for the 'location info' page of the wizard.
*
* This is the second step of the wizard. This step asks for a textfield value:
* a City. This step also includes a validation declared later.
*
* @ingroup form_example
*/
function form_example_wizard_location_info($form, &$form_state) {
$form = array();
$form['city'] = array(
'#type' => 'textfield',
'#title' => t('City'),
'#description' => t('Hint: Do not enter "San Francisco", and do not leave this out.'),
'#required' => TRUE,
'#default_value' => !empty($form_state['values']['city']) ? $form_state['values']['city'] : '',
);
return $form;
}
/**
* Custom validation form for the 'location info' page of the wizard.
*
* This is the validation function for the second step of the wizard.
* The city cannot be empty or be "San Francisco".
*
* @ingroup form_example
*/
function form_example_wizard_location_info_validate($form, &$form_state) {
if ($form_state['values']['city'] == 'San Francisco') {
form_set_error('city', t('You were warned not to enter "San Francisco"'));
}
}
/**
* Returns form elements for the 'other info' page of the wizard.
*
* This is the third and last step of the example wizard.
*
* @ingroup form_example
*/
function form_example_wizard_other_info($form, &$form_state) {
$form = array();
$form['aunts_name'] = array(
'#type' => 'textfield',
'#title' => t("Your first cousin's aunt's Social Security number"),
'#default_value' => !empty($form_state['values']['aunts_name']) ? $form_state['values']['aunts_name'] : '',
);
return $form;
}
/**
* Wizard form submit handler.
*
* This function:
* - Saves away $form_state['values']
* - Process all the form values.
*
* And now comes the magic of the wizard, the function that should handle all
* the inputs from the user on each different step.
*
* This demonstration handler just do a drupal_set_message() with the
* information collected on each different step of the wizard.
*
* @ingroup form_example
*/
function form_example_wizard_submit($form, &$form_state) {
$current_step = &$form_state['step'];
$form_state['step_information'][$current_step]['stored_values'] = $form_state['values'];
// In this case we've completed the final page of the wizard, so process the
// submitted information.
drupal_set_message(t('This information was collected by this wizard:'));
foreach ($form_state['step_information'] as $index => $value) {
// Remove FAPI fields included in the values (form_token, form_id and
// form_build_id. This is not required, you may access the values using
// $value['stored_values'] but I'm removing them to make a more clear
// representation of the collected information as the complete array will
// be passed through drupal_set_message().
unset($value['stored_values']['form_id']);
unset($value['stored_values']['form_build_id']);
unset($value['stored_values']['form_token']);
// Now show all the values.
drupal_set_message(t('Step @num collected the following values: <pre>@result</pre>',
array(
'@num' => $index,
'@result' => print_r($value['stored_values'], TRUE),
)
));
}
}

View File

@@ -0,0 +1,13 @@
name = Image Example
description = Example implementation of image.module hooks.
package = Example modules
core = 7.x
dependencies[] = image
files[] = image_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,54 @@
<?php
/**
* @file
* Install, update, and uninstall functions for the image_example module.
*/
/**
* Implements hook_install().
*
* @ingroup image_example
*/
function image_example_install() {
// Set a variable containing the name of the style to use when the module
// outputs an image.
variable_set('image_example_style_name', 'image_example_style');
}
/**
* Implements hook_uninstall().
*
* @ingroup image_example
*/
function image_example_uninstall() {
variable_del('image_example_style_name');
variable_del('image_example_image_fid');
}
/**
* Implements hook_enable().
*
* @ingroup image_example
*/
function image_example_enable() {
// There is currently no way to manually flush an image style which causes
// problems when installing a new module that implements
// hook_image_styles_alter(). If the new module modifies an image style that
// modification will not be applied to any images that have already been
// generated unless the styles are flushed. This is one way around that.
$styles = image_styles();
foreach ($styles as $style) {
image_style_flush($style);
}
}
/**
* Implements hook_disable().
*
* @ingroup image_example
*/
function image_example_disable() {
// Solves the same problem as image_example_enable().
image_example_enable();
}

View File

@@ -0,0 +1,378 @@
<?php
/**
* @file
* Module file for image_example
*/
/**
* @defgroup image_example Example: Image
* @ingroup examples
* @{
* Demonstrates the basic use of image API.
*
* This module demonstrates the use of Drupal 7's new image styles and effects
* including the following topics.
* - Define default image styles in code. Useful for modules that want to ship
* with predefined image styles and for site developers who want their image
* style configurations to be in version control.
* hook_image_default_styles().
* - Define new image effects. Demonstrates how a module can add additional
* effects to the options available when creating image styles.
* hook_image_effect_info().
* - Alter existing image styles. Demonstrates the use of
* hook_image_styles_alter() to modify existing image effects, especially
* those defined by other modules in hook_image_default_styles() without
* having to override the styles.
* - Demonstrates the use of hook_image_style_save() and
* hook_image_style_delete() to update module specific variables when an
* image style is either re-named or deleted.
* - Generate a form with a field of type #managed_file that allows the user
* to upload an image and choose a style to use when displaying that image.
* - Demonstrates the use of theme_image_style() to display images using an
* image style.
*
* @see hook_image_default_styles().
* @see hook_image_effect_info().
* @see hook_image_style_save().
* @see hook_image_style_delete().
* @see theme_image_style().
*/
/**
* Implements hook_menu().
*
* Provide a menu item and a page to demonstrate features of this example
* module.
*/
function image_example_menu() {
$items = array();
$items['image_example/styles'] = array(
'title' => 'Image Example',
'page callback' => 'drupal_get_form',
'page arguments' => array('image_example_style_form'),
'access arguments' => array('access content'),
'file' => 'image_example.pages.inc',
);
return $items;
}
/**
* Implements hook_help().
*/
function image_example_help($path) {
switch ($path) {
case 'image_example/styles':
$output = '<p>' . t('Use this form to upload an image and choose an Image Style to use when displaying the image. This demonstrates basic use of the Drupal 7 Image styles & effects system.') . '</p>';
$output .= '<p>' . t('Image styles can be added/edited using the !link.', array('!link' => l(t('Image styles UI'), 'admin/config/media/image-styles'))) . '</p>';
return $output;
}
}
/**
* Implements hook_image_default_styles().
*
* hook_image_default_styles() declares to Drupal any image styles that are
* provided by the module. An image style is a collection of image effects that
* are performed in a specified order, manipulating the image and generating a
* new derivative image.
*
* This hook can be used to declare image styles that your module depends on or
* allow you to define image styles in code and gain the benefits of using
* a version control system.
*/
function image_example_image_default_styles() {
// This hook returns an array, each component of which describes an image
// style. The array keys are the machine-readable image style names and
// to avoid namespace conflicts should begin with the name of the
// implementing module. e.g.) 'mymodule_stylename'. Styles names should
// use only alpha-numeric characters, underscores (_), and hyphens (-).
$styles = array();
$styles['image_example_style'] = array();
// Each style array consists of an 'effects' array that is made up of
// sub-arrays which define the individual image effects that are combined
// together to create the image style.
$styles['image_example_style']['effects'] = array(
array(
// Name of the image effect. See image_image_effect_info() in
// modules/image/image.effects.inc for a list of image effects available
// in Drupal 7 core.
'name' => 'image_scale',
// Arguments to pass to the effect callback function.
// The arguments that an effect accepts are documented with each
// individual image_EFFECT_NAME_effect function. See image_scale_effect()
// for an example.
'data' => array(
'width' => 100,
'height' => 100,
'upscale' => 1,
),
// The order in which image effects should be applied when using this
// style.
'weight' => 0,
),
// Add a second effect to this image style. Effects are executed in order
// and are cumulative. When applying an image style to an image the result
// will be the combination of all effects associated with that style.
array(
'name' => 'image_example_colorize',
'data' => array(
'color' => '#FFFF66',
),
'weight' => 1,
),
);
return $styles;
}
/**
* Implements hook_image_style_save().
*
* Allows modules to respond to updates to an image style's
* settings.
*/
function image_example_image_style_save($style) {
// The $style parameter is an image style array with one notable exception.
// When a user has chosen to replace a deleted style with another style the
// $style['name'] property contains the name of the replacement style and
// $style['old_name'] contains the name of the style being deleted.
//
// Here we update a variable that contains the name of the image style that
// the block provided by this module uses when formatting images to use the
// new user chosen style name.
if (isset($style['old_name']) && $style['old_name'] == variable_get('image_example_style_name', '')) {
variable_set('image_example_style_name', $style['name']);
}
}
/**
* Implements hook_image_style_delete().
*
* This hook allows modules to respond to image styles being deleted.
*
* @see image_example_style_save()
*/
function image_example_image_style_delete($style) {
// See information about $style paramater in documentation for
// image_example_style_save().
//
// Update the modules variable that contains the name of the image style
// being deleted to the name of the replacement style.
if (isset($style['old_name']) && $style['old_name'] == variable_get('image_example_style_name', '')) {
variable_set('image_example_style_name', $style['name']);
}
}
/**
* Implements hook_image_style_flush().
*
* This hook allows modules to respond when a style is being flushed. Styles
* are flushed any time a style is updated, an effect associated with the style
* is updated, a new effect is added to the style, or an existing effect is
* removed.
*
* Flushing removes all images generated using this style from the host. Once a
* style has been flushed derivative images will need to be regenerated. New
* images will be generated automatically as needed but it is worth noting that
* on a busy site with lots of images this could have an impact on performance.
*
* Note: This function does not currently have any effect as the example module
* does not use any caches. It is demonstrated here for completeness sake only.
*/
function image_example_style_flush($style) {
// Empty any caches populated by our module that could contain stale data
// after the style has been flushed. Stale data occurs because the module may
// have cached content with a reference to the derivative image which is
// being deleted.
cache_clear_all('*', 'image_example', TRUE);
}
/**
* Implements hook_image_styles_alter().
*
* Allows your module to modify, add, or remove image styles provided
* by other modules. The best use of this hook is to modify default styles that
* have not been overriden by the user. Altering styles that have been
* overriden by the user could have an adverse affect on the user experience.
* If you add an effect to a style through this hook and the user attempts to
* remove the effect it will immediatly be re-applied.
*/
function image_example_image_styles_alter(&$styles) {
// The $styles paramater is an array of image style arrays keyed by style
// name. You can check to see if a style has been overriden by checking the
// $styles['stylename']['storage'] property.
// Verify that the effect has not been overriden.
if ($styles['thumbnail']['storage'] == IMAGE_STORAGE_DEFAULT) {
// Add an additional colorize effect to the system provided thumbnail
// effect.
$styles['thumbnail']['effects'][] = array(
'label' => t('Colorize #FFFF66'),
'name' => 'image_example_colorize',
'effect callback' => 'image_example_colorize_effect',
'data' => array(
'color' => '#FFFF66',
),
'weight' => 1,
);
}
}
/**
* Implements hook_image_effect_info().
*
* This hook allows your module to define additional image manipulation effects
* that can be used with image styles.
*/
function image_example_image_effect_info() {
$effects = array();
// The array is keyed on the machine-readable effect name.
$effects['image_example_colorize'] = array(
// Human readable name of the effect.
'label' => t('Colorize'),
// (optional) Brief description of the effect that will be shown when
// adding or configuring this image effect.
'help' => t('The colorize effect will first remove all color from the source image and then tint the image using the color specified.'),
// Name of function called to perform this effect.
'effect callback' => 'image_example_colorize_effect',
// (optional) Name of function that provides a $form array with options for
// configuring the effect. Note that you only need to return the fields
// specific to your module. Submit buttons will be added automatically, and
// configuration options will be serailized and added to the 'data' element
// of the effect. The function will recieve the $effect['data'] array as
// its only parameter.
'form callback' => 'image_example_colorize_form',
// (optional) Name of a theme function that will output a summary of this
// effects configuation. Used when displaying list of effects associated
// with an image style. In this example the function
// theme_image_example_colorize_summary will be called via the theme()
// function. Your module must also implement hook_theme() in order for this
// function to work correctly. See image_example_theme() and
// theme_image_example_colorize_summary().
'summary theme' => 'image_example_colorize_summary',
);
return $effects;
}
/**
* Form Builder; Configuration settings for colorize effect.
*
* Create a $form array with the fields necessary for configuring the
* image_example_colorize effect.
*
* Note that this is not a complete form, it only contains the portion of the
* form for configuring the colorize options. Therefore it does not not need to
* include metadata about the effect, nor a submit button.
*
* @param array $data
* The current configuration for this colorize effect.
*/
function image_example_colorize_form($data) {
$form = array();
// You do not need to worry about handling saving/updating/deleting of the
// data collected. The image module will automatically serialize and store
// all data associated with an effect.
$form['color'] = array(
'#type' => 'textfield',
'#title' => t('Color'),
'#description' => t('The color to use when colorizing the image. Use web-style hex colors. e.g.) #FF6633.'),
'#default_value' => isset($data['color']) ? $data['color'] : '',
'#size' => 7,
'#max_length' => 7,
'#required' => TRUE,
);
return $form;
}
/**
* Image effect callback; Colorize an image resource.
*
* @param object $image
* An image object returned by image_load().
* @param array $data
* An array of attributes to use when performing the colorize effect with the
* following items:
* - "color": The web-style hex color to use when colorizing the image.
*
* @return bool
* TRUE on success. FALSE on failure to colorize image.
*/
function image_example_colorize_effect(&$image, $data) {
// Image manipulation should be done to the $image->resource, which will be
// automatically saved as a new image once all effects have been applied.
// If your effect makes changes to the $image->resource that relate to any
// information stored in the $image->info array (width, height, etc.) you
// should update that information as well. See modules/system/image.gd.inc
// for examples of functions that perform image manipulations.
//
// Not all GD installations are created equal. It is a good idea to check for
// the existence of image manipulation functions before using them.
// PHP installations using non-bundled GD do not have imagefilter(). More
// information about image manipulation functions is available in the PHP
// manual. http://www.php.net/manual/en/book.image.php
if (!function_exists('imagefilter')) {
watchdog('image', 'The image %image could not be colorized because the imagefilter() function is not available in this PHP installation.', array('%file' => $image->source));
return FALSE;
}
// Verify that Drupal is using the PHP GD library for image manipulations
// since this effect depends on functions in the GD library.
if ($image->toolkit != 'gd') {
watchdog('image', 'Image colorize failed on %path. Using non GD toolkit.', array('%path' => $image->source), WATCHDOG_ERROR);
return FALSE;
}
// Convert short #FFF syntax to full #FFFFFF syntax.
if (strlen($data['color']) == 4) {
$c = $data['color'];
$data['color'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3];
}
// Convert #FFFFFF syntax to hexadecimal colors.
$data['color'] = hexdec(str_replace('#', '0x', $data['color']));
// Convert the hexadecimal color value to a color index value.
$rgb = array();
for ($i = 16; $i >= 0; $i -= 8) {
$rgb[] = (($data['color'] >> $i) & 0xFF);
}
// First desaturate the image, and then apply the new color.
imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
imagefilter($image->resource, IMG_FILTER_COLORIZE, $rgb[0], $rgb[1], $rgb[2]);
return TRUE;
}
/**
* Implements hook_theme().
*/
function image_example_theme() {
return array(
'image_example_colorize_summary' => array(
'variables' => array('data' => NULL),
),
'image_example_image' => array(
'variables' => array('image' => NULL, 'style' => NULL),
'file' => 'image_example.pages.inc',
),
);
}
/**
* Formats a summary of an image colorize effect.
*
* @param array $variables
* An associative array containing:
* - data: The current configuration for this colorize effect.
*/
function theme_image_example_colorize_summary($variables) {
$data = $variables['data'];
return t('as color #@color.', array('@color' => $data['color']));
}
/**
* @} End of "defgroup image_example".
*/

View File

@@ -0,0 +1,166 @@
<?php
/**
* @file
* Page/form showing image styles in use.
*/
/**
* Form for uploading and displaying an image using selected style.
*
* This page provides a form that allows the user to upload an image and choose
* a style from the list of defined image styles to use when displaying the
* the image. This serves as an example of integrating image styles with your
* module and as a way to demonstrate that the styles and effects defined by
* this module are available via Drupal's image handling system.
*
* @see theme_image_style()
*
* @ingroup image_example
*/
function image_example_style_form($form, &$form_state) {
// If there is already an uploaded image display the image here.
if ($image_fid = variable_get('image_example_image_fid', FALSE)) {
$image = file_load($image_fid);
$style = variable_get('image_example_style_name', 'thumbnail');
$form['image'] = array(
'#markup' => theme('image_example_image', array('image' => $image, 'style' => $style)),
);
}
// Use the #managed_file FAPI element to upload an image file.
$form['image_example_image_fid'] = array(
'#title' => t('Image'),
'#type' => 'managed_file',
'#description' => t('The uploaded image will be displayed on this page using the image style chosen below.'),
'#default_value' => variable_get('image_example_image_fid', ''),
'#upload_location' => 'public://image_example_images/',
);
// Provide a select field for choosing an image style to use when displaying
// the image.
$form['image_example_style_name'] = array(
'#title' => t('Image style'),
'#type' => 'select',
'#description' => t('Choose an image style to use when displaying this image.'),
// The image_style_options() function returns an array of all available
// image styles both the key and the value of the array are the image
// style's name. The function takes on paramater, a boolean flag
// signifying whether or not the array should include a <none> option.
'#options' => image_style_options(TRUE),
'#default_value' => variable_get('image_example_style_name', ''),
);
// Submit Button.
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
/**
* Verifies that the user supplied an image with the form..
*
* @ingroup image_example
*/
function image_example_style_form_validate($form, &$form_state) {
if (!isset($form_state['values']['image_example_image_fid']) || !is_numeric($form_state['values']['image_example_image_fid'])) {
form_set_error('image_example_image_fid', t('Please select an image to upload.'));
}
}
/**
* Form Builder; Display a form for uploading an image.
*
* @ingroup image_example
*/
function image_example_style_form_submit($form, &$form_state) {
// When using the #managed_file form element the file is automatically
// uploaded an saved to the {file} table. The value of the corresponding
// form element is set to the {file}.fid of the new file.
//
// If fid is not 0 we have a valid file.
if ($form_state['values']['image_example_image_fid'] != 0) {
// The new file's status is set to 0 or temporary and in order to ensure
// that the file is not removed after 6 hours we need to change it's status
// to 1. Save the ID of the uploaded image for later use.
$file = file_load($form_state['values']['image_example_image_fid']);
$file->status = FILE_STATUS_PERMANENT;
file_save($file);
// When a module is managing a file, it must manage the usage count.
// Here we increment the usage count with file_usage_add().
file_usage_add($file, 'image_example', 'sample_image', 1);
// Save the fid of the file so that the module can reference it later.
variable_set('image_example_image_fid', $file->fid);
drupal_set_message(t('The image @image_name was uploaded and saved with an ID of @fid and will be displayed using the style @style.',
array(
'@image_name' => $file->filename,
'@fid' => $file->fid,
'@style' => $form_state['values']['image_example_style_name'],
)
));
}
// If the file was removed we need to remove the module's reference to the
// removed file's fid, and remove the file.
elseif ($form_state['values']['image_example_image_fid'] == 0) {
// Retrieve the old file's id.
$fid = variable_get('image_example_image_fid', FALSE);
$file = $fid ? file_load($fid) : FALSE;
if ($file) {
// When a module is managing a file, it must manage the usage count.
// Here we decrement the usage count with file_usage_delete().
file_usage_delete($file, 'image_example', 'sample_image', 1);
// The file_delete() function takes a file object and checks to see if
// the file is being used by any other modules. If it is the delete
// operation is cancelled, otherwise the file is deleted.
file_delete($file);
}
// Either way the module needs to update it's reference since even if the
// file is in use by another module and not deleted we no longer want to
// use it.
variable_set('image_example_image_fid', FALSE);
drupal_set_message(t('The image @image_name was removed.', array('@image_name' => $file->filename)));
}
// Save the name of the image style chosen by the user.
variable_set('image_example_style_name', $form_state['values']['image_example_style_name']);
}
/**
* Theme function displays an image rendered using the specified style.
*
* @ingroup image_example
*/
function theme_image_example_image($variables) {
$image = $variables['image'];
$style = $variables['style'];
// theme_image_style() is the primary method for displaying images using
// one of the defined styles. The $variables array passed to the theme
// contains the following two important values:
// - 'style_name': the name of the image style to use when displaying the
// image.
// - 'path': the $file->uri of the image to display.
//
// When given a style and an image path the function will first determine
// if a derivative image already exists, in which case the existing image
// will be displayed. If the derivative image does not already exist the
// function returns an <img> tag with a specially crafted callback URL
// as the src attribute for the tag. When accessed, the callback URL will
// generate the derivative image and serve it to the browser.
$output = theme('image_style',
array(
'style_name' => $style,
'path' => $image->uri,
'getsize' => FALSE,
)
);
$output .= '<p>' . t('This image is being displayed using the image style %style_name.', array('%style_name' => $style)) . '</p>';
return $output;
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* @file
* Test case for testing the image example module.
*
* This file contains the tests cases to check if the module is performing as
* expected.
*/
/**
* Functional tests for the Image Example module.
*
* @ingroup image_example
*/
class ImageExampleTestCase extends DrupalWebTestCase {
protected $webUser;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Image example functionality',
'description' => 'Test functionality of the Image Example module.',
'group' => 'Examples',
);
}
/**
* Enable modules and create user with specific permissions.
*/
public function setUp() {
parent::setUp('image_example');
// Create user with permission to administer image styles.
$this->webUser = $this->drupalCreateUser(array('administer image styles', 'administer blocks'));
}
/**
* Test implementations of image API hooks.
*/
public function testImageExample() {
// Login the admin user.
$this->drupalLogin($this->webUser);
// Verify that the default style added by
// image_example_image_default_styles() is in the list of image styles.
$image_styles = image_styles();
$this->assertTrue(isset($image_styles['image_example_style']), 'The default style image_example_style is in the list of image styles.');
// Verify that the effect added to the default 'thumbnail' style by
// image_example_image_styles_alter() is present.
$this->assertTrue((isset($image_styles['thumbnail']['effects'][1]['name']) && $image_styles['thumbnail']['effects'][1]['name'] == 'image_example_colorize'), 'Effect added to the thumbnail style via hook_image_styles_alter() is present.');
// Create a new image style and add the effect provided by
// image_example_effect_info().
$new_style = array('name' => drupal_strtolower($this->randomName()));
$new_style = image_style_save($new_style);
$this->assertTrue(isset($new_style['isid']), format_string('Image style @style_name created.', array('@style_name' => $new_style['name'])));
$edit = array(
'new' => 'image_example_colorize',
);
$this->drupalPost('admin/config/media/image-styles/edit/' . $new_style['name'], $edit, t('Add'));
// Verify the 'color' field provided by image_example_colorize_form()
// appears on the effect configuration page. And that we can fill it out.
$this->assertField('data[color]', 'Color field provided by image_example_effect_colorize_form is present on effect configuration page.');
$edit = array(
'data[color]' => '#000000',
);
$this->drupalPost(NULL, $edit, t('Add effect'));
$this->assertText(t('The image effect was successfully applied.'), format_string('Colorize effect added to @style_name.', array('@style_name' => $new_style['name'])));
// Set the variable 'image_example_style_name' to the name of our new style
// then rename the style and ensure the variable name is changed.
// @todo Enable this block once http://drupal.org/node/713872 is fixed.
if (defined('bug_713872_fixed')) {
$style = image_style_load($new_style['name']);
variable_set('image_example_style_name', $style['name']);
$style['name'] = drupal_strtolower($this->randomName());
$style = image_style_save($style);
$variable = variable_get('image_example_style_name', '');
$this->assertTrue(($variable == $style['name']), 'Variable image_example_style_name successfully updated when renaming image style.');
}
}
/**
* Tests for image block provided by module.
*/
public function testImageExamplePage() {
// Login the admin user.
$this->drupalLogin($this->webUser);
$this->drupalCreateNode(array('promote' => 1));
// Upload an image to the image page.
$images = $this->drupalGetTestFiles('image');
$edit = array(
'files[image_example_image_fid]' => drupal_realpath($images[0]->uri),
'image_example_style_name' => 'image_example_style',
);
$this->drupalPost('image_example/styles', $edit, t('Save'));
$this->assertText(t('The image @image_name was uploaded', array('@image_name' => $images[0]->filename)), 'Image uploaded to image block.');
// Verify the image is displayed.
$this->drupalGet('image_example/styles');
$fid = variable_get('image_example_image_fid', FALSE);
$image = isset($fid) ? file_load($fid) : NULL;
$this->assertRaw(file_uri_target($image->uri), 'Image is displayed');
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* @file
* Template file for js_example module.
*/
?>
<div class="demo">
<h2><?php print $title; ?></h2>
<div id="accordion">
<h3><a href="#">Section 1</a></h3>
<div>
<p>
Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer
ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit
amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut
odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.
</p>
</div>
<h3><a href="#">Section 2</a></h3>
<div>
<p>
Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet
purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor
velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In
suscipit faucibus urna.
</p>
</div>
<h3><a href="#">Section 3</a></h3>
<div>
<p>
Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis.
Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero
ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis
lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.
</p>
<ul>
<li>List item one</li>
<li>List item two</li>
<li>List item three</li>
</ul>
</div>
<h3><a href="#">Section 4</a></h3>
<div>
<p>
Cras dictum. Pellentesque habitant morbi tristique senectus et netus
et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in
faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia
mauris vel est.
</p>
<p>
Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus.
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
inceptos himenaeos.
</p>
</div>
</div>
</div><!-- End demo -->

View File

@@ -0,0 +1,5 @@
div#js-weights div {
font-size: 20px;
font-weight: bold;
}

View File

@@ -0,0 +1,9 @@
(function ($) {
Drupal.behaviors.jsWeightsBlack = {
attach: function (context, settings) {
var weight = settings.jsWeights.black;
var newDiv = $('<div />').css('color', 'black').html('I have a weight of ' + weight);
$('#js-weights').append(newDiv);
}
};
})(jQuery);

View File

@@ -0,0 +1,9 @@
(function ($) {
Drupal.behaviors.jsWeightsBlue = {
attach: function (context, settings) {
var weight = settings.jsWeights.blue;
var newDiv = $('<div />').css('color', 'blue').html('I have a weight of ' + weight);
$('#js-weights').append(newDiv);
}
};
})(jQuery);

View File

@@ -0,0 +1,9 @@
(function ($) {
Drupal.behaviors.jsWeightsBrown = {
attach: function (context, settings) {
var weight = settings.jsWeights.brown;
var newDiv = $('<div />').css('color', 'brown').html('I have a weight of ' + weight);
$('#js-weights').append(newDiv);
}
};
})(jQuery);

View File

@@ -0,0 +1,9 @@
(function ($) {
Drupal.behaviors.jsWeightsGreen = {
attach: function (context, settings) {
var weight = settings.jsWeights.green;
var newDiv = $('<div></div>').css('color', 'green').html('I have a weight of ' + weight);
$('#js-weights').append(newDiv);
}
};
})(jQuery);

View File

@@ -0,0 +1,9 @@
(function ($) {
Drupal.behaviors.jsWeightsPurple = {
attach: function (context, settings) {
var weight = settings.jsWeights.purple;
var newDiv = $('<div />').css('color', 'purple').html('I have a weight of ' + weight);
$('#js-weights').append(newDiv);
}
};
})(jQuery);

View File

@@ -0,0 +1,9 @@
(function ($) {
Drupal.behaviors.jsWeightsRed = {
attach: function (context, settings) {
var weight = settings.jsWeights.red;
var newDiv = $('<div />').css('color', 'red').html('I have a weight of ' + weight);
$('#js-weights').append(newDiv);
}
};
})(jQuery);

View File

@@ -0,0 +1,12 @@
name = JS Example
description = An example module showing how to use some of the new JavaScript features in Drupal 7
package = Example modules
core = 7.x
files[] = js_example.test
; Information added by Drupal.org packaging script on 2016-09-18
version = "7.x-1.x-dev"
core = "7.x"
project = "examples"
datestamp = "1474218553"

View File

@@ -0,0 +1,122 @@
<?php
/**
* @file
* Examples showing how to use some of the new JavaScript features in Drupal 7.
*/
/**
* @defgroup js_example Example: JavaScript
* @ingroup examples
* @{
* Examples using Drupal 7's built-in JavaScript.
*/
/**
* Implements hook_theme().
*/
function js_example_theme() {
return array(
'my_accordion' => array(
'template' => 'accordion',
'variables' => array('title' => NULL),
),
);
}
/**
* Implements hook_menu().
*/
function js_example_menu() {
$items = array();
$items['js_example/weights'] = array(
'title' => 'JS Example: see weighting in action',
'page callback' => 'js_example_js_weights',
'access callback' => TRUE,
);
$items['js_example/accordion'] = array(
'title' => 'JS Example: jQuery UI accordion',
'page callback' => 'js_example_accordion',
'access callback' => TRUE,
);
return $items;
}
/**
* Weights demonstration.
*
* Here we demonstrate attaching a number of scripts to the render array.
* These scripts generate content according to 'weight' and color.
*
* On the Drupal side, we do three main things:
* - Create a container DIV, with an ID all the scripts can recognize.
* - Attach some scripts which generate color-coded content. We use the
* 'weight' attribute to set the order in which the scripts are included.
* - Add the color->weight array to the settings variable in each *color*.js
* file. This is where Drupal passes data out to JavaScript.
*
* Each of the color scripts (red.js, blue.js, etc) uses jQuery to find our
* DIV, and then add some content to it. The order in which the color scripts
* execute will end up being the order of the content.
*
* The 'weight' form atttribute determines the order in which a script is
* output to the page. To see this in action:
* - Uncheck the 'Aggregate Javascript files' setting at:
* admin/config/development/performance.
* - Load the page: js_example/weights. Examine the page source.
* You will see that the color js scripts have been added in the <head>
* element in weight order.
*
* To test further, change a weight in the $weights array below, then save
* this file and reload js_example/weights. Examine the new source to see the
* reordering.
*
* @return array
* A renderable array.
*/
function js_example_js_weights() {
// Add some css to show which line is output by which script.
drupal_add_css(drupal_get_path('module', 'js_example') . '/css/jsweights.css');
// Create an array of items with random-ish weight values.
$weights = array(
'red' => 100,
'blue' => 23,
'green' => 3,
'brown' => 45,
'black' => 5,
'purple' => 60,
);
// Attach the weights array to our JavaScript settings. This allows the
// color scripts to discover their weight values, by accessing
// settings.jsWeights.*color*. The color scripts only use this information for
// display to the user.
drupal_add_js(array('jsWeights' => $weights), array('type' => 'setting'));
// Add our individual scripts. We add them in an arbitrary order, but the
// 'weight' attribute will cause Drupal to render (and thus load and execute)
// them in the weighted order.
drupal_add_js(drupal_get_path('module', 'js_example') . '/js/red.js', array('weight' => $weights['red']));
drupal_add_js(drupal_get_path('module', 'js_example') . '/js/blue.js', array('weight' => $weights['blue']));
drupal_add_js(drupal_get_path('module', 'js_example') . '/js/green.js', array('weight' => $weights['green']));
drupal_add_js(drupal_get_path('module', 'js_example') . '/js/brown.js', array('weight' => $weights['brown']));
drupal_add_js(drupal_get_path('module', 'js_example') . '/js/black.js', array('weight' => $weights['black']));
drupal_add_js(drupal_get_path('module', 'js_example') . '/js/purple.js', array('weight' => $weights['purple']));
// Main container DIV. We give it a unique ID so that the JavaScript can
// find it using jQuery.
$output = '<div id="js-weights"></div>';
return $output;
}
/**
* Demonstrate accordion effect.
*/
function js_example_accordion() {
$title = t('Click sections to expand or collapse:');
$build['myelement'] = array(
'#theme' => 'my_accordion',
'#title' => $title,
);
$build['myelement']['#attached']['library'][] = array('system', 'ui.accordion');
$build['myelement']['#attached']['js'][] = array('data' => '(function($){$(function() { $("#accordion").accordion(); })})(jQuery);', 'type' => 'inline');
$output = drupal_render($build);
return $output;
}

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