FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 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,53 @@
<?php
// $Id$
/*
* Copyright © 2012 New Signature
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
* You can contact New Signature by electronic mail at labs@newsignature.com -or- by U.S. Postal Service at 1100 H St. NW, Suite 940, Washington, DC 20005.
*/
/**
* @file Provides an Administrative UI for the addressfield_tokens module.
*/
/**
* Provides a form for configuring regional options for addresses.
*
* @param array $form
* @param array $form_state
* @return array The form.
*/
function addressfield_tokens_admin_form($form, &$form_state) {
$options = array('' => t('- None -')) + _addressfield_tokens_countries();
$form['addressfield_tokens_default_country'] = array(
'#type' => 'select',
'#title' => t('Default country'),
'#description' => t('In the city/state views, addresses in this country will not display the country.'),
'#options' => $options,
'#default_value' => addressfield_tokens_default_country(),
);
$form['addressfield_tokens_property_names'] = array(
'#type' => 'fieldset',
'#title' => t('Custom address property names'),
'#description' => t('Change the names of address fields when selecting tokens.'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#tree' => TRUE,
);
$names = addressfield_tokens_property_names();
$props = addressfield_data_property_info();
foreach ($props as $name => $prop) {
$form['addressfield_tokens_property_names'][$name] = array(
'#type' => 'textfield',
'#title' => $prop['label'],
'#description' => $name,
'#default_value' => !empty($names[$name]) ? $names[$name] : $prop['label'],
);
}
return system_settings_form($form);
}

View File

@@ -0,0 +1,398 @@
<?php
// $Id$
/*
* Copyright © 2012 New Signature
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
* You can contact New Signature by electronic mail at labs@newsignature.com -or- by U.S. Postal Service at 1100 H St. NW, Suite 940, Washington, DC 20005.
*/
/**
* @file Webform Component information for an address field type
*/
/**
* Specify the default properties of a component.
*
* @return
* An array defining the default structure of a component.
*/
function _webform_defaults_addressfield() {
return array (
'name' => '',
'form_key' => NULL,
'mandatory' => 0,
'pid' => 0,
'weight' => 0,
'extra' => array (
'title_display' => 0,
'private' => FALSE,
'attributes' => array (),
'description' => '',
'available_countries' => array(),
'csv_separate' => 0,
),
);
}
/**
* Generate the form for editing a component.
* Create a set of form elements to be displayed on the form for editing this
* component. Use care naming the form items, as this correlates directly to the
* database schema. The component "Name" and "Description" fields are added to
* every component type and are not necessary to specify here (although they
* may be overridden if desired).
*
* @param $component
* A Webform component array.
*
* @return
* An array of form items to be displayed on the edit component page
*/
function _webform_edit_addressfield($component) {
$form = array ();
$form['extra']['available_countries'] = array(
'#type' => 'select',
'#multiple' => TRUE,
'#title' => t('Available countries'),
'#description' => t('If no countries are selected, all countries will be available.'),
'#options' => _addressfield_country_options_list(),
'#default_value' => $component['extra']['available_countries'],
);
$form['extra']['csv_separate'] = array(
'#type' => 'radios',
'#title' => t('CSV download'),
'#description' => t('How would you like addresses presented in CSV downloads?'),
'#options' => array(
0 => t('Display entire address in a single column'),
1 => t('Display each address component in a separate column'),
),
'#default_value' => $component['extra']['csv_separate'],
);
return $form;
}
/**
* Render a Webform component to be part of a form.
*
* @param $component
* A Webform component array.
* @param $value
* If editing an existing submission or resuming a draft, this will contain
* an array of values to be shown instead of the default in the component
* configuration. This value will always be an array, keyed numerically for
* each value saved in this field.
* @param $filter
* Whether or not to filter the contents of descriptions and values when
* rendering the component. Values need to be unfiltered to be editable by
* Form Builder.
*
* @see _webform_client_form_add_component()
*/
function _webform_render_addressfield($component, $value = NULL, $filter = TRUE) {
$element = array (
'#type' => 'fieldset',
'#title' => $filter ? _webform_filter_xss($component['name']) : $component['name'],
'#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before',
'#attributes' => $component['extra']['attributes'],
'#theme_wrappers' => array ( 'webform_element' ),
'#description' => $filter ? _webform_filter_descriptions($component['extra']['description']) : $component['extra']['description'],
'#required' => $component['mandatory'],
'#weight' => $component['weight'],
'#translatable' => array (
'title',
'description',
),
);
$available = !empty($component['extra']['available_countries']) ? $component['extra']['available_countries'] : NULL;
// Get the current address
if (!empty($value[0])) {
if (is_string($value[0])) {
$address = unserialize($value[0]);
}
else {
$address = $value[0];
}
}
elseif (!empty($component['value'])) {
$address = $component['value'];
}
else {
$address = _webform_addressfield($component['cid']);
}
if (empty($address)) {
$address = addressfield_default_values($available);
}
// Generate the address form.
$context = array(
'mode' => 'form',
);
$element += addressfield_generate($address, array('address'), $context);
if (!empty($available)) {
$element['country']['#options'] = array_intersect_key($element['country']['#options'], $available);
}
$element['country']['#element_validate'] = array('_webform_addressfield_country_validate');
$element['country']['#cid'] = $component['cid'];
$element['country']['#limit_validation_errors'] = array();
$form_state = array();
drupal_alter('field_widget_addressfield_standard_form', $element, $form_state, $context);
return $element;
}
/**
* Stores an addressfield submitted in a webform component. Ideally we should store
* it in the $form_state instead, but there appears to be no way to get it to actually
* pass through to _webform_render_addressfield().
*
* @param $cid integer The ID of the webform component.
* @param $address array If set, this address will be stored with the given $cid.
* @return array The address stored with the given $cid, if there is one; otherwise, NULL.
*/
function _webform_addressfield($cid, $address = NULL) {
$out = &drupal_static(__FUNCTION__, array());
if (isset($address)) {
$out[$cid] = $address;
}
if (isset($out[$cid])) {
return $out[$cid];
}
return NULL;
}
/**
* Validates a country, and if it changes, rebuilds the form for the new country
*/
function _webform_addressfield_country_validate(&$element, &$form_state) {
// If the country was changed, rebuild the form.
if ($element['#default_value'] != $element['#value']) {
$form_state['rebuild'] = TRUE;
}
$cid = $element['#cid'];
$parents = $element['#parents'];
array_pop($parents);
// Search through the form values to find the current address.
$address = drupal_array_get_nested_value($form_state['values'], $parents);
_webform_addressfield($cid, $address);
}
/**
* Display the result of a submission for a component.
* The output of this function will be displayed under the "Results" tab then
* "Submissions". This should output the saved data in some reasonable manner.
*
* @param $component
* A Webform component array.
* @param $value
* An array of information containing the submission result, directly
* correlating to the webform_submitted_data database table schema.
* @param $format
* Either 'html' or 'text'. Defines the format that the content should be
* returned as. Make sure that returned content is run through check_plain()
* or other filtering functions when returning HTML.
*
* @return
* A renderable element containing at the very least these properties:
* - #title
* - #weight
* - #component
* - #format
* - #value
* Webform also uses #theme_wrappers to output the end result to the user,
* which will properly format the label and content for use within an e-mail
* (such as wrapping the text) or as HTML (ensuring consistent output).
*/
function _webform_display_addressfield($component, $value, $format = 'html') {
$address = NULL;
if (isset($value[0])) {
$address = $value[0];
if (is_string($address)) {
$address = unserialize($address);
}
}
return array (
'#title' => $component['name'],
'#weight' => $component['weight'],
'#theme' => $format == 'html' ? 'addressfield_formatter' : 'addressfield_formatter__linear',
'#theme_wrappers' => $format == 'html' ? array ( 'webform_element' ) : array ( 'webform_element_text' ),
'#post_render' => array ( 'webform_element_wrapper' ),
'#component' => $component,
'#format' => $format,
'#address' => $address,
);
}
/**
* A hook for changing the input values before saving to the database.
* Webform expects a component to consist of a single field, or a single array
* of fields. If you have a component that requires a deeper form tree
* you must flatten the data into a single array using this callback
* or by setting #parents on each field to avoid data loss and/or unexpected
* behavior.
* Note that Webform will save the result of this function directly into the
* database.
*
* @param $component
* A Webform component array.
* @param $value
* The POST data associated with the user input.
*
* @return
* An array of values to be saved into the database. Note that this should be
* a numerically keyed array.
*/
function _webform_submit_addressfield($component, $value) {
return serialize($value);
}
/**
* Calculate and returns statistics about results for this component.
* This takes into account all submissions to this webform. The output of this
* function will be displayed under the "Results" tab then "Analysis".
*
* @param $component
* An array of information describing the component, directly correlating to
* the webform_component database schema.
* @param $sids
* An optional array of submission IDs (sid). If supplied, the analysis will
* be limited to these sids.
* @param $single
* Boolean flag determining if the details about a single component are being
* shown. May be used to provided detailed information about a single
* component's analysis, such as showing "Other" options within a select list.
*
* @return
* An array of data rows, each containing a statistic for this component's
* submissions.
*/
function _webform_analysis_addressfield($component, $sids = array (), $single = FALSE) {
// TODO Update this function
// Generate the list of options and questions.
$query = db_select('webform_submitted_data', 'wsd')
->fields('wsd', array ('data'))
->condition('nid', $component['nid'])
->condition('cid', $component['cid']);
if ( count($sids) ) {
$query->condition('sid', $sids, 'IN');
}
$non_blanks = 0;
$submissions = 0;
$results = $query->execute();
foreach ($results as $row) {
if ( drupal_strlen(trim($row->data)) > 0 ) {
$non_blanks++;
}
$submissions++;
}
$rows[0] = array (
t('Left Blank'),
( $submissions - $non_blanks )
);
$rows[1] = array (
t('User entered value'),
$non_blanks
);
return $rows;
}
/**
* Return the result of a component value for display in a table.
* The output of this function will be displayed under the "Results" tab then
* "Table".
*
* @param $component
* A Webform component array.
* @param $value
* An array of information containing the submission result, directly
* correlating to the webform_submitted_data database schema.
*
* @return
* Textual output formatted for human reading.
*/
function _webform_table_addressfield($component, $value) {
if (!empty($value[0])) {
return theme('addressfield_formatter', array( 'address' => $value[0] ));
}
return '';
}
/**
* Return the header for this component to be displayed in a CSV file.
* The output of this function will be displayed under the "Results" tab then
* "Download".
*
* @param $component
* A Webform component array.
* @param $export_options
* An array of options that may configure export of this field.
*
* @return
* An array of data to be displayed in the first three rows of a CSV file, not
* including either prefixed or trailing commas.
*/
function _webform_csv_headers_addressfield($component, $export_options) {
$header = array ();
if (!empty($component['extra']['csv_separate']) && $component['extra']['csv_separate'] == 1) {
$header[0] = array();
$header[1] = array();
$header[2] = array();
foreach (addressfield_tokens_property_names() as $key => $name) {
$header[0][] = '';
$header[1][] = (empty($header[1])) ? $component['name'] : '';
$header[2][] = $name;
}
}
else {
$header[0] = array('');
$header[1] = array();
$header[2] = array($component['name']);
}
return $header;
}
/**
* Format the submitted data of a component for CSV downloading.
* The output of this function will be displayed under the "Results" tab then
* "Download".
*
* @param $component
* A Webform component array.
* @param $export_options
* An array of options that may configure export of this field.
* @param $value
* An array of information containing the submission result, directly
* correlating to the webform_submitted_data database schema.
*
* @return
* An array of items to be added to the CSV file. Each value within the array
* will be another column within the file. This function is called once for
* every row of data.
*/
function _webform_csv_data_addressfield($component, $export_options, $value) {
if (!empty($component['extra']['csv_separate']) && $component['extra']['csv_separate'] == 1) {
$return = array();
foreach (addressfield_tokens_property_names() as $key => $name) {
$return[] = (isset($value[0][$key])) ? $value[0][$key] : '';
}
return $return;
}
else {
if (!empty($value[0])) {
return theme('addressfield_formatter__linear', array( 'address' => $value[0] ));
}
return '';
}
}

View File

@@ -0,0 +1,24 @@
; $Id$
; Copyright © 2012 New Signature
;
; 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 3 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, see <http://www.gnu.org/licenses/>.
; You can contact New Signature by electronic mail at labs@newsignature.com <20>or- by U.S. Postal Service at 1100 H St. NW, Suite 940, Washington, DC 20005.
name = Address Field Tokens
description = Creates tokens for address fields, adds new addressfield renders, and adds webform integration.
core = 7.x
package = Fields
version = 7.x-1.0-dev
dependencies[] = addressfield
dependencies[] = entity_token
dependencies[] = token
; Information added by Drupal.org packaging script on 2014-05-14
version = "7.x-1.4"
core = "7.x"
project = "addressfield_tokens"
datestamp = "1400081330"

View File

@@ -0,0 +1,439 @@
<?php
/**
* @file
*
* Copyright 2011 New Signature
* http://www.newsignature.com
*
* @author Andrew Marcus
* @since Oct 4, 2011
*/
function addressfield_tokens_menu() {
$items = array();
$items['admin/config/regional/address'] = array(
'title' => 'Addresses',
'description' => 'Settings for address fields and tokens',
'page callback' => 'drupal_get_form',
'page arguments' => array('addressfield_tokens_admin_form'),
'access arguments' => array('administer site configuration'),
'file' => 'addressfield_tokens.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implements hook_theme().
*/
function addressfield_tokens_theme($existing, $type, $theme, $path) {
$theme = array(
'addressfield_formatter' => array(
'variables' => array('address' => NULL, 'handlers' => array('address')),
'file' => 'addressfield_tokens.theme.inc',
),
'addressfield_formatter__citystate' => array(
'variables' => array('address' => NULL),
'file' => 'addressfield_tokens.theme.inc',
),
'addressfield_formatter__linear' => array(
'variables' => array('address' => NULL, 'premise' => TRUE, 'organisation_name' => TRUE, 'name_line' => TRUE),
'file' => 'addressfield_tokens.theme.inc',
),
'addressfield_formatter__components' => array(
'variables' => array(
'address' => NULL,
'components' => array('thoroughfare', 'premise', 'locality', 'administrative_area', 'postal_code', 'country'),
'separator' => ', ',
),
'file' => 'addressfield_tokens.theme.inc',
),
);
return $theme;
}
/**
* Implements hook_field_formatter_info().
*/
function addressfield_tokens_field_formatter_info() {
return array(
'addressfield_citystate' => array(
'label' => t('City/State'),
'field types' => array('addressfield'),
),
'addressfield_linear' => array(
'label' => t('One line'),
'field types' => array('addressfield'),
),
'addressfield_state' => array(
'label' => t('State'),
'field types' => array('addressfield'),
),
'addressfield_country' => array(
'label' => t('Country'),
'field types' => array('addressfield'),
),
'addressfield_components' => array(
'label' => t('Address components'),
'field types' => array('addressfield'),
'settings' => array(
'components' => array('thoroughfare', 'premise', 'locality', 'administrative_area', 'postal_code', 'country'),
'separator' => ', ',
),
),
);
}
/**
* Implements hook_field_formatter_settings_form().
*/
function addressfield_tokens_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$element = array();
if ($display['type'] == 'addressfield_components') {
$element['components'] = array(
'#type' => 'select',
'#title' => t('Components to render'),
'#multiple' => TRUE,
'#rows' => 10,
'#options' => addressfield_tokens_components(),
'#default_value' => $settings['components'],
'#required' => TRUE,
);
$element['separator'] = array(
'#type' => 'textfield',
'#title' => t('Separator'),
'#description' => t('The separator to use between components. Use \n for a line break.'),
'#default_value' => $settings['separator'],
);
}
return $element;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function addressfield_tokens_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$summary = '';
if ($display['type'] == 'addressfield_components') {
$comps = array_intersect_key(addressfield_tokens_components(), array_flip($settings['components']));
$sep = str_replace('\n', '<br/>', $settings['separator']);
$summary = implode($sep, $comps);
}
return $summary;
}
/**
* Implements hook_field_formatter_view().
*/
function addressfield_tokens_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
switch ($display['type']) {
case 'addressfield_citystate':
$theme = array('addressfield_formatter__citystate', 'addressfield_formatter');
if (!empty($item['country'])) {
array_unshift($theme, 'addressfield_formatter__citystate__' . $item['country']);
}
foreach ($items as $delta => $item) {
$element[$delta] = array(
'#theme' => $theme,
'#address' => $item,
);
}
break;
case 'addressfield_linear':
$theme = array('addressfield_formatter__linear', 'addressfield_formatter');
if (!empty($item['country'])) {
array_unshift($theme, 'addressfield_formatter__linear__' . $item['country']);
}
foreach ($items as $delta => $item) {
$element[$delta] = array(
'#theme' => $theme,
'#address' => $item,
);
}
break;
case 'addressfield_country':
foreach ($items as $delta => $item) {
if (!empty($item['country'])) {
$country = _addressfield_tokens_country($item['country']);
$element[$delta] = array(
'#type' => 'markup',
'#markup' => $country,
'#prefix' => '<span class="addressfield-country">',
'#suffix' => '</span>',
);
}
}
break;
case 'addressfield_state':
foreach ($items as $delta => $item) {
if (!empty($item['country']) && !empty($item['administrative_area'])) {
$state = _addressfield_tokens_state($item['country'], $item['administrative_area']);
$element[$delta] = array(
'#type' => 'markup',
'#markup' => $state,
'#prefix' => '<span class="addressfield-state">',
'#suffix' => '</span>',
);
}
}
break;
case 'addressfield_components':
$theme = array('addressfield_formatter__components', 'addressfield_formatter');
if (!empty($item['country'])) {
array_unshift($theme, 'addressfield_formatter__components__' . $item['country']);
}
$settings = $display['settings'];
foreach ($items as $delta => $item) {
$element[$delta] = array(
'#theme' => $theme,
'#address' => $item,
'#components' => $settings['components'],
'#separator' => $settings['separator'],
);
}
break;
}
return $element;
}
/**
* Returns the country that has been configured as the default country.
*/
function addressfield_tokens_default_country() {
return variable_get('addressfield_tokens_default_country', 'US');
}
/**
* Returns the names that have been configured for each address field.
*/
function addressfield_tokens_property_names() {
$names = variable_get('addressfield_tokens_property_names', array());
if (empty($names)) {
$props = addressfield_data_property_info();
foreach($props as $name => $prop) {
$names[$name] = $prop['label'];
}
}
return $names;
}
function addressfield_tokens_components() {
$comps = &drupal_static(__FUNCTION__, array());
if (empty($comps)) {
$names = addressfield_tokens_property_names();
foreach (array('first_name', 'last_name', 'name_line', 'organisation_name', 'thoroughfare', 'premise', 'locality', 'dependent_locality', 'administrative_area', 'sub_administrative_area', 'postal_code', 'country') as $key) {
$comps[$key] = $names[$key];
if (in_array($key, array('administrative_area', 'country'))) {
$comps[$key . '_full'] = t('@name (full)', array(
'@name' => $names[$key],
));
}
}
}
return $comps;
}
/**
* Gets the list of countries from the locale settings in Drupal.
*
* @return array An array of countries. The keys are the 2-letter
* abbreviations and the values are the full country names.
*/
function _addressfield_tokens_countries() {
$countries = &drupal_static(__FUNCTION__);
if (empty($countries)) {
include_once('includes/locale.inc');
$countries = country_get_list();
}
return $countries;
}
/**
* Gets the name of the country with the given abbreviation.
*
* @param string $country The 2-letter abbreviation of the country.
* @return string The name of the country, or FALSE.
*/
function _addressfield_tokens_country($country) {
$countries = _addressfield_tokens_countries();
// Country abbreviations will always be two uppercase letters.
$country = drupal_strtoupper($country);
if (!empty($country) && isset($countries[$country])) {
return $countries[$country];
}
return $country;
}
/**
* Gets the abbreviation of the country with the given name
*
* @param string The name of the country
* @return string $country The 2-letter abbreviation of the country, or FALSE.
*/
function _addressfield_tokens_country_abbr($country) {
$countries = array_flip(array_map('strtolower', _addressfield_tokens_countries()));
if (isset($countries[strtolower($country)])) {
return $countries[strtolower($country)];
}
return $country;
}
/**
* Gets the list of states in the given country.
*
* @param string $country The 2-letter abbreviation of the country.
*
* @return array An array of countries. The keys are the 2-letter
* abbreviations and the values are the full country names.
*/
function _addressfield_tokens_states($country) {
$states = &drupal_static(__FUNCTION__);
$country = drupal_strtoupper($country);
if (!isset($states[$country])) {
$cache = cache_get('addressfield_tokens_states');
if ($cache) {
$states = $cache->data;
}
}
if (!isset($states[$country])) {
$format = addressfield_generate(array('country' => $country), array('address'), array('mode' => 'render'));
if (isset($format['locality_block']['administrative_area']['#options'])) {
$states[$country] = $format['locality_block']['administrative_area']['#options'];
}
else {
$states[$country] = array();
}
cache_set('addressfield_tokens_states', $states);
}
return $states[$country];
}
/**
* Gets the name of the state with the given abbreviation.
*
* @param string $country The 2-letter abbreviation of the country.
* @param string $state The 2-letter abbreviation of the state.
* @return string The name of the state, or FALSE.
*/
function _addressfield_tokens_state($country, $state) {
$states = _addressfield_tokens_states($country);
// State abbreviations will usually be two uppercase letters.
$state = drupal_strtoupper($state);
if (!empty($state) && !empty($states[$state])) {
return $states[$state];
}
return $state;
}
/**
* Implements hook_webform_component_info().
*/
function addressfield_tokens_webform_component_info() {
$components = array ();
$components['addressfield'] = array (
'label' => t('Address'),
'description' => t('Address field.'),
'features' => array (
// Add content to CSV downloads. Defaults to TRUE.
'csv' => TRUE,
// Show this component in e-mailed submissions. Defaults to TRUE.
'email' => TRUE,
// Allow this component to be used as an e-mail FROM or TO address.
// Defaults to FALSE.
'email_address' => FALSE,
// Allow this component to be used as an e-mail SUBJECT or FROM name.
// Defaults to FALSE.
'email_name' => FALSE,
// This component may be toggled as required or not. Defaults to TRUE.
'required' => TRUE,
// This component has a title that can be toggled as displayed or not.
'title_display' => TRUE,
// This component has a title that can be displayed inline.
'title_inline' => FALSE,
// If this component can be used as a conditional SOURCE. All components
// may always be displayed conditionally, regardless of this setting.
// Defaults to TRUE.
'conditional' => FALSE,
// If this component allows other components to be grouped within it
// (like a fieldset or tabs). Defaults to FALSE.
'group' => FALSE,
// If this component can be used for SPAM analysis, usually with Mollom.
'spam_analysis' => FALSE,
// If this component saves a file that can be used as an e-mail
// attachment. Defaults to FALSE.
'attachment' => FALSE,
),
'file' => 'addressfield_tokens.components.inc',
);
return $components;
}
function _addressfield_tokens_webform_components($nid) {
$components = &drupal_static(__FUNCTION__, array());
if (!isset($components[$nid])) {
$components[$nid] = db_select('webform_component')
->fields('webform_component')
->condition('type', 'addressfield')
->condition('nid', $nid)
->execute()
->fetchAllAssoc('cid', PDO::FETCH_ASSOC);
}
return $components[$nid];
}
/**
* Implements hook_webform_submission_load().
*/
function addressfield_tokens_webform_submission_load(&$submissions) {
$submissions_reset = reset($submissions);
$nid = $submissions_reset->nid;
$components = _addressfield_tokens_webform_components($nid);
foreach ($submissions as $sid => $submission) {
foreach ($components as $cid => $component) {
if (!empty($submission->data[$cid])) {
$parents = array($cid);
if (!empty($submission->data[$cid]['value'])) {
$parents[] = 'value';
}
$addresses = array();
foreach (drupal_array_get_nested_value($submission->data, $parents) as $delta => $data) {
$data = empty($data) ? array() : unserialize($data);
$addresses[$delta] = $data;
}
drupal_array_set_nested_value($submission->data, $parents, $addresses);
}
}
}
}
/**
* Implements hook_webform_validator_alter().
*/
function addressfield_tokens_webform_validator_alter(&$validators) {
$validators['unique']['component_types'][] = 'addressfield';
$validators['oneoftwo']['component_types'][] = 'addressfield';
$validators['oneofseveral']['component_types'][] = 'addressfield';
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* Implements theme_field();
*
* Themes an address according to the default renderer.
*/
function theme_addressfield_formatter($vars) {
$address = $vars['address'];
$handlers = $vars['handlers'];
$out = addressfield_generate($address, $handlers, array('mode' => 'render'));
return '<div class="addressfield">' . render($out) . '</div>';
}
/**
* Implements theme_field();
*
* Themes an address field into "city state, country"
*/
function theme_addressfield_formatter__citystate($vars) {
$loc = $vars['address'];
// Determine which location components to render
$out = array();
if (!empty($loc['locality'])) {
$out[] = $loc['locality'];
}
if (!empty($loc['administrative_area'])) {
$out[] = $loc['administrative_area'];
}
if ($loc['country'] != addressfield_tokens_default_country() && $country_name = _addressfield_tokens_country($loc['country'])) {
$out[] = $country_name;
}
// If there's no location, render an alternate
if (empty($out)) {
$out[] = 'Undefined';
}
// Render the location components
$output = '<span class="addressfield-citystate">' . implode(', ', $out) . '</span>';
return $output;
}
/**
* Implements theme_field();
*
* Themes an address field into "name, street1, street2, city state zip country"
*/
function theme_addressfield_formatter__linear($vars) {
$loc = $vars['address'];
// Determine which location components to render
$out = array();
if (!empty($loc['name_line']) && $vars['name_line']) {
$out[] = $loc['name_line'];
}
if (!empty($loc['organisation_name']) && $vars['organisation_name']) {
$out[] = $loc['organisation_name'];
}
if (!empty($loc['thoroughfare'])) {
$out[] = $loc['thoroughfare'];
}
if (!empty($loc['premise']) && $vars['premise']) {
$out[] = $loc['premise'];
}
if (!empty($loc['locality'])) {
$out[] = $loc['locality'];
}
if (!empty($loc['administrative_area'])) {
$out[] = $loc['administrative_area'];
}
if (!empty($loc['postal_code'])) {
$out[] = $loc['postal_code'];
}
if ($loc['country'] != addressfield_tokens_default_country() && $country_name = _addressfield_tokens_country($loc['country'])) {
$out[] = $country_name;
}
// Render the location components
$output = implode(', ', $out);
return $output;
}
function theme_addressfield_formatter__components($vars) {
$loc = $vars['address'];
$components = $vars['components'];
$separator = $vars['separator'];
$out = array();
foreach ($components as $key) {
if (!empty($loc[$key])) {
$out[$key] = $loc[$key];
}
elseif ($key == 'country_full' && !empty($loc['country'])) {
$out[$key] = _addressfield_tokens_country($loc['country']);
}
elseif ($key == 'administrative_area_full' && !empty($loc['country']) && !empty($loc['administrative_area'])) {
$out[$key] = _addressfield_tokens_state($loc['country'], $loc['administrative_area']);
}
}
return filter_xss(implode($separator, $out));
}

View File

@@ -0,0 +1,264 @@
<?php
// $Id$
/*
* Copyright © 2012 New Signature
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
* You can contact New Signature by electronic mail at labs@newsignature.com -or- by U.S. Postal Service at 1100 H St. NW, Suite 940, Washington, DC 20005.
*/
/**
* @file Provides token replacements for address fields.
*/
/**
* Implements hook_token_info_alter().
*
* Attaches tokens to all addressfield properties. The default names of each
* addressfield component can be altered by administrators according to the site's locale.
*
* @param array $info The existing token info.
*/
function addressfield_tokens_token_info_alter(&$info) {
// Define the address field token types
$info['types']['addressfield'] = array(
'name' => t('Address field'),
'description' => t('An address associated with an entity.'),
'needs-data' => 'addressfield',
);
// Add tokens for each component of the address
$info['tokens'] += array( 'addressfield' => array() );
$props = addressfield_data_property_info();
$names = addressfield_tokens_property_names();
$params = array(
'@default_country' => addressfield_tokens_default_country(),
);
foreach ($props as $field => $data) {
$fieldtoken = str_replace('_', '-', $field);
$name = '';
$descr = '';
if (!empty($names[$field])) {
$name = $names[$field];
$descr = $data['label'];
}
else {
$name = $data['label'];
$descr = $name;
$matches = array();
if (preg_match('/^(.*)\s+\(i\.e\.\s+(.*)\)$/', $name, $matches)) {
$name = $matches[1];
$descr = $matches[2];
}
}
$info['tokens']['addressfield'][$fieldtoken] = array(
'name' => $name,
'description' => $descr,
'type' => 'text',
);
$params['@' . $field] = $name;
}
$info['tokens']['addressfield']['administrative-area']['name'] .= ' (abbreviation)';
$info['tokens']['addressfield']['country']['name'] .= ' (abbreviation)';
// Add tokens for the formatted address and text-only version.
$info['tokens']['addressfield'] += array(
'full' => array(
'name' => t('Formatted address'),
'description' => t('The full formatted address.'),
'type' => 'text',
),
'text' => array(
'name' => t('Text-only address'),
'description' => t('The full address with line breaks but no formatting.'),
'type' => 'text',
),
'city-state' => array(
'name' => t('City, State'),
'description' => t('@locality and @administrative_area separated by commas (and @country if outside @default_country)', $params),
'type' => 'text',
),
'state-name' => array(
'name' => t('@administrative_area (full name)', $params),
'description' => t('The full name of the @administrative_area', $params),
'type' => 'text',
),
'country-name' => array(
'name' => t('@country (full name)', $params),
'description' => t('The full name of the @country', $params),
'type' => 'text',
),
);
// Add user tokens that are useful for MailChimp.
if (module_exists('mailchimp')) {
$info['tokens']['addressfield'] += array(
'mc-address' => array(
'name' => t('MailChimp Address'),
'description' => t('A full address formatted for integration with MailChimp.'),
'type' => 'text',
),
);
}
// Attach tokens to all address fields
$valid_types = entity_token_types();
$entity_info = entity_get_info();
foreach ($valid_types as $token_type => $type) {
foreach (entity_get_all_property_info($type) as $name => $property) {
$name = str_replace('_', '-', $name);
if (!isset($info['tokens'][$token_type][$name]) && isset($property['type']) && $property['type'] == 'addressfield') {
$info['tokens'][$token_type][$name] = array(
'name' => $property['label'],
'type' => 'addressfield',
'description' => isset($property['description']) ? $property['description'] : t('Address field'),
);
}
}
}
}
/**
* Implements hook_tokens().
*
* @param string $type The type of tokens to replace. We are looking for 'addressfield', but can also chain
* addressfields onto entities that have addressfields as properties.
* @param array $tokens The tokens to replace.
* @param array $data The data to use as replacements. We are looking for an 'addressfield' property.
* @param array $options Additional options for the tokenizer.
*
* @return array The replaced values.
*/
function addressfield_tokens_tokens($type, $tokens, array $data = array(), array $options = array()) {
$url_options = array();
if (isset($options['language'])) {
$url_options['language'] = $options['language'];
$language_code = $options['language']->language;
}
else {
$language_code = LANGUAGE_NONE;
}
$sanitize = !empty($options['sanitize']);
$replacements = array();
// Process address field tokens
if ($type == 'addressfield' && !empty($data['addressfield'])) {
foreach ($tokens as $name => $original) {
$name = str_replace('-', '_', $name);
$address = $data['addressfield'];
// If the address field exists, use it.
if (isset($address[$name])) {
$replacements[$original] = $address[$name];
}
else {
// Otherwise, it's a special token
switch ($name) {
case 'full':
$render = addressfield_generate($address, array('address'), array(
'mode' => 'render',
));
$replacements[$original] = drupal_render($render);
break;
case 'text':
$out = array();
if (!empty($address['thoroughfare'])) {
$out[0] = $address['thoroughfare'];
}
$out[1] = array();
if (!empty($address['locality'])) {
$out[1][] = $address['locality'];
}
if (!empty($address['administrative_area'])) {
$out[1][] = $address['administrative_area'];
}
$out[1] = implode(', ', $out[1]);
if (!empty($address['postal_code'])) {
$out[1] .= ' ' . $address['postal_code'];
}
if (!empty($address['country']) && $address['country'] != addressfield_tokens_default_country()) {
$out[2] = _addressfield_tokens_country($address['country']);
}
$replacements[$original] = implode("\n", $out);
break;
case 'city_state':
$out = array();
if (!empty($address['locality'])) {
$out[] = $address['locality'];
}
if (!empty($address['administrative_area'])) {
$out[] = $address['administrative_area'];
}
if (!empty($address['country']) && $address['country'] != addressfield_tokens_default_country()) {
$out[] = _addressfield_tokens_country($address['country']);
}
$replacements[$original] = implode(", ", $out);
break;
case 'state_name':
if (!empty($address['administrative_area']) && !empty($address['country'])) {
$replacements[$original] = _addressfield_tokens_state($address['country'], $address['administrative_area']);
}
break;
case 'country_name':
if (!empty($address['country'])) {
$replacements[$original] = _addressfield_tokens_country($address['country']);
}
break;
case 'mc_address':
$address_components = array('thoroughfare', 'premise', 'locality', 'administrative_area', 'postal_code', 'country');
$mc_address = array();
foreach ($address_components as $component) {
if (!empty($address[$component])) {
$mc_address[] = check_plain($address[$component]);
}
}
// MailChimp requires the address to be a string of double-space
// delimited address fields. (http://kb.mailchimp.com/article/how-do-i-set-up-the-address-field-type-for-import)
$replacements[$original] = !empty($mc_address) ? implode(' ', $mc_address) : '';
break;
}
}
}
if (!isset($replacements[$original])) {
$replacements[$original] = '';
}
}
else {
$token_types = entity_token_types();
$info = token_info();
if (isset($info['tokens'][$type])) {
// Otherwise, chain address fields attached to other entities
foreach ($info['tokens'][$type] as $name => $token_info) {
if (isset($token_info['type']) && $token_info['type'] == 'addressfield') {
if ($chained_tokens = token_find_with_prefix($tokens, $name)) {
$wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper;
$property_name = str_replace('-', '_', $name);
try {
$address = $wrapper->$property_name->value();
$replacements += token_generate('addressfield', $chained_tokens, array('addressfield' => $address), $options);
}
catch (EntityMetadataWrapperException $e) {
// The property doesn't exist, so skip it.
$replacements[$original] = '';
}
}
}
}
}
}
return $replacements;
}

View File

@@ -0,0 +1,232 @@
<?php
/**
* @file
*
* Class for handling background processes.
*/
/**
* BackgroundProcess class.
*/
class BackgroundProcess {
public $handle;
public $connection;
public $service_host;
public $service_group;
public $uid;
public static function load($process) {
$new = new BackgroundProcess($process->handle);
@$new->callback = $process->callback;
@$new->args = $process->args;
@$new->uid = $process->uid;
@$new->token = $process->token;
@$new->service_host = $process->service_host;
@$new->service_group = $process->service_group;
@$new->exec_status = $process->exec_status;
@$new->start_stamp = $process->start_stamp;
@$new->status = $process->exec_status;
@$new->start = $process->start_stamp;
return $new;
}
/**
* Constructor.
*
* @param type $handle
* Handle to use. Optional; leave out for auto-handle.
*/
public function __construct($handle = NULL) {
$this->handle = $handle ? $handle : background_process_generate_handle('auto');
$this->token = background_process_generate_handle('token');
$this->service_group = variable_get('background_process_default_service_group', 'default');
}
public function lock($status = BACKGROUND_PROCESS_STATUS_LOCKED) {
// Preliminary select to avoid unnecessary write-attempt
if (background_process_get_process($this->handle)) {
// watchdog('bg_process', 'Will not attempt to lock handle %handle, already exists', array('%handle' => $this->handle), WATCHDOG_NOTICE);
return FALSE;
}
// "Lock" handle
$this->start_stamp = $this->start = microtime(TRUE);
if (!background_process_lock_process($this->handle, $status)) {
// If this happens, we might have a race condition or an md5 clash
watchdog('bg_process', 'Could not lock handle %handle', array('%handle' => $this->handle), WATCHDOG_ERROR);
return FALSE;
}
$this->exec_status = $this->status = BACKGROUND_PROCESS_STATUS_LOCKED;
$this->sendMessage('locked');
return TRUE;
}
/**
* Start background process
*
* Calls the service handler through http passing function arguments as serialized data
* Be aware that the callback will run in a new request
*
* @global string $base_url
* Base URL for this Drupal request
*
* @param $callback
* Function to call.
* @param $args
* Array containg arguments to pass on to the callback.
* @return mixed
* TRUE on success, NULL on failure, FALSE on handle locked.
*/
public function start($callback, $args = array()) {
if (!$this->lock()) {
return FALSE;
}
return $this->execute($callback, $args);
}
public function queue($callback, $args = array()) {
if (!$this->lock(BACKGROUND_PROCESS_STATUS_QUEUED)) {
return FALSE;
}
if (!background_process_set_process($this->handle, $callback, $this->uid, $args, $this->token)) {
// Could not update process
return NULL;
}
module_invoke_all('background_process_pre_execute', $this->handle, $callback, $args, $this->token);
// Initialize progress stats
$old_db = db_set_active('background_process');
progress_remove_progress($this->handle);
db_set_active($old_db);
$queues = variable_get('background_process_queues', array());
$queue_name = isset($queues[$callback]) ? 'bgp:' . $queues[$callback] : 'background_process';
$queue = DrupalQueue::get($queue_name);
$queue->createItem(array(rawurlencode($this->handle), rawurlencode($this->token)));
_background_process_ensure_cleanup($this->handle, TRUE);
}
public function determineServiceHost() {
// Validate explicitly selected service host
$service_hosts = background_process_get_service_hosts();
if ($this->service_host && empty($service_hosts[$this->service_host])) {
$this->service_host = variable_get('background_process_default_service_host', 'default');
if (empty($service_hosts[$this->service_host])) {
$this->service_host = NULL;
}
}
// Find service group if a service host is not explicitly specified.
if (!$this->service_host) {
if (!$this->service_group) {
$this->service_group = variable_get('background_process_default_service_group', 'default');
}
if ($this->service_group) {
$service_groups = variable_get('background_process_service_groups', array());
if (isset($service_groups[$this->service_group])) {
$service_group = $service_groups[$this->service_group];
// Default method if none is provided
$service_group += array(
'method' => 'background_process_service_group_round_robin'
);
if (is_callable($service_group['method'])) {
$this->service_host = call_user_func($service_group['method'], $service_group);
// Revalidate service host
if ($this->service_host && empty($service_hosts[$this->service_host])) {
$this->service_host = NULL;
}
}
}
}
}
// Fallback service host
if (!$this->service_host || empty($service_hosts[$this->service_host])) {
$this->service_host = variable_get('background_process_default_service_host', 'default');
if (empty($service_hosts[$this->service_host])) {
$this->service_host = 'default';
}
}
return $this->service_host;
}
public function execute($callback, $args = array()) {
if (!background_process_set_process($this->handle, $callback, $this->uid, $args, $this->token)) {
// Could not update process
return NULL;
}
module_invoke_all('background_process_pre_execute', $this->handle, $callback, $args, $this->token);
// Initialize progress stats
$old_db = db_set_active('background_process');
progress_remove_progress($this->handle);
db_set_active($old_db);
$this->connection = FALSE;
$this->determineServiceHost();
return $this->dispatch();
}
function dispatch() {
$this->sendMessage('dispatch');
$handle = rawurlencode($this->handle);
$token = rawurlencode($this->token);
list($url, $headers) = background_process_build_request('bgp-start/' . $handle . '/' . $token, $this->service_host);
background_process_set_service_host($this->handle, $this->service_host);
$options = array('method' => 'POST', 'headers' => $headers);
$result = background_process_http_request($url, $options);
if (empty($result->error)) {
$this->connection = $result->fp;
_background_process_ensure_cleanup($this->handle, TRUE);
return TRUE;
}
else {
background_process_remove_process($this->handle);
watchdog('bg_process', 'Could not call service %handle for callback %callback: %error', array('%handle' => $this->handle, '%callback' => $callback, '%error' => $result->error), WATCHDOG_ERROR);
// Throw exception here instead?
return NULL;
}
return FALSE;
}
function sendMessage($action) {
if (module_exists('nodejs')) {
if (!isset($this->progress_object)) {
if ($progress = progress_get_progress($this->handle)) {
$this->progress_object = $progress;
$this->progress = $progress->progress;
$this->progress_message = $progress->message;
}
else {
$this->progress = 0;
$this->progress_message = '';
}
}
$object = clone $this;
$message = (object) array(
'channel' => 'background_process',
'data' => (object) array(
'action' => $action,
'background_process' => $object,
'timestamp' => microtime(TRUE),
),
'callback' => 'nodejsBackgroundProcess',
);
drupal_alter('background_process_message', $message);
nodejs_send_content_channel_message($message);
}
}
}

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,14 @@
name = Background Batch
description = Adds background processing to Drupals batch API
core = 7.x
php = 5.0
dependencies[] = background_process
dependencies[] = progress
; Information added by drupal.org packaging script on 2013-01-06
version = "7.x-1.14"
core = "7.x"
project = "background_process"
datestamp = "1357473962"

View File

@@ -0,0 +1,15 @@
<?php
/**
* @file
* This is the installation file for the Background Batch submodule
*/
/**
* Implements hook_uninstall().
*/
function background_batch_uninstall() {
// Removing used variables.
variable_del('background_batch_delay');
variable_del('background_batch_process_lifespan');
variable_del('background_batch_show_eta');
}

View File

@@ -0,0 +1,377 @@
<?php
/**
* @file
* This module adds background processing to Drupals batch API
*
* @todo Add option to stop a running batch job.
*/
/**
* Default value for delay (in microseconds).
*/
define('BACKGROUND_BATCH_DELAY', 1000000);
/**
* Default value for process lifespan (in miliseconds).
*/
define('BACKGROUND_BATCH_PROCESS_LIFESPAN', 10000);
/**
* Default value wether ETA information should be shown.
*/
define('BACKGROUND_BATCH_PROCESS_ETA', TRUE);
/**
* Implements hook_menu().
*/
function background_batch_menu() {
$items = array();
$items['admin/config/system/batch/settings'] = array(
'type' => MENU_DEFAULT_LOCAL_TASK,
'title' => 'Settings',
'weight' => 1,
);
$items['admin/config/system/batch'] = array(
'title' => 'Batch',
'description' => 'Administer batch jobs',
'page callback' => 'drupal_get_form',
'page arguments' => array('background_batch_settings_form'),
'access arguments' => array('administer site'),
'file' => 'background_batch.pages.inc',
);
$items['admin/config/system/batch/overview'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Overview',
'description' => 'Batch job overview',
'page callback' => 'background_batch_overview_page',
'access arguments' => array('administer site'),
'file' => 'background_batch.pages.inc',
'weight' => 3,
);
return $items;
}
/**
* Implements hook_menu_alter().
*/
function background_batch_menu_alter(&$items) {
$items['batch'] = array(
'page callback' => 'background_batch_page',
'access callback' => TRUE,
'theme callback' => '_system_batch_theme',
'type' => MENU_CALLBACK,
'file' => 'background_batch.pages.inc',
'module' => 'background_batch',
);
}
/**
* Implements hook_batch_alter().
* Steal the operation and hook into context data.
*/
function background_batch_batch_alter(&$batch) {
if ($batch['progressive'] && $batch['url'] == 'batch') {
foreach ($batch['sets'] as &$set) {
if (!empty($set['operations'])) {
foreach ($set['operations'] as &$operation) {
$operation = array('_background_batch_operation', array($operation));
}
}
}
$batch['timestamp'] = microtime(TRUE);
}
// In order to make this batch session independend we save the owner UID.
global $user;
$batch['uid'] = $user->uid;
}
/**
* Implements hook_library().
*/
function background_batch_library() {
$libraries = array();
$libraries['background-process.batch'] = array(
'title' => 'Background batch API',
'version' => '1.0.0',
'js' => array(
drupal_get_path('module', 'background_batch') . '/js/batch.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
),
'dependencies' => array(
array('background_batch', 'background-process.progress'),
),
);
$libraries['background-process.progress'] = array(
'title' => 'Background batch progress',
'version' => VERSION,
'js' => array(
drupal_get_path('module', 'background_batch') . '/js/progress.js' => array('group' => JS_DEFAULT, 'cache' => FALSE),
),
);
return $libraries;
}
/**
* Run a batch operation with "listening" context.
* @param $operation
* Batch operation definition.
* @param &$context
* Context for the batch operation.
*/
function _background_batch_operation($operation, &$context) {
// Steal context and trap finished variable
$fine_progress = !empty($context['sandbox']['background_batch_fine_progress']);
if ($fine_progress) {
$batch_context = new BackgroundBatchContext($context);
}
else {
$batch_context = $context;
}
// Call the original operation
$operation[1][] = &$batch_context;
call_user_func_array($operation[0], $operation[1]);
if ($fine_progress) {
// Transfer back context result to batch api
$batch_context = (array)$batch_context;
foreach (array_keys($batch_context) as $key) {
$context[$key] = $batch_context[$key];
}
}
else {
$batch_context = new BackgroundBatchContext($context);
$batch_context['finished'] = $context['finished'];
}
}
/**
* Process a batch step
* @param type $id
* @return type
*/
function _background_batch_process($id = NULL) {
if (!$id) {
return;
}
// Retrieve the current state of batch from db.
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(':bid' => $id))->fetchColumn();
if (!$data) {
return;
}
require_once('includes/batch.inc');
$batch =& batch_get();
$batch = unserialize($data);
// Check if the current user owns (has access to) this batch.
global $user;
if ($batch['uid'] != $user->uid) {
return drupal_access_denied();
}
// Register database update for the end of processing.
drupal_register_shutdown_function('_batch_shutdown');
timer_start('background_batch_processing');
$percentage = 0;
$mem_max_used = 0;
$mem_last_used = memory_get_usage();
$mem_limit = ini_get('memory_limit');
preg_match('/(\d+)(\w)/', $mem_limit, $matches);
switch ($matches[2]) {
case 'M':
default:
$mem_limit = $matches[1] * 1024 * 1024;
break;
}
while ($percentage < 100) {
list ($percentage, $message) = _batch_process();
$mem_used = memory_get_usage();
// If we memory usage of last run will exceed the memory limit in next run
// then bail out
if ($mem_limit < $mem_used + $mem_last_used) {
break;
}
$mem_last_used = $mem_used - $mem_last_used;
// If we maximum memory usage of previous runs will exceed the memory limit in next run
// then bail out
$mem_max_used = $mem_max_used < $mem_last_used ? $mem_last_used : $mem_max_used;
if ($mem_limit < $mem_used + $mem_max_used) {
break;
}
// Restart background process after X miliseconds
if (timer_read('background_batch_processing') > variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN)) {
break;
}
}
if ($percentage < 100) {
background_process_keepalive($id);
}
}
/**
* Processes the batch.
*
* Unless the batch has been marked with 'progressive' = FALSE, the function
* issues a drupal_goto and thus ends page execution.
*
* This function is not needed in form submit handlers; Form API takes care
* of batches that were set during form submission.
*
* @param $redirect
* (optional) Path to redirect to when the batch has finished processing.
* @param $url
* (optional - should only be used for separate scripts like update.php)
* URL of the batch processing page.
*/
function background_batch_process_batch($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') {
$batch =& batch_get();
drupal_theme_initialize();
if (isset($batch)) {
// Add process information
$process_info = array(
'current_set' => 0,
'progressive' => TRUE,
'url' => $url,
'url_options' => array(),
'source_url' => $_GET['q'],
'redirect' => $redirect,
'theme' => $GLOBALS['theme_key'],
'redirect_callback' => $redirect_callback,
);
$batch += $process_info;
// The batch is now completely built. Allow other modules to make changes
// to the batch so that it is easier to reuse batch processes in other
// environments.
drupal_alter('batch', $batch);
// Assign an arbitrary id: don't rely on a serial column in the 'batch'
// table, since non-progressive batches skip database storage completely.
$batch['id'] = db_next_id();
// Move operations to a job queue. Non-progressive batches will use a
// memory-based queue.
foreach ($batch['sets'] as $key => $batch_set) {
_batch_populate_queue($batch, $key);
}
// Initiate processing.
// Now that we have a batch id, we can generate the redirection link in
// the generic error message.
$t = get_t();
$batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished')))));
// Clear the way for the drupal_goto() redirection to the batch processing
// page, by saving and unsetting the 'destination', if there is any.
if (isset($_GET['destination'])) {
$batch['destination'] = $_GET['destination'];
unset($_GET['destination']);
}
// Store the batch.
db_insert('batch')
->fields(array(
'bid' => $batch['id'],
'timestamp' => REQUEST_TIME,
'token' => drupal_get_token($batch['id']),
'batch' => serialize($batch),
))
->execute();
// Set the batch number in the session to guarantee that it will stay alive.
$_SESSION['batches'][$batch['id']] = TRUE;
// Redirect for processing.
$function = $batch['redirect_callback'];
if (function_exists($function)) {
// $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id'])));
}
}
background_process_start('_background_batch_process_callback', $batch);
}
function _background_batch_process_callback($batch) {
$rbatch =& batch_get();
$rbatch = $batch;
require_once('background_batch.pages.inc');
_background_batch_page_start();
}
/**
* Class batch context.
* Automatically updates progress when 'finished' index is changed.
*/
class BackgroundBatchContext extends ArrayObject {
private $batch = NULL;
private $interval = NULL;
private $progress = NULL;
public function __construct() {
$this->interval = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY) / 1000000;
$args = func_get_args();
return call_user_func_array(array('parent', '__construct'), $args);
}
/**
* Set progress update interval in seconds (float).
*/
public function setInterval($interval) {
$this->interval = $interval;
}
/**
* Override offsetSet().
* Update progress if needed.
*/
public function offsetSet($name, $value) {
if ($name == 'finished') {
if (!isset($this->batch)) {
$this->batch =& batch_get();
$this->progress = progress_get_progress('_background_batch:' . $this->batch['id']);
}
if ($this->batch) {
$total = $this->batch['sets'][$this->batch['current_set']]['total'];
$count = $this->batch['sets'][$this->batch['current_set']]['count'];
$elapsed = $this->batch['sets'][$this->batch['current_set']]['elapsed'];
$progress_message = $this->batch['sets'][$this->batch['current_set']]['progress_message'];
$current = $total - $count;
$step = 1 / $total;
$base = $current * $step;
$progress = $base + $value * $step;
progress_estimate_completion($this->progress);
$elapsed = floor($this->progress->current - $this->progress->start);
$values = array(
'@remaining' => $count,
'@total' => $total,
'@current' => $current,
'@percentage' => $progress * 100,
'@elapsed' => format_interval($elapsed),
// If possible, estimate remaining processing time.
'@estimate' => format_interval(floor($this->progress->estimate) - floor($this->progress->current)),
);
$message = strtr($progress_message, $values);
$message .= $message && $this['message'] ? '<br/>' : '';
$message .= $this['message'];
progress_set_intervalled_progress('_background_batch:' . $this->batch['id'], $message ? $message : $this->progress->message, $progress, $this->interval);
}
}
return parent::offsetSet($name, $value);
}
}

View File

@@ -0,0 +1,328 @@
<?php
/**
* @file
*
* Pages for background batch.
*
* @todo Implement proper error page instead of just 404.
*/
/**
* System settings page.
*/
function background_batch_settings_form() {
$form = array();
$form['background_batch_delay'] = array(
'#type' => 'textfield',
'#default_value' => variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY),
'#title' => 'Delay',
'#description' => t('Time in microseconds for progress refresh'),
);
$form['background_batch_process_lifespan'] = array(
'#type' => 'textfield',
'#default_value' => variable_get('background_batch_process_lifespan', BACKGROUND_BATCH_PROCESS_LIFESPAN),
'#title' => 'Process lifespan',
'#description' => t('Time in milliseconds for progress lifespan'),
);
$form['background_batch_show_eta'] = array(
'#type' => 'checkbox',
'#default_value' => variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA),
'#title' => 'Show ETA of batch process',
'#description' => t('Whether ETA (estimated time of arrival) information should be shown'),
);
return system_settings_form($form);
}
/**
* Overview of current and recent batch jobs.
*/
function background_batch_overview_page() {
$data = array();
$bids = db_select('batch', 'b')
->fields('b', array('bid'))
->orderBy('b.bid', 'ASC')
->execute()
->fetchAllKeyed(0, 0);
foreach ($bids as $bid) {
$progress = progress_get_progress('_background_batch:' . $bid);
$eta = progress_estimate_completion($progress);
$data[] = array(
$progress->end ? $bid : l($bid, 'batch', array('query' => array('op' => 'start', 'id' => $bid))),
sprintf("%.2f%%", $progress->progress * 100),
$progress->message,
$progress->start ? format_date((int)$progress->start, 'small') : t('N/A'),
$progress->end ? format_date((int)$progress->end, 'small') : ($eta ? format_date((int)$eta, 'small') : t('N/A')),
);
}
$header = array('Batch ID', 'Progress', 'Message', 'Started', 'Finished/ETA');
return theme('table', array(
'header' => $header,
'rows' => $data
));
}
/**
* State-based dispatcher for the batch processing page.
*/
function background_batch_page() {
$id = isset($_REQUEST['id']) ? $_REQUEST['id'] : FALSE;
if (!$id) {
return drupal_not_found();
}
// Retrieve the current state of batch from db.
$data = db_query("SELECT batch FROM {batch} WHERE bid = :bid", array(':bid' => $id))->fetchColumn();
if (!$data) {
return drupal_not_found();
}
$batch =& batch_get();
$batch = unserialize($data);
// Check if the current user owns (has access to) this batch.
global $user;
if ($batch['uid'] != $user->uid) {
return drupal_access_denied();
}
$op = isset($_REQUEST['op']) ? $_REQUEST['op'] : '';
switch ($op) {
case 'start':
return _background_batch_page_start();
case 'do':
return _background_batch_page_do_js();
case 'do_nojs':
return _background_batch_page_do_nojs();
case 'finished':
progress_remove_progress('_background_batch:' . $id);
return _batch_finished();
default:
drupal_goto('admin/config/system/batch/overview');
}
}
/**
* Start a batch job in the background
*/
function _background_batch_initiate($process = NULL) {
require_once('includes/batch.inc');
$batch =& batch_get();
$id = $batch['id'];
$handle = 'background_batch:' . $id;
if (!$process) {
$process = background_process_get_process($handle);
}
if ($process) {
// If batch is already in progress, goto to the status page instead of starting it.
if ($process->exec_status == BACKGROUND_PROCESS_STATUS_RUNNING) {
return $process;
}
// If process is locked and hasn't started for X seconds, then relaunch
if (
$process->exec_status == BACKGROUND_PROCESS_STATUS_LOCKED &&
$process->start_stamp + variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD) < time()
) {
$process = BackgroundProcess::load($process);
$process->dispatch();
}
return $process;
}
else {
// Hasn't run yet or has stopped. (re)start batch job.
$process = new BackgroundProcess($handle);
$process->service_host = 'background_batch';
if ($process->lock()) {
$message = $batch['sets'][0]['init_message'];
progress_initialize_progress('_' . $handle, $message);
if (function_exists('progress_set_progress_start')) {
progress_set_progress_start('_' . $handle, $batch['timestamp']);
}
else {
db_query("UPDATE {progress} SET start = :start WHERE name = :name", array(':start' => $batch['timestamp'], ':name' => '_' . $handle));
}
$result = $process->execute('_background_batch_process', array($id));
return $process;
}
}
}
function _background_batch_page_start() {
_background_batch_initiate();
if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) {
return _background_batch_page_progress_js();
}
else {
return _background_batch_page_do_nojs();
}
}
/**
* Batch processing page with JavaScript support.
*/
function _background_batch_page_progress_js() {
require_once('includes/batch.inc');
$batch = batch_get();
$current_set = _batch_current_set();
drupal_set_title($current_set['title'], PASS_THROUGH);
// Merge required query parameters for batch processing into those provided by
// batch_set() or hook_batch_alter().
$batch['url_options']['query']['id'] = $batch['id'];
$js_setting['batch'] = array();
$js_setting['batch']['errorMessage'] = $current_set['error_message'] . '<br />' . $batch['error_message'];
// Check wether ETA information should be shown.
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
$js_setting['batch']['initMessage'] = 'ETA: ' . t('N/A') . '<br/>' . $current_set['init_message'];
}
else {
$js_setting['batch']['initMessage'] = $current_set['init_message'];
}
$js_setting['batch']['uri'] = url($batch['url'], $batch['url_options']);
$js_setting['batch']['delay'] = variable_get('background_batch_delay', BACKGROUND_BATCH_DELAY);
drupal_add_js($js_setting, 'setting');
drupal_add_library('background_batch', 'background-process.batch');
return '<div id="progress"></div>';
}
/**
* Do one pass of execution and inform back the browser about progression
* (used for JavaScript-mode only).
*/
function _background_batch_page_do_js() {
// HTTP POST required.
if ($_SERVER['REQUEST_METHOD'] != 'POST') {
drupal_set_message(t('HTTP POST is required.'), 'error');
drupal_set_title(t('Error'));
return '';
}
$batch = &batch_get();
$id = $batch['id'];
drupal_save_session(FALSE);
$percentage = t('N/A');
$message = '';
if ($progress = progress_get_progress('_background_batch:' . $id)) {
$percentage = $progress->progress * 100;
$message = $progress->message;
progress_estimate_completion($progress);
// Check wether ETA information should be shown.
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
$message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
}
else {
$js_setting['batch']['initMessage'] = $message;
}
}
if ($batch['sets'][$batch['current_set']]['count'] == 0) {
// The background process has self-destructed, and the batch job is done.
$percentage = 100;
$message = '';
}
elseif ($process = background_process_get_process('background_batch:' . $id)) {
_background_batch_initiate($process);
}
else {
// Not running ... and stale?
_background_batch_initiate();
}
drupal_json_output(array('status' => TRUE, 'percentage' => sprintf("%.02f", $percentage), 'message' => $message));
}
/**
* Output a batch processing page without JavaScript support.
*
* @see _batch_process()
*/
function _background_batch_page_do_nojs() {
$batch = &batch_get();
$id = $batch['id'];
_background_batch_initiate();
$current_set = _batch_current_set();
drupal_set_title($current_set['title'], PASS_THROUGH);
$new_op = 'do_nojs';
// This is one of the later requests; do some processing first.
// Error handling: if PHP dies due to a fatal error (e.g. a nonexistent
// function), it will output whatever is in the output buffer, followed by
// the error message.
ob_start();
$fallback = $current_set['error_message'] . '<br />' . $batch['error_message'];
$fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => FALSE));
// We strip the end of the page using a marker in the template, so any
// additional HTML output by PHP shows up inside the page rather than below
// it. While this causes invalid HTML, the same would be true if we didn't,
// as content is not allowed to appear after </html> anyway.
list($fallback) = explode('<!--partial-->', $fallback);
print $fallback;
$percentage = t('N/A');
$message = '';
// Get progress
if ($progress = progress_get_progress('_background_batch:' . $id)) {
$percentage = $progress->progress * 100;
$message = $progress->message;
progress_estimate_completion($progress);
// Check wether ETA information should be shown.
if (variable_get('background_batch_show_eta', BACKGROUND_BATCH_PROCESS_ETA)) {
$message = "ETA: " . ($progress->estimate ? format_date((int)$progress->estimate, 'large') : t('N/A')) . "<br/>$message";
}
}
if ($batch['sets'][$batch['current_set']]['count'] == 0) {
// The background process has self-destructed, and the batch job is done.
$percentage = 100;
$message = '';
}
elseif ($process = background_process_get_process('background_batch:' . $id)) {
_background_batch_initiate($process);
}
else {
// Not running ... and stale?
_background_batch_initiate();
}
if ($percentage == 100) {
$new_op = 'finished';
}
// PHP did not die; remove the fallback output.
ob_end_clean();
// Merge required query parameters for batch processing into those provided by
// batch_set() or hook_batch_alter().
$batch['url_options']['query']['id'] = $batch['id'];
$batch['url_options']['query']['op'] = $new_op;
$url = url($batch['url'], $batch['url_options']);
$element = array(
'#tag' => 'meta',
'#attributes' => array(
'http-equiv' => 'Refresh',
'content' => '0; URL=' . $url,
),
);
drupal_add_html_head($element, 'batch_progress_meta_refresh');
return theme('progress_bar', array('percent' => sprintf("%.02f", $percentage), 'message' => $message));
}

View File

@@ -0,0 +1,32 @@
(function ($) {
/**
* Attaches the batch behavior to progress bars.
*/
Drupal.behaviors.batch = {
attach: function (context, settings) {
$('#progress', context).once('batch', function () {
var holder = $(this);
// Success: redirect to the summary.
var updateCallback = function (progress, status, pb) {
if (progress == 100) {
pb.stopMonitoring();
window.location = settings.batch.uri + '&op=finished';
}
};
var errorCallback = function (pb) {
holder.prepend($('<p class="error"></p>').html(settings.batch.errorMessage));
$('#wait').hide();
};
var progress = new Drupal.progressBar('updateprogress', updateCallback, 'POST', errorCallback);
progress.setProgress(0, settings.batch.initMessage);
holder.append(progress.element);
progress.startMonitoring(settings.batch.uri + '&op=do', Drupal.settings.batch.delay / 1000);
});
}
};
})(jQuery);

View File

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

View File

@@ -0,0 +1,152 @@
<?php
/**
* @file
*/
/**
* FAPI definition for settings page.
*/
function background_process_settings_form() {
$form = array();
$form['background_process_service_timeout'] = array(
'#type' => 'textfield',
'#title' => t('Service timeout'),
'#description' => t('Timeout for service call in seconds (0 = disabled)'),
'#default_value' => variable_get('background_process_service_timeout', BACKGROUND_PROCESS_SERVICE_TIMEOUT),
);
$form['background_process_connection_timeout'] = array(
'#type' => 'textfield',
'#title' => t('Connection timeout'),
'#description' => t('Timeout for connection in seconds'),
'#default_value' => variable_get('background_process_connection_timeout', BACKGROUND_PROCESS_CONNECTION_TIMEOUT),
);
$form['background_process_stream_timeout'] = array(
'#type' => 'textfield',
'#title' => t('Stream timeout'),
'#description' => t('Timeout for stream in seconds'),
'#default_value' => variable_get('background_process_stream_timeout', BACKGROUND_PROCESS_STREAM_TIMEOUT),
);
$form['background_process_redispatch_threshold'] = array(
'#type' => 'textfield',
'#title' => t('Redispatch threshold (for locked processes)'),
'#description' => t('Seconds to wait before redispatching processes that never started.'),
'#default_value' => variable_get('background_process_redispatch_threshold', BACKGROUND_PROCESS_REDISPATCH_THRESHOLD),
);
$form['background_process_cleanup_age'] = array(
'#type' => 'textfield',
'#title' => t('Cleanup age (for locked processes)'),
'#description' => t('Seconds to wait before unlocking processes that never started.'),
'#default_value' => variable_get('background_process_cleanup_age', BACKGROUND_PROCESS_CLEANUP_AGE),
);
$form['background_process_cleanup_age_running'] = array(
'#type' => 'textfield',
'#title' => t('Cleanup age (for running processes)'),
'#description' => t('Unlock processes that has been running for more than X seconds.'),
'#default_value' => variable_get('background_process_cleanup_age_running', BACKGROUND_PROCESS_CLEANUP_AGE_RUNNING),
);
$form['background_process_cleanup_age_queue'] = array(
'#type' => 'textfield',
'#title' => t('Cleanup age for queued jobs'),
'#description' => t('Unlock queued processes that have been more than X seconds to start.'),
'#default_value' => variable_get('background_process_cleanup_age_queue', BACKGROUND_PROCESS_CLEANUP_AGE_QUEUE),
);
$options = background_process_get_service_hosts();
foreach ($options as $key => &$value) {
$new = empty($value['description']) ? $key : $value['description'];
$base_url = empty($value['base_url']) ? $base_url : $value['base_url'];
$http_host = empty($value['http_host']) ? parse_url($base_url, PHP_URL_HOST) : $value['http_host'];
$new .= ' (' . $base_url . ' - ' . $http_host . ')';
$value = $new;
}
$form['background_process_default_service_host'] = array(
'#type' => 'select',
'#title' => t('Default service host'),
'#description' => t('The default service host to use'),
'#options' => $options,
'#default_value' => variable_get('background_process_default_service_host', 'default'),
);
$methods = module_invoke_all('service_group');
$options = background_process_get_service_groups();
foreach ($options as $key => &$value) {
$value = (empty($value['description']) ? $key : $value['description']) . ' (' . join(',', $value['hosts']) . ') : ' . $methods['methods'][$value['method']];
}
$form['background_process_default_service_group'] = array(
'#type' => 'select',
'#title' => t('Default service group'),
'#description' => t('The default service group to use.'),
'#options' => $options,
'#default_value' => variable_get('background_process_default_service_group', 'default'),
);
$form = system_settings_form($form);
// Add determine button and make sure all the buttons are shown last.
$form['buttons']['#weight'] = 1000;
$form['buttons']['determine'] = array(
'#value' => t("Determine default service host"),
'#description' => t('Tries to determine the default service host.'),
'#type' => 'submit',
'#submit' => array('background_process_settings_form_determine_submit'),
);
return $form;
}
/**
* Submit handler for determining default service host
*/
function background_process_settings_form_determine_submit($form, &$form_state) {
background_process_determine_and_save_default_service_host();
}
/**
* Overview of background processes.
*/
function background_process_overview_page() {
$processes = background_process_get_processes();
$data = array();
foreach ($processes as $process) {
$progress = progress_get_progress($process->handle);
$data[] = array(
$process->handle,
$process->callback,
$process->uid,
$process->service_host,
format_date((int)$process->start, 'custom', 'Y-m-d H:i:s'),
$progress ? sprintf("%.02f%%", $progress->progress * 100) : t('N/A'),
l(t('Unlock'), 'background-process/unlock/' . rawurlencode($process->handle),
array('attributes' => array('class' => 'button-unlock'), 'query' => drupal_get_destination())
)
);
}
$header = array('Handle', 'Callback', 'User', 'Host', 'Start time', 'Progress', '');
$output = '';
$output .= theme('table', array(
'header' => $header,
'rows' => $data,
'class' => 'background-process-overview'
));
return $output;
}
/**
* Unlock background process.
*
* @param $handle
* Handle of process to unlock
*/
function background_process_service_unlock($handle) {
$handle = rawurldecode($handle);
if (background_process_unlock($handle)) {
drupal_set_message(t('Process %handle unlocked', array('%handle' => $handle)));
}
else {
drupal_set_message(t('Process %handle could not be unlocked', array('%handle' => $handle)), 'error');
}
drupal_goto();
}

View File

@@ -0,0 +1,14 @@
name = Background Process
description = Provides framework for running code in the background
core = 7.x
php = 5.0
dependencies[] = progress
configure = admin/config/system/background-process
; Information added by drupal.org packaging script on 2013-01-06
version = "7.x-1.14"
core = "7.x"
project = "background_process"
datestamp = "1357473962"

View File

@@ -0,0 +1,206 @@
<?php
/**
* @file
* This is the installation file for the Background Process module
*/
/**
* Implements of hook_enable().
*/
function background_process_enable() {
$_SESSION['background_process_determine_default_service_host'] = TRUE;
}
/**
* Implements of hook_schema().
*/
function background_process_schema() {
$schema = array();
$schema['background_process'] = array(
'fields' => array(
'handle' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'callback' => array(
'type' => 'text',
'not null' => FALSE,
),
'args' => array(
'type' => 'blob',
'not null' => FALSE,
),
'uid' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'token' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
),
'service_host' => array(
'type' => 'varchar',
'length' => 64,
'not null' => TRUE,
'default' => '',
),
'start_stamp' => array(
'type' => 'varchar',
'length' => '18',
'not null' => FALSE,
),
'exec_status' => array(
'type' => 'int',
'size' => 'normal',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('handle'),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function background_process_uninstall() {
// Removing process variables.
variable_del('background_process_service_timeout');
variable_del('background_process_connection_timeout');
variable_del('background_process_stream_timeout');
variable_del('background_process_service_groups');
variable_del('background_process_default_service_group');
variable_del('background_process_service_hosts');
variable_del('background_process_default_service_host');
variable_del('background_process_cleanup_age');
variable_del('background_process_queues');
variable_del('background_process_derived_default_host');
variable_del('background_process_token');
}
/**
* Implements hook_requirements().
*/
function background_process_requirements($phase) {
$response = array();
switch ($phase) {
case 'install':
return $response;
case 'runtime':
$response['title'] = 'Background Process';
$response['value'] = t('OK');
$response['severity'] = REQUIREMENT_OK;
if (ini_get('safe_mode')) {
$desc = t('Safe mode enabled. Background Process is unable to control maximum execution time for background processes. This may cause background processes to end prematurely.');
if ($response['severity'] < REQUIREMENT_WARNING) {
$response['severity'] = REQUIREMENT_WARNING;
$response['value'] = t('Safe mode enabled');
$response['description'] = $desc;
}
else {
$response['description'] .= '<br/>' . $desc;
}
}
$result = array();
$result['background_process'] = $response;
return $result;
}
}
/**
* Major version upgrade of Drupal
*/
function background_process_update_7000(&$context) {
$context['sandbox']['major_version_upgrade'] = array(
7101 => TRUE,
7102 => TRUE,
7103 => TRUE,
7104 => TRUE,
7105 => TRUE,
7106 => TRUE,
);
}
/**
* Add status column to background_process table.
*/
function background_process_update_7101() {
if (!empty($context['sandbox']['major_version_upgrade'][7101])) {
// This udate is already part of latest 6.x
return;
}
db_add_field('background_process', 'status', array(
'type' => 'int',
'size' => 'normal',
'not null' => TRUE,
'default' => 0,
));
}
/**
* Determine default service host
*/
function background_process_update_7102() {
}
/**
* Determine default service host
*/
function background_process_update_7103() {
}
/**
* Change start column from double to numeric
*/
function background_process_update_7104() {
if (!empty($context['sandbox']['major_version_upgrade'][7104])) {
// This udate is already part of latest 6.x
return;
}
db_change_field('background_process', 'start', 'start', array(
'type' => 'numeric',
'precision' => '16',
'scale' => '6',
'not null' => FALSE,
));
}
/**
* Re-determine default service host.
*/
function background_process_update_7105() {
if (!empty($context['sandbox']['major_version_upgrade'][7105])) {
// This udate is already part of latest 6.x
return;
}
$_SESSION['background_process_determine_default_service_host'] = TRUE;
}
/**
* Change schema to SQL 99 compliance
*/
function background_process_update_7106() {
if (!empty($context['sandbox']['major_version_upgrade'][7106])) {
// This udate is already part of latest 6.x
return;
}
db_change_field('background_process', 'start', 'start_stamp', array(
'type' => 'varchar',
'length' => '18',
'not null' => FALSE,
));
db_change_field('background_process', 'status', 'exec_status', array(
'type' => 'int',
'size' => 'normal',
'not null' => TRUE,
'default' => 0,
));
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
<?php
/**
* @file
* @TODO is this file neccessary?
*/
/**
* Callback for token validation.
*/
function background_process_check_token() {
header("Content-Type: text/plain");
print variable_get('background_process_token', '');
exit;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @file
*/
/**
* FAPI definition for settings page.
*/
function background_process_ass_settings_form() {
$form = array();
$form['background_process_ass_max_age'] = array(
'#type' => 'textfield',
'#title' => t('Max age'),
'#description' => t('Time in seconds to wait before considering a process dead.'),
'#default_value' => variable_get('background_process_ass_max_age', BACKGROUND_PROCESS_ASS_MAX_AGE),
);
return system_settings_form($form);
}

View File

@@ -0,0 +1,12 @@
name = Background Process Apache server status
description = Automatically unlocks dead processes using Apache server status
core = 7.x
dependencies[] = background_process
; Information added by drupal.org packaging script on 2013-01-06
version = "7.x-1.14"
core = "7.x"
project = "background_process"
datestamp = "1357473962"

View File

@@ -0,0 +1,384 @@
<?php
/**
* @file
*
* @todo Implement admin interface.
* @todo Fix runtime check of running process.
*/
/**
* Default max age before unlock process.
*/
define('BACKGROUND_PROCESS_ASS_MAX_AGE', 30);
/**
* Implements hook_menu().
*/
function background_process_ass_menu() {
$items = array();
$items['admin/config/system/background-process/ass'] = array(
'type' => MENU_LOCAL_TASK,
'title' => 'Apache Server Status',
'description' => 'Administer background process apache server status',
'page callback' => 'drupal_get_form',
'page arguments' => array('background_process_ass_settings_form'),
'access arguments' => array('administer background process'),
'file' => 'background_process_ass.admin.inc',
'weight' => 3,
);
return $items;
}
/**
* Implements hook_cron().
*/
function background_process_ass_cron() {
// Don't use more than 30 seconds to unlock
@set_time_limit(30);
background_process_ass_auto_unlock();
}
/**
* Implements hook_cronapi().
*/
function background_process_ass_cronapi($op, $job = NULL) {
switch ($op) {
case 'list':
return array('background_process_ass_cron' => t('Unlock dead processes'));
case 'rule':
return '* * * * *';
case 'configure':
return 'admin/config/system/background-process/ass';
}
}
/**
* Implements hook_cron_alter().
*/
function background_process_ass_cron_alter(&$items) {
$items['background_process_ass_cron']['override_congestion_protection'] = TRUE;
// Unlock background if too old.
// @todo Move to some access handler or pre-execute?
if ($process = background_process_get_process('uc:background_process_ass_cron')) {
if ($process->start + 30 < time()) {
background_process_unlock($process->handle, t('Self unlocking stale lock'));
}
}
}
/**
* Implements hook_service_group().
*/
function background_process_ass_service_group() {
$info = array();
$info['methods']['background_process_ass_service_group_idle'] = t('Idle workers');
return $info;
}
/**
* Determine host with most idle workers and claim it.
*
* @param $service_group
* Service group to check
* @return
* Claimed service host on success, NULL if none found
*/
function background_process_ass_service_group_idle($service_group, $reload = FALSE) {
$result = NULL;
$max = 0;
$msg = "";
$workers = &drupal_static('background_process_ass_idle_workers', array());
// Load idle worker status for all hosts
foreach ($service_group['hosts'] as $idx => $host) {
$name = $host . '_ass';
if ($reload || !isset($workers[$name])) {
$workers[$name] = background_process_ass_get_server_status($name, TRUE, $reload);
}
// Reload apache server status for all hosts, if any is fully loaded
if ($workers[$name] <= 0 && !$reload) {
return background_process_ass_service_group_idle($service_group, TRUE);
}
if ($max < $workers[$name]) {
$result = $host;
$max = $workers[$name];
}
}
if (isset($result)) {
// Claim host and tell caller
$workers[$result . '_ass']--;
return $result;
}
else {
// Could not determine most idle host, fallback to pseudo round robin
return background_process_service_group_round_robin($service_group);
}
}
/**
* Unlock locked processes that aren't really running.
*/
function background_process_ass_auto_unlock() {
$processes = background_process_get_processes();
$service_hosts = background_process_get_service_hosts();
foreach ($processes as $process) {
// Don't even dare try determining state, if not properly configured.
if (!$process->service_host) {
continue;
}
// Naming convention suffix "_ass" for a given service hosts defines the
// host to use for server-status.
if (!isset($service_hosts[$process->service_host . '_ass'])) {
continue;
}
if (!isset($service_hosts[$process->service_host])) {
continue;
}
list($url, $headers) = background_process_build_request('bgp-start/' . rawurlencode($process->handle), $process->service_host);
$process->http_host = $headers['Host'];
// Locate our connection
$url = parse_url($url);
$path = $url['path'] . (isset($url['query']) ? '?' . $url['query'] : '');
if (strlen("POST $path") > 64) {
// Request is larger than 64 characters, which is the max length of
// requests in the extended Apache Server Status. We cannot determine
// if it's running or not ... skip this process!
continue;
}
if ($process->status != BACKGROUND_PROCESS_STATUS_RUNNING) {
// Not ready for unlock yet
continue;
}
if ($process->start > time() - variable_get('background_process_ass_max_age', BACKGROUND_PROCESS_ASS_MAX_AGE)) {
// Not ready for unlock yet
continue;
}
$server_status = background_process_ass_get_server_status($process->service_host . '_ass');
if ($server_status) {
if (!background_process_ass_check_process($process, $server_status, $path)) {
_background_process_ass_unlock($process);
}
}
}
}
/**
* Check if process is really running.
*
* @param $process
* Process object
* @param $server_status
* Server status data
* @return boolean
* TRUE if running, FALSE if not.
*/
function background_process_ass_check_process($process, $server_status, $path) {
$active = TRUE;
// Is status reliable?
if ($server_status && $server_status['status']['Current Timestamp'] > $process->start) {
// Check if process is in the server status
if (!empty($server_status['connections'])) {
$active = FALSE;
foreach ($server_status['connections'] as $conn) {
if ($conn['M'] == 'R') {
// We cannot rely on the server status, assume connection is still
// active, and bail out.
watchdog('bg_process', 'Found reading state ...', array(), WATCHDOG_WARNING);
$active = TRUE;
break;
}
// Empty connections, skip them
if ($conn['M'] == '.' || $conn['M'] == '_') {
continue;
}
if (
$conn['VHost'] == $process->http_host &&
strpos($conn['Request'], 'POST ' . $path) === 0
) {
$active = TRUE;
break;
}
}
}
}
return $active;
}
function _background_process_ass_unlock($process) {
watchdog('bg_process', 'Unlocking: ' . $process->handle);
if ($process->status == BACKGROUND_PROCESS_STATUS_RUNNING) {
$msg = t('Died unexpectedly (auto unlock due to missing connection)');
// Unlock the process
if (background_process_unlock($process->handle, $msg, $process->start)) {
drupal_set_message(t("%handle unlocked: !msg", array('%handle' => $process->handle, '!msg' => $msg)));
}
}
}
/**
* Get apache extended server status.
*
* @staticvar $server_status
* Cached statically to avoid multiple requests to server-status.
* @param $name
* Name of service host for server-status.
* @param $auto
* Load only idle workers, not entire server status.
* @param $reload
* Don't load from cache.
* @return array
* Server status data.
*/
function background_process_ass_get_server_status($name, $auto = FALSE, $reload = FALSE) {
// Sanity check ...
if (!$name) {
return;
}
$service_hosts = variable_get('background_process_service_hosts', array());
if (empty($service_hosts[$name])) {
return;
}
$service_host = $service_hosts[$name];
// Static caching.
$cache = &drupal_static('background_process_ass_server_status', array());
if (!$reload && isset($cache[$name][$auto])) {
return $cache[$name][$auto];
}
$server_status = array();
$options = array();
if ($auto) {
$options['query']['auto'] = 1;
}
list($url, $headers) = background_process_build_request('', $name, $options);
$timestamp = time();
$response = drupal_http_request($url, array('headers' => $headers));
if ($response->code != 200) {
watchdog('bg_process', 'Could not acquire server status from %url - error: %error', array('%url' => $url, '%error' => $response->error), WATCHDOG_ERROR);
return NULL;
}
// If "auto" only collect idle workers
if ($auto) {
preg_match('/IdleWorkers:\s+(\d+)/', $response->data, $matches);
$server_status = $matches[1];
}
else {
$tables = _background_process_ass_parse_table($response->data);
$dls = _background_process_ass_parse_definition_list($response->data);
$server_status = array(
'response' => $response,
'connections' => $tables[0],
'status' => $dls[1],
);
preg_match('/.*?,\s+(\d+-.*?-\d+\s+\d+:\d+:\d+)/', $server_status['status']['Restart Time'], $matches);
// @hack Convert monthly names from Danish to English for strtotime() to work
str_replace('Maj', 'May', $matches[1]);
str_replace('May', 'Oct', $matches[1]);
$server_status['status']['Restart Timestamp'] = strtotime($matches[1]);
$server_status['status']['Current Timestamp'] = $timestamp;
}
$cache[$name][$auto] = $server_status;
return $server_status;
}
/**
* Converts an HTML table into an associative array.
*
* @param $html
* HTML containing table.
* @return array
* Table data.
*/
function _background_process_ass_parse_table($html) {
// Find the table
preg_match_all("/<table.*?>.*?<\/[\s]*table>/s", $html, $table_htmls);
$tables = array();
foreach ($table_htmls[0] as $table_html) {
// Get title for each row
preg_match_all("/<th.*?>(.*?)<\/[\s]*th>/s", $table_html, $matches);
$row_headers = $matches[1];
// Iterate each row
preg_match_all("/<tr.*?>(.*?)<\/[\s]*tr>/s", $table_html, $matches);
$table = array();
foreach ($matches[1] as $row_html) {
$row_html = preg_replace("/\r|\n/", '', $row_html);
preg_match_all("/<td.*?>(.*?)<\/[\s]*td>/", $row_html, $td_matches);
$row = array();
for ($i=0; $i<count($td_matches[1]); $i++) {
$td = strip_tags(html_entity_decode($td_matches[1][$i]));
$i2 = isset($row_headers[$i]) ? $row_headers[$i] : $i;
$row[$i2] = $td;
}
if (count($row) > 0) {
$table[] = $row;
}
}
$tables[] = $table;
}
return $tables;
}
/**
* Converts an HTML table into an associative array.
*
* @param $html
* HTML containing table.
* @return array
* Table data.
*/
function _background_process_ass_parse_definition_list($html) {
// Find the table
preg_match_all("/<dl.*?>.*?<\/[\s]*dl>/s", $html, $dl_htmls);
$dls = array();
foreach ($dl_htmls[0] as $dl_html) {
// Get title for each row
preg_match_all("/<dl.*?>(.*?)<\/[\s]*dl>/s", $dl_html, $matches);
$dl = array();
foreach ($matches[1] as $row_html) {
$row_html = preg_replace("/\r|\n/", '', $row_html);
preg_match_all("/<dt.*?>(.*?)<\/[\s]*dt>/", $row_html, $dt_matches);
$row = array();
for ($i=0; $i<count($dt_matches[1]); $i++) {
$dt = strip_tags(html_entity_decode($dt_matches[1][$i]));
if (strpos($dt, ':') !== FALSE) {
list($key, $value) = explode(': ', $dt, 2);
$dl[$key] = $value;
}
}
}
$dls[] = $dl;
}
return $dls;
}

View File

@@ -0,0 +1,10 @@
(function ($) {
Drupal.Nodejs.callbacks.nodejsBackgroundProcess = {
callback: function (message) {
}
};
}(jQuery));

View File

@@ -0,0 +1,4 @@
-------------------------------------------------------------------------------------
7.x-1.0 07/11/2011
-------------------------------------------------------------------------------------
- First official 1.0 release for D7.

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,38 @@
<?php
/**
* @file
* Hooks provided by Bundle copy.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Implements hook_bundle_copy_info().
*
* Return info for bundle copy. The first key is
* the name of the entity_type.
*/
function hook_bundle_copy_info() {
return array(
'node' => array(
'bundle_export_callback' => 'node_type_get_type',
'bundle_save_callback' => 'node_type_save',
'export_menu' => array(
'path' => 'admin/structure/types/export',
'access arguments' => 'administer content types',
),
'import_menu' => array(
'path' => 'admin/structure/types/import',
'access arguments' => 'administer content types',
),
),
);
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,12 @@
name="Bundle copy"
description="Import and exports bundles through the UI."
core=7.x
dependencies[] = ctools
package="Fields"
files[] = bundle_copy.module
; Information added by drupal.org packaging script on 2012-03-28
version = "7.x-1.1"
core = "7.x"
project = "bundle_copy"
datestamp = "1332926440"

View File

@@ -0,0 +1,560 @@
<?php
/**
* @file
* Bundle copy.
*/
/**
* Api function to get the bundle copy info.
*/
function bundle_copy_get_info() {
static $info = FALSE;
if (!$info) {
return module_invoke_all('bundle_copy_info');
}
return $info;
}
/**
* Implements hook_bundle_copy_info().
*/
function bundle_copy_bundle_copy_info() {
$info = array();
$info['node'] = array(
'bundle_export_callback' => 'node_type_get_type',
'bundle_save_callback' => 'node_type_save',
'export_menu' => array(
'path' => 'admin/structure/types/export',
'access arguments' => 'administer content types',
),
'import_menu' => array(
'path' => 'admin/structure/types/import',
'access arguments' => 'administer content types',
),
);
$info['user'] = array(
'bundle_export_callback' => '_bc_bundle_export_ignore',
'bundle_save_callback' => '_bc_bundle_save_ignore',
'export_menu' => array(
'path' => 'admin/config/people/accounts/export',
'access arguments' => 'administer users',
),
'import_menu' => array(
'path' => 'admin/config/people/accounts/import',
'access arguments' => 'administer users',
),
);
if (module_exists('taxonomy')) {
$info['taxonomy_term'] = array(
'bundle_export_callback' => '_bc_copy_taxonomy_load',
'bundle_save_callback' => '_bc_copy_taxonomy_save',
'export_menu' => array(
'path' => 'admin/structure/taxonomy/export',
'access arguments' => 'administer taxonomy',
),
'import_menu' => array(
'path' => 'admin/structure/taxonomy/import',
'access arguments' => 'administer taxonomy',
),
);
}
return $info;
}
/**
* Implements hook_menu().
*/
function bundle_copy_menu() {
$items = array();
$bc_info = bundle_copy_get_info();
foreach ($bc_info as $entity_type => $info) {
$items[$info['export_menu']['path']] = array(
'title' => 'Export',
'page callback' => 'drupal_get_form',
'page arguments' => array('bundle_copy_export', $entity_type),
'access arguments' => array($info['export_menu']['access arguments']),
'type' => MENU_LOCAL_TASK
);
$items[$info['import_menu']['path']] = array(
'title' => 'Import',
'page callback' => 'drupal_get_form',
'page arguments' => array('bundle_copy_import', $entity_type),
'access callback' => 'bundle_copy_import_access',
'access arguments' => array($info['import_menu']['access arguments']),
'type' => MENU_LOCAL_TASK
);
}
return $items;
}
/**
* Bundle copy import access callback.
*
* Bundle copy imports require an additional access check because they are PHP
* code and PHP is more locked down than the general permission.
*/
function bundle_copy_import_access($permission) {
return user_access($permission) && user_access('use PHP for settings');
}
/**
* Menu callback: present the export page.
*/
function bundle_copy_export($form, &$form_state, $entity_type = 'node') {
if (isset($form_state['step'])) {
$step = $form_state['step'];
}
else {
$step = 1;
$form_state['step'] = $step;
}
switch ($step) {
// Select the bundles.
case 1:
$bundles = _bundle_copy_bundle_info($entity_type, TRUE);
$form['bundle-info'] = array(
'#markup' => t('Select bundles you want to export.'),
);
$form['bundles'] = array(
'#type' => 'tableselect',
'#header' => array('label' => t('Bundle')),
'#options' => $bundles,
'#required' => TRUE,
'#empty' => t('No bundles found.'),
);
$form['next'] = array(
'#type' => 'submit',
'#value' => t('Next'),
);
break;
// List the fields / field groups.
case 2:
// Field group.
$all_groups = function_exists('field_group_info_groups') ? field_group_info_groups() : array();
// Fields.
$field_options = $instances = array();
$selected_bundles = $form_state['page_values'][1]['bundles'];
foreach ($selected_bundles as $key => $bundle) {
if ($key === $bundle) {
$instances += field_info_instances($entity_type, $bundle);
}
}
ksort($instances);
foreach ($instances as $key => $info) {
$field_options[$key]['field'] = $info['field_name']; // Same as $key.
$field_options[$key]['label'] = $info['label'];
}
$form['fields-info'] = array(
'#markup' => t('Select fields you want to export.'),
);
$form['fields'] = array(
'#type' => 'tableselect',
'#header' => array('field' => t('Field name'), 'label' => t('Label')),
'#options' => $field_options,
'#empty' => t('No fields found.'),
);
// Field group support.
if (!empty($all_groups)) {
$group_options = $fieldgroups = array();
if (isset($all_groups[$entity_type])) {
foreach ($selected_bundles as $key => $bundle) {
if ($key === $bundle) {
if (!isset($all_groups[$entity_type][$key])) {
continue;
}
foreach ($all_groups[$entity_type][$key] as $view_mode => $groups) {
foreach ($groups as $field_group) {
$group_options[$field_group->id]['fieldgroup'] = $field_group->label . ' (' . $field_group->bundle . ' - ' . $field_group->mode .')';
$fieldgroups[$field_group->id] = $field_group;
}
}
}
}
}
if (!empty($group_options)) {
$form['fieldgroups-info'] = array(
'#markup' => t('Select field groups you want to export.'),
);
$form['fieldgroups'] = array(
'#type' => 'tableselect',
'#header' => array('fieldgroup' => t('Field group name')),
'#options' => $group_options,
);
$form['fieldgroups-full'] = array(
'#type' => 'value',
'#value' => $fieldgroups,
);
}
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['next'] = array(
'#type' => 'submit',
'#value' => t('Export'),
);
$bc_info = bundle_copy_get_info();
$form['actions']['cancel'] = array(
'#markup' => l(t('Cancel'), $bc_info[$entity_type]['export_menu']['path']),
);
break;
// Export data.
case 3:
$data = _bundle_copy_export_data($entity_type, $form_state['page_values']);
$form['export'] = array(
'#title' => t('Export data'),
'#type' => 'textarea',
'#cols' => 60,
'#value' => $data,
'#rows' => 40,
'#description' => t('Copy the export text and paste it into another bundle using the import function.'),
);
break;
}
return $form;
}
/**
* Submit callback: export data.
*/
function bundle_copy_export_submit($form, &$form_state) {
// Save the form state values.
$step = $form_state['step'];
$form_state['page_values'][$step] = $form_state['values'];
// Add step and rebuild.
$form_state['step'] = $form_state['step'] + 1;
$form_state['rebuild'] = TRUE;
}
/**
* Menu callback: present the import page.
*/
function bundle_copy_import($form, $form_state, $entity_type = 'node') {
$form['entity_type'] = array('#type' => 'value', '#value' => $entity_type);
$form['info'] = array(
'#markup' => t('This form will import bundle and field definitions.'),
);
//$form['type_name'] = array(
// '#title' => t('Bundle'),
// '#description' => t('Select the bundle to import these fields into.<br/>Select &lt;Create&gt; to create a new bundle to contain the fields.'),
// '#type' => 'select',
// '#options' => array('<create>' => t('<Create>')) + _bundle_copy_bundle_info($entity_type),
//);
$form['macro'] = array(
'#type' => 'textarea',
'#rows' => 10,
'#title' => t('Import data'),
'#required' => TRUE,
'#description' => t('Paste the text created by a bundle export into this field.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
return $form;
}
/**
* Submit callback: import data.
*/
function bundle_copy_import_submit($form, &$form_state) {
// Evaluate data.
eval($form_state['values']['macro']);
if (isset($data) && is_array($data)) {
$modules = module_list();
$bc_info = bundle_copy_get_info();
// Create bundles.
foreach ($data['bundles'] as $key => $bundle) {
$entity_type = '';
if (is_object($bundle)) {
$entity_type = $bundle->bc_entity_type;
}
elseif (is_array($bundle)) {
$entity_type = $bundle['bc_entity_type'];
}
if (!empty($entity_type)) {
$existing_bundles = _bundle_copy_bundle_info($entity_type);
$bundle_save_callback = $bc_info[$entity_type]['bundle_save_callback'];
$bundle_info = $bundle_save_callback($bundle);
if (!isset($existing_bundles[$key])) {
drupal_set_message(t('%bundle bundle has been created.', array('%bundle' => $bundle->name)));
}
else {
drupal_set_message(t('%bundle bundle has been updated.', array('%bundle' => $bundle->name)));
}
}
}
// Create or update fields and their instances
if (isset($data['fields'])) {
foreach ($data['fields'] as $key => $field) {
// Check if the field module exists.
$module = $field['module'];
if (!isset($modules[$module])) {
drupal_set_message(t('%field_name field could not be created because the module %module is disabled or missing.', array('%field_name' => $key, '%module' => $module)), 'error');
continue;
}
if (isset($data['instances'][$key])) {
// Create or update field.
$prior_field = field_read_field($field['field_name'], array('include_inactive' => TRUE));
if (!$prior_field) {
field_create_field($field);
drupal_set_message(t('%field_name field has been created.', array('%field_name' => $key)));
}
else {
$field['id'] = $prior_field['id'];
field_update_field($field);
drupal_set_message(t('%field_name field has been updated.', array('%field_name' => $key)));
}
// Create or update field instances.
foreach ($data['instances'][$key] as $ikey => $instance) {
// Make sure the needed key exists.
if (!isset($instance['field_name'])) {
continue;
}
$prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle']);
if (!$prior_instance) {
field_create_instance($instance);
drupal_set_message(t('%field_name instance has been created for @bundle in @entity_type.', array('%field_name' => $key, '@bundle' => $instance['bundle'], '@entity_type' => $instance['entity_type'])));
}
else {
$instance['id'] = $prior_instance['id'];
$instance['field_id'] = $prior_instance['field_id'];
field_update_instance($instance);
drupal_set_message(t('%field_name instance has been updated for @bundle in @entity_type.', array('%field_name' => $key, '@bundle' => $instance['bundle'], '@entity_type' => $instance['entity_type'])));
}
}
}
}
}
// Create / update fieldgroups.
if (isset($data['fieldgroups'])) {
if (module_exists('field_group')) {
ctools_include('export');
$existing_field_groups = field_group_info_groups();
foreach ($data['fieldgroups'] as $identifier => $fieldgroup) {
if (isset($existing_field_groups[$fieldgroup->entity_type][$fieldgroup->bundle][$fieldgroup->mode][$fieldgroup->group_name])) {
$existing = $existing_field_groups[$fieldgroup->entity_type][$fieldgroup->bundle][$fieldgroup->mode][$fieldgroup->group_name];
$fieldgroup->id = $existing->id;
if (!isset($fieldgroup->disabled)) {
$fieldgroup->disabled = FALSE;
}
ctools_export_crud_save('field_group', $fieldgroup);
ctools_export_crud_set_status('field_group', $fieldgroup, $fieldgroup->disabled);
drupal_set_message(t('%fieldgroup fieldgroup has been updated for @bundle in @entity_type.', array('%fieldgroup' => $fieldgroup->label, '@bundle' => $fieldgroup->bundle, '@entity_type' => $fieldgroup->entity_type)));
}
else {
unset($fieldgroup->id);
unset($fieldgroup->export_type);
if (!isset($fieldgroup->disabled)) {
$fieldgroup->disabled = FALSE;
}
ctools_export_crud_save('field_group', $fieldgroup);
$fieldgroup->export_type = 1;
ctools_export_crud_set_status('field_group', $fieldgroup, $fieldgroup->disabled);
drupal_set_message(t('%fieldgroup fieldgroup has been saved for @bundle in @entity_type.', array('%fieldgroup' => $fieldgroup->label, '@bundle' => $fieldgroup->bundle, '@entity_type' => $fieldgroup->entity_type)));
}
}
}
else {
drupal_set_message(t('The fieldgroups could not be saved because the <em>Field group</em> module is disabled or missing.'), 'error');
}
}
// Clear caches.
field_info_cache_clear();
if (module_exists('field_group')) {
cache_clear_all('field_groups', 'cache_field');
}
}
else {
drupal_set_message(t('The pasted text did not contain any valid export data.'), 'error');
}
}
/**
* Return bundles for a certain entity type.
*
* @param $entity_type
* The name of the entity type.
* @param $table_select
* Whether we're returning for the table select or not.
*/
function _bundle_copy_bundle_info($entity_type, $table_select = FALSE) {
static $bundles = array();
if (!isset($bundles[$entity_type])) {
$bundles[$entity_type] = array();
$entity_info = entity_get_info($entity_type);
$entity_bundles = $entity_info['bundles'];
ksort($entity_bundles);
foreach ($entity_bundles as $key => $info) {
$label = isset($info['label']) ? $info['label'] : drupal_ucfirst(str_replace('_', ' ', $key));
if ($table_select) {
$bundles[$entity_type][$key]['label'] = $label;
}
else {
$bundles[$entity_type][$key] = $label;
}
}
}
return $bundles[$entity_type];
}
/**
* Creates export data
*
* @param $entity_type
* The name of the entity_type
* @param $selected_data
* The selected data.
*/
function _bundle_copy_export_data($entity_type, $selected_data) {
ctools_include('export');
$bc_info = bundle_copy_get_info();
$selected_bundles = $selected_data[1]['bundles'];
$selected_fields = $selected_data[2]['fields'];
$selected_fieldgroups = isset($selected_data[2]['fieldgroups']) ? $selected_data[2]['fieldgroups'] : array();
$full_fieldgroups = isset($selected_data[2]['fieldgroups-full']) ? $selected_data[2]['fieldgroups-full'] : array();
$data = $instances = array();
$fields = field_info_fields();
foreach ($selected_bundles as $bkey => $binfo) {
if ($bkey !== $binfo) {
continue;
}
$field_instances = field_info_instances($entity_type, $bkey);
ksort($field_instances);
// Bundles export data.
$bundle_info_callback = $bc_info[$entity_type]['bundle_export_callback'];
$bundle_info = $bundle_info_callback($bkey, $entity_type);
if (is_object($bundle_info)) {
$bundle_info->bc_entity_type = $entity_type;
}
elseif (is_array($bundle_info)) {
$bundle_info['bc_entity_type'] = $entity_type;
}
$data['bundles'][$bkey] = $bundle_info;
// Fields export data.
foreach ($selected_fields as $fkey => $finfo) {
if ($fkey === $finfo) {
if (!isset($data['fields'][$fkey])) {
unset($fields[$fkey]['id']);
$data['fields'][$fkey] = $fields[$fkey];
}
if (isset($field_instances[$fkey])) {
unset($field_instances[$fkey]['id']);
unset($field_instances[$fkey]['field_id']);
$instances[$fkey][] = $field_instances[$fkey];
}
}
}
}
ksort($instances);
$data['instances'] = $instances;
// Field group export data.
if (!empty($selected_fieldgroups)) {
foreach ($selected_fieldgroups as $key => $value) {
if ($value !== 0) {
$data['fieldgroups'][$full_fieldgroups[$key]->identifier] = $full_fieldgroups[$key];
}
}
}
return '$data = ' . ctools_var_export($data) . ';';
}
/**
* Helper function to load the taxonomy, but remove the vid on the object.
*
* @param $name
* The name of the bundle.
*/
function _bc_copy_taxonomy_load($name) {
$bundle = taxonomy_vocabulary_machine_name_load($name);
return $bundle;
}
/**
* Helper function to save the taxonomy.
*/
function _bc_copy_taxonomy_save($bundle) {
if ($bundle->vid) {
unset($bundle->vid);
}
$vid = db_query('SELECT vid FROM {taxonomy_vocabulary} WHERE machine_name = :machine_name', array(':machine_name' => $bundle->machine_name))->fetchField();
if ($vid) {
$bundle->vid = $vid;
}
taxonomy_vocabulary_save($bundle);
}
/**
* Helper function to ignore a bundle on export.
*/
function _bc_bundle_export_ignore($name) {
}
/**
* Helper function to ignore a bundle save.
*/
function _bc_bundle_save_ignore($bundle) {
}

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,37 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Installation
* Implementation
INTRODUCTION
------------
Current Maintainer: Travis Carden <http://drupal.org/user/236758>
Checklist API Provides a simple interface for modules to create fillable,
persistent checklists that track progress with completion times and users. See
checklistapi_example.module for an example implementation.
INSTALLATION
------------
Checklist API is installed in the usual way. See
http://drupal.org/documentation/install/modules-themes/modules-7.
IMPLEMENTATION
--------------
Checklists are declared as multidimensional arrays using
hook_checklistapi_checklist_info(). They can be altered using
hook_checklistapi_checklist_info_alter(). Checklist API handles creation of menu
items and permissions. Progress details are saved in one Drupal variable per
checklist. (Note: it is the responsibility of implementing modules to remove
their own variables on hook_uninstall().)
See checklistapi.api.php for more details.

View File

@@ -0,0 +1,50 @@
<?php
/**
* @file
* Admin page callback file for the Checklist API module.
*/
/**
* Page callback: Form constructor for the report form.
*
* @see checklistapi_menu()
*
* @ingroup forms
*/
function checklistapi_report_form() {
$header = array(
t('Checklist'),
t('Progress'),
t('Last updated'),
t('Last updated by'),
t('Operations'),
);
$definitions = checklistapi_get_checklist_info();
if (count($definitions)) {
$rows = array();
foreach ($definitions as $id => $definition) {
$checklist = checklistapi_checklist_load($id);
$row = array();
$row[] = array(
'data' => ($checklist->userHasAccess()) ? l($checklist->title, $checklist->path) : drupal_placeholder($checklist->title),
'title' => (!empty($checklist->description)) ? $checklist->description : '',
);
$row[] = t('@completed of @total (@percent%)', array(
'@completed' => $checklist->getNumberCompleted(),
'@total' => $checklist->getNumberOfItems(),
'@percent' => round($checklist->getPercentComplete()),
));
$row[] = $checklist->getLastUpdatedDate();
$row[] = $checklist->getLastUpdatedUser();
$row[] = ($checklist->userHasAccess('edit') && $checklist->hasSavedProgress()) ? l(t('clear saved progress'), $checklist->path . '/clear', array(
'query' => array('destination' => 'admin/reports/checklistapi'),
)) : '';
$rows[] = $row;
}
}
else {
$rows[][] = array('data' => t('No checklists available.'), 'colspan' => 5);
}
return theme('table', array('header' => $header, 'rows' => $rows));
}

View File

@@ -0,0 +1,133 @@
<?php
/**
* @file
* Hooks provided by the Checklist API module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Define all checklists provided by the module.
*
* Any number of checklists can be defined in an implementation of this hook.
* Checklist API will register menu items and create permissions for each one.
*
* @return array
* An array of checklist definitions. Each definition is keyed by an arbitrary
* unique identifier. The corresponding multidimensional array describing the
* checklist may contain the following key-value pairs:
* - #title: The title of the checklist.
* - #path: The Drupal path where the checklist will be accessed.
* - #description: (optional) A brief description of the checklist for its
* corresponding menu item.
* - #help: (optional) User help to be displayed in the "System help" block
* via hook_help().
* - #menu_name: (optional) The machine name of a menu to place the checklist
* into (e.g. "main-menu" or "navigation"). If this is omitted, Drupal will
* try to infer the correct menu placement from the specified path.
* - #weight: (optional) A floating point number used to sort the list of
* checklists before being output. Lower numbers appear before higher
* numbers.
* - Any number of arrays representing groups of items, to be presented as
* vertical tabs. Each group is keyed by an arbitrary identifier, unique in
* the scope of the checklist. The corresponding multimensional array
* describing the group may contain the following key-value pairs:
* - #title: The title of the group, used as the vertical tab label.
* - #description: (optional) A description of the group.
* - #weight: (optional) A floating point number used to sort the list of
* groups before being output. Lower numbers appear before higher numbers.
* - Any number of arrays representing checklist items. Each item is keyed
* by an arbitrary identifier, unique in the scope of the checklist. The
* corresponding multimensional array describing the item may contain the
* following key-value pairs:
* - #title: The title of the item.
* - #description: (optional) A description of the item, for display
* beneath the title.
* - #default_value: (optional) The default checked state of the
* item--TRUE for checked or FALSE for unchecked. Defaults to FALSE.
* This is useful for automatically checking items that can be
* programmatically tested (e.g. a module is installed or a variable has
* a certain value).
* - #weight: (optional) A floating point number used to sort the list of
* items before being output. Lower numbers appear before higher
* numbers.
* - Any number of arrays representing links. Each link is keyed by an
* arbitrary unique identifier. The corresponding multimensional array
* describing the link may contain the following key-value pairs:
* - #text: The link text.
* - #path: The link path.
* - #options: (optional) An associative array of additional options
* used by the l() function.
* - #weight: (optional) A floating point number used to sort the list
* of items before being output. Lower numbers appear before higher
* numbers.
*
* For a working example, see checklistapi_example.module.
*
* @see checklistapi_example_checklistapi_checklist_info()
* @see hook_checklistapi_checklist_info_alter()
*/
function hook_checklistapi_checklist_info() {
$definitions = array();
$definitions['example_checklist'] = array(
'#title' => t('Example checklist'),
'#path' => 'example-checklist',
'#description' => t('An example checklist.'),
'#help' => t('<p>This is an example checklist.</p>'),
'example_group' => array(
'#title' => t('Example group'),
'#description' => t('<p>Here are some example items.</p>'),
'example_item_1' => array(
'#title' => t('Example item 1'),
'example_link' => array(
'#text' => t('Example.com'),
'#path' => 'http://www.example.com/',
),
),
'example_item_2' => array(
'#title' => t('Example item 2'),
),
),
);
return $definitions;
}
/**
* Alter checklist definitions.
*
* This hook is invoked by checklistapi_get_checklist_info(). The checklist
* definitions are passed in by reference. Additional checklists may be added,
* or existing checklists may be altered or removed.
*
* @param array $definitions
* The multidimensional array of checklist definitions returned by
* hook_checklistapi_checklist_info().
*
* For a working example, see checklistapi_example.module.
*
* @see checklistapi_get_checklist_info()
* @see hook_checklistapi_checklist_info()
*/
function hook_checklistapi_checklist_info_alter(array &$definitions) {
// Add an item.
$definitions['example_checklist']['example_group']['new_item'] = array(
'title' => t('New item'),
);
// Add a group.
$definitions['example_checklist']['new_group'] = array(
'#title' => t('New group'),
);
// Move an item.
$definitions['example_checklist']['new_group']['example_item_1'] = $definitions['example_checklist']['example_group']['example_item_1'];
unset($definitions['example_checklist']['example_group']['example_item_1']);
// Remove an item.
unset($definitions['example_checklist']['example_group']['example_item_2']);
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,25 @@
#checklistapi-checklist-form div.description p {
margin: .5em 0;
}
#checklistapi-checklist-form span.completion-details {
font-style: italic;
opacity: 0.66;
}
#checklistapi-checklist-form.compact-mode div.description p {
display: none;
}
/**
* Progress bar.
*
* @see system.theme.css
*/
#checklistapi-checklist-form .progress {
font-weight: normal;
margin-bottom: 0.5em;
}
#checklistapi-checklist-form .progress .bar,
#checklistapi-checklist-form .progress .filled {
background-image: none;
}

View File

@@ -0,0 +1,14 @@
name = Checklist API
description = Provides an API for creating fillable, persistent checklists.
core = 7.x
package = Other
files[] = lib/Drupal/checklistapi/ChecklistapiChecklist.php
files[] = tests/checklistapi.test
configure = admin/reports/checklistapi
; Information added by drupal.org packaging script on 2013-04-24
version = "7.x-1.0-beta4+4-dev"
core = "7.x"
project = "checklistapi"
datestamp = "1366763817"

View File

@@ -0,0 +1,59 @@
(function ($) {
"use strict";
/**
* Provides the summary information for the checklist form vertical tabs.
*/
Drupal.behaviors.checklistapiFieldsetSummaries = {
attach: function (context) {
$('#checklistapi-checklist-form .vertical-tabs-panes > fieldset', context).drupalSetSummary(function (context) {
var total = $(':checkbox.checklistapi-item', context).size(), args = {};
if (total) {
args['@complete'] = $(':checkbox.checklistapi-item:checked', context).size();
args['@total'] = total;
args['@percent'] = Math.round(args['@complete'] / args['@total'] * 100);
return Drupal.t('@complete of @total (@percent%)', args);
}
});
}
};
/**
* Adds dynamic item descriptions toggling.
*/
Drupal.behaviors.checklistapiCompactModeLink = {
attach: function (context) {
$('#checklistapi-checklist-form .compact-link a', context).click(function () {
$(this).closest('#checklistapi-checklist-form').toggleClass('compact-mode');
var is_compact_mode = $(this).closest('#checklistapi-checklist-form').hasClass('compact-mode');
$(this)
.text(is_compact_mode ? Drupal.t('Show item descriptions') : Drupal.t('Hide item descriptions'))
.attr('title', is_compact_mode ? Drupal.t('Expand layout to include item descriptions.') : Drupal.t('Compress layout by hiding item descriptions.'));
document.cookie = 'Drupal.visitor.checklistapi_compact_mode=' + (is_compact_mode ? 1 : 0);
return false;
});
}
};
/**
* Prompts the user if they try to leave the page with unsaved changes.
*
* Note: Auto-checked items are not considered unsaved changes for the purpose
* of this feature.
*/
Drupal.behaviors.checklistapiPromptBeforeLeaving = {
getFormState: function () {
return $('#checklistapi-checklist-form :checkbox.checklistapi-item').serializeArray().toString();
},
attach: function () {
var beginningState = this.getFormState();
$(window).bind('beforeunload', function () {
var endingState = Drupal.behaviors.checklistapiPromptBeforeLeaving.getFormState();
if (beginningState !== endingState) {
return Drupal.t('Your changes will be lost if you leave the page without saving.');
}
});
}
};
})(jQuery);

View File

@@ -0,0 +1,297 @@
<?php
/**
* @file
* An API for creating fillable, persistent checklists.
*
* Provides an interface for creating checklists that track progress with
* completion times and users.
*/
/**
* Access callback: Checks the current user's access to a checklist.
*
* @param string $id
* The checklist ID.
* @param string $operation
* The operation to test access for. Possible values are "view", "edit", and
* "any". Defaults to "any".
*
* @return bool
* Returns TRUE if the current user has access to perform a given operation on
* the specified checklist, or FALSE if not.
*/
function checklistapi_checklist_access($id, $operation = 'any') {
$access['view'] = user_access('view any checklistapi checklist') || user_access('view ' . $id . ' checklistapi checklist');
$access['edit'] = user_access('edit any checklistapi checklist') || user_access('edit ' . $id . ' checklistapi checklist');
$access['any'] = $access['view'] || $access['edit'];
if (isset($access[$operation])) {
return $access[$operation];
}
else {
throw new Exception(t('No such operation "@operation"', array(
'@operation' => $operation,
)));
}
}
/**
* Loads a checklist object.
*
* @param string $id
* The checklist ID.
*
* @return ChecklistapiChecklist|false
* A fully-loaded checklist object, or FALSE if the checklist is not found.
*/
function checklistapi_checklist_load($id) {
$definition = checklistapi_get_checklist_info($id);
return ($definition) ? new ChecklistapiChecklist($definition) : FALSE;
}
/**
* Gets checklist definitions.
*
* @param string $id
* (optional) A checklist ID. Defaults to NULL.
*
* @return array|false
* The definition of the specified checklist, or FALSE if no such checklist
* exists, or an array of all checklist definitions if none is specified.
*/
function checklistapi_get_checklist_info($id = NULL) {
$definitions = &drupal_static(__FUNCTION__);
if (!isset($definitions)) {
// Get definitions.
$definitions = module_invoke_all('checklistapi_checklist_info');
$definitions = checklistapi_sort_array($definitions);
// Let other modules alter them.
drupal_alter('checklistapi_checklist_info', $definitions);
$definitions = checklistapi_sort_array($definitions);
// Inject checklist IDs.
foreach ($definitions as $key => $value) {
$definitions[$key] = array('#id' => $key) + $definitions[$key];
}
}
if (!empty($id)) {
return (!empty($definitions[$id])) ? $definitions[$id] : FALSE;
}
return $definitions;
}
/**
* Implements hook_help().
*/
function checklistapi_help($path, $arg) {
foreach (checklistapi_get_checklist_info() as $definition) {
if ($definition['#path'] == $path && !empty($definition['#help'])) {
return $definition['#help'];
}
}
}
/**
* Implements hook_init().
*/
function checklistapi_init() {
// Disable page caching on all Checklist API module paths.
$module_paths = array_keys(checklistapi_menu());
if (in_array(current_path(), $module_paths)) {
drupal_page_is_cacheable(FALSE);
}
}
/**
* Implements hook_menu().
*/
function checklistapi_menu() {
$items = array();
// Checklists report.
$items['admin/reports/checklistapi'] = array(
'title' => 'Checklists',
'page callback' => 'checklistapi_report_form',
'access arguments' => array('view checklistapi checklists report'),
'description' => 'Get an overview of your installed checklists with progress details.',
'file' => 'checklistapi.admin.inc',
);
// Individual checklists.
foreach (checklistapi_get_checklist_info() as $id => $definition) {
if (empty($definition['#path']) || empty($definition['#title'])) {
continue;
}
// View/edit checklist.
$items[$definition['#path']] = array(
'title' => $definition['#title'],
'description' => (!empty($definition['#description'])) ? $definition['#description'] : '',
'page callback' => 'drupal_get_form',
'page arguments' => array('checklistapi_checklist_form', $id),
'access callback' => 'checklistapi_checklist_access',
'access arguments' => array($id),
'file' => 'checklistapi.pages.inc',
);
if (!empty($definition['#menu_name'])) {
$items[$definition['#path']]['menu_name'] = $definition['#menu_name'];
}
// Clear saved progress.
$items[$definition['#path'] . '/clear'] = array(
'title' => 'Clear',
'page callback' => 'drupal_get_form',
'page arguments' => array('checklistapi_checklist_clear_confirm', $id),
'access callback' => 'checklistapi_checklist_access',
'access arguments' => array($id, 'edit'),
'file' => 'checklistapi.pages.inc',
'type' => MENU_CALLBACK,
);
// Toggle compact mode.
$items[$definition['#path'] . '/compact'] = array(
'title' => 'Compact mode',
'page callback' => 'checklistapi_compact_page',
'access callback' => 'checklistapi_checklist_access',
'access arguments' => array($id),
'file' => 'checklistapi.pages.inc',
'type' => MENU_CALLBACK,
);
}
return $items;
}
/**
* Implements hook_permission().
*/
function checklistapi_permission() {
$perms = array();
// Universal permissions.
$perms['view checklistapi checklists report'] = array(
'title' => t(
'View the !name report',
array('!name' => (user_access('view checklistapi checklists report')) ? l(t('Checklists'), 'admin/reports/checklistapi') : drupal_placeholder('Checklists'))
),
);
$perms['view any checklistapi checklist'] = array(
'title' => t('View any checklist'),
'description' => $view_checklist_perm_description = t('Read-only access: View list items and saved progress.'),
);
$perms['edit any checklistapi checklist'] = array(
'title' => t('Edit any checklist'),
'description' => $edit_checklist_perm_description = t('Check and uncheck list items and save changes, or clear saved progress.'),
);
// Per checklist permissions.
foreach (checklistapi_get_checklist_info() as $id => $definition) {
if (empty($id)) {
continue;
}
$perms['view ' . $id . ' checklistapi checklist'] = array(
'title' => t(
'View the !name checklist',
array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
),
'description' => $view_checklist_perm_description,
);
$perms['edit ' . $id . ' checklistapi checklist'] = array(
'title' => t(
'Edit the !name checklist',
array('!name' => (checklistapi_checklist_access($id)) ? l($definition['#title'], $definition['#path']) : drupal_placeholder($definition['#title']))
),
'description' => $edit_checklist_perm_description,
);
}
return $perms;
}
/**
* Recursively sorts array elements by #weight.
*
* @param array $array
* A nested array of elements and properties, such as the checklist
* definitions returned by hook_checklistapi_checklist_info().
*
* @return array
* The input array sorted recursively by #weight.
*
* @see checklistapi_get_checklist_info()
*/
function checklistapi_sort_array(array $array) {
$child_keys = element_children($array);
if (!count($child_keys)) {
// No children to sort.
return $array;
}
$incrementer = 0;
$children = array();
foreach ($child_keys as $key) {
// Move child to a temporary array for sorting.
$children[$key] = $array[$key];
unset($array[$key]);
// Supply a default weight if missing or invalid.
if (empty($children[$key]['#weight']) || !is_numeric($children[$key]['#weight'])) {
$children[$key]['#weight'] = 0;
}
// Increase each weight incrementally to preserve the original order when
// not overridden. This accounts for undefined behavior in PHP's uasort()
// function when its comparison callback finds two values equal.
$children[$key]['#weight'] += ($incrementer++ / 1000);
// Descend into child.
$children[$key] = checklistapi_sort_array($children[$key]);
}
// Sort by weight.
uasort($children, 'element_sort');
// Remove incremental weight hack.
foreach ($children as $key => $child) {
$children[$key]['#weight'] = floor($children[$key]['#weight']);
}
// Put children back in the main array.
$array += $children;
return $array;
}
/**
* Converts a string to lowerCamel case, suitably for a class property name.
*
* @param string $string
* The input string.
*
* @return string
* The input string converted to camelCase.
*/
function checklistapi_strtolowercamel($string) {
$string = str_replace('_', ' ', $string);
$string = ucwords($string);
$string = str_replace(' ', '', $string);
// Lowercase first character. lcfirst($string) would be nicer, but let's not
// create a dependency on PHP 5.3 just for that.
$string[0] = strtolower($string[0]);
return $string;
}
/**
* Implements hook_theme().
*/
function checklistapi_theme() {
return array(
'checklistapi_compact_link' => array(
'file' => 'checklistapi.pages.inc',
),
'checklistapi_progress_bar' => array(
'path' => drupal_get_path('module', 'checklistapi') . '/templates',
'template' => 'checklistapi-progress-bar',
'variables' => array(
'message' => '&nbsp;',
'number_complete' => 0,
'number_of_items' => 0,
'percent_complete' => 0,
),
),
);
}

View File

@@ -0,0 +1,257 @@
<?php
/**
* @file
* Page callbacks for the Checklist API module.
*/
/**
* Page callback: Form constructor for "Clear saved progress" confirmation form.
*
* @param string $id
* The checklist ID.
*
* @see checklistapi_menu()
*
* @ingroup forms
*/
function checklistapi_checklist_clear_confirm($form, &$form_state, $id) {
$checklist = checklistapi_checklist_load($id);
$form['#checklist'] = $checklist;
return confirm_form(
$form,
t('Are you sure you want to clear %title saved progress?', array(
'%title' => $checklist->title,
)),
$checklist->path,
t('All progress details will be erased. This action cannot be undone.'),
t('Clear'),
t('Cancel')
);
}
/**
* Form submission handler for checklistapi_checklist_clear_confirm().
*/
function checklistapi_checklist_clear_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
$form['#checklist']->clearSavedProgress();
}
$form_state['redirect'] = $form['#checklist']->path;
}
/**
* Page callback: Form constructor for the checklist form.
*
* @param string $id
* The checklist ID.
*
* @see checklistapi_checklist_form_submit()
* @see checklistapi_menu()
*
* @ingroup forms
*/
function checklistapi_checklist_form($form, &$form_state, $id) {
$form['#checklist'] = $checklist = checklistapi_checklist_load($id);
$form['progress_bar'] = array(
'#type' => 'markup',
'#markup' => theme('checklistapi_progress_bar', array(
'message' => ($checklist->hasSavedProgress()) ? t('Last updated @date by !user', array(
'@date' => $checklist->getLastUpdatedDate(),
'!user' => $checklist->getLastUpdatedUser(),
)) : '&nbsp;',
'number_complete' => $checklist->getNumberCompleted(),
'number_of_items' => $checklist->getNumberOfItems(),
'percent_complete' => round($checklist->getPercentComplete()),
)),
);
if (checklistapi_compact_mode()) {
$form['#attributes']['class'] = array('compact-mode');
}
$form['compact_mode_link'] = array(
'#markup' => theme('checklistapi_compact_link'),
);
$form['checklistapi'] = array(
'#attached' => array(
'css' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.css'),
'js' => array(drupal_get_path('module', 'checklistapi') . '/checklistapi.js'),
),
'#tree' => TRUE,
'#type' => 'vertical_tabs',
);
// Loop through groups.
$num_autochecked_items = 0;
$groups = $checklist->items;
foreach (element_children($groups) as $group_key) {
$group = &$groups[$group_key];
$form['checklistapi'][$group_key] = array(
'#title' => filter_xss($group['#title']),
'#type' => 'fieldset',
);
if (!empty($group['#description'])) {
$form['checklistapi'][$group_key]['#description'] = filter_xss_admin($group['#description']);
}
// Loop through items.
foreach (element_children($group) as $item_key) {
$item = &$group[$item_key];
$saved_item = !empty($checklist->savedProgress[$item_key]) ? $checklist->savedProgress[$item_key] : 0;
// Build title.
$title = filter_xss($item['#title']);
if ($saved_item) {
// Append completion details.
$user = user_load($saved_item['#uid']);
$title .= t(
'<span class="completion-details"> - Completed @time by !user</a>',
array(
'@time' => format_date($saved_item['#completed'], 'short'),
'!user' => theme('username', array('account' => $user)),
)
);
}
// Set default value.
$default_value = FALSE;
if ($saved_item) {
$default_value = TRUE;
}
elseif (!empty($item['#default_value'])) {
if ($default_value = $item['#default_value']) {
$num_autochecked_items++;
}
}
// Get description.
$description = (isset($item['#description'])) ? '<p>' . filter_xss_admin($item['#description']) . '</p>' : '';
// Append links.
$links = array();
foreach (element_children($item) as $link_key) {
$link = &$item[$link_key];
$options = (!empty($link['#options']) && is_array($link['#options'])) ? $link['#options'] : array();
$links[] = l($link['#text'], $link['#path'], $options);
}
if (count($links)) {
$description .= '<div class="links">' . implode(' | ', $links) . '</div>';
}
// Compile the list item.
$form['checklistapi'][$group_key][$item_key] = array(
'#attributes' => array('class' => array('checklistapi-item')),
'#default_value' => $default_value,
'#description' => filter_xss_admin($description),
'#disabled' => !($user_has_edit_access = $checklist->userHasAccess('edit')),
'#title' => filter_xss_admin($title),
'#type' => 'checkbox',
);
}
}
$form['actions'] = array(
'#access' => $user_has_edit_access,
'#type' => 'actions',
'#weight' => 100,
'save' => array(
'#submit' => array('checklistapi_checklist_form_submit'),
'#type' => 'submit',
'#value' => t('Save'),
),
'clear' => array(
'#access' => $checklist->hasSavedProgress(),
'#href' => $checklist->path . '/clear',
'#title' => t('Clear saved progress'),
'#type' => 'link',
),
);
// Alert the user of autochecked items. Only set the message on GET requests
// to prevent it from reappearing after saving the form. (Testing the request
// method may not be the "correct" way to accomplish this.)
if ($num_autochecked_items && $_SERVER['REQUEST_METHOD'] == 'GET') {
drupal_set_message(t(
format_plural(
$num_autochecked_items,
'%checklist found 1 unchecked item that was already completed and checked it for you. Save the form to record the change.',
'%checklist found @num unchecked items that were already completed and checked them for you. Save the form to record the changes.'
),
array(
'%checklist' => $checklist->title,
'@num' => $num_autochecked_items,
)
), 'status');
}
return $form;
}
/**
* Form submission handler for checklistapi_checklist_form().
*/
function checklistapi_checklist_form_submit($form, &$form_state) {
$form['#checklist']->saveProgress($form_state['values']['checklistapi']);
}
/**
* Determines whether the current user is in compact mode.
*
* Compact mode shows checklist forms with less description text.
*
* Whether the user is in compact mode is determined by a cookie, which is set
* for the user by checklistapi_compact_page().
*
* If the user does not have the cookie, the default value is given by the
* system variable 'checklistapi_compact_mode', which itself defaults to FALSE.
* This does not have a user interface to set it: it is a hidden variable which
* can be set in the settings.php file.
*
* @return bool
* TRUE when in compact mode, FALSE when in expanded mode.
*/
function checklistapi_compact_mode() {
return isset($_COOKIE['Drupal_visitor_checklistapi_compact_mode']) ? $_COOKIE['Drupal_visitor_checklistapi_compact_mode'] : variable_get('checklistapi_compact_mode', FALSE);
}
/**
* Menu callback: Sets whether the admin menu is in compact mode or not.
*
* @param string $mode
* Valid values are 'on' and 'off'.
*/
function checklistapi_compact_page($mode = 'off') {
user_cookie_save(array('checklistapi_compact_mode' => ($mode == 'on')));
drupal_goto();
}
/**
* Returns HTML for a link to show or hide inline item descriptions.
*
* @ingroup themeable
*/
function theme_checklistapi_compact_link() {
$output = '<div class="compact-link">';
if (checklistapi_compact_mode()) {
$output .= l(
t('Show item descriptions'),
current_path() . '/compact/off',
array(
'attributes' => array(
'title' => t('Expand layout to include item descriptions.'),
),
'query' => drupal_get_destination(),
)
);
}
else {
$output .= l(
t('Hide item descriptions'),
current_path() . '/compact/on',
array(
'attributes' => array(
'title' => t('Compress layout by hiding item descriptions.'),
),
'query' => drupal_get_destination(),
)
);
}
$output .= '</div>';
return $output;
}

View File

@@ -0,0 +1,13 @@
name = Checklist API example
description = Provides an example implementation of the Checklist API.
core = 7.x
package = Example modules
dependencies[] = checklistapi
configure = admin/config/development/checklistapi-example
; Information added by drupal.org packaging script on 2013-04-24
version = "7.x-1.0-beta4+4-dev"
core = "7.x"
project = "checklistapi"
datestamp = "1366763817"

View File

@@ -0,0 +1,15 @@
<?php
/**
* @file
* Install, update, and uninstall functions for the Checklist API Example
* module.
*/
/**
* Implements hook_uninstall().
*/
function checklistapi_example_uninstall() {
// Remove saved progress.
variable_del('checklistapi_checklist_example_checklist');
}

View File

@@ -0,0 +1,284 @@
<?php
/**
* @file
* An example implementation of the Checklist API.
*/
/**
* Implements hook_checklistapi_checklist_info().
*
* Defines a checklist based on
* @link http://buytaert.net/drupal-learning-curve Dries Buytaert's Drupal learning curve @endlink
* .
*/
function checklistapi_example_checklistapi_checklist_info() {
$definitions = array();
$definitions['example_checklist'] = array(
'#title' => t('Checklist API example'),
'#path' => 'admin/config/development/checklistapi-example',
'#description' => t('An example implementation of the Checklist API.'),
'#help' => t('<p>This checklist based on <a href="http://buytaert.net/drupal-learning-curve">Dries Buytaert\'s Drupal learning curve</a> is an example implementation of the <a href="http://drupal.org/project/checklistapi">Checklist API</a>.</p>'),
'i_suck' => array(
'#title' => t('I suck'),
'#description' => t('<p>Gain these skills to pass the <em><a href="http://headrush.typepad.com/creating_passionate_users/2005/10/getting_users_p.html">suck threshold</a></em> and start being creative with Drupal.</p>'),
'install_configure' => array(
'#title' => t('Installation and configuration of Drupal core'),
'handbook_page' => array(
'#text' => t('Installation Guide'),
'#path' => 'http://drupal.org/documentation/install',
),
),
'node_system' => array(
'#title' => t('Node system'),
'handbook_page' => array(
'#text' => t('Manage nodes'),
'#path' => 'http://drupal.org/node/306808',
),
),
'block_system' => array(
'#title' => t('Block system'),
'handbook_page' => array(
'#text' => t('Working with blocks (content in regions)'),
'#path' => 'http://drupal.org/documentation/modules/block',
),
),
'users' => array(
'#title' => t('Users, roles and permissions'),
'handbook_page' => array(
'#text' => t('Managing users'),
'#path' => 'http://drupal.org/node/627158',
),
),
'contrib' => array(
'#title' => t('Installing contributed themes and modules'),
'handbook_page' => array(
'#text' => t('Installing modules and themes'),
'#path' => 'http://drupal.org/documentation/install/modules-themes',
),
),
),
'i_get_by' => array(
'#title' => t('I get by'),
'#description' => t('<p>Gain these skills to pass the <em><a href="http://headrush.typepad.com/creating_passionate_users/2005/10/getting_users_p.html">passion threshold</a></em> and start kicking butt with Drupal.</p>'),
'upgrade_patch_monitor' => array(
'#title' => t('Upgrading, patching, (security) monitoring'),
'handbook_page_upgrading' => array(
'#text' => t('Upgrading from previous versions'),
'#path' => 'http://drupal.org/upgrade',
),
'handbook_page_patching' => array(
'#text' => t('Applying patches'),
'#path' => 'http://drupal.org/patch/apply',
),
'security_advisories' => array(
'#text' => t('Security advisories'),
'#path' => 'http://drupal.org/security',
),
'handbook_page_monitoring' => array(
'#text' => t('Monitoring a site'),
'#path' => 'http://drupal.org/node/627162',
),
),
'navigation_menus_taxonomy' => array(
'#title' => t('Navigation, menus, taxonomy'),
'handbook_page_menus' => array(
'#text' => t('Working with Menus'),
'#path' => 'http://drupal.org/documentation/modules/menu',
),
'handbook_page_taxonomy' => array(
'#text' => t('Organizing content with taxonomy'),
'#path' => 'http://drupal.org/documentation/modules/taxonomy',
),
),
'locale_i18n' => array(
'#title' => t('Locale and internationalization'),
'handbook_page' => array(
'#text' => t('Multilingual Guide'),
'#path' => 'http://drupal.org/documentation/multilingual',
),
),
'customize_front_page' => array(
'#title' => t('Drastically customize front page'),
'handbook_page' => array(
'#text' => t('Totally customize the LOOK of your front page'),
'#path' => 'http://drupal.org/node/317461',
),
),
'theme_modification' => array(
'#title' => t('Theme and template modifications'),
'handbook_page' => array(
'#text' => t('Theming Guide'),
'#path' => 'http://drupal.org/documentation/theme',
),
),
),
'i_kick_butt' => array(
'#title' => t('I kick butt'),
'contribute_docs_support' => array(
'#title' => t('Contributing documentation and support'),
'handbook_page_docs' => array(
'#text' => t('Contribute to documentation'),
'#path' => 'http://drupal.org/contribute/documentation',
),
'handbook_page_support' => array(
'#text' => t('Provide online support'),
'#path' => 'http://drupal.org/contribute/support',
),
),
'content_types_views' => array(
'#title' => t('Content types and views'),
'handbook_page_content_types' => array(
'#text' => t('Working with nodes, content types and fields'),
'#path' => 'http://drupal.org/node/717120',
),
'handbook_page_views' => array(
'#text' => t('Working with Views'),
'#path' => 'http://drupal.org/documentation/modules/views',
),
),
'actions_workflows' => array(
'#title' => t('Actions and workflows'),
'handbook_page' => array(
'#text' => t('Actions and Workflows'),
'#path' => 'http://drupal.org/node/924538',
),
),
'development' => array(
'#title' => t('Theme and module development'),
'handbook_page_theming' => array(
'#text' => t('Theming Guide'),
'#path' => 'http://drupal.org/documentation/theme',
),
'handbook_page_development' => array(
'#text' => t('Develop for Drupal'),
'#path' => 'http://drupal.org/documentation/develop',
),
),
'advanced_tasks' => array(
'#title' => t('jQuery, Form API, security audits, performance tuning'),
'handbook_page_jquery' => array(
'#text' => t('JavaScript and jQuery'),
'#path' => 'http://drupal.org/node/171213',
),
'handbook_page_form_api' => array(
'#text' => t('Form API'),
'#path' => 'http://drupal.org/node/37775',
),
'handbook_page_security' => array(
'#text' => t('Securing your site'),
'#path' => 'http://drupal.org/security/secure-configuration',
),
'handbook_page_performance' => array(
'#text' => t('Managing site performance'),
'#path' => 'http://drupal.org/node/627252',
),
),
'contribute_code' => array(
'#title' => t('Contributing code, designs and patches back to Drupal'),
'handbook_page' => array(
'#text' => t('Contribute to development'),
'#path' => 'http://drupal.org/contribute/development',
),
),
'professional' => array(
'#title' => t('Drupal consultant or working for a Drupal shop'),
),
'chx_or_unconed' => array(
'#title' => t(
"I'm a !chx or !UnConeD.",
array(
'!chx' => l(t('chx'), 'http://drupal.org/user/9446'),
'!UnConeD' => l(t('UnConeD'), 'http://drupal.org/user/10'),
)
),
),
),
);
return $definitions;
}
/**
* Implements hook_checklistapi_checklist_info_alter().
*
* Alters the checklist from checklistapi_example_checklistapi_checklist_info()
* according to
* @link http://www.unleashedmind.com/files/drupal-learning-curve.png sun's modifications @endlink
* of
* @link http://buytaert.net/drupal-learning-curve Dries Buytaert's Drupal learning curve @endlink
* .
*/
function checklistapi_example_checklistapi_checklist_info_alter(&$definitions) {
$definitions['example_checklist']['#help'] = t('<p>This checklist based on <a href="http://www.unleashedmind.com/files/drupal-learning-curve.png">sun\'s modification</a> of <a href="http://buytaert.net/drupal-learning-curve">Dries Buytaert\'s Drupal learning curve</a> is an example implementation of the <a href="http://drupal.org/project/checklistapi">Checklist API</a>.</p>');
$definitions['example_checklist']['i_kick_butt']['advanced_tasks']['#title'] = t('jQuery, Form API, theme and module development');
$definitions['example_checklist']['i_kick_butt']['advanced_tasks'] += $definitions['example_checklist']['i_kick_butt']['development'];
unset($definitions['example_checklist']['i_kick_butt']['development']);
$definitions['example_checklist']['i_kick_butt']['contribute_code']['#title'] = t('Contributing code, designs and patches back to Drupal contrib');
unset($definitions['example_checklist']['i_kick_butt']['chx_or_unconed']);
$definitions['example_checklist']['core_contributor'] = array(
'#title' => t("I'm a core contributor"),
'contribute_core_code' => array(
'#title' => t('Contribute code and patches to Drupal core'),
'handbook_page' => array(
'#text' => t('Core contribution mentoring (core office hours)'),
'#path' => 'http://drupal.org/core-office-hours',
),
'issue_queue' => array(
'#text' => t('Core issue queue'),
'#path' => 'http://drupal.org/project/issues/drupal',
),
),
'unit_tests' => array(
'#title' => t('Write unit tests to get own patch committed.'),
'handbook_page' => array(
'#text' => t('Unit Testing with Simpletest'),
'#path' => 'http://drupal.org/node/811254',
),
),
'review_core_patches' => array(
'#title' => t("Review other people's core patches, understanding coding standards."),
'pending_patches' => array(
'#text' => t('Pending patches'),
'#path' => 'http://drupal.org/project/issues/search/drupal?status[]=8&status[]=13&status[]=14',
),
'handbook_page' => array(
'#text' => t('Coding standards'),
'#path' => 'http://drupal.org/coding-standards',
),
),
'security_performance' => array(
'#title' => t('Security audits, performance tuning.'),
'handbook_page_security' => $definitions['example_checklist']['i_kick_butt']['advanced_tasks']['handbook_page_security'],
'handbook_page_performance' => $definitions['example_checklist']['i_kick_butt']['advanced_tasks']['handbook_page_performance'],
),
);
unset($definitions['example_checklist']['i_kick_butt']['advanced_tasks']['handbook_page_security']);
unset($definitions['example_checklist']['i_kick_butt']['advanced_tasks']['handbook_page_performance']);
$definitions['example_checklist']['core_maintainer'] = array(
'#title' => t("I'm trustworthy for core maintainership"),
'add_sub_system' => array(
'#title' => t('Rewrite or add a Drupal core sub-system.'),
),
'sub_system_maintainer' => array(
'#title' => t('Sub-system maintainer.'),
),
'core_branch_maintainer' => array(
'#title' => t('Core branch maintainer'),
),
);
$definitions['example_checklist']['know_every_bit_of_core'] = array(
'#title' => t('I know every bit of core'),
'im_chx' => array(
'#title' => t(
"I'm !chx.",
array('!chx' => l(t('chx'), 'http://drupal.org/user/9446'))
),
),
);
$definitions['example_checklist']['understand_all_core_patch_implications'] = array(
'#title' => t('I understand all implications of a core patch'),
'im_chuck_norris' => array(
'#title' => t("I'm Chuck Norris."),
),
);
}

View File

@@ -0,0 +1,283 @@
<?php
/**
* @file
* Class for Checklist API checklists.
*/
/**
* Defines the checklist class.
*/
class ChecklistapiChecklist {
/**
* The checklist ID.
*
* @var string
*/
public $id;
/**
* The checklist title.
*
* @var string
*/
public $title;
/**
* The menu item description.
*
* @var string
*/
public $description;
/**
* The checklist path.
*
* @var string
*/
public $path;
/**
* The checklist help.
*
* @var string
*/
public $help;
/**
* The name of the menu to put the menu item in.
*
* @var string
*/
public $menuName;
/**
* The checklist weight.
*
* @var float
*/
public $weight;
/**
* The number of list items in the checklist.
*
* @var int
*/
public $numberOfItems = 0;
/**
* The checklist groups and items.
*
* @var array
*/
public $items = array();
/**
* The saved progress data.
*
* @var array
*/
public $savedProgress;
/**
* Constructs a ChecklistapiChecklist object.
*
* @param array $definition
* A checklist definition, as returned by checklistapi_get_checklist_info().
*/
public function __construct(array $definition) {
foreach (element_children($definition) as $group_key) {
$this->numberOfItems += count(element_children($definition[$group_key]));
$this->items[$group_key] = $definition[$group_key];
unset($definition[$group_key]);
}
foreach ($definition as $property_key => $value) {
$property_name = checklistapi_strtolowercamel(drupal_substr($property_key, 1));
$this->$property_name = $value;
}
$this->savedProgress = variable_get($this->getSavedProgressVariableName(), array());
}
/**
* Gets the total number of completed items.
*
* @return int
* The number of completed items.
*/
public function getNumberCompleted() {
return (!empty($this->savedProgress['#completed_items'])) ? $this->savedProgress['#completed_items'] : 0;
}
/**
* Gets the total number of items.
*
* @return int
* The number of items.
*/
public function getNumberOfItems() {
return $this->numberOfItems;
}
/**
* Gets the name of the last user to update the checklist.
*
* @return string
* The themed name of the last user to update the checklist, or 'n/a' if
* there is no record of such a user.
*/
public function getLastUpdatedUser() {
if (isset($this->savedProgress['#changed_by'])) {
$last_updated_user = user_load($this->savedProgress['#changed_by']);
return theme('username', array('account' => $last_updated_user));
}
else {
return t('n/a');
}
}
/**
* Gets the last updated date.
*
* @return string
* The last updated date formatted with format_date(), or 'n/a' if there is
* no saved progress.
*/
public function getLastUpdatedDate() {
return (!empty($this->savedProgress['#changed'])) ? format_date($this->savedProgress['#changed']) : t('n/a');
}
/**
* Gets the percentage of items complete.
*
* @return float
* The percent complete.
*/
public function getPercentComplete() {
if ($this->getNumberOfItems() == 0) {
return 100;
}
return ($this->getNumberCompleted() / $this->getNumberOfItems()) * 100;
}
/**
* Clears the saved progress for the checklist.
*
* Deletes the Drupal variable containing the checklist's saved progress.
*/
public function clearSavedProgress() {
variable_del($this->getSavedProgressVariableName());
drupal_set_message(t('%title saved progress has been cleared.', array(
'%title' => $this->title,
)));
}
/**
* Gets the name of the Drupal variable for the checklist's saved progress.
*
* @return string
* The Drupal variable name.
*/
public function getSavedProgressVariableName() {
return 'checklistapi_checklist_' . $this->id;
}
/**
* Determines whether the checklist has saved progress.
*
* @return bool
* TRUE if the checklist has saved progress, or FALSE if it doesn't.
*/
public function hasSavedProgress() {
return (bool) variable_get($this->getSavedProgressVariableName(), FALSE);
}
/**
* Saves checklist progress to a Drupal variable.
*
* @param array $values
* A multidimensional array of form state checklist values.
*
* @see checklistapi_checklist_form_submit()
*/
public function saveProgress(array $values) {
global $user;
$time = time();
$num_changed_items = 0;
$progress = array(
'#changed' => $time,
'#changed_by' => $user->uid,
'#completed_items' => 0,
);
// Loop through groups.
foreach ($values as $group_key => $group) {
if (!is_array($group)) {
continue;
}
// Loop through items.
foreach ($group as $item_key => $item) {
$definition = checklistapi_get_checklist_info($this->id);
if (!in_array($item_key, array_keys($definition[$group_key]))) {
// This item wasn't in the checklist definition. Don't include it with
// saved progress.
continue;
}
$old_item = (!empty($this->savedProgress[$item_key])) ? $this->savedProgress[$item_key] : 0;
if ($item == 1) {
// Item is checked.
$progress['#completed_items']++;
if ($old_item) {
// Item was previously checked. Use saved value.
$new_item = $old_item;
}
else {
// Item is newly checked. Set new value.
$new_item = array(
'#completed' => $time,
'#uid' => $user->uid,
);
$num_changed_items++;
}
}
else {
// Item is unchecked.
$new_item = 0;
if ($old_item) {
// Item was previously checked.
$num_changed_items++;
}
}
$progress[$item_key] = $new_item;
}
}
// Sort array elements alphabetically so changes to the order of items in
// checklist definitions over time don't affect the order of elements in the
// saved progress variable. This simplifies use with Strongarm.
ksort($progress);
variable_set($this->getSavedProgressVariableName(), $progress);
drupal_set_message(format_plural(
$num_changed_items,
'%title progress has been saved. 1 item changed.',
'%title progress has been saved. @count items changed.',
array('%title' => $this->title)
));
}
/**
* Determines whether the current user has access to the checklist.
*
* @param string $operation
* The operation to test access for. Possible values are "view", "edit", and
* "any". Defaults to "any".
*
* @return bool
* Returns TRUE if the user has access, or FALSE if not.
*/
public function userHasAccess($operation = 'any') {
return checklistapi_checklist_access($this->id, $operation);
}
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* @file
* Default theme implementation for the Checklist API progress bar.
*
* Available variables:
* - $message: The progress message.
* - $number_complete: The number of items complete.
* - $number_of_items: The total number of items.
* - $percent_complete: The percent of items complete.
*
* @see template_preprocess()
* @see template_preprocess_checklistapi_progress_bar()
* @see template_process()
*/
?>
<div class="progress">
<div class="bar"><div class="filled" style="width:<?php print $percent_complete; ?>%;"></div></div>
<div class="percentage"><?php print $number_complete; ?> of <?php print $number_of_items; ?> (<?php print $percent_complete; ?>%)</div>
<div class="message"><?php print $message; ?></div>
</div>

View File

@@ -0,0 +1,114 @@
<?php
/**
* @file
* Tests for Checklist API module.
*/
/**
* Unit tests for Checklist API.
*/
class ChecklistapiUnitTestCase extends DrupalUnitTestCase {
public static function getInfo() {
return array(
'name' => 'Unit tests',
'description' => 'Test Checklist API classes and functions.',
'group' => 'Checklist API',
);
}
public function setUp() {
drupal_load('module', 'checklistapi');
drupal_load('module', 'checklistapi_test');
parent::setUp();
}
/**
* Test checklistapi_sort_array().
*/
public function testChecklistapiSortArray() {
$input = array_pop(checklistapi_test_checklistapi_checklist_info());
$output = checklistapi_sort_array($input);
$this->assertEqual($output['group_two']['#weight'], 0, 'Supplied a default for omitted element weight.');
$this->assertEqual($output['group_three']['#weight'], 0, 'Supplied a default in place of invalid element weight.');
$this->assertEqual($output['group_one']['#weight'], -1, 'Retained a valid element weight.');
$this->assertEqual(
element_children($output),
array('group_one', 'group_two', 'group_three', 'group_four'),
'Sorted elements by weight.'
);
$this->assertEqual(
element_children($output['group_one']['item_one']),
array('link_one', 'link_two', 'link_three'),
'Recursed through element descendants.'
);
}
/**
* Test checklistapi_strtolowercamel().
*/
public function testChecklistapiStrtolowercamel() {
$this->assertEqual(checklistapi_strtolowercamel('Abc def_ghi'), 'abcDefGhi', 'Converted string to lowerCamel case.');
}
}
/**
* Functional tests for Checklist API.
*
* @todo Add tests for vertical tabs progress indicators.
* @todo Add tests for saving and retrieving checklist progress.
* @todo Add tests for clearing saved progress.
*/
class ChecklistapiWebTestCase extends DrupalWebTestCase {
protected $privilegedUser;
public static function getInfo() {
return array(
'name' => 'Functional tests',
'description' => 'Test the functionality of Checklist API.',
'group' => 'Checklist API',
);
}
public function setUp() {
parent::setUp('checklistapi_example');
$permissions = array('edit any checklistapi checklist');
$this->privilegedUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->privilegedUser);
}
/**
* Test checklist access.
*/
public function testAccessChecklist() {
$this->drupalGet('admin/config/development/checklistapi-example');
$this->assertResponse(200, 'Granted access to user with "edit any checklistapi checklist" permission.');
$permissions = array('edit example_checklist checklistapi checklist');
$semi_privileged_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($semi_privileged_user);
$this->drupalGet('admin/config/development/checklistapi-example');
$this->assertResponse(200, 'Granted access to user with checklist-specific permission.');
$this->drupalLogout();
$this->drupalGet('admin/config/development/checklistapi-example');
$this->assertResponse(403, 'Denied access to nonprivileged user.');
}
/**
* Test checklist composition.
*/
public function testChecklistComposition() {
$menu_item = menu_get_item('admin/config/development/checklistapi-example');
$this->assertEqual($menu_item['path'], 'admin/config/development/checklistapi-example', 'Created per-checklist menu item.');
$permissions = array('edit example_checklist checklistapi checklist');
$this->assertTrue($this->checkPermissions($permissions), 'Created per-checklist permission.');
$this->drupalGet('admin/config/development/checklistapi-example');
$this->assertRaw('id="block-system-help"', 'Created per-checklist help block.');
}
}

View File

@@ -0,0 +1,14 @@
name = Checklist API test module
description = Provides an implementation of the Checklist API for testing.
package = Testing
version = VERSION
core = 7.x
dependencies[] = checklistapi
hidden = TRUE
; Information added by drupal.org packaging script on 2013-04-24
version = "7.x-1.0-beta4+4-dev"
core = "7.x"
project = "checklistapi"
datestamp = "1366763817"

View File

@@ -0,0 +1,63 @@
<?php
/**
* @file
* Test module for Checklist API.
*/
/**
* Implements hook_checklistapi_checklist_info().
*/
function checklistapi_test_checklistapi_checklist_info() {
$definitions = array();
$definitions['test_checklist'] = array(
'#title' => t('Checklist API test'),
'#path' => 'admin/config/development/checklistapi-test',
'#description' => t('A test checklist.'),
'#help' => t('<p>This is a test checklist.</p>'),
'group_two' => array(
'#title' => t('Group two'),
),
'group_one' => array(
'#title' => t('Group one'),
'#description' => t('<p>Group one description.</p>'),
'#weight' => -1,
'item_three' => array(
'#title' => t('Item three'),
'#weight' => 1,
),
'item_one' => array(
'#title' => t('Item one'),
'#description' => t('Item one description'),
'#weight' => -1,
'link_three' => array(
'#text' => t('Link three'),
'#path' => 'http://example.com/three',
'#weight' => 3,
),
'link_two' => array(
'#text' => t('Link two'),
'#path' => 'http://example.com/two',
'#weight' => 2,
),
'link_one' => array(
'#text' => t('Link one'),
'#path' => 'http://example.com/one',
'#weight' => 1,
),
),
'item_two' => array(
'#title' => t('Item two'),
),
),
'group_four' => array(
'#title' => t('Group four'),
'#weight' => 1,
),
'group_three' => array(
'#title' => t('Group three'),
'#weight' => 'invalid',
),
);
return $definitions;
}

View File

@@ -0,0 +1,51 @@
Current API Version: 2.0.8
Please note that the API version is an internal number and does not match release numbers. It is entirely possible that releases will not increase the API version number, and increasing this number too often would burden contrib module maintainers who need to keep up with API changes.
This file contains a log of changes to the API.
API Version 2.0.8
Introduce ctools_class_add().
Introduce ctools_class_remove().
API Version 2.0.7
All ctools object cache database functions can now accept session_id as an optional
argument to facilitate using non-session id keys.
API Version 2.0.6
Introduce a hook to alter the implementors of a certain api via hook_[ctools_api_hook]_alter.
API Version 2.0.5
Introduce ctools_fields_get_fields_by_type().
Add language.inc
Introduce hook_ctools_content_subtype_alter($subtype, $plugin);
API Version 2.0.4
Introduce ctools_form_include_file()
API Version 2.0.3
Introduce ctools_field_invoke_field() and ctools_field_invoke_field_default().
API Version 2.0.2
Introduce ctools_export_crud_load_multiple() and 'load multiple callback' to
export schema.
API Version 2.0.1
Introduce ctools_export_crud_enable(), ctools_export_crud_disable() and
ctools_export_crud_set_status() and requisite changes.
Introduce 'object factory' to export schema, allowing modules to control
how the exportable objects are instantiated.
Introduce 'hook_ctools_math_expression_functions_alter'.
API Version 2.0
Remove the deprecated callback-based behavior of the 'defaults' property on
plugin types; array addition is now the only option. If you need more
complex logic, do it with the 'process' callback.
Introduce a global plugin type registration hook and remove the per-plugin
type magic callbacks.
Introduce $owner . '_' . $api . '_hook_name' allowing modules to use their own
API hook in place of 'hook_ctools_plugin_api'.
Introduce ctools_plugin_api_get_hook() to get the hook name above.
Introduce 'cache defaults' and 'default cache bin' keys to export.inc
Versions prior to 2.0 have been removed from this document. See the D6 version
for that information.

View File

@@ -0,0 +1,82 @@
Current API VERSION: 2.0. See API.txt for more information.
ctools 7.x-1.x-dev
==================
#1008120: "New custom content" shows empty form if custom content panes module is not enabled.
#999302 by troky: Fix jump menu. Apparently this wasn't actually committed the last time it was committed.
#1065976 by tekante and David_Rothstein: Reset plugin static cache during module enable to prevent stale data from harming export ui.
#1016510 by EclipseGC: Make the taxonomy system page functional.
ctools 7.x-1.x-alpha2 (05-Jan-2011)
===================================
#911396 by alex_b: Prevent notices in export UI.
#919768 by mikey_p: Allow url options to be sent to ctools_ajax_command_url().
#358953 by cedarm: Allow term context to return lowercase, spaces to dashes versions of terms.
#931434 by EclipseGc: Argument plugin for node revision ID.
#910656: CTools AJAX sample wizard demo "domesticated" checkbox value not stored.
#922442 by EugenMayer, neclimdul and voxpelli: Make sure ctools_include can handle '' or NULL directory.
#919956 by traviss359: Correct example in wizard advanced help.
#942968: Fix taxonomy term access rule with tag term vocabs.
#840344: node add argument had crufty code causing notices.
#944462 by longhairedgit: Invalid character in regex causes rare notice.
#938778 by dereine: Fix profile content type for D7 updates.
Add detach event to modal close so that wysiwyg can detach the editor.
Variant titles showing up as blank if more than one variant on a page.
#940016: token support was not yet updated for D7.
#940446: Skip validation on back and cancel buttons in all wizards.
#954492: Redirect not always working in wizard.inc
#955348: Lack of redirect on "Update" button in Page Manager causing data loss sometimes.
#941778: Update and save button should not appear in the "Add variant" path.
#955070 by EclipseGc: Update ctools internal page tokens to work properly on content all content.
#956890 by EclipseGc: Update views_content to not use views dependency since that is gone.
#954728 by EclipseGc: Update node template page function name to not collide with new hook_node_view().
#946534 by EclipseGc: Add support for field content on all entitities.
#952586 by EclipseGc: Fix node_author content type.
#959206: If a context is not set when rendering content, attempt to guess the context (fixes Views panes where "From context" was added but pane was never edited.)
#961654 by benshell: drupal_alter() only supports 4 arguments.
#911362 by alex_b: Facilitate plugin cache resets for tests.
#945360 by naxoc: node_tag_new() not updated to D7.
#953804 by EclipseGc: Fix node comment rendering.
#953542 by EclipseGc: Fix node rendering.
#953776 by EclipseGc: Fix node link rendering.
#954772 by EclipseGc: Fix node build mode selection in node content type.
#954762 by EclipseGc: Fix comment forbidden theme call.
#954894 by EclipseGc: Fix breadcrumb content type.
#955180 by EclipseGc: Fix page primary navigation type.
#957190 by EclipseGc: Fix page secondary navigation type.
#957194 by EclipseGc: Remove mission content type, since D7 no longer has a site mission.
#957348 by EclipseGc: Fix search form URL path.
#952586 by andypost: Use format_username for displaying unlinked usernames.
#963800 by benshell: Fix query to fetch custom block title.
#983496 by Amitaibu: Fix term argument to use proper load function.
#989484 by Amitaibu: Fix notice in views plugin.
#982496: Fix token context.
#995026: Fix export UI during enable/disable which would throw notices and not properly set/unset menu items.
#998870 by Amitaibu: Fix notice when content has no icon by using function already designed for that.
#983576 by Amitaibu: Node view fallback task showed white screen.
#1004644 by pillarsdotnet: Update a missed theme() call to D7.
#1006162 by aspilicious: .info file cleanup.
#998312 by dereine: Support the expanded/hidden options that Views did for dependent.js
#955030: Remove no longer supported footer message content type.
Fix broken query in term context config.
#992022 by pcambra: Fix node autocomplete.
#946302 by BerdArt and arywyr: Fix PHP 5.3 reference error.
#980528 by das-peter: Notice fix with entity settings.
#999302 by troky: ctools_jump_menu() needed updating to new form parameters.
#964174: stylizer plugin theme delegation was in the wrong place, causing errors.
#991658 by burlap: Fully load the "user" context for the logged in user because not all fields are in $user.
#1014866 by das-peter: Smarter title panes, notice fix on access plugin descriptions.
#1015662 by troky: plugin .info files were not using correct filepaths.
#941780 by EclipseGc: Restore the "No blocks" functionality.
#951048 by EclipseGc: Tighter entity integration so that new entities are automatic contexts and relationships.
#941800 by me and aspilicious: Use Drupal 7 #machine_name automation on page manager pages and all export_ui defaults.
Disabled exportables and pages not properly greyed out.
#969208 by me and benshell: Get user_view and user profile working.
#941796: Recategorize blocks
ctools 7.x-1.x-alpha1
=====================
Changelog reset for 7.x
Basic conversion done during sprint.

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,63 @@
Upgrading from ctools-6.x-1.x to ctools-7.x-2.x:
- Remove ctools_ajax_associate_url_to_element as it shouldn't be necessary
with the new AJAX api's in Drupal core.
- All calls to the ctools_ajax_command_prepend() should be replace with
the core function ajax_command_prepend();
This is also the case for append, insert, after, before, replace, html,
and remove commands.
Each of these commands have been incorporated into the
Drupal.ajax.prototype.commands.insert
function with a corresponding parameter specifying which method to use.
- All calls to ctools_ajax_render() should be replaced with calls to core
ajax_render(). Note that ctools_ajax_render() printed the json object and
exited, ajax_render() gives you this responsibility.
ctools_ajax_render()
becomes
print ajax_render();
exit;
- All calls to ctools_static*() should be replaced with corresponding calls
to drupal_static*().
- All calls to ctools_css_add_css should be replaced with calls to
drupal_add_css(). Note that the arguments to drupal_add_css() have changed.
- All wizard form builder functions must now return a form array().
- ctools_build_form is very close to being removed. In anticipation of this,
all $form_state['wrapper callback']s must now be
$form_state['wrapper_callback']. In addition to this $form_state['args']
must now be $form_state['build_info']['args'].
NOTE: Previously checking to see if the return from ctools_build_form()
is empty would be enough to see if the form was submitted. This is no
longer true. Please check for $form_state['executed']. If using a wizard
check for $form_state['complete'].
- Plugin types now must be explicitly registered via a registration hook,
hook_ctools_plugin_type(); info once provided in magically-named functions
(e.g., ctools_ctools_plugin_content_types() was the old function to
provide plugin type info for ctools' content_type plugins) now must be
provided in that global hook. See http://drupal.org/node/910538 for more
details.
- Plugins that use 'theme arguments' now use 'theme variables' instead.
- Context, argument and relationship plugins now use 'add form' and/or
'edit form' rather than 'settings form'. These plugins now support
form wizards just like content plugins. These forms now all take
$form, &$form_state as arguments, and the configuration for the plugin
can be found in $form_state['conf'].
For all these forms, the submit handler MUST put appropriate data in
$form_state['conf']. Data will no longer be stored automatically.
For all of these forms, the separate settings #trees in the form are now
gone, so form ids may be adjusted. Also, these are now all real forms
using CTools form wizard instead of fake subforms as previously.

View File

@@ -0,0 +1,18 @@
.export-container {
width: 48%;
float: left;
padding: 5px 1% 0;
}
.export-container table {
width: 100%;
}
.export-container table input,
.export-container table th,
.export-container table td {
padding: 0 0 .2em .5em;
margin: 0;
vertical-align: middle;
}
.export-container .select-all {
width: 1.5em;
}

View File

@@ -0,0 +1,12 @@
name = Bulk Export
description = Performs bulk exporting of data objects known about by Chaos tools.
core = 7.x
dependencies[] = ctools
package = Chaos tool suite
; Information added by drupal.org packaging script on 2013-04-03
version = "7.x-1.3"
core = "7.x"
project = "ctools"
datestamp = "1365013512"

View File

@@ -0,0 +1,29 @@
/**
* @file
* CTools Bulk Export javascript functions.
*/
(function ($) {
Drupal.behaviors.CToolsBulkExport = {
attach: function (context) {
$('#bulk-export-export-form .vertical-tabs-pane', context).drupalSetSummary(function (context) {
// Check if any individual checkbox is checked.
if ($('.bulk-selection input:checked', context).length > 0) {
return Drupal.t('Exportables selected');
}
return '';
});
// Special bind click on the select-all checkbox.
$('.select-all').bind('click', function(context) {
$(this, '.vertical-tabs-pane').drupalSetSummary(context);
});
}
};
})(jQuery);

View File

@@ -0,0 +1,279 @@
<?php
/**
* @file
* Perform bulk exports.
*/
/**
* Implements hook_permission().
*/
function bulk_export_permission() {
return array(
'use bulk exporter' => array(
'title' => t('Access Bulk Exporter'),
'description' => t('Export various system objects into code.'),
),
);
}
/**
* Implements hook_menu().
*/
function bulk_export_menu() {
$items['admin/structure/bulk-export'] = array(
'title' => 'Bulk Exporter',
'description' => 'Bulk-export multiple CTools-handled data objects to code.',
'access arguments' => array('use bulk exporter'),
'page callback' => 'bulk_export_export',
);
$items['admin/structure/bulk-export/results'] = array(
'access arguments' => array('use bulk exporter'),
'page callback' => 'bulk_export_export',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* FAPI gateway to the bulk exporter.
*
* @param $cli
* Whether this function is called from command line.
* @param $options
* A collection of options, only passed in by drush_ctools_export().
*/
function bulk_export_export($cli = FALSE, $options = array()) {
ctools_include('export');
$form = array();
$schemas = ctools_export_get_schemas(TRUE);
$exportables = $export_tables = array();
foreach ($schemas as $table => $schema) {
if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) {
$exportables[$table] = $schema['export']['list callback']();
}
else {
$exportables[$table] = ctools_export_default_list($table, $schema);
}
natcasesort($exportables[$table]);
$export_tables[$table] = $schema['module'];
}
if ($exportables) {
$form_state = array(
're_render' => FALSE,
'no_redirect' => TRUE,
'exportables' => $exportables,
'export_tables' => $export_tables,
'name' => '',
'code' => '',
'module' => '',
);
// If called from drush_ctools_export, get the module name and
// select all exportables and call the submit function directly.
if ($cli) {
$module_name = $options['name'];
$form_state['values']['name'] = $module_name;
if (isset($options['selections'])) {
$exportables = $options['selections'];
}
$form_state['values']['tables'] = array();
foreach ($exportables as $table => $names) {
if (!empty($names)) {
$form_state['values']['tables'][] = $table;
$form_state['values'][$table] = array();
foreach ($names as $name => $title) {
$form_state['values'][$table][$name] = $name;
}
}
}
$output = bulk_export_export_form_submit($form, $form_state);
}
else {
$output = drupal_build_form('bulk_export_export_form', $form_state);
$module_name = $form_state['module'];
}
if (!empty($form_state['submitted']) || $cli) {
drupal_set_title(t('Bulk export results'));
$output = '';
$module_code = '';
$api_code = array();
$dependencies = $file_data = array();
foreach ($form_state['code'] as $module => $api_info) {
if ($module == 'general') {
$module_code .= $api_info;
}
else {
foreach ($api_info as $api => $info) {
$api_hook = ctools_plugin_api_get_hook($module, $api);
if (empty($api_code[$api_hook])) {
$api_code[$api_hook] = '';
}
$api_code[$api_hook] .= " if (\$module == '$module' && \$api == '$api') {\n";
$api_code[$api_hook] .= " return array('version' => $info[version]);\n";
$api_code[$api_hook] .= " }\n";
$dependencies[$module] = TRUE;
$file = $module_name . '.' . $api . '.inc';
$code = "<?php\n\n";
$code .= "/**\n";
$code .= " * @file\n";
$code .= " * Bulk export of $api objects generated by Bulk export module.\n";
$code .= " */\n\n";
$code .= $info['code'];
if ($cli) {
$file_data[$file] = $code;
}
else {
$export_form = drupal_get_form('ctools_export_form', $code, t('Place this in @file', array('@file' => $file)));
$output .= drupal_render($export_form);
}
}
}
}
// Add hook_ctools_plugin_api at the top of the module code, if there is any.
if ($api_code) {
foreach ($api_code as $api_hook => $text) {
$api = "\n/**\n";
$api .= " * Implements hook_$api_hook().\n";
$api .= " */\n";
$api .= "function {$module_name}_$api_hook(\$module, \$api) {\n";
$api .= $text;
$api .= "}\n";
$module_code = $api . $module_code;
}
}
if ($module_code) {
$module = "<?php\n\n";
$module .= "/**\n";
$module .= " * @file\n";
$module .= " * Bulk export of objects generated by Bulk export module.\n";
$module .= " */\n";
$module .= $module_code;
if ($cli) {
$file_data[$module_name . '.module'] = $module;
}
else {
$export_form = drupal_get_form('ctools_export_form', $module, t('Place this in @file', array('@file' => $form_state['module'] . '.module')));
$output = drupal_render($export_form) . $output;
}
}
$info = strtr("name = @module export module\n", array('@module' => $form_state['module']));
$info .= strtr("description = Export objects from CTools\n", array('@module' => $form_state['values']['name']));
foreach ($dependencies as $module => $junk) {
$info .= "dependencies[] = $module\n";
}
$info .= "package = Chaos tool suite\n";
$info .= "core = 7.x\n";
if ($cli) {
$file_data[$module_name . '.info'] = $info;
}
else {
$export_form = drupal_get_form('ctools_export_form', $info, t('Place this in @file', array('@file' => $form_state['module'] . '.info')));
$output = drupal_render($export_form) . $output;
}
}
if ($cli) {
return $file_data;
}
else {
return $output;
}
}
else {
return t('There are no objects to be exported at this time.');
}
}
/**
* FAPI definition for the bulk exporter form.
*
*/
function bulk_export_export_form($form, &$form_state) {
$files = system_rebuild_module_data();
$form['additional_settings'] = array(
'#type' => 'vertical_tabs',
);
$options = $tables = array();
foreach ($form_state['exportables'] as $table => $list) {
if (empty($list)) {
continue;
}
foreach ($list as $id => $title) {
$options[$table][$id] = array($title);
$options[$table][$id]['#attributes'] = array('class' => array('bulk-selection'));
}
$module = $form_state['export_tables'][$table];
$header = array($table);
$module_name = $files[$module]->info['name'];
$tables[] = $table;
if (!isset($form[$module_name])) {
$form[$files[$module]->info['name']] = array(
'#type' => 'fieldset',
'#group' => 'additional_settings',
'#title' => $module_name,
);
}
$form[$module_name]['tables'][$table] = array(
'#prefix' => '<div class="export-container">',
'#suffix' => '</div>',
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options[$table],
);
}
$form['tables'] = array(
'#type' => 'value',
'#value' => $tables,
);
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Module name'),
'#description' => t('Enter the module name to export code to.'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Export'),
);
$form['#action'] = url('admin/structure/bulk-export/results');
$form['#attached']['css'][] = drupal_get_path('module', 'bulk_export') . '/bulk_export.css';
$form['#attached']['js'][] = drupal_get_path('module', 'bulk_export') . '/bulk_export.js';
return $form;
}
/**
* Process the bulk export submit form and make the results available.
*/
function bulk_export_export_form_submit($form, &$form_state) {
$code = array();
$name = empty($form_state['values']['name']) ? 'foo' : $form_state['values']['name'];
$tables = $form_state['values']['tables'];
foreach ($tables as $table) {
$names = array_keys(array_filter($form_state['values'][$table]));
if ($names) {
natcasesort($names);
ctools_export_to_hook_code($code, $table, $names, $name);
}
}
$form_state['code'] = $code;
$form_state['module'] = $name;
}

View File

@@ -0,0 +1,31 @@
.ctools-button-processed {
border-style: solid;
border-width: 1px;
display: inline-block;
line-height: 1;
}
.ctools-button-processed:hover {
cursor: pointer;
}
.ctools-button-processed .ctools-content {
padding-bottom: 2px;
padding-top: 2px;
}
.ctools-no-js .ctools-content ul,
.ctools-button-processed .ctools-content ul {
list-style-image: none;
list-style-type: none;
}
.ctools-button-processed li {
line-height: 1.3333;
}
.ctools-button li a {
padding-left: 12px;
padding-right: 12px;
}

View File

@@ -0,0 +1,26 @@
.ctools-collapsible-container .ctools-toggle {
float: left;
width: 21px;
height: 21px;
cursor: pointer;
background-position: 7px 7px;
background-repeat: no-repeat;
background-image: url(../images/collapsible-expanded.png);
}
.ctools-collapsible-container .ctools-collapsible-handle {
display: none;
}
html.js .ctools-collapsible-container .ctools-collapsible-handle {
display: block;
}
.ctools-collapsible-container .ctools-collapsible-handle {
cursor: pointer;
}
.ctools-collapsible-container .ctools-toggle-collapsed {
background-image: url(../images/collapsible-collapsed.png);
}

View File

@@ -0,0 +1,10 @@
.ctools-context-holder .ctools-context-title {
float: left;
width: 49%;
font-style: italic;
}
.ctools-context-holder .ctools-context-content {
float: right;
width: 49%;
}

View File

@@ -0,0 +1,25 @@
.ctools-locked {
color: red;
border: 1px solid red;
padding: 1em;
}
.ctools-owns-lock {
background: #FFFFDD none repeat scroll 0 0;
border: 1px solid #F0C020;
padding: 1em;
}
a.ctools-ajaxing,
input.ctools-ajaxing,
button.ctools-ajaxing,
select.ctools-ajaxing {
padding-right: 18px !important;
background: url(../images/status-active.gif) right center no-repeat;
}
div.ctools-ajaxing {
float: left;
width: 18px;
background: url(../images/status-active.gif) center center no-repeat;
}

View File

@@ -0,0 +1,66 @@
.ctools-dropbutton-processed {
padding-right: 18px;
position: relative;
background-color: inherit;
}
.ctools-dropbutton-processed.open {
z-index: 200;
}
.ctools-dropbutton-processed .ctools-content li,
.ctools-dropbutton-processed .ctools-content a {
display: block;
}
.ctools-dropbutton-processed .ctools-link {
bottom: 0;
display: block;
height: auto;
position: absolute;
right: 0;
text-indent: -9999px; /* LTR */
top: 0;
width: 17px;
}
.ctools-dropbutton-processed .ctools-link a {
overflow: hidden;
}
.ctools-dropbutton-processed .ctools-content ul {
margin: 0;
overflow: hidden;
}
.ctools-dropbutton-processed.open li + li {
padding-top: 4px;
}
/**
* This creates the dropbutton arrow and inherits the link color
*/
.ctools-twisty {
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
border-style: solid;
border-width: 4px 4px 0;
line-height: 0;
right: 6px;
position: absolute;
top: 0.75em;
}
.ctools-dropbutton-processed.open .ctools-twisty {
border-bottom: 4px solid;
border-left-color: transparent;
border-right-color: transparent;
border-top-color: transparent;
top: 0.5em;
}
.ctools-no-js .ctools-twisty {
display: none;
}

View File

@@ -0,0 +1,73 @@
html.js div.ctools-dropdown div.ctools-dropdown-container {
z-index: 1001;
display: none;
text-align: left;
position: absolute;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li a {
display: block;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul {
list-style-type: none;
margin: 0;
padding: 0;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li {
display: block;
/* prevent excess right margin in IE */
margin-right: 0;
margin-left: 0;
padding-right: 0;
padding-left: 0;
background-image: none; /* prevent list backgrounds from mucking things up */
}
.ctools-dropdown-no-js .ctools-dropdown-link,
.ctools-dropdown-no-js span.text {
display: none;
}
/* Everything from here down is purely visual style and can be overridden. */
html.js div.ctools-dropdown a.ctools-dropdown-text-link {
background: url(../images/collapsible-expanded.png) 3px 5px no-repeat;
padding-left: 12px;
}
html.js div.ctools-dropdown div.ctools-dropdown-container {
width: 175px;
background: #fff;
border: 1px solid black;
margin: 4px 1px 0 0;
padding: 0;
color: #494949;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li li a {
padding-left: 25px;
width: 150px;
color: #027AC6;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li a {
text-decoration: none;
padding-left: 5px;
width: 170px;
color: #027AC6;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li span {
display: block;
}
html.js div.ctools-dropdown div.ctools-dropdown-container ul li span.text {
font-style: italic;
padding-left: 5px;
}
html.js .ctools-dropdown-hover {
background-color: #ECECEC;
}

View File

@@ -0,0 +1,45 @@
body form#ctools-export-ui-list-form {
margin: 0 0 20px 0;
}
#ctools-export-ui-list-form .form-item {
padding-right: 1em; /* LTR */
float: left; /* LTR */
margin-top: 0;
margin-bottom: 0;
}
#ctools-export-ui-list-items {
width: 100%;
}
#edit-order-wrapper {
clear: left; /* LTR */
}
#ctools-export-ui-list-form .form-submit {
margin-top: 1.65em;
float: left; /* LTR */
}
tr.ctools-export-ui-disabled td {
color: #999;
}
th.ctools-export-ui-operations,
td.ctools-export-ui-operations {
text-align: right; /* LTR */
vertical-align: top;
}
/* Force the background color to inherit so that the dropbuttons do not need
a specific background color. */
td.ctools-export-ui-operations {
background-color: inherit;
}
td.ctools-export-ui-operations .ctools-dropbutton {
text-align: left; /* LTR */
position: absolute;
right: 10px;
}

View File

@@ -0,0 +1,126 @@
div.ctools-modal-content {
background: #fff;
color: #000;
padding: 0;
margin: 2px;
border: 1px solid #000;
width: 600px;
text-align: left;
}
div.ctools-modal-content .modal-title {
font-size: 120%;
font-weight: bold;
color: white;
overflow: hidden;
white-space: nowrap;
}
div.ctools-modal-content .modal-header {
background-color: #2385c2;
padding: 0 .25em 0 1em;
}
div.ctools-modal-content .modal-header a {
color: white;
}
div.ctools-modal-content .modal-content {
padding: 1em 1em 0 1em;
overflow: auto;
position: relative; /* Keeps IE7 from flowing outside the modal. */
}
div.ctools-modal-content .modal-form {
}
div.ctools-modal-content a.close {
color: white;
float: right;
}
div.ctools-modal-content a.close:hover {
text-decoration: none;
}
div.ctools-modal-content a.close img {
position: relative;
top: 1px;
}
div.ctools-modal-content .modal-content .modal-throbber-wrapper {
text-align: center;
}
div.ctools-modal-content .modal-content .modal-throbber-wrapper img {
margin-top: 160px;
}
/** modal forms CSS **/
div.ctools-modal-content .form-item label {
width: 15em;
float: left;
}
div.ctools-modal-content .form-item label.option {
width: auto;
float: none;
}
div.ctools-modal-content .form-item .description {
clear: left;
}
div.ctools-modal-content .form-item .description .tips {
margin-left: 2em;
}
div.ctools-modal-content .no-float .form-item * {
float: none;
}
div.ctools-modal-content .modal-form .no-float label {
width: auto;
}
div.ctools-modal-content fieldset,
div.ctools-modal-content .form-radios,
div.ctools-modal-content .form-checkboxes {
clear: left;
}
div.ctools-modal-content .resizable-textarea {
width: auto;
margin-left: 15em;
margin-right: 5em;
}
div.ctools-modal-content .container-inline .form-item {
margin-right: 2em;
}
#views-exposed-pane-wrapper .form-item {
margin-top: 0;
margin-bottom: 0;
}
div.ctools-modal-content label.hidden-options {
background: transparent url(../images/arrow-active.png) no-repeat right;
height: 12px;
padding-right: 12px;
}
div.ctools-modal-content label.expanded-options {
background: transparent url(../images/expanded-options.png) no-repeat right;
height: 12px;
padding-right: 16px;
}
div.ctools-modal-content .option-text-aligner label.expanded-options,
div.ctools-modal-content .option-text-aligner label.hidden-options {
background: none;
}
div.ctools-modal-content .dependent-options {
padding-left: 30px;
}

View File

@@ -0,0 +1,11 @@
.ctools-right-container {
float: right;
padding: 0 0 0 .5em;
margin: 0;
width: 48.5%;
}
.ctools-left-container {
padding-right: .5em;
width: 48.5%;
}

View File

@@ -0,0 +1,129 @@
/* Farbtastic placement */
.color-form {
max-width: 50em;
position: relative;
min-height: 195px;
}
#placeholder {
/*
position: absolute;
top: 0;
right: 0;
*/
margin: 0 auto;
width: 195px;
}
/* Palette */
.color-form .form-item {
height: 2em;
line-height: 2em;
padding-left: 1em; /* LTR */
margin: 0.5em 0;
}
.color-form .form-item input {
margin-top: .2em;
}
.color-form label {
float: left; /* LTR */
clear: left; /* LTR */
width: 14em;
}
.color-form .form-text, .color-form .form-select {
float: left; /* LTR */
}
.color-form .form-text {
text-align: center;
margin-right: 5px; /* LTR */
cursor: pointer;
}
#palette .hook {
float: left; /* LTR */
margin-top: 3px;
width: 16px;
height: 16px;
}
#palette .up {
background-position: 100% -27px; /* LTR */
}
#palette .both {
background-position: 100% -54px; /* LTR */
}
#palette .form-item {
width: 24em;
}
#palette .item-selected {
background: #eee;
}
/* Preview */
#preview {
width: 45%;
float: right;
margin: 0;
}
#ctools_stylizer_color_scheme_form {
float: left;
width: 45%;
margin: 0;
}
/* general style for the layout-icon */
.ctools-style-icon .caption {
width: 100px;
margin-bottom: 1em;
line-height: 1em;
text-align: center;
cursor: default;
}
.ctools-style-icons .form-item {
width: 100px;
float: left;
margin: 0 3px !important;
}
.ctools-style-icons .form-item .ctools-style-icon {
float: none;
height: 150px;
width: 100px;
}
.ctools-style-icons .form-item label.option {
width: 100px;
display: block;
text-align: center;
}
.ctools-style-icons .form-item label.option input {
margin: 0 auto;
}
.ctools-style-icons .ctools-style-category {
height: 190px;
}
.ctools-style-icons .ctools-style-category label {
font-weight: bold;
width: 100%;
float: left;
}
/**
* Stylizer font editor widget
*/
.ctools-stylizer-spacing-form .form-item {
float: left;
margin: .25em;
}
#edit-font-font {
width: 9em;
}

View File

@@ -0,0 +1,8 @@
.wizard-trail {
font-size: 120%;
}
.wizard-trail-current {
font-weight: bold;
}

View File

@@ -0,0 +1,268 @@
<?php
/**
* @file
* Hooks provided by the Chaos Tool Suite.
*
* This file is divided into static hooks (hooks with string literal names) and
* dynamic hooks (hooks with pattern-derived string names).
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Inform CTools about plugin types.
*
* @return array
* An array of plugin types, keyed by the type name.
* See the advanced help topic 'plugins-creating' for details of the array
* properties.
*/
function hook_ctools_plugin_type() {
$plugins['my_type'] = array(
'load themes' => TRUE,
);
return $plugins;
}
/**
* This hook is used to inform the CTools plugin system about the location of a
* directory that should be searched for files containing plugins of a
* particular type. CTools invokes this same hook for all plugins, using the
* two passed parameters to indicate the specific type of plugin for which it
* is searching.
*
* The $plugin_type parameter is self-explanatory - it is the string name of the
* plugin type (e.g., Panels' 'layouts' or 'styles'). The $owner parameter is
* necessary because CTools internally namespaces plugins by the module that
* owns them. This is an extension of Drupal best practices on avoiding global
* namespace pollution by prepending your module name to all its functions.
* Consequently, it is possible for two different modules to create a plugin
* type with exactly the same name and have them operate in harmony. In fact,
* this system renders it impossible for modules to encroach on other modules'
* plugin namespaces.
*
* Given this namespacing, it is important that implementations of this hook
* check BOTH the $owner and $plugin_type parameters before returning a path.
* If your module does not implement plugins for the requested module/plugin
* combination, it is safe to return nothing at all (or NULL). As a convenience,
* it is also safe to return a path that does not exist for plugins your module
* does not implement - see form 2 for a use case.
*
* Note that modules implementing a plugin also must implement this hook to
* instruct CTools as to the location of the plugins. See form 3 for a use case.
*
* The conventional structure to return is "plugins/$plugin_type" - that is, a
* 'plugins' subdirectory in your main module directory, with individual
* directories contained therein named for the plugin type they contain.
*
* @param string $owner
* The system name of the module owning the plugin type for which a base
* directory location is being requested.
* @param string $plugin_type
* The name of the plugin type for which a base directory is being requested.
* @return string
* The path where CTools' plugin system should search for plugin files,
* relative to your module's root. Omit leading and trailing slashes.
*/
function hook_ctools_plugin_directory($owner, $plugin_type) {
// Form 1 - for a module implementing only the 'content_types' plugin owned
// by CTools, this would cause the plugin system to search the
// <moduleroot>/plugins/content_types directory for .inc plugin files.
if ($owner == 'ctools' && $plugin_type == 'content_types') {
return 'plugins/content_types';
}
// Form 2 - if your module implements only Panels plugins, and has 'layouts'
// and 'styles' plugins but no 'cache' or 'display_renderers', it is OK to be
// lazy and return a directory for a plugin you don't actually implement (so
// long as that directory doesn't exist). This lets you avoid ugly in_array()
// logic in your conditional, and also makes it easy to add plugins of those
// types later without having to change this hook implementation.
if ($owner == 'panels') {
return "plugins/$plugin_type";
}
// Form 3 - CTools makes no assumptions about where your plugins are located,
// so you still have to implement this hook even for plugins created by your
// own module.
if ($owner == 'mymodule') {
// Yes, this is exactly like Form 2 - just a different reasoning for it.
return "plugins/$plugin_type";
}
// Finally, if nothing matches, it's safe to return nothing at all (or NULL).
}
/**
* Alter a plugin before it has been processed.
*
* This hook is useful for altering flags or other information that will be
* used or possibly overriden by the process hook if defined.
*
* @param $plugin
* An associative array defining a plugin.
* @param $info
* An associative array of plugin type info.
*/
function hook_ctools_plugin_pre_alter(&$plugin, &$info) {
// Override a function defined by the plugin.
if ($info['type'] == 'my_type') {
$plugin['my_flag'] = 'new_value';
}
}
/**
* Alter a plugin after it has been processed.
*
* This hook is useful for overriding the final values for a plugin after it
* has been processed.
*
* @param $plugin
* An associative array defining a plugin.
* @param $info
* An associative array of plugin type info.
*/
function hook_ctools_plugin_post_alter(&$plugin, &$info) {
// Override a function defined by the plugin.
if ($info['type'] == 'my_type') {
$plugin['my_function'] = 'new_function';
}
}
/**
* Alter the list of modules/themes which implement a certain api.
*
* The hook named here is just an example, as the real existing hooks are named
* for example 'hook_views_api_alter'.
*
* @param array $list
* An array of informations about the implementors of a certain api.
* The key of this array are the module names/theme names.
*/
function hook_ctools_api_hook_alter(&$list) {
// Alter the path of the node implementation.
$list['node']['path'] = drupal_get_path('module', 'node');
}
/**
* Alter the available functions to be used in ctools math expression api.
*
* One usecase would be to create your own function in your module and
* allow to use it in the math expression api.
*
* @param $functions
* An array which has the functions as value.
*/
function hook_ctools_math_expression_functions_alter(&$functions) {
// Allow to convert from degrees to radiant.
$functions[] = 'deg2rad';
}
/**
* Alter everything.
*
* @param $info
* An associative array containing the following keys:
* - content: The rendered content.
* - title: The content's title.
* - no_blocks: A boolean to decide if blocks should be displayed.
* @param $page
* If TRUE then this renderer owns the page and can use theme('page')
* for no blocks; if false, output is returned regardless of any no
* blocks settings.
* @param $context
* An associative array containing the following keys:
* - args: The raw arguments behind the contexts.
* - contexts: The context objects in use.
* - task: The task object in use.
* - subtask: The subtask object in use.
* - handler: The handler object in use.
*/
function hook_ctools_render_alter(&$info, &$page, &$context) {
if ($context['handler']->name == 'my_handler') {
ctools_add_css('my_module.theme', 'my_module');
}
}
/**
* Alter a content plugin subtype.
*
* While content types can be altered via hook_ctools_plugin_pre_alter() or
* hook_ctools_plugin_post_alter(), the subtypes that content types rely on
* are special and require their own hook.
*
* This hook can be used to add things like 'render last' or change icons
* or categories or to rename content on specific sites.
*/
function hook_ctools_content_subtype_alter($subtype, $plugin) {
$subtype['render last'] = TRUE;
}
/**
* Alter the definition of an entity context plugin.
*
* @param array $plugin
* An associative array defining a plugin.
* @param array $entity
* The entity info array of a specific entity type.
* @param string $plugin_id
* The plugin ID, in the format NAME:KEY.
*/
function hook_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
ctools_include('context');
switch ($plugin_id) {
case 'entity_id:taxonomy_term':
$plugin['no ui'] = TRUE;
case 'entity:user':
$plugin = ctools_get_context('user');
unset($plugin['no ui']);
unset($plugin['no required context ui']);
break;
}
}
/**
* Alter the definition of entity context plugins.
*
* @param array $plugins
* An associative array of plugin definitions, keyed by plugin ID.
*
* @see hook_ctools_entity_context_alter()
*/
function hook_ctools_entity_contexts_alter(&$plugins) {
$plugins['entity_id:taxonomy_term']['no ui'] = TRUE;
}
/**
* Change cleanstring settings.
*
* @param array $settings
* An associative array of cleanstring settings.
*
* @see ctools_cleanstring()
*/
function hook_ctools_cleanstring_alter(&$settings) {
// Convert all strings to lower case.
$settings['lower case'] = TRUE;
}
/**
* Change cleanstring settings for a specific clean ID.
*
* @param array $settings
* An associative array of cleanstring settings.
*
* @see ctools_cleanstring()
*/
function hook_ctools_cleanstring_CLEAN_ID_alter(&$settings) {
// Convert all strings to lower case.
$settings['lower case'] = TRUE;
}
/**
* @} End of "addtogroup hooks".
*/

View File

@@ -0,0 +1,14 @@
name = Chaos tools
description = A library of helpful tools by Merlin of Chaos.
core = 7.x
package = Chaos tool suite
files[] = includes/context.inc
files[] = includes/math-expr.inc
files[] = includes/stylizer.inc
; Information added by drupal.org packaging script on 2013-04-03
version = "7.x-1.3"
core = "7.x"
project = "ctools"
datestamp = "1365013512"

View File

@@ -0,0 +1,219 @@
<?php
/**
* @file
* Contains install and update functions for ctools.
*/
/**
* Use requirements to ensure that the CTools CSS cache directory can be
* created and that the PHP version requirement is met.
*/
function ctools_requirements($phase) {
$requirements = array();
if ($phase == 'runtime') {
$requirements['ctools_css_cache'] = array(
'title' => t('CTools CSS Cache'),
'severity' => REQUIREMENT_OK,
'value' => t('Exists'),
);
$path = 'public://ctools/css';
if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
$requirements['ctools_css_cache']['description'] = t('The CTools CSS cache directory, %path could not be created due to a misconfigured files directory. Please ensure that the files directory is correctly configured and that the webserver has permission to create directories.', array('%path' => file_uri_target($path)));
$requirements['ctools_css_cache']['severity'] = REQUIREMENT_ERROR;
$requirements['ctools_css_cache']['value'] = t('Unable to create');
}
if (!function_exists('error_get_last')) {
$requirements['ctools_php_52']['title'] = t('CTools PHP requirements');
$requirements['ctools_php_52']['description'] = t('CTools requires certain features only available in PHP 5.2.0 or higher.');
$requirements['ctools_php_52']['severity'] = REQUIREMENT_WARNING;
$requirements['ctools_php_52']['value'] = t('PHP !version', array('!version' => phpversion()));
}
}
return $requirements;
}
/**
* Implements hook_schemea
*/
function ctools_schema() {
return ctools_schema_2();
}
/**
* Version 2 of the CTools schema.
*/
function ctools_schema_2() {
$schema = ctools_schema_1();
// update the 'name' field to be 128 bytes long:
$schema['ctools_object_cache']['fields']['name']['length'] = 128;
// Update the 'data' field to be type 'blob'.
$schema['ctools_object_cache']['fields']['data'] = array(
'type' => 'blob',
'size' => 'big',
'description' => 'Serialized data being stored.',
'serialize' => TRUE,
);
// DO NOT MODIFY THIS TABLE -- this definition is used to create the table.
// Changes to this table must be made in schema_3 or higher.
$schema['ctools_css_cache'] = array(
'description' => 'A special cache used to store CSS that must be non-volatile.',
'fields' => array(
'cid' => array(
'type' => 'varchar',
'length' => '128',
'description' => 'The CSS ID this cache object belongs to.',
'not null' => TRUE,
),
'filename' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'The filename this CSS is stored in.',
),
'css' => array(
'type' => 'text',
'size' => 'big',
'description' => 'CSS being stored.',
'serialize' => TRUE,
),
'filter' => array(
'type' => 'int',
'size' => 'tiny',
'description' => 'Whether or not this CSS needs to be filtered.',
),
),
'primary key' => array('cid'),
);
return $schema;
}
/**
* CTools' initial schema; separated for the purposes of updates.
*
* DO NOT MAKE CHANGES HERE. This schema version is locked.
*/
function ctools_schema_1() {
$schema['ctools_object_cache'] = array(
'description' => t('A special cache used to store objects that are being edited; it serves to save state in an ordinarily stateless environment.'),
'fields' => array(
'sid' => array(
'type' => 'varchar',
'length' => '64',
'not null' => TRUE,
'description' => 'The session ID this cache object belongs to.',
),
'name' => array(
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'description' => 'The name of the object this cache is attached to.',
),
'obj' => array(
'type' => 'varchar',
'length' => '32',
'not null' => TRUE,
'description' => 'The type of the object this cache is attached to; this essentially represents the owner so that several sub-systems can use this cache.',
),
'updated' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'The time this cache was created or updated.',
),
'data' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Serialized data being stored.',
'serialize' => TRUE,
),
),
'primary key' => array('sid', 'obj', 'name'),
'indexes' => array('updated' => array('updated')),
);
return $schema;
}
/**
* Enlarge the ctools_object_cache.name column to prevent truncation and weird
* errors.
*/
function ctools_update_6001() {
// Perform updates like this to reduce code duplication.
$schema = ctools_schema_2();
db_change_field('ctools_object_cache', 'name', 'name', $schema['ctools_object_cache']['fields']['name']);
}
/**
* Add the new css cache table.
*/
function ctools_update_6002() {
// Schema 2 is locked and should not be changed.
$schema = ctools_schema_2();
db_create_table('ctools_css_cache', $schema['ctools_css_cache']);
}
/**
* Take over for the panels_views module if it was on.
*/
function ctools_update_6003() {
$result = db_query('SELECT status FROM {system} WHERE name = :name', array(':name' => 'panels_views'))->fetchField();
if ($result) {
db_delete('system')->condition('name', 'panels_views')->execute();
module_enable(array('views_content'), TRUE);
}
}
/**
* Add primary key to the ctools_object_cache table.
*/
function ctools_update_6004() {
db_add_primary_key('ctools_object_cache', array('sid', 'obj', 'name'));
db_drop_index('ctools_object_cache', 'sid_obj_name');
}
/**
* Removed update.
*/
function ctools_update_6005() {
return array();
}
/**
* ctools_custom_content table was originally here, but is now moved to
* its own module.
*/
function ctools_update_6007() {
$ret = array();
if (db_table_exists('ctools_custom_content')) {
// Enable the module to make everything as seamless as possible.
module_enable(array('ctools_custom_content'), TRUE);
}
return $ret;
}
/**
* ctools_object_cache needs to be defined as a blob.
*/
function ctools_update_6008() {
db_delete('ctools_object_cache')
->execute();
db_change_field('ctools_object_cache', 'data', 'data', array(
'type' => 'blob',
'size' => 'big',
'description' => 'Serialized data being stored.',
'serialize' => TRUE,
)
);
}

View File

@@ -0,0 +1,905 @@
<?php
/**
* @file
* CTools primary module file.
*
* Most of the CTools tools are in their own .inc files. This contains
* nothing more than a few convenience functions and some hooks that
* must be implemented in the module file.
*/
define('CTOOLS_API_VERSION', '2.0.7');
/**
* Test the CTools API version.
*
* This function can always be used to safely test if CTools has the minimum
* API version that your module can use. It can also try to protect you from
* running if the CTools API version is too new, but if you do that you need
* to be very quick about watching CTools API releases and release new versions
* of your software as soon as the new release is made, or people might end up
* updating CTools and having your module shut down without any recourse.
*
* It is recommended that every hook of your module that might use CTools or
* might lead to a use of CTools be guarded like this:
*
* @code
* if (!module_invoke('ctools', 'api_version', '1.0')) {
* return;
* }
* @endcode
*
* Note that some hooks such as _menu() or _theme() must return an array().
*
* You can use it in your hook_requirements to report this error condition
* like this:
*
* @code
* define('MODULENAME_MINIMUM_CTOOLS_API_VERSION', '1.0');
* define('MODULENAME_MAXIMUM_CTOOLS_API_VERSION', '1.1');
*
* function MODULENAME_requirements($phase) {
* $requirements = array();
* if (!module_invoke('ctools', 'api_version', MODULENAME_MINIMUM_CTOOLS_API_VERSION, MODULENAME_MAXIMUM_CTOOLS_API_VERSION)) {
* $requirements['MODULENAME_ctools'] = array(
* 'title' => $t('MODULENAME required Chaos Tool Suite (CTools) API Version'),
* 'value' => t('Between @a and @b', array('@a' => MODULENAME_MINIMUM_CTOOLS_API_VERSION, '@b' => MODULENAME_MAXIMUM_CTOOLS_API_VERSION)),
* 'severity' => REQUIREMENT_ERROR,
* );
* }
* return $requirements;
* }
* @endcode
*
* Please note that the version is a string, not an floating point number.
* This will matter once CTools reaches version 1.10.
*
* A CTools API changes history will be kept in API.txt. Not every new
* version of CTools will necessarily update the API version.
* @param $minimum
* The minimum version of CTools necessary for your software to run with it.
* @param $maximum
* The maximum version of CTools allowed for your software to run with it.
*/
function ctools_api_version($minimum, $maximum = NULL) {
if (version_compare(CTOOLS_API_VERSION, $minimum, '<')) {
return FALSE;
}
if (isset($maximum) && version_compare(CTOOLS_API_VERSION, $maximum, '>')) {
return FALSE;
}
return TRUE;
}
// -----------------------------------------------------------------------
// General utility functions
/**
* Include .inc files as necessary.
*
* This fuction is helpful for including .inc files for your module. The
* general case is including ctools funcitonality like this:
*
* @code
* ctools_include('plugins');
* @endcode
*
* Similar funcitonality can be used for other modules by providing the $module
* and $dir arguments like this:
*
* @code
* // include mymodule/includes/import.inc
* ctools_include('import', 'mymodule');
* // include mymodule/plugins/foobar.inc
* ctools_include('foobar', 'mymodule', 'plugins');
* @endcode
*
* @param $file
* The base file name to be included.
* @param $module
* Optional module containing the include.
* @param $dir
* Optional subdirectory containing the include file.
*/
function ctools_include($file, $module = 'ctools', $dir = 'includes') {
static $used = array();
$dir = '/' . ($dir ? $dir . '/' : '');
if (!isset($used[$module][$dir][$file])) {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "$dir$file.inc";
$used[$module][$dir][$file] = TRUE;
}
}
/**
* Include .inc files in a form context.
*
* This is a variant of ctools_include that will save information in the
* the form_state so that cached forms will properly include things.
*/
function ctools_form_include(&$form_state, $file, $module = 'ctools', $dir = 'includes') {
if (!isset($form_state['build_info']['args'])) {
$form_state['build_info']['args'] = array();
}
$dir = '/' . ($dir ? $dir . '/' : '');
form_load_include($form_state, 'inc', $module, $dir . $file);
}
/**
* Add an arbitrary path to the $form_state so it can work with form cache.
*
* module_load_include uses an unfortunately annoying syntax to work, making it
* difficult to translate the more simple $path + $file syntax.
*/
function ctools_form_include_file(&$form_state, $filename) {
if (!isset($form_state['build_info']['args'])) {
$form_state['build_info']['args'] = array();
}
// Now add this to the build info files so that AJAX requests will know to load it.
$form_state['build_info']['files']["$filename"] = $filename;
require_once DRUPAL_ROOT . '/' . $filename;
}
/**
* Provide the proper path to an image as necessary.
*
* This helper function is used by ctools but can also be used in other
* modules in the same way as explained in the comments of ctools_include.
*
* @param $image
* The base file name (with extension) of the image to be included.
* @param $module
* Optional module containing the include.
* @param $dir
* Optional subdirectory containing the include file.
*/
function ctools_image_path($image, $module = 'ctools', $dir = 'images') {
return drupal_get_path('module', $module) . "/$dir/" . $image;
}
/**
* Include css files as necessary.
*
* This helper function is used by ctools but can also be used in other
* modules in the same way as explained in the comments of ctools_include.
*
* @param $file
* The base file name to be included.
* @param $module
* Optional module containing the include.
* @param $dir
* Optional subdirectory containing the include file.
*/
function ctools_add_css($file, $module = 'ctools', $dir = 'css') {
drupal_add_css(drupal_get_path('module', $module) . "/$dir/$file.css");
}
/**
* Format a css file name for use with $form['#attached']['css'].
*
* This helper function is used by ctools but can also be used in other
* modules in the same way as explained in the comments of ctools_include.
*
* @code
* $form['#attached']['css'] = array(ctools_attach_css('collapsible-div'));
* $form['#attached']['css'][ctools_attach_css('collapsible-div')] = array('preprocess' => FALSE);
* @endcode
*
* @param $file
* The base file name to be included.
* @param $module
* Optional module containing the include.
* @param $dir
* Optional subdirectory containing the include file.
*/
function ctools_attach_css($file, $module = 'ctools', $dir = 'css') {
return drupal_get_path('module', $module) . "/$dir/$file.css";
}
/**
* Include js files as necessary.
*
* This helper function is used by ctools but can also be used in other
* modules in the same way as explained in the comments of ctools_include.
*
* @param $file
* The base file name to be included.
* @param $module
* Optional module containing the include.
* @param $dir
* Optional subdirectory containing the include file.
*/
function ctools_add_js($file, $module = 'ctools', $dir = 'js') {
drupal_add_js(drupal_get_path('module', $module) . "/$dir/$file.js");
}
/**
* Format a javascript file name for use with $form['#attached']['js'].
*
* This helper function is used by ctools but can also be used in other
* modules in the same way as explained in the comments of ctools_include.
*
* @code
* $form['#attached']['js'] = array(ctools_attach_js('auto-submit'));
* @endcode
*
* @param $file
* The base file name to be included.
* @param $module
* Optional module containing the include.
* @param $dir
* Optional subdirectory containing the include file.
*/
function ctools_attach_js($file, $module = 'ctools', $dir = 'js') {
return drupal_get_path('module', $module) . "/$dir/$file.js";
}
/**
* Get a list of roles in the system.
*
* @return
* An array of role names keyed by role ID.
*
* @deprecated
* user_roles() should be used instead.
*/
function ctools_get_roles() {
return user_roles();
}
/*
* Break x,y,z and x+y+z into an array. Numeric only.
*
* @param $str
* The string to parse.
*
* @return $object
* An object containing
* - operator: Either 'and' or 'or'
* - value: An array of numeric values.
*/
function ctools_break_phrase($str) {
$object = new stdClass();
if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
// The '+' character in a query string may be parsed as ' '.
$object->operator = 'or';
$object->value = preg_split('/[+ ]/', $str);
}
else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
$object->operator = 'and';
$object->value = explode(',', $str);
}
// Keep an 'error' value if invalid strings were given.
if (!empty($str) && (empty($object->value) || !is_array($object->value))) {
$object->value = array(-1);
$object->invalid_input = TRUE;
return $object;
}
if (empty($object->value)) {
$object->value = array();
}
// Doubly ensure that all values are numeric only.
foreach ($object->value as $id => $value) {
$object->value[$id] = intval($value);
}
return $object;
}
/**
* Set a token/value pair to be replaced later in the request, specifically in
* ctools_preprocess_page().
*
* @param $token
* The token to be replaced later, during page rendering. This should
* ideally be a string inside of an HTML comment, so that if there is
* no replacement, the token will not render on the page.
* @param $type
* The type of the token. Can be either 'variable', which will pull data
* directly from the page variables
* @param $argument
* If $type == 'variable' then argument should be the key to fetch from
* the $variables. If $type == 'callback' then it should either be the
* callback, or an array that will be sent to call_user_func_array().
*
* @return
* A array of token/variable names to be replaced.
*/
function ctools_set_page_token($token = NULL, $type = NULL, $argument = NULL) {
static $tokens = array();
if (isset($token)) {
$tokens[$token] = array($type, $argument);
}
return $tokens;
}
/**
* Easily set a token from the page variables.
*
* This function can be used like this:
* $token = ctools_set_variable_token('tabs');
*
* $token will then be a simple replacement for the 'tabs' about of the
* variables available in the page template.
*/
function ctools_set_variable_token($token) {
$string = '<!-- ctools-page-' . $token . ' -->';
ctools_set_page_token($string, 'variable', $token);
return $string;
}
/**
* Easily set a token from the page variables.
*
* This function can be used like this:
* $token = ctools_set_variable_token('id', 'mymodule_myfunction');
*/
function ctools_set_callback_token($token, $callback) {
// If the callback uses arguments they are considered in the token.
if (is_array($callback)) {
$token .= '-' . md5(serialize($callback));
}
$string = '<!-- ctools-page-' . $token . ' -->';
ctools_set_page_token($string, 'callback', $callback);
return $string;
}
/**
* Tell CTools that sidebar blocks should not be rendered.
*
* It is often desirable to not display sidebars when rendering a page,
* particularly when using Panels. This informs CTools to alter out any
* sidebar regions during block render.
*/
function ctools_set_no_blocks($blocks = FALSE) {
$status = &drupal_static(__FUNCTION__, TRUE);
$status = $blocks;
}
/**
* Add an array of classes to the body.
*
* @param mixed $classes
* A string or an array of class strings to add.
* @param string $hook
* The theme hook to add the class to. The default is 'html' which will
* affect the body tag.
*/
function ctools_class_add($classes, $hook = 'html') {
if (!is_array($classes)) {
$classes = array($classes);
}
$static = &drupal_static('ctools_process_classes', array());
if (!isset($static[$hook]['add'])) {
$static[$hook]['add'] = array();
}
foreach ($classes as $class) {
$static[$hook]['add'][] = $class;
}
}
/**
* Remove an array of classes from the body.
*
* @param mixed $classes
* A string or an array of class strings to remove.
* @param string $hook
* The theme hook to remove the class from. The default is 'html' which will
* affect the body tag.
*/
function ctools_class_remove($classes, $hook = 'html') {
if (!is_array($classes)) {
$classes = array($classes);
}
$static = &drupal_static('ctools_process_classes', array());
if (!isset($static[$hook]['remove'])) {
$static[$hook]['remove'] = array();
}
foreach ($classes as $class) {
$static[$hook]['remove'][] = $class;
}
}
// -----------------------------------------------------------------------
// Drupal core hooks
/**
* Implement hook_init to keep our global CSS at the ready.
*/
function ctools_init() {
ctools_add_css('ctools');
// If we are sure that CTools' AJAX is in use, change the error handling.
if (!empty($_REQUEST['ctools_ajax'])) {
ini_set('display_errors', 0);
register_shutdown_function('ctools_shutdown_handler');
}
// Clear plugin cache on the module page submit.
if ($_GET['q'] == 'admin/modules/list/confirm' && !empty($_POST)) {
cache_clear_all('ctools_plugin_files:', 'cache', TRUE);
}
}
/**
* Shutdown handler used during ajax operations to help catch fatal errors.
*/
function ctools_shutdown_handler() {
if (function_exists('error_get_last') AND ($error = error_get_last())) {
switch ($error['type']) {
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
// Do this manually because including files here is dangerous.
$commands = array(
array(
'command' => 'alert',
'title' => t('Error'),
'text' => t('Unable to complete operation. Fatal error in @file on line @line: @message', array(
'@file' => $error['file'],
'@line' => $error['line'],
'@message' => $error['message'],
)),
),
);
// Change the status code so that the client will read the AJAX returned.
header('HTTP/1.1 200 OK');
drupal_json($commands);
}
}
}
/**
* Implements hook_theme().
*/
function ctools_theme() {
ctools_include('utility');
$items = array();
ctools_passthrough('ctools', 'theme', $items);
return $items;
}
/**
* Implements hook_menu().
*/
function ctools_menu() {
ctools_include('utility');
$items = array();
ctools_passthrough('ctools', 'menu', $items);
return $items;
}
/**
* Implementation of hook_cron. Clean up old caches.
*/
function ctools_cron() {
ctools_include('utility');
$items = array();
ctools_passthrough('ctools', 'cron', $items);
}
/**
* Ensure the CTools CSS cache is flushed whenever hook_flush_caches is invoked.
*/
function ctools_flush_caches() {
// Do not actually flush caches if running on cron. Drupal uses this hook
// in an inconsistent fashion and it does not necessarily mean to *flush*
// caches when running from cron. Instead it's just getting a list of cache
// tables and may not do any flushing.
if (!empty($GLOBALS['locks']['cron'])) {
return;
}
ctools_include('css');
ctools_css_flush_caches();
}
/**
* Implements hook_element_info_alter().
*
*/
function ctools_element_info_alter(&$type) {
ctools_include('dependent');
ctools_dependent_element_info_alter($type);
}
/**
* Implementation of hook_file_download()
*
* When using the private file system, we have to let Drupal know it's ok to
* download CSS and image files from our temporary directory.
*/
function ctools_file_download($filepath) {
if (strpos($filepath, 'ctools') === 0) {
$mime = file_get_mimetype($filepath);
// For safety's sake, we allow only text and images.
if (strpos($mime, 'text') === 0 || strpos($mime, 'image') === 0) {
return array('Content-type:' . $mime);
}
}
}
/**
* Implements hook_registry_files_alter().
*
* Alter the registry of files to automagically include all classes in
* class-based plugins.
*/
function ctools_registry_files_alter(&$files, $indexed_modules) {
ctools_include('registry');
return _ctools_registry_files_alter($files, $indexed_modules);
}
// -----------------------------------------------------------------------
// CTools hook implementations.
/**
* Implementation of hook_ctools_plugin_directory() to let the system know
* where all our own plugins are.
*/
function ctools_ctools_plugin_directory($owner, $plugin_type) {
if ($owner == 'ctools') {
return 'plugins/' . $plugin_type;
}
}
/**
* Implements hook_ctools_plugin_type().
*/
function ctools_ctools_plugin_type() {
ctools_include('utility');
$items = array();
// Add all the plugins that have their own declaration space elsewhere.
ctools_passthrough('ctools', 'plugin-type', $items);
return $items;
}
// -----------------------------------------------------------------------
// Drupal theme preprocess hooks that must be in the .module file.
/**
* A theme preprocess function to automatically allow panels-based node
* templates based upon input when the panel was configured.
*/
function ctools_preprocess_node(&$vars) {
// The 'ctools_template_identifier' attribute of the node is added when the pane is
// rendered.
if (!empty($vars['node']->ctools_template_identifier)) {
$vars['ctools_template_identifier'] = check_plain($vars['node']->ctools_template_identifier);
$vars['theme_hook_suggestions'][] = 'node__panel__' . check_plain($vars['node']->ctools_template_identifier);
}
}
function ctools_page_alter(&$page) {
$page['#post_render'][] = 'ctools_page_token_processing';
}
/**
* A theme post_render callback to allow content type plugins to use page
* template variables which are not yet available when the content type is
* rendered.
*/
function ctools_page_token_processing($children, $elements) {
$tokens = ctools_set_page_token();
if (!empty($tokens)) {
foreach ($tokens as $token => $key) {
list($type, $argument) = $key;
switch ($type) {
case 'variable':
$tokens[$token] = isset($variables[$argument]) ? $variables[$argument] : '';
break;
case 'callback':
if (is_string($argument) && function_exists($argument)) {
$tokens[$token] = $argument($variables);
}
if (is_array($argument) && function_exists($argument[0])) {
$function = array_shift($argument);
$argument = array_merge(array(&$variables), $argument);
$tokens[$token] = call_user_func_array($function, $argument);
}
break;
}
}
$children = strtr($children, $tokens);
}
return $children;
}
/**
* Implements hook_process().
*
* Add and remove CSS classes from the variables array. We use process so that
* we alter anything added in the preprocess hooks.
*/
function ctools_process(&$variables, $hook) {
if (!isset($variables['classes'])) {
return;
}
$classes = drupal_static('ctools_process_classes', array());
// Process the classses to add.
if (!empty($classes[$hook]['add'])) {
$add_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['add']);
$variables['classes_array'] = array_unique(array_merge($variables['classes_array'], $add_classes));
}
// Process the classes to remove.
if (!empty($classes[$hook]['remove'])) {
$remove_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['remove']);
$variables['classes_array'] = array_diff($variables['classes_array'], $remove_classes);
}
// Since this runs after template_process(), we need to re-implode the
// classes array.
$variables['classes'] = implode(' ', $variables['classes_array']);
}
// -----------------------------------------------------------------------
// Menu callbacks that must be in the .module file.
/**
* Determine if the current user has access via a plugin.
*
* This function is meant to be embedded in the Drupal menu system, and
* therefore is in the .module file since sub files can't be loaded, and
* takes arguments a little bit more haphazardly than ctools_access().
*
* @param $access
* An access control array which contains the following information:
* - 'logic': and or or. Whether all tests must pass or one must pass.
* - 'plugins': An array of access plugins. Each contains:
* - - 'name': The name of the plugin
* - - 'settings': The settings from the plugin UI.
* - - 'context': Which context to use.
* @param ...
* zero or more context arguments generated from argument plugins. These
* contexts must have an 'id' attached to them so that they can be
* properly associated. The argument plugin system should set this, but
* if the context is coming from elsewhere it will need to be set manually.
*
* @return
* TRUE if access is granted, false if otherwise.
*/
function ctools_access_menu($access) {
// Short circuit everything if there are no access tests.
if (empty($access['plugins'])) {
return TRUE;
}
$contexts = array();
foreach (func_get_args() as $arg) {
if (is_object($arg) && get_class($arg) == 'ctools_context') {
$contexts[$arg->id] = $arg;
}
}
ctools_include('context');
return ctools_access($access, $contexts);
}
/**
* Determine if the current user has access via checks to multiple different
* permissions.
*
* This function is a thin wrapper around user_access that allows multiple
* permissions to be easily designated for use on, for example, a menu callback.
*
* @param ...
* An indexed array of zero or more permission strings to be checked by
* user_access().
*
* @return
* Iff all checks pass will this function return TRUE. If an invalid argument
* is passed (e.g., not a string), this function errs on the safe said and
* returns FALSE.
*/
function ctools_access_multiperm() {
foreach (func_get_args() as $arg) {
if (!is_string($arg) || !user_access($arg)) {
return FALSE;
}
}
return TRUE;
}
/**
* Check to see if the incoming menu item is js capable or not.
*
* This can be used as %ctools_js as part of a path in hook menu. CTools
* ajax functions will automatically change the phrase 'nojs' to 'ajax'
* when it attaches ajax to a link. This can be used to autodetect if
* that happened.
*/
function ctools_js_load($js) {
if ($js == 'ajax') {
return TRUE;
}
return 0;
}
/**
* Menu _load hook.
*
* This function will be called to load an object as a replacement for
* %ctools_export_ui in menu paths.
*/
function ctools_export_ui_load($item_name, $plugin_name) {
$return = &drupal_static(__FUNCTION__, FALSE);
if (!$return) {
ctools_include('export-ui');
$plugin = ctools_get_export_ui($plugin_name);
$handler = ctools_export_ui_get_handler($plugin);
if ($handler) {
return $handler->load_item($item_name);
}
}
return $return;
}
// -----------------------------------------------------------------------
// Caching callbacks on behalf of export-ui.
/**
* Menu access callback for various tasks of export-ui.
*/
function ctools_export_ui_task_access($plugin_name, $op, $item = NULL) {
ctools_include('export-ui');
$plugin = ctools_get_export_ui($plugin_name);
$handler = ctools_export_ui_get_handler($plugin);
if ($handler) {
return $handler->access($op, $item);
}
// Deny access if the handler cannot be found.
return FALSE;
}
/**
* Callback for access control ajax form on behalf of export ui.
*
* Returns the cached access config and contexts used.
* Note that this is assuming that access will be in $item->access -- if it
* is not, an export UI plugin will have to make its own callbacks.
*/
function ctools_export_ui_ctools_access_get($argument) {
ctools_include('export-ui');
list($plugin_name, $key) = explode(':', $argument, 2);
$plugin = ctools_get_export_ui($plugin_name);
$handler = ctools_export_ui_get_handler($plugin);
if ($handler) {
ctools_include('context');
$item = $handler->edit_cache_get($key);
if (!$item) {
$item = ctools_export_crud_load($handler->plugin['schema'], $key);
}
$contexts = ctools_context_load_contexts($item);
return array($item->access, $contexts);
}
}
/**
* Callback for access control ajax form on behalf of export ui
*
* Returns the cached access config and contexts used.
* Note that this is assuming that access will be in $item->access -- if it
* is not, an export UI plugin will have to make its own callbacks.
*/
function ctools_export_ui_ctools_access_set($argument, $access) {
ctools_include('export-ui');
list($plugin_name, $key) = explode(':', $argument, 2);
$plugin = ctools_get_export_ui($plugin_name);
$handler = ctools_export_ui_get_handler($plugin);
if ($handler) {
ctools_include('context');
$item = $handler->edit_cache_get($key);
if (!$item) {
$item = ctools_export_crud_load($handler->plugin['schema'], $key);
}
$item->access = $access;
return $handler->edit_cache_set_key($item, $key);
}
}
/**
* Implements hook_menu_local_tasks_alter().
*/
function ctools_menu_local_tasks_alter(&$data, $router_item, $root_path) {
ctools_include('menu');
_ctools_menu_add_dynamic_items($data, $router_item, $root_path);
}
/**
* Implement hook_block_list_alter() to potentially remove blocks.
*
* This exists in order to replicate Drupal 6's "no blocks" functionality.
*/
function ctools_block_list_alter(&$blocks) {
$check = drupal_static('ctools_set_no_blocks', TRUE);
if (!$check) {
foreach ($blocks as $block_id => $block) {
// @todo -- possibly we can set configuration for this so that users can
// specify which blocks will not get rendered.
if (strpos($block->region, 'sidebar') !== FALSE) {
unset($blocks[$block_id]);
}
}
}
}
/**
* Implement hook_modules_enabled to clear static caches for detecting new plugins
*/
function ctools_modules_enabled($modules) {
ctools_include('plugins');
ctools_get_plugins_reset();
}
/**
* Menu theme callback.
*
* This simply ensures that Panels ajax calls are rendered in the same
* theme as the original page to prevent .css file confusion.
*
* To use this, set this as the theme callback on AJAX related menu
* items. Since the ajax page state won't be sent during ajax requests,
* it should be safe to use even if ajax isn't invoked.
*/
function ctools_ajax_theme_callback() {
if (!empty($_POST['ajax_page_state']['theme'])) {
return $_POST['ajax_page_state']['theme'];
}
}
/**
* Implements hook_ctools_entity_context_alter().
*/
function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
ctools_include('context');
switch ($plugin_id) {
case 'entity_id:taxonomy_term':
$plugin['no ui'] = TRUE;
break;
case 'entity:user':
$plugin = ctools_get_context('user');
unset($plugin['no ui']);
unset($plugin['no required context ui']);
break;
}
// Apply restrictions on taxonomy term reverse relationships whose
// restrictions are in the settings on the field.
if (!empty($plugin['parent']) &&
$plugin['parent'] == 'entity_from_field' &&
!empty($plugin['reverse']) &&
$plugin['to entity'] == 'taxonomy_term') {
$field = field_info_field($plugin['field name']);
if (isset($field['settings']['allowed_values'][0]['vocabulary'])) {
$plugin['required context']->restrictions = array('vocabulary' => array($field['settings']['allowed_values'][0]['vocabulary']));
}
}
}

View File

@@ -0,0 +1,12 @@
name = Custom rulesets
description = Create custom, exportable, reusable access rulesets for applications like Panels.
core = 7.x
package = Chaos tool suite
dependencies[] = ctools
; Information added by drupal.org packaging script on 2013-04-03
version = "7.x-1.3"
core = "7.x"
project = "ctools"
datestamp = "1365013512"

View File

@@ -0,0 +1,82 @@
<?php
/**
* Schema for customizable access rulesets.
*/
function ctools_access_ruleset_schema() {
return ctools_access_ruleset_schema_1();
}
function ctools_access_ruleset_schema_1() {
$schema = array();
$schema['ctools_access_ruleset'] = array(
'description' => 'Contains exportable customized access rulesets.',
'export' => array(
'identifier' => 'ruleset',
'bulk export' => TRUE,
'primary key' => 'rsid',
'api' => array(
'owner' => 'ctools_access_ruleset',
'api' => 'ctools_rulesets',
'minimum_version' => 1,
'current_version' => 1,
),
),
'fields' => array(
'rsid' => array(
'type' => 'serial',
'description' => 'A database primary key to ensure uniqueness',
'not null' => TRUE,
'no export' => TRUE,
),
'name' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Unique ID for this ruleset. Used to identify it programmatically.',
),
'admin_title' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative title for this ruleset.',
),
'admin_description' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Administrative description for this ruleset.',
'object default' => '',
),
'requiredcontexts' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Any required contexts for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
'contexts' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Any embedded contexts for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
'relationships' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Any relationships for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
'access' => array(
'type' => 'text',
'size' => 'big',
'description' => 'The actual group of access plugins for this ruleset.',
'serialize' => TRUE,
'object default' => array(),
),
),
'primary key' => array('rsid'),
);
return $schema;
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* @file
* ctools_access_ruleset module
*
* This module allows styles to be created and managed on behalf of modules
* that implement styles.
*
* The ctools_access_ruleset tool allows recolorable styles to be created via a miniature
* scripting language. Panels utilizes this to allow administrators to add
* styles directly to any panel display.
*/
/**
* Implementation of hook_permission()
*/
function ctools_access_ruleset_permission() {
return array(
'administer ctools access ruleset' => array(
'title' => t('Administer access rulesets'),
'description' => t('Add, delete and edit custom access rulesets.'),
),
);
}
/**
* Implementation of hook_ctools_plugin_directory() to let the system know
* we implement task and task_handler plugins.
*/
function ctools_access_ruleset_ctools_plugin_directory($module, $plugin) {
// Most of this module is implemented as an export ui plugin, and the
// rest is in ctools/includes/ctools_access_ruleset.inc
if ($module == 'ctools' && ($plugin == 'export_ui' || $plugin == 'access')) {
return 'plugins/' . $plugin;
}
}
/**
* Implementation of hook_panels_dashboard_blocks().
*
* Adds page information to the Panels dashboard.
*/
function ctools_access_ruleset_panels_dashboard_blocks(&$vars) {
$vars['links']['ctools_access_ruleset'] = array(
'title' => l(t('Custom ruleset'), 'admin/structure/ctools-rulesets/add'),
'description' => t('Custom rulesets are combinations of access plugins you can use for access control, selection criteria and pane visibility.'),
);
// Load all mini panels and their displays.
ctools_include('export');
$items = ctools_export_crud_load_all('ctools_access_ruleset');
$count = 0;
$rows = array();
foreach ($items as $item) {
$rows[] = array(
check_plain($item->admin_title),
array(
'data' => l(t('Edit'), "admin/structure/ctools-rulesets/list/$item->name/edit"),
'class' => 'links',
),
);
// Only show 10.
if (++$count >= 10) {
break;
}
}
if ($rows) {
$content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
}
else {
$content = '<p>' . t('There are no custom rulesets.') . '</p>';
}
$vars['blocks']['ctools_access_ruleset'] = array(
'title' => t('Manage custom rulesets'),
'link' => l(t('Go to list'), 'admin/structure/ctools-rulesets'),
'content' => $content,
'class' => 'dashboard-ruleset',
'section' => 'right',
);
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* @file
* Plugin to provide access control based on user rulesetission strings.
*/
/**
* Plugins are described by creating a $plugin array which will be used
* by the system that includes this file.
*/
$plugin = array(
'title' => '',
'description' => '',
'callback' => 'ctools_ruleset_ctools_access_check',
'settings form' => 'ctools_ruleset_ctools_access_settings',
'summary' => 'ctools_ruleset_ctools_access_summary',
// This access plugin actually just contains child plugins that are
// exportable, UI configured rulesets.
'get child' => 'ctools_ruleset_ctools_access_get_child',
'get children' => 'ctools_ruleset_ctools_access_get_children',
);
/**
* Merge the main access plugin with a loaded ruleset to form a child plugin.
*/
function ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item) {
$plugin['name'] = $parent . ':' . $item->name;
$plugin['title'] = check_plain($item->admin_title);
$plugin['description'] = check_plain($item->admin_description);
// TODO: Generalize this in CTools.
if (!empty($item->requiredcontexts)) {
$plugin['required context'] = array();
foreach ($item->requiredcontexts as $context) {
$info = ctools_get_context($context['name']);
// TODO: allow an optional setting
$plugin['required context'][] = new ctools_context_required($context['identifier'], $info['context name']);
}
}
// Store the loaded ruleset in the plugin.
$plugin['ruleset'] = $item;
return $plugin;
}
/**
* Get a single child access plugin.
*/
function ctools_ruleset_ctools_access_get_child($plugin, $parent, $child) {
ctools_include('export');
$item = ctools_export_crud_load('ctools_access_ruleset', $child);
if ($item) {
return ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item);
}
}
/**
* Get all child access plugins.
*/
function ctools_ruleset_ctools_access_get_children($plugin, $parent) {
$plugins = array();
ctools_include('export');
$items = ctools_export_crud_load_all('ctools_access_ruleset');
foreach ($items as $name => $item) {
$child = ctools_ruleset_ctools_access_merge_plugin($plugin, $parent, $item);
$plugins[$child['name']] = $child;
}
return $plugins;
}
/**
* Settings form for the 'by ruleset' access plugin
*/
function ctools_ruleset_ctools_access_settings(&$form, &$form_state, $conf) {
if (!empty($form_state['plugin']['ruleset']->admin_description)) {
$form['markup'] = array(
'#markup' => '<div class="description">' . check_plain($form_state['plugin']['ruleset']->admin_description) . '</div>',
);
}
return $form;
}
/**
* Check for access.
*/
function ctools_ruleset_ctools_access_check($conf, $context, $plugin) {
// Load up any contexts we might be using.
$contexts = ctools_context_match_required_contexts($plugin['ruleset']->requiredcontexts, $context);
$contexts = ctools_context_load_contexts($plugin['ruleset'], FALSE, $contexts);
return ctools_access($plugin['ruleset']->access, $contexts);
}
/**
* Provide a summary description based upon the checked roles.
*/
function ctools_ruleset_ctools_access_summary($conf, $context, $plugin) {
if (!empty($plugin['ruleset']->admin_description)) {
return check_plain($plugin['ruleset']->admin_description);
}
else {
return check_plain($plugin['ruleset']->admin_title);
}
}

View File

@@ -0,0 +1,29 @@
<?php
$plugin = array(
'schema' => 'ctools_access_ruleset',
'access' => 'administer ctools access ruleset',
'menu' => array(
'menu item' => 'ctools-rulesets',
'menu title' => 'Custom access rulesets',
'menu description' => 'Add, edit or delete custom access rulesets for use with Panels and other systems that utilize CTools content plugins.',
),
'title singular' => t('ruleset'),
'title singular proper' => t('Ruleset'),
'title plural' => t('rulesets'),
'title plural proper' => t('Rulesets'),
'handler' => 'ctools_access_ruleset_ui',
'use wizard' => TRUE,
'form info' => array(
'order' => array(
'basic' => t('Basic information'),
'context' => t('Contexts'),
'rules' => t('Rules'),
),
),
);

View File

@@ -0,0 +1,53 @@
<?php
class ctools_access_ruleset_ui extends ctools_export_ui {
function edit_form_context(&$form, &$form_state) {
ctools_include('context-admin');
ctools_context_admin_includes();
ctools_add_css('ruleset');
$form['right'] = array(
'#prefix' => '<div class="ctools-right-container">',
'#suffix' => '</div>',
);
$form['left'] = array(
'#prefix' => '<div class="ctools-left-container clearfix">',
'#suffix' => '</div>',
);
// Set this up and we can use CTools' Export UI's built in wizard caching,
// which already has callbacks for the context cache under this name.
$module = 'export_ui::' . $this->plugin['name'];
$name = $this->edit_cache_get_key($form_state['item'], $form_state['form type']);
ctools_context_add_context_form($module, $form, $form_state, $form['right']['contexts_table'], $form_state['item'], $name);
ctools_context_add_required_context_form($module, $form, $form_state, $form['left']['required_contexts_table'], $form_state['item'], $name);
ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $form_state['item'], $name);
}
function edit_form_rules(&$form, &$form_state) {
// The 'access' UI passes everything via $form_state, unlike the 'context' UI.
// The main difference is that one is about 3 years newer than the other.
ctools_include('context');
ctools_include('context-access-admin');
$form_state['access'] = $form_state['item']->access;
$form_state['contexts'] = ctools_context_load_contexts($form_state['item']);
$form_state['module'] = 'ctools_export_ui';
$form_state['callback argument'] = $form_state['object']->plugin['name'] . ':' . $form_state['object']->edit_cache_get_key($form_state['item'], $form_state['form type']);
$form_state['no buttons'] = TRUE;
$form = ctools_access_admin_form($form, $form_state);
}
function edit_form_rules_submit(&$form, &$form_state) {
$form_state['item']->access['logic'] = $form_state['values']['logic'];
}
function edit_form_submit(&$form, &$form_state) {
parent::edit_form_submit($form, $form_state);
}
}

View File

@@ -0,0 +1,134 @@
div.ctools-sample-modal-content {
background:none;
border:0;
color:#000000;
margin:0;
padding:0;
text-align:left;
}
div.ctools-sample-modal-content .modal-scroll{
overflow:hidden;
overflow-y:auto;
}
div.ctools-sample-modal-content #popups-overlay {
background-color:transparent;
}
div.ctools-sample-modal-content #popups-loading {
width:248px;
position:absolute;
display:none;
opacity:1;
-moz-border-radius: 8px;
-webkit-border-radius: 8px;
z-index:99;
}
div.ctools-sample-modal-content #popups-loading span.popups-loading-message {
background:#FFF url(../images/loading-large.gif) no-repeat 8px center;
display:block;
color:#444444;
font-family:Arial;
font-size:22px;
font-weight:bold;
height:36px;
line-height:36px;
padding:0 40px;
}
div.ctools-sample-modal-content #popups-loading table,
div.ctools-sample-modal-content .popups-box table {
margin:0px;
}
div.ctools-sample-modal-content #popups-loading tbody,
div.ctools-sample-modal-content .popups-box tbody {
border:none;
}
div.ctools-sample-modal-content .popups-box tr {
background-color:transparent;
}
div.ctools-sample-modal-content td.popups-border {
background: url(../images/popups-border.png);
background-color:transparent;
border: none;
}
div.ctools-sample-modal-content td.popups-tl,
div.ctools-sample-modal-content td.popups-tr,
div.ctools-sample-modal-content td.popups-bl,
div.ctools-sample-modal-content td.popups-br {
background-repeat: no-repeat;
height:10px;
padding:0px;
}
div.ctools-sample-modal-content td.popups-tl { background-position: 0px 0px; }
div.ctools-sample-modal-content td.popups-t,
div.ctools-sample-modal-content td.popups-b {
background-position: 0px -40px;
background-repeat: repeat-x;
}
div.ctools-sample-modal-content td.popups-tr { background-position: 0px -10px; width: 10px; }
div.ctools-sample-modal-content td.popups-cl,
div.ctools-sample-modal-content td.popups-cr {
background-position: -10px 0;
background-repeat: repeat-y;
width:10px;
}
div.ctools-sample-modal-content td.popups-cl,
div.ctools-sample-modal-content td.popups-cr,
div.ctools-sample-modal-content td.popups-c { padding:0; border: none; }
div.ctools-sample-modal-content td.popups-c { background:#fff; }
div.ctools-sample-modal-content td.popups-bl { background-position: 0px -20px; }
div.ctools-sample-modal-content td.popups-br { background-position: 0px -30px; width: 10px; }
div.ctools-sample-modal-content .popups-box,
div.ctools-sample-modal-content #popups-loading {
border: 0px solid #454545;
opacity:1;
overflow:hidden;
padding:0;
background-color:transparent;
}
div.ctools-sample-modal-content .popups-container {
overflow:hidden;
height:100%;
background-color:#fff;
}
div.ctools-sample-modal-content div.popups-title {
-moz-border-radius-topleft: 0px;
-webkit-border-radius-topleft: 0px;
margin-bottom:0px;
background-color:#ff7200;
border:1px solid #ce5c00;
padding:4px 10px 5px;
color:white;
font-size:1em;
font-weight:bold;
}
div.ctools-sample-modal-content .popups-body {
background-color:#fff;
padding:8px;
}
div.ctools-sample-modal-content .popups-box .popups-buttons,
div.ctools-sample-modal-content .popups-box .popups-footer {
background-color:#fff;
}
div.ctools-sample-modal-content .popups-title a.close {
color: #fff;
text-decoration:none;
}
div.ctools-sample-modal-content .popups-close {
font-size:120%;
float:right;
text-align:right;
}
div.ctools-sample-modal-content .modal-loading-wrapper {
width:220px;
height:19px;
margin:0 auto;
margin-top:2%;
}
div.ctools-sample-modal-content tbody{
border:none;
}
div.ctools-sample-modal-content .modal-content .modal-throbber-wrapper img {
margin-top: 100px;
}

View File

@@ -0,0 +1,12 @@
name = Chaos Tools (CTools) AJAX Example
description = Shows how to use the power of Chaos AJAX.
package = Chaos tool suite
dependencies[] = ctools
core = 7.x
; Information added by drupal.org packaging script on 2013-04-03
version = "7.x-1.3"
core = "7.x"
project = "ctools"
datestamp = "1365013512"

View File

@@ -0,0 +1,19 @@
<?php
/**
* @file
*/
/**
* Implementation of hook_install()
*/
function ctools_ajax_sample_install() {
}
/**
* Implementation of hook_uninstall()
*/
function ctools_ajax_sample_uninstall() {
}

View File

@@ -0,0 +1,756 @@
<?php
/**
* @file
* Sample AJAX functionality so people can see some of the CTools AJAX
* features in use.
*/
// ---------------------------------------------------------------------------
// Drupal hooks.
/**
* Implementation of hook_menu()
*/
function ctools_ajax_sample_menu() {
$items['ctools_ajax_sample'] = array(
'title' => 'Chaos Tools AJAX Demo',
'page callback' => 'ctools_ajax_sample_page',
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
);
$items['ctools_ajax_sample/simple_form'] = array(
'title' => 'Simple Form',
'page callback' => 'ctools_ajax_simple_form',
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/hello'] = array(
'title' => 'Hello World',
'page callback' => 'ctools_ajax_sample_hello',
'page arguments' => array(1),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/tablenix/%'] = array(
'title' => 'Hello World',
'page callback' => 'ctools_ajax_sample_tablenix',
'page arguments' => array(1, 3),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/login'] = array(
'title' => 'Login',
'page callback' => 'ctools_ajax_sample_login',
'page arguments' => array(1),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/animal'] = array(
'title' => 'Animal',
'page callback' => 'ctools_ajax_sample_animal',
'page arguments' => array(1),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/%ctools_js/login/%'] = array(
'title' => 'Post-Login Action',
'page callback' => 'ctools_ajax_sample_login_success',
'page arguments' => array(1, 3),
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['ctools_ajax_sample/jumped'] = array(
'title' => 'Successful Jumping',
'page callback' => 'ctools_ajax_sample_jump_menu_page',
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
function ctools_ajax_simple_form() {
ctools_include('content');
ctools_include('context');
$node = node_load(1);
$context = ctools_context_create('node', $node);
$context = array('context_node_1' => $context);
return ctools_content_render('node_comment_form', 'node_comment_form', ctools_ajax_simple_form_pane(), array(), array(), $context);
}
function ctools_ajax_simple_form_pane() {
$configuration = array(
'anon_links' => 0,
'context' => 'context_node_1',
'override_title' => 0,
'override_title_text' => '',
);
return $configuration;
}
/**
* Implementation of hook_theme()
*
* Render some basic output for this module.
*/
function ctools_ajax_sample_theme() {
return array(
// Sample theme functions.
'ctools_ajax_sample_container' => array(
'arguments' => array('content' => NULL),
),
);
}
// ---------------------------------------------------------------------------
// Page callbacks
/**
* Page callback to display links and render a container for AJAX stuff.
*/
function ctools_ajax_sample_page() {
global $user;
// Include the CTools tools that we need.
ctools_include('ajax');
ctools_include('modal');
// Add CTools' javascript to the page.
ctools_modal_add_js();
// Create our own javascript that will be used to theme a modal.
$sample_style = array(
'ctools-sample-style' => array(
'modalSize' => array(
'type' => 'fixed',
'width' => 500,
'height' => 300,
'addWidth' => 20,
'addHeight' => 15,
),
'modalOptions' => array(
'opacity' => .5,
'background-color' => '#000',
),
'animation' => 'fadeIn',
'modalTheme' => 'CToolsSampleModal',
'throbber' => theme('image', array('path' => ctools_image_path('ajax-loader.gif', 'ctools_ajax_sample'), 'alt' => t('Loading...'), 'title' => t('Loading'))),
),
);
drupal_add_js($sample_style, 'setting');
// Since we have our js, css and images in well-known named directories,
// CTools makes it easy for us to just use them without worrying about
// using drupal_get_path() and all that ugliness.
ctools_add_js('ctools-ajax-sample', 'ctools_ajax_sample');
ctools_add_css('ctools-ajax-sample', 'ctools_ajax_sample');
// Create a list of clickable links.
$links = array();
// Only show login links to the anonymous user.
if ($user->uid == 0) {
$links[] = ctools_modal_text_button(t('Modal Login (default style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'));
// The extra class points to the info in ctools-sample-style which we added
// to the settings, prefixed with 'ctools-modal'.
$links[] = ctools_modal_text_button(t('Modal Login (custom style)'), 'ctools_ajax_sample/nojs/login', t('Login via modal'), 'ctools-modal-ctools-sample-style');
}
// Four ways to do our animal picking wizard.
$button_form = ctools_ajax_sample_ajax_button_form();
$links[] = l(t('Wizard (no modal)'), 'ctools_ajax_sample/nojs/animal');
$links[] = ctools_modal_text_button(t('Wizard (default modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'));
$links[] = ctools_modal_text_button(t('Wizard (custom modal)'), 'ctools_ajax_sample/nojs/animal', t('Pick an animal'), 'ctools-modal-ctools-sample-style');
$links[] = drupal_render($button_form);
$links[] = ctools_ajax_text_button(t('Hello world!'), "ctools_ajax_sample/nojs/hello", t('Replace text with "hello world"'));
$output = theme('item_list', array('items' => $links, 'title' => t('Actions')));
// This container will have data AJAXed into it.
$output .= theme('ctools_ajax_sample_container', array('content' => '<h1>' . t('Sample Content') . '</h1>'));
// Create a table that we can have data removed from via AJAX.
$header = array(t('Row'), t('Content'), t('Actions'));
$rows = array();
for($i = 1; $i < 11; $i++) {
$rows[] = array(
'class' => array('ajax-sample-row-'. $i),
'data' => array(
$i,
md5($i),
ctools_ajax_text_button("remove", "ctools_ajax_sample/nojs/tablenix/$i", t('Delete this row')),
),
);
}
$output .= theme('table', array('header' => $header, 'rows' => $rows, array('class' => array('ajax-sample-table'))));
// Show examples of ctools javascript widgets
$output .= '<h2>'. t('CTools Javascript Widgets') .'</h2>';
// Create a drop down menu
$links = array();
$links[] = array('title' => t('Link 1'), 'href' => $_GET['q']);
$links[] = array('title' => t('Link 2'), 'href' => $_GET['q']);
$links[] = array('title' => t('Link 3'), 'href' => $_GET['q']);
$output .= '<h3>' . t('Drop Down Menu') . '</h3>';
$output .= theme('ctools_dropdown', array('title' => t('Click to Drop Down'), 'links' => $links));
// Create a collapsible div
$handle = t('Click to Collapse');
$content = 'Nulla ligula ante, aliquam at adipiscing egestas, varius vel arcu. Etiam laoreet elementum mi vel consequat. Etiam scelerisque lorem vel neque consequat quis bibendum libero congue. Nulla facilisi. Mauris a elit a leo feugiat porta. Phasellus placerat cursus est vitae elementum.';
$output .= '<h3>'. t('Collapsible Div') .'</h3>';
$output .= theme('ctools_collapsible', array('handle' => $handle, 'content' => $content, 'collapsed' => FALSE));
// Create a jump menu
ctools_include('jump-menu');
$form = drupal_get_form('ctools_ajax_sample_jump_menu_form');
$output .= '<h3>'. t('Jump Menu') .'</h3>';
$output .= drupal_render($form);
return array('markup' => array('#markup' => $output));
}
/**
* Returns a "take it all over" hello world style request.
*/
function ctools_ajax_sample_hello($js = NULL) {
$output = '<h1>' . t('Hello World') . '</h1>';
if ($js) {
ctools_include('ajax');
$commands = array();
$commands[] = ajax_command_html('#ctools-sample', $output);
print ajax_render($commands); // this function exits.
exit;
}
else {
return $output;
}
}
/**
* Nix a row from a table and restripe.
*/
function ctools_ajax_sample_tablenix($js, $row) {
if (!$js) {
// We don't support degrading this from js because we're not
// using the server to remember the state of the table.
return MENU_ACCESS_DENIED;
}
ctools_include('ajax');
$commands = array();
$commands[] = ajax_command_remove("tr.ajax-sample-row-$row");
$commands[] = ajax_command_restripe("table.ajax-sample-table");
print ajax_render($commands);
exit;
}
/**
* A modal login callback.
*/
function ctools_ajax_sample_login($js = NULL) {
// Fall back if $js is not set.
if (!$js) {
return drupal_get_form('user_login');
}
ctools_include('modal');
ctools_include('ajax');
$form_state = array(
'title' => t('Login'),
'ajax' => TRUE,
);
$output = ctools_modal_form_wrapper('user_login', $form_state);
if (!empty($form_state['executed'])) {
// We'll just overwrite the form output if it was successful.
$output = array();
$inplace = ctools_ajax_text_button(t('remain here'), 'ctools_ajax_sample/nojs/login/inplace', t('Go to your account'));
$account = ctools_ajax_text_button(t('your account'), 'ctools_ajax_sample/nojs/login/user', t('Go to your account'));
$output[] = ctools_modal_command_display(t('Login Success'), '<div class="modal-message">Login successful. You can now choose whether to '. $inplace .', or go to '. $account.'.</div>');
}
print ajax_render($output);
exit;
}
/**
* Post-login processor: should we go to the user account or stay in place?
*/
function ctools_ajax_sample_login_success($js, $action) {
if (!$js) {
// we should never be here out of ajax context
return MENU_NOT_FOUND;
}
ctools_include('ajax');
ctools_add_js('ajax-responder');
$commands = array();
if ($action == 'inplace') {
// stay here
$commands[] = ctools_ajax_command_reload();
}
else {
// bounce bounce
$commands[] = ctools_ajax_command_redirect('user');
}
print ajax_render($commands);
exit;
}
/**
* A modal login callback.
*/
function ctools_ajax_sample_animal($js = NULL, $step = NULL) {
if ($js) {
ctools_include('modal');
ctools_include('ajax');
}
$form_info = array(
'id' => 'animals',
'path' => "ctools_ajax_sample/" . ($js ? 'ajax' : 'nojs') . "/animal/%step",
'show trail' => TRUE,
'show back' => TRUE,
'show cancel' => TRUE,
'show return' => FALSE,
'next callback' => 'ctools_ajax_sample_wizard_next',
'finish callback' => 'ctools_ajax_sample_wizard_finish',
'cancel callback' => 'ctools_ajax_sample_wizard_cancel',
// this controls order, as well as form labels
'order' => array(
'start' => t('Choose animal'),
),
// here we map a step to a form id.
'forms' => array(
// e.g. this for the step at wombat/create
'start' => array(
'form id' => 'ctools_ajax_sample_start'
),
),
);
// We're not using any real storage here, so we're going to set our
// object_id to 1. When using wizard forms, id management turns
// out to be one of the hardest parts. Editing an object with an id
// is easy, but new objects don't usually have ids until somewhere
// in creation.
//
// We skip all this here by just using an id of 1.
$object_id = 1;
if (empty($step)) {
// We reset the form when $step is NULL because that means they have
// for whatever reason started over.
ctools_ajax_sample_cache_clear($object_id);
$step = 'start';
}
// This automatically gets defaults if there wasn't anything saved.
$object = ctools_ajax_sample_cache_get($object_id);
$animals = ctools_ajax_sample_animals();
// Make sure we can't somehow accidentally go to an invalid animal.
if (empty($animals[$object->type])) {
$object->type = 'unknown';
}
// Now that we have our object, dynamically add the animal's form.
if ($object->type == 'unknown') {
// If they haven't selected a type, add a form that doesn't exist yet.
$form_info['order']['unknown'] = t('Configure animal');
$form_info['forms']['unknown'] = array('form id' => 'nothing');
}
else {
// Add the selected animal to the order so that it shows up properly in the trail.
$form_info['order'][$object->type] = $animals[$object->type]['config title'];
}
// Make sure all animals forms are represented so that the next stuff can
// work correctly:
foreach ($animals as $id => $animal) {
$form_info['forms'][$id] = array('form id' => $animals[$id]['form']);
}
$form_state = array(
'ajax' => $js,
// Put our object and ID into the form state cache so we can easily find
// it.
'object_id' => $object_id,
'object' => &$object,
);
// Send this all off to our form. This is like drupal_get_form only wizardy.
ctools_include('wizard');
$form = ctools_wizard_multistep_form($form_info, $step, $form_state);
$output = drupal_render($form);
if ($output === FALSE || !empty($form_state['complete'])) {
// This creates a string based upon the animal and its setting using
// function indirection.
$animal = $animals[$object->type]['output']($object);
}
// If $output is FALSE, there was no actual form.
if ($js) {
// If javascript is active, we have to use a render array.
$commands = array();
if ($output === FALSE || !empty($form_state['complete'])) {
// Dismiss the modal.
$commands[] = ajax_command_html('#ctools-sample', $animal);
$commands[] = ctools_modal_command_dismiss();
}
else if (!empty($form_state['cancel'])) {
// If cancelling, return to the activity.
$commands[] = ctools_modal_command_dismiss();
}
else {
$commands = ctools_modal_form_render($form_state, $output);
}
print ajax_render($commands);
exit;
}
else {
if ($output === FALSE || !empty($form_state['complete'])) {
return $animal;
}
else if (!empty($form_state['cancel'])) {
drupal_goto('ctools_ajax_sample');
}
else {
return $output;
}
}
}
// ---------------------------------------------------------------------------
// Themes
/**
* Theme function for main rendered output.
*/
function theme_ctools_ajax_sample_container($vars) {
$output = '<div id="ctools-sample">';
$output .= $vars['content'];
$output .= '</div>';
return $output;
}
// ---------------------------------------------------------------------------
// Stuff needed for our little wizard.
/**
* Get a list of our animals and associated forms.
*
* What we're doing is making it easy to add more animals in just one place,
* which is often how it will work in the real world. If using CTools, what
* you would probably really have, here, is a set of plugins for each animal.
*/
function ctools_ajax_sample_animals() {
return array(
'sheep' => array(
'title' => t('Sheep'),
'config title' => t('Configure sheep'),
'form' => 'ctools_ajax_sample_configure_sheep',
'output' => 'ctools_ajax_sample_show_sheep',
),
'lizard' => array(
'title' => t('Lizard'),
'config title' => t('Configure lizard'),
'form' => 'ctools_ajax_sample_configure_lizard',
'output' => 'ctools_ajax_sample_show_lizard',
),
'raptor' => array(
'title' => t('Raptor'),
'config title' => t('Configure raptor'),
'form' => 'ctools_ajax_sample_configure_raptor',
'output' => 'ctools_ajax_sample_show_raptor',
),
);
}
// ---------------------------------------------------------------------------
// Wizard caching helpers.
/**
* Store our little cache so that we can retain data from form to form.
*/
function ctools_ajax_sample_cache_set($id, $object) {
ctools_include('object-cache');
ctools_object_cache_set('ctools_ajax_sample', $id, $object);
}
/**
* Get the current object from the cache, or default.
*/
function ctools_ajax_sample_cache_get($id) {
ctools_include('object-cache');
$object = ctools_object_cache_get('ctools_ajax_sample', $id);
if (!$object) {
// Create a default object.
$object = new stdClass;
$object->type = 'unknown';
$object->name = '';
}
return $object;
}
/**
* Clear the wizard cache.
*/
function ctools_ajax_sample_cache_clear($id) {
ctools_include('object-cache');
ctools_object_cache_clear('ctools_ajax_sample', $id);
}
// ---------------------------------------------------------------------------
// Wizard in-between helpers; what to do between or after forms.
/**
* Handle the 'next' click on the add/edit pane form wizard.
*
* All we need to do is store the updated pane in the cache.
*/
function ctools_ajax_sample_wizard_next(&$form_state) {
ctools_ajax_sample_cache_set($form_state['object_id'], $form_state['object']);
}
/**
* Handle the 'finish' click on teh add/edit pane form wizard.
*
* All we need to do is set a flag so the return can handle adding
* the pane.
*/
function ctools_ajax_sample_wizard_finish(&$form_state) {
$form_state['complete'] = TRUE;
}
/**
* Handle the 'cancel' click on the add/edit pane form wizard.
*/
function ctools_ajax_sample_wizard_cancel(&$form_state) {
$form_state['cancel'] = TRUE;
}
// ---------------------------------------------------------------------------
// Wizard forms for our simple info collection wizard.
/**
* Wizard start form. Choose an animal.
*/
function ctools_ajax_sample_start($form, &$form_state) {
$form_state['title'] = t('Choose animal');
$animals = ctools_ajax_sample_animals();
foreach ($animals as $id => $animal) {
$options[$id] = $animal['title'];
}
$form['type'] = array(
'#title' => t('Choose your animal'),
'#type' => 'radios',
'#options' => $options,
'#default_value' => $form_state['object']->type,
'#required' => TRUE,
);
return $form;
}
/**
* They have selected a sheep. Set it.
*/
function ctools_ajax_sample_start_submit(&$form, &$form_state) {
$form_state['object']->type = $form_state['values']['type'];
// Override where to go next based on the animal selected.
$form_state['clicked_button']['#next'] = $form_state['values']['type'];
}
/**
* Wizard form to configure your sheep.
*/
function ctools_ajax_sample_configure_sheep($form, &$form_state) {
$form_state['title'] = t('Configure sheep');
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name your sheep'),
'#default_value' => $form_state['object']->name,
'#required' => TRUE,
);
$form['sheep'] = array(
'#title' => t('What kind of sheep'),
'#type' => 'radios',
'#options' => array(
t('Wensleydale') => t('Wensleydale'),
t('Merino') => t('Merino'),
t('Corriedale') => t('Coriedale'),
),
'#default_value' => !empty($form_state['object']->sheep) ? $form_state['object']->sheep : '',
'#required' => TRUE,
);
return $form;
}
/**
* Submit the sheep and store the values from the form.
*/
function ctools_ajax_sample_configure_sheep_submit(&$form, &$form_state) {
$form_state['object']->name = $form_state['values']['name'];
$form_state['object']->sheep = $form_state['values']['sheep'];
}
/**
* Provide some output for our sheep.
*/
function ctools_ajax_sample_show_sheep($object) {
return t('You have a @type sheep named "@name".', array(
'@type' => $object->sheep,
'@name' => $object->name,
));
}
/**
* Wizard form to configure your lizard.
*/
function ctools_ajax_sample_configure_lizard($form, &$form_state) {
$form_state['title'] = t('Configure lizard');
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name your lizard'),
'#default_value' => $form_state['object']->name,
'#required' => TRUE,
);
$form['lizard'] = array(
'#title' => t('Venomous'),
'#type' => 'checkbox',
'#default_value' => !empty($form_state['object']->lizard),
);
return $form;
}
/**
* Submit the lizard and store the values from the form.
*/
function ctools_ajax_sample_configure_lizard_submit(&$form, &$form_state) {
$form_state['object']->name = $form_state['values']['name'];
$form_state['object']->lizard = $form_state['values']['lizard'];
}
/**
* Provide some output for our raptor.
*/
function ctools_ajax_sample_show_lizard($object) {
return t('You have a @type lizard named "@name".', array(
'@type' => empty($object->lizard) ? t('non-venomous') : t('venomous'),
'@name' => $object->name,
));
}
/**
* Wizard form to configure your raptor.
*/
function ctools_ajax_sample_configure_raptor($form, &$form_state) {
$form_state['title'] = t('Configure raptor');
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Name your raptor'),
'#default_value' => $form_state['object']->name,
'#required' => TRUE,
);
$form['raptor'] = array(
'#title' => t('What kind of raptor'),
'#type' => 'radios',
'#options' => array(
t('Eagle') => t('Eagle'),
t('Hawk') => t('Hawk'),
t('Owl') => t('Owl'),
t('Buzzard') => t('Buzzard'),
),
'#default_value' => !empty($form_state['object']->raptor) ? $form_state['object']->raptor : '',
'#required' => TRUE,
);
$form['domesticated'] = array(
'#title' => t('Domesticated'),
'#type' => 'checkbox',
'#default_value' => !empty($form_state['object']->domesticated),
);
return $form;
}
/**
* Submit the raptor and store the values from the form.
*/
function ctools_ajax_sample_configure_raptor_submit(&$form, &$form_state) {
$form_state['object']->name = $form_state['values']['name'];
$form_state['object']->raptor = $form_state['values']['raptor'];
$form_state['object']->domesticated = $form_state['values']['domesticated'];
}
/**
* Provide some output for our raptor.
*/
function ctools_ajax_sample_show_raptor($object) {
return t('You have a @type @raptor named "@name".', array(
'@type' => empty($object->domesticated) ? t('wild') : t('domesticated'),
'@raptor' => $object->raptor,
'@name' => $object->name,
));
}
/**
* Helper function to provide a sample jump menu form
*/
function ctools_ajax_sample_jump_menu_form() {
$url = url('ctools_ajax_sample/jumped');
$form_state = array();
$form = ctools_jump_menu(array(), $form_state, array($url => t('Jump!')), array());
return $form;
}
/**
* Provide a message to the user that the jump menu worked
*/
function ctools_ajax_sample_jump_menu_page() {
$return_link = l(t('Return to the examples page.'), 'ctools_ajax_sample');
$output = t('You successfully jumped! !return_link', array('!return_link' => $return_link));
return $output;
}
/**
* Provide a form for an example ajax modal button
*/
function ctools_ajax_sample_ajax_button_form() {
$form = array();
$form['url'] = array(
'#type' => 'hidden',
// The name of the class is the #id of $form['ajax_button'] with "-url"
// suffix.
'#attributes' => array('class' => array('ctools-ajax-sample-button-url')),
'#value' => url('ctools_ajax_sample/nojs/animal'),
);
$form['ajax_button'] = array(
'#type' => 'button',
'#value' => 'Wizard (button modal)',
'#attributes' => array('class' => array('ctools-use-modal')),
'#id' => 'ctools-ajax-sample-button',
);
return $form;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 B

View File

@@ -0,0 +1,42 @@
/**
* Provide the HTML to create the modal dialog.
*/
Drupal.theme.prototype.CToolsSampleModal = function () {
var html = '';
html += '<div id="ctools-modal" class="popups-box">';
html += ' <div class="ctools-modal-content ctools-sample-modal-content">';
html += ' <table cellpadding="0" cellspacing="0" id="ctools-face-table">';
html += ' <tr>';
html += ' <td class="popups-tl popups-border"></td>';
html += ' <td class="popups-t popups-border"></td>';
html += ' <td class="popups-tr popups-border"></td>';
html += ' </tr>';
html += ' <tr>';
html += ' <td class="popups-cl popups-border"></td>';
html += ' <td class="popups-c" valign="top">';
html += ' <div class="popups-container">';
html += ' <div class="modal-header popups-title">';
html += ' <span id="modal-title" class="modal-title"></span>';
html += ' <span class="popups-close"><a class="close" href="#">' + Drupal.CTools.Modal.currentSettings.closeText + '</a></span>';
html += ' <div class="clear-block"></div>';
html += ' </div>';
html += ' <div class="modal-scroll"><div id="modal-content" class="modal-content popups-body"></div></div>';
html += ' <div class="popups-buttons"></div>'; //Maybe someday add the option for some specific buttons.
html += ' <div class="popups-footer"></div>'; //Maybe someday add some footer.
html += ' </div>';
html += ' </td>';
html += ' <td class="popups-cr popups-border"></td>';
html += ' </tr>';
html += ' <tr>';
html += ' <td class="popups-bl popups-border"></td>';
html += ' <td class="popups-b popups-border"></td>';
html += ' <td class="popups-br popups-border"></td>';
html += ' </tr>';
html += ' </table>';
html += ' </div>';
html += '</div>';
return html;
}

View File

@@ -0,0 +1,12 @@
name = Custom content panes
description = Create custom, exportable, reusable content panes for applications like Panels.
core = 7.x
package = Chaos tool suite
dependencies[] = ctools
; Information added by drupal.org packaging script on 2013-04-03
version = "7.x-1.3"
core = "7.x"
project = "ctools"
datestamp = "1365013512"

View File

@@ -0,0 +1,67 @@
<?php
/**
* Schema for CTools custom content.
*/
function ctools_custom_content_schema() {
return ctools_custom_content_schema_1();
}
function ctools_custom_content_schema_1() {
$schema = array();
$schema['ctools_custom_content'] = array(
'description' => 'Contains exportable customized content for this site.',
'export' => array(
'identifier' => 'content',
'bulk export' => TRUE,
'primary key' => 'cid',
'api' => array(
'owner' => 'ctools_custom_content',
'api' => 'ctools_content',
'minimum_version' => 1,
'current_version' => 1,
),
'create callback' => 'ctools_content_type_new',
),
'fields' => array(
'cid' => array(
'type' => 'serial',
'description' => 'A database primary key to ensure uniqueness',
'not null' => TRUE,
'no export' => TRUE,
),
'name' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Unique ID for this content. Used to identify it programmatically.',
),
'admin_title' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative title for this content.',
),
'admin_description' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Administrative description for this content.',
'object default' => '',
),
'category' => array(
'type' => 'varchar',
'length' => '255',
'description' => 'Administrative category for this content.',
),
'settings' => array(
'type' => 'text',
'size' => 'big',
'description' => 'Serialized settings for the actual content to be used',
'serialize' => TRUE,
'object default' => array(),
),
),
'primary key' => array('cid'),
);
return $schema;
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* @file
* ctools_custom_content module
*
* This module allows styles to be created and managed on behalf of modules
* that implement styles.
*
* The ctools_custom_content tool allows recolorable styles to be created via a miniature
* scripting language. Panels utilizes this to allow administrators to add
* styles directly to any panel display.
*/
/**
* Implementation of hook_permission()
*/
function ctools_custom_content_permission() {
return array(
'administer custom content' => array(
'title' => t('Administer custom content'),
'description' => t('Add, edit and delete CTools custom stored custom content'),
),
);
}
/**
* Implementation of hook_ctools_plugin_directory() to let the system know
* we implement task and task_handler plugins.
*/
function ctools_custom_content_ctools_plugin_directory($module, $plugin) {
// Most of this module is implemented as an export ui plugin, and the
// rest is in ctools/includes/ctools_custom_content.inc
if ($module == 'ctools' && $plugin == 'export_ui') {
return 'plugins/' . $plugin;
}
}
/**
* Create callback for creating a new CTools custom content type.
*
* This ensures we get proper defaults from the plugin for its settings.
*/
function ctools_content_type_new($set_defaults) {
$item = ctools_export_new_object('ctools_custom_content', $set_defaults);
ctools_include('content');
$plugin = ctools_get_content_type('custom');
$item->settings = ctools_content_get_defaults($plugin, array());
return $item;
}
/**
* Implementation of hook_panels_dashboard_blocks().
*
* Adds page information to the Panels dashboard.
*/
function ctools_custom_content_panels_dashboard_blocks(&$vars) {
$vars['links']['ctools_custom_content'] = array(
'title' => l(t('Custom content'), 'admin/structure/ctools-content/add'),
'description' => t('Custom content panes are basic HTML you enter that can be reused in all of your panels.'),
);
// Load all mini panels and their displays.
ctools_include('export');
$items = ctools_export_crud_load_all('ctools_custom_content');
$count = 0;
$rows = array();
foreach ($items as $item) {
$rows[] = array(
check_plain($item->admin_title),
array(
'data' => l(t('Edit'), "admin/structure/ctools-content/list/$item->name/edit"),
'class' => 'links',
),
);
// Only show 10.
if (++$count >= 10) {
break;
}
}
if ($rows) {
$content = theme('table', array('rows' => $rows, 'attributes' => array('class' => 'panels-manage')));
}
else {
$content = '<p>' . t('There are no custom content panes.') . '</p>';
}
$vars['blocks']['ctools_custom_content'] = array(
'title' => t('Manage custom content'),
'link' => l(t('Go to list'), 'admin/structure/ctools-content'),
'content' => $content,
'class' => 'dashboard-content',
'section' => 'right',
);
}

View File

@@ -0,0 +1,20 @@
<?php
$plugin = array(
'schema' => 'ctools_custom_content',
'access' => 'administer custom content',
'menu' => array(
'menu item' => 'ctools-content',
'menu title' => 'Custom content panes',
'menu description' => 'Add, edit or delete custom content panes.',
),
'title singular' => t('content pane'),
'title singular proper' => t('Content pane'),
'title plural' => t('content panes'),
'title plural proper' => t('Content panes'),
'handler' => 'ctools_custom_content_ui',
);

View File

@@ -0,0 +1,129 @@
<?php
class ctools_custom_content_ui extends ctools_export_ui {
function edit_form(&$form, &$form_state) {
// Correct for an error that came in because filter format changed.
if (is_array($form_state['item']->settings['body'])) {
$form_state['item']->settings['format'] = $form_state['item']->settings['body']['format'];
$form_state['item']->settings['body'] = $form_state['item']->settings['body']['value'];
}
parent::edit_form($form, $form_state);
$form['category'] = array(
'#type' => 'textfield',
'#title' => t('Category'),
'#description' => t('What category this content should appear in. If left blank the category will be "Miscellaneous".'),
'#default_value' => $form_state['item']->category,
);
$form['title'] = array(
'#type' => 'textfield',
'#default_value' => $form_state['item']->settings['title'],
'#title' => t('Title'),
);
$form['body'] = array(
'#type' => 'text_format',
'#title' => t('Body'),
'#default_value' => $form_state['item']->settings['body'],
'#format' => $form_state['item']->settings['format'],
);
$form['substitute'] = array(
'#type' => 'checkbox',
'#title' => t('Use context keywords'),
'#description' => t('If checked, context keywords will be substituted in this content.'),
'#default_value' => !empty($form_state['item']->settings['substitute']),
);
}
function edit_form_submit(&$form, &$form_state) {
parent::edit_form_submit($form, $form_state);
// Since items in our settings are not in the schema, we have to do these manually:
$form_state['item']->settings['title'] = $form_state['values']['title'];
$form_state['item']->settings['body'] = $form_state['values']['body']['value'];
$form_state['item']->settings['format'] = $form_state['values']['body']['format'];
$form_state['item']->settings['substitute'] = $form_state['values']['substitute'];
}
function list_form(&$form, &$form_state) {
parent::list_form($form, $form_state);
$options = array('all' => t('- All -'));
foreach ($this->items as $item) {
$options[$item->category] = $item->category;
}
$form['top row']['category'] = array(
'#type' => 'select',
'#title' => t('Category'),
'#options' => $options,
'#default_value' => 'all',
'#weight' => -10,
);
}
function list_filter($form_state, $item) {
if ($form_state['values']['category'] != 'all' && $form_state['values']['category'] != $item->category) {
return TRUE;
}
return parent::list_filter($form_state, $item);
}
function list_sort_options() {
return array(
'disabled' => t('Enabled, title'),
'title' => t('Title'),
'name' => t('Name'),
'category' => t('Category'),
'storage' => t('Storage'),
);
}
function list_build_row($item, &$form_state, $operations) {
// Set up sorting
switch ($form_state['values']['order']) {
case 'disabled':
$this->sorts[$item->name] = empty($item->disabled) . $item->admin_title;
break;
case 'title':
$this->sorts[$item->name] = $item->admin_title;
break;
case 'name':
$this->sorts[$item->name] = $item->name;
break;
case 'category':
$this->sorts[$item->name] = $item->category;
break;
case 'storage':
$this->sorts[$item->name] = $item->type . $item->admin_title;
break;
}
$ops = theme('links__ctools_dropbutton', array('links' => $operations, 'attributes' => array('class' => array('links', 'inline'))));
$this->rows[$item->name] = array(
'data' => array(
array('data' => check_plain($item->name), 'class' => array('ctools-export-ui-name')),
array('data' => check_plain($item->admin_title), 'class' => array('ctools-export-ui-title')),
array('data' => check_plain($item->category), 'class' => array('ctools-export-ui-category')),
array('data' => $ops, 'class' => array('ctools-export-ui-operations')),
),
'title' => check_plain($item->admin_description),
'class' => array(!empty($item->disabled) ? 'ctools-export-ui-disabled' : 'ctools-export-ui-enabled'),
);
}
function list_table_header() {
return array(
array('data' => t('Name'), 'class' => array('ctools-export-ui-name')),
array('data' => t('Title'), 'class' => array('ctools-export-ui-title')),
array('data' => t('Category'), 'class' => array('ctools-export-ui-category')),
array('data' => t('Operations'), 'class' => array('ctools-export-ui-operations')),
);
}
}

View File

@@ -0,0 +1,14 @@
The CTools Plugin Example is an example for developers of how to CTools
access, argument, content type, context, and relationship plugins.
There are a number of ways to profit from this:
1. The code itself intends to be as simple and self-explanatory as possible.
Nothing fancy is attempted: It's just trying to use the plugin API to show
how it can be used.
2. There is a sample panel. You can access it at /ctools_plugin_example/xxxx
to see how it works.
3. There is Advanced Help at admin/advanced_help/ctools_plugin_example.

View File

@@ -0,0 +1,15 @@
name = Chaos Tools (CTools) Plugin Example
description = Shows how an external module can provide ctools plugins (for Panels, etc.).
package = Chaos tool suite
dependencies[] = ctools
dependencies[] = panels
dependencies[] = page_manager
dependencies[] = advanced_help
core = 7.x
; Information added by drupal.org packaging script on 2013-04-03
version = "7.x-1.3"
core = "7.x"
project = "ctools"
datestamp = "1365013512"

View File

@@ -0,0 +1,94 @@
<?php
/*
* @file
*
* Working sample module to demonstrate CTools 3 plugins
*
* This sample module is only intended to demonstrate how external modules can
* provide ctools plugins. There is no useful functionality, and it's only
* intended for developers or for educational use.
*
* As far as possible, everything is kept very simple, not exercising all of
* the capabilities of CTools or Panels.
*
* Although the ctools documentation suggests that strict naming conventions
* be followed, this code attempts to follow only the conventions which are
* required (the hooks), in order to demonstrate the difference. You can
* certainly use the conventions, but it's important to know the difference
* between a convention and a requirement.
*
* The advanced_help module is required, because both CTools and this module
* provide help that way.
*
* There is a demonstration panel provided at /ctools_plugin_example/123
*/
/**
* Implements hook_menu
*/
function ctools_plugin_example_menu() {
$items = array();
$items["admin/settings/ctools_plugin_example"] = array(
'title' => 'CTools plugin example',
'description' => t("Demonstration code, advanced help, and a demo panel to show how to build ctools plugins."),
'page callback' => 'ctools_plugin_example_explanation_page',
'access arguments' => array('administer site configuration'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Implements hook_ctools_plugin_directory().
*
* It simply tells panels where to find the .inc files that define various
* args, contexts, content_types. In this case the subdirectories of
* ctools_plugin_example/panels are used.
*/
function ctools_plugin_example_ctools_plugin_directory($module, $plugin) {
if ($module == 'ctools' && !empty($plugin)) {
return "plugins/$plugin";
}
}
/**
* Implement hook_ctools_plugin_api().
*
* If you do this, CTools will pick up default panels pages in
* <modulename>.pages_default.inc
*/
function ctools_plugin_example_ctools_plugin_api($module, $api) {
// @todo -- this example should explain how to put it in a different file.
if ($module == 'panels_mini' && $api == 'panels_default') {
return array('version' => 1);
}
if ($module == 'page_manager' && $api == 'pages_default') {
return array('version' => 1);
}
}
/**
* Just provide an explanation page for the admin section
* @return unknown_type
*/
function ctools_plugin_example_explanation_page() {
$content = '<p>' . t("The CTools Plugin Example is simply a developer's demo of how to create plugins for CTools. It provides no useful functionality for an ordinary user.") . '</p>';
$content .= '<p>' . t(
'There is a demo panel demonstrating much of the functionality provided at
<a href="@demo_url">CTools demo panel</a>, and you can find documentation on the examples at
!ctools_plugin_example_help.
CTools itself provides documentation at !ctools_help. Mostly, though, the code itself is intended to be the teacher.
You can find it in %path.',
array(
'@demo_url' => url('ctools_plugin_example/xxxxx'),
'!ctools_plugin_example_help' => theme('advanced_help_topic', array('module' => 'ctools_plugin_example', 'topic' => 'Chaos-Tools--CTools--Plugin-Examples', 'type' => 'title')),
'!ctools_help' => theme('advanced_help_topic', array('module' => 'ctools', 'topic' => 'plugins', 'type' => 'title')),
'%path' => drupal_get_path('module', 'ctools_plugin_example'),
)) . '</p>';
return $content;
}

View File

@@ -0,0 +1,451 @@
<?php
/**
* @file
* This module provides default panels to demonstrate the behavior of the plugins.
*/
/**
* Default panels pages for CTools Plugin Example
*
* To pick up this file, your module needs to implement
* hook_ctools_plugin_api() - See ctools_plugin_example_ctools_plugin_api() in
* ctools_plugin_example.module.
*
*
* Note the naming of the file: <modulename>.pages_default.inc
* With this naming, no additional code needs to be provided. CTools will just find the file.
* The name of the hook is <modulename>_default_page_manager_pages()
*
* This example provides two pages, but the returned array could
* have several pages.
*
* @return
* Array of pages, normally exported from Panels.
*/
function ctools_plugin_example_default_page_manager_pages() {
// begin exported panel.
$page = new stdClass;
$page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */
$page->api_version = 1;
$page->name = 'ctools_plugin_example';
$page->task = 'page';
$page->admin_title = 'CTools plugin example';
$page->admin_description = 'This panel provides no functionality to a working Drupal system. It\'s intended to display the various sample plugins provided by the CTools Plugin Example module. ';
$page->path = 'ctools_plugin_example/%sc';
$page->access = array(
'logic' => 'and',
);
$page->menu = array(
'type' => 'normal',
'title' => 'CTools plugin example',
'name' => 'navigation',
'weight' => '0',
'parent' => array(
'type' => 'none',
'title' => '',
'name' => 'navigation',
'weight' => '0',
),
);
$page->arguments = array(
'sc' => array(
'id' => 2,
'identifier' => 'simplecontext-arg',
'name' => 'simplecontext_arg',
'settings' => array(),
),
);
$page->conf = array();
$page->default_handlers = array();
$handler = new stdClass;
$handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */
$handler->api_version = 1;
$handler->name = 'page_ctools_panel_context';
$handler->task = 'page';
$handler->subtask = 'ctools_plugin_example';
$handler->handler = 'panel_context';
$handler->weight = 0;
$handler->conf = array(
'title' => 'Panel',
'no_blocks' => FALSE,
'css_id' => '',
'css' => '',
'contexts' => array(
'0' => array(
'name' => 'simplecontext',
'id' => 1,
'identifier' => 'Configured simplecontext (not from argument)',
'keyword' => 'configured_simplecontext',
'context_settings' => array(
'sample_simplecontext_setting' => 'default simplecontext setting',
),
),
),
'relationships' => array(
'0' => array(
'context' => 'argument_simplecontext_arg_2',
'name' => 'relcontext_from_simplecontext',
'id' => 1,
'identifier' => 'Relcontext from simplecontext (from relationship)',
'keyword' => 'relcontext',
),
),
'access' => array(
'logic' => 'and',
),
);
$display = new panels_display;
$display->layout = 'threecol_33_34_33_stacked';
$display->layout_settings = array();
$display->panel_settings = array(
'style' => 'rounded_corners',
'style_settings' => array(
'default' => array(
'corner_location' => 'pane',
),
),
);
$display->cache = array();
$display->title = 'CTools plugin example panel';
$display->hide_title = FALSE;
$display->title_pane = 1;
$display->content = array();
$display->panels = array();
$pane = new stdClass;
$pane->pid = 'new-1';
$pane->panel = 'left';
$pane->type = 'no_context_content_type';
$pane->subtype = 'no_context_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'item1' => 'contents of config item 1',
'item2' => 'contents of config item 2',
'override_title' => 0,
'override_title_text' => '',
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-1'] = $pane;
$display->panels['left'][0] = 'new-1';
$pane = new stdClass;
$pane->pid = 'new-2';
$pane->panel = 'left';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array(
'plugins' => array(
'0' => array(
'name' => 'arg_length',
'settings' => array(
'greater_than' => '1',
'arg_length' => '4',
),
'context' => 'argument_simplecontext_arg_2',
),
),
);
$pane->configuration = array(
'title' => 'Long Arg Visibility Block',
'body' => 'This block will be here when the argument is longer than configured arg length. It uses the \'arg_length\' access plugin to test against the length of the argument used for Simplecontext.',
'format' => '1',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 1;
$display->content['new-2'] = $pane;
$display->panels['left'][1] = 'new-2';
$pane = new stdClass;
$pane->pid = 'new-3';
$pane->panel = 'left';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array(
'plugins' => array(
'0' => array(
'name' => 'arg_length',
'settings' => array(
'greater_than' => '0',
'arg_length' => '4',
),
'context' => 'argument_simplecontext_arg_2',
),
),
);
$pane->configuration = array(
'title' => 'Short Arg Visibility',
'body' => 'This block appears when the simplecontext argument is <i>less than</i> the configured length.',
'format' => '1',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 2;
$display->content['new-3'] = $pane;
$display->panels['left'][2] = 'new-3';
$pane = new stdClass;
$pane->pid = 'new-4';
$pane->panel = 'middle';
$pane->type = 'simplecontext_content_type';
$pane->subtype = 'simplecontext_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'buttons' => NULL,
'#validate' => NULL,
'#submit' => NULL,
'#action' => NULL,
'context' => 'argument_simplecontext_arg_2',
'aligner_start' => NULL,
'override_title' => 1,
'override_title_text' => 'Simplecontext (with an arg)',
'aligner_stop' => NULL,
'override_title_markup' => NULL,
'config_item_1' => 'Config item 1 contents',
'#build_id' => NULL,
'#type' => NULL,
'#programmed' => NULL,
'form_build_id' => 'form-19c4ae6cb54fad8f096da46e95694e5a',
'#token' => NULL,
'form_token' => '17141d3531eaa7b609da78afa6f3b560',
'form_id' => 'simplecontext_content_type_edit_form',
'#id' => NULL,
'#description' => NULL,
'#attributes' => NULL,
'#required' => NULL,
'#tree' => NULL,
'#parents' => NULL,
'#method' => NULL,
'#post' => NULL,
'#processed' => NULL,
'#defaults_loaded' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-4'] = $pane;
$display->panels['middle'][0] = 'new-4';
$pane = new stdClass;
$pane->pid = 'new-5';
$pane->panel = 'middle';
$pane->type = 'simplecontext_content_type';
$pane->subtype = 'simplecontext_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'buttons' => NULL,
'#validate' => NULL,
'#submit' => NULL,
'#action' => NULL,
'context' => 'context_simplecontext_1',
'aligner_start' => NULL,
'override_title' => 1,
'override_title_text' => 'Configured simplecontext content type (not from arg)',
'aligner_stop' => NULL,
'override_title_markup' => NULL,
'config_item_1' => '(configuration for simplecontext)',
'#build_id' => NULL,
'#type' => NULL,
'#programmed' => NULL,
'form_build_id' => 'form-d016200490abd015dc5b8a7e366d76ea',
'#token' => NULL,
'form_token' => '17141d3531eaa7b609da78afa6f3b560',
'form_id' => 'simplecontext_content_type_edit_form',
'#id' => NULL,
'#description' => NULL,
'#attributes' => NULL,
'#required' => NULL,
'#tree' => NULL,
'#parents' => NULL,
'#method' => NULL,
'#post' => NULL,
'#processed' => NULL,
'#defaults_loaded' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 1;
$display->content['new-5'] = $pane;
$display->panels['middle'][1] = 'new-5';
$pane = new stdClass;
$pane->pid = 'new-6';
$pane->panel = 'middle';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'admin_title' => 'Simplecontext keyword usage',
'title' => 'Simplecontext keyword usage',
'body' => 'Demonstrating context keyword usage:
item1 is %sc:item1
item2 is %sc:item2
description is %sc:description',
'format' => '1',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 2;
$display->content['new-6'] = $pane;
$display->panels['middle'][2] = 'new-6';
$pane = new stdClass;
$pane->pid = 'new-7';
$pane->panel = 'right';
$pane->type = 'relcontext_content_type';
$pane->subtype = 'relcontext_content_type';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'buttons' => NULL,
'#validate' => NULL,
'#submit' => NULL,
'#action' => NULL,
'context' => 'relationship_relcontext_from_simplecontext_1',
'aligner_start' => NULL,
'override_title' => 0,
'override_title_text' => '',
'aligner_stop' => NULL,
'override_title_markup' => NULL,
'config_item_1' => 'some stuff in this one',
'#build_id' => NULL,
'#type' => NULL,
'#programmed' => NULL,
'form_build_id' => 'form-8485f84511bd06e51b4a48e998448054',
'#token' => NULL,
'form_token' => '1c3356396374d51d7d2531a10fd25310',
'form_id' => 'relcontext_edit_form',
'#id' => NULL,
'#description' => NULL,
'#attributes' => NULL,
'#required' => NULL,
'#tree' => NULL,
'#parents' => NULL,
'#method' => NULL,
'#post' => NULL,
'#processed' => NULL,
'#defaults_loaded' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-7'] = $pane;
$display->panels['right'][0] = 'new-7';
$pane = new stdClass;
$pane->pid = 'new-8';
$pane->panel = 'top';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'title' => 'Demonstrating ctools plugins',
'body' => 'The CTools Plugin Example module (and this panel page) are just here to demonstrate how to build CTools plugins.
',
'format' => '2',
'substitute' => 1,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-8'] = $pane;
$display->panels['top'][0] = 'new-8';
$handler->conf['display'] = $display;
$page->default_handlers[$handler->name] = $handler;
// end of exported panel.
$pages['ctools_plugin_example_demo_page'] = $page;
// begin exported panel
$page = new stdClass;
$page->disabled = FALSE; /* Edit this to true to make a default page disabled initially */
$page->api_version = 1;
$page->name = 'ctools_plugin_example_base';
$page->task = 'page';
$page->admin_title = 'CTools Plugin Example base page';
$page->admin_description = 'This panel is for when people hit /ctools_plugin_example without an argument. We can use it to tell people to move on.';
$page->path = 'ctools_plugin_example';
$page->access = array();
$page->menu = array();
$page->arguments = array();
$page->conf = array();
$page->default_handlers = array();
$handler = new stdClass;
$handler->disabled = FALSE; /* Edit this to true to make a default handler disabled initially */
$handler->api_version = 1;
$handler->name = 'page_ctools_plugin_example_base_panel_context';
$handler->task = 'page';
$handler->subtask = 'ctools_plugin_example_base';
$handler->handler = 'panel_context';
$handler->weight = 0;
$handler->conf = array(
'title' => 'Panel',
'no_blocks' => FALSE,
'css_id' => '',
'css' => '',
'contexts' => array(),
'relationships' => array(),
);
$display = new panels_display;
$display->layout = 'onecol';
$display->layout_settings = array();
$display->panel_settings = array();
$display->cache = array();
$display->title = '';
$display->hide_title = FALSE;
$display->content = array();
$display->panels = array();
$pane = new stdClass;
$pane->pid = 'new-1';
$pane->panel = 'middle';
$pane->type = 'custom';
$pane->subtype = 'custom';
$pane->shown = TRUE;
$pane->access = array();
$pane->configuration = array(
'title' => 'Use this page with an argument',
'body' => 'This demo page works if you use an argument, like <a href="ctools_plugin_example/xxxxx">ctools_plugin_example/xxxxx</a>.',
'format' => '1',
'substitute' => NULL,
);
$pane->cache = array();
$pane->style = array();
$pane->css = array();
$pane->extras = array();
$pane->position = 0;
$display->content['new-1'] = $pane;
$display->panels['middle'][0] = 'new-1';
$handler->conf['display'] = $display;
$page->default_handlers[$handler->name] = $handler;
// end exported panel.
$pages['base_page'] = $page;
return $pages;
}

View File

@@ -0,0 +1,17 @@
<div id="node-16" class="node">
<div class="content clear-block">
<p>We can use access plugins to determine access to a page or visibility of the panes in a page. Basically, we just determine access based on configuration settings and the various contexts that are available to us.</p>
<p>The arbitrary example in plugins/access/arg_length.inc determines access based on the length of the simplecontext argument. You can configure whether access should be granted if the simplecontext argument is greater or less than some number.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div id="node-12" class="node">
<div class="content clear-block">
<p>Contexts are fundamental to CTools, and they almost always start with an argument to a panels page, so we'll start there too.</p>
<p>We first need to process an argument.</p>
<p>We're going to work with a "Simplecontext" context type and argument, and then with a content type that displays it. So we'll start by with the Simplecontext argument plugin in plugins/arguments/simplecontext_arg.inc.</p>
<p>Note that the name of the file (simplecontext_arg.inc) is built from the machine name of our plugin (simplecontext_arg). And note also that the primary function that we use to provide our argument (ctools_plugin_example_simplecontext_arg_ctools_arguments()) is also built from the machine name. This magic is most of the naming magic that you have to know.</p>
<p>You can browse plugins/arguments/simplecontext_arg.inc and see the little that it does.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>

View File

@@ -0,0 +1,19 @@
<div id="node-10" class="node">
<div class="content clear-block">
<p>This demonstration module is intended for developers to look at and play with. CTools plugins are not terribly difficult to do, but it can be hard to sort through the various arguments and required functions. The idea here is that you should have a starting point for most anything you want to do. Just work through the example, and then start experimenting with changing it.</p>
<p>There are two parts to this demo: </p>
<p>First, there is a sample panel provided that uses all the various plugins. It's at <a href="/ctools_plugin_example/12345">ctools_example/12345</a>. You can edit the panel and configure all the panes on it.</p>
<p>Second, the code is there for you to experiment with and change as you see fit. Sometimes starting with simple code and working with it can take you places that it's hard to go when you're looking at more complex examples.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<div id="node-14" class="node">
<div class="content clear-block">
<p>Now we get to the heart of the matter: Building a content type plugin. A content type plugin uses the contexts available to it to display something. plugins/content_types/simplecontext_content_type.inc does this work for us.</p>
<p>Note that our content type also has an edit form which can be used to configure its behavior. This settings form is accessed through the panels interface, and it's up to you what the settings mean to the code and the generation of content in the display rendering.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>

View File

@@ -0,0 +1,21 @@
<div id="node-13" class="node">
<div class="content clear-block">
<p>Now that we have a plugin for a simplecontext argument, we can create a plugin for a simplecontext context. </p>
<p>Normally, a context would take an argument which is a key like a node ID (nid) and retrieve a more complex object from a database or whatever. In our example, we'll artificially transform the argument into an arbitrary "context" data object. </p>
<p>plugins/contexts/simplecontext.inc implements our context.</p>
<p>Note that there are actually two ways to create a context. The normal one, which we've been referring to, is to create a context from an argument. However, it is also possible to configure a context in a panel using the panels interface. This is quite inflexible, but might be useful for configuring single page. However, it means that we have a settings form for exactly that purpose. Our context would have to know how to create itself from a settings form as well as from an argument. Simplecontext can do that.</p>
<p>A context plugin can also provide keywords that expose parts of its context using keywords like masterkeyword:dataitem. The node plugin for ctools has node:nid and node:title, for example. The simplecontext plugin here provides the simplest of keywords.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>

View File

@@ -0,0 +1,20 @@
<div id="node-11" class="node">
<div class="content clear-block">
<p>Your module must provide a few things so that your plugins can be found.</p>
<p>First, you need to implement hook_ctools_plugin_directory(). Here we're telling CTools that our plugins will be found in the module's directory in the plugins/&lt;plugintype&gt; directory. Context plugins will be in ctools_plugin_example/plugins/contexts, Content-type plugins will be in ctools_plugin_example/plugins/content_types.</p>
<p><div class="codeblock"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">ctools_plugin_example_ctools_plugin_directory</span><span style="color: #007700">(</span><span style="color: #0000BB">$module</span><span style="color: #007700">, </span><span style="color: #0000BB">$plugin</span><span style="color: #007700">) {<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'ctools' </span><span style="color: #007700">&amp;&amp; !empty(</span><span style="color: #0000BB">$plugin</span><span style="color: #007700">)) {<br />&nbsp;&nbsp;&nbsp; return </span><span style="color: #DD0000">"plugins/$plugin"</span><span style="color: #007700">;<br />&nbsp; }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></p>
<p>Second, if you module wants to provide default panels pages, you can implement hook_ctools_plugin_api(). CTools will then pick up your panels pages in the file named &lt;modulename&gt;.pages_default.inc.</p>
<p><div class="codeblock"><code><span style="color: #000000"><span style="color: #0000BB">&lt;?php<br /></span><span style="color: #007700">function </span><span style="color: #0000BB">ctools_plugin_example_ctools_plugin_api</span><span style="color: #007700">(</span><span style="color: #0000BB">$module</span><span style="color: #007700">, </span><span style="color: #0000BB">$api</span><span style="color: #007700">) {<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'panels_mini' </span><span style="color: #007700">&amp;&amp; </span><span style="color: #0000BB">$api </span><span style="color: #007700">== </span><span style="color: #DD0000">'panels_default'</span><span style="color: #007700">) {<br />&nbsp;&nbsp;&nbsp; return array(</span><span style="color: #DD0000">'version' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">1</span><span style="color: #007700">);<br />&nbsp; }<br />&nbsp; if (</span><span style="color: #0000BB">$module </span><span style="color: #007700">== </span><span style="color: #DD0000">'page_manager' </span><span style="color: #007700">&amp;&amp; </span><span style="color: #0000BB">$api </span><span style="color: #007700">== </span><span style="color: #DD0000">'pages_default'</span><span style="color: #007700">) {<br />&nbsp;&nbsp;&nbsp; return array(</span><span style="color: #DD0000">'version' </span><span style="color: #007700">=&gt; </span><span style="color: #0000BB">1</span><span style="color: #007700">);<br />&nbsp; }<br />}<br /></span><span style="color: #0000BB">?&gt;</span></span></code></div></p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>

View File

@@ -0,0 +1,18 @@
<div id="node-15" class="node">
<div class="content clear-block">
<p>Often a single data type can lead us to other data types. For example, a node has a user (the author) and the user has data associated with it.</p>
<p>A relationship plugin allows this kind of data to be accessed. </p>
<p>An example relationship plugin is provided in plugins/relationships/relcontext_from_simplecontext.inc. It looks at a simplecontext (which we got from an argument) and builds an (artificial) "relcontext" from that.</p>
</div>
<div class="clear-block">
<div class="meta">
</div>
</div>
</div>

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