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

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

View File

@@ -0,0 +1,693 @@
Terminology
-----------
- item: an item in the hierarchy. A hierarchy can also be seen as a tree. In
that case, an item can be either a parent or a child. However, if
"multiple parents" are supported (i.e. a child can have multiple
parents), then it's actually not a tree but a directed acyclic graph
(see http://en.wikipedia.org/wiki/Directed_acyclic_graph), in which
each case technically is a "node".
An example: in the case of taxonomy, this is the term id (tid).
- label: the label associated with an item in the hierarchy. You may now it
as "title" or something else similar.
An example: in the case of taxonomy, this is the actual term.
- item type: a per-level, human-readable name that describes what kind of
items that level contains.
- entity: an item is often associated with an entity. E.g. a term is usually
associated with a node.
- form element: a form element allows the developer to assign a new value to
a #type property in a form item. Examples of form elements
supported by Drupal core are: select, checkboxes, textfield.
- form item: an instance of a form element, with various other properties
defined, such as #title, #default_value and #description. These
are used to define a form in Drupal.
- Hierarchical Select: this is the name of the module.
- hierarchical_select: this is the internal name of the Hierarchical Select
form element.
- hierarchical select: (note the difference in case) this is the part of the
widget with the multiple selects.
- dropbox: this is the part of the widget where the selections are stored when
multiple selections are allowed.
Form API usage
--------------
You have to make sure your form item is using the "hierarchical_select" form
element type:
$form['select_some_term'] = array(
'#type' => 'hierarchical_select',
'#title' => t('Select the tag you wish to use.'),
'#size' => 1,
'#config' => array(
'module' => 'hs_taxonomy',
'params' => array(
'vid' => $vid,
),
'save_lineage' => 0,
'enforce_deepest' => 0,
'resizable' => 1,
'level_labels' => array(
'status' => 0,
'labels' => array(
0 => t('Main category'),
1 => t('Subcategory'),
2 => t('Third level category'),
),
),
'dropbox' => array(
'status' => 0,
'title' => t('All selections'),
'limit' => 0,
'reset_hs' => 1,
'sort' => 1,
),
'editability' => array(
'status' => 0,
'item_types' => array(),
'allowed_levels' => array(
0 => 0,
1 => 0,
2 => 1,
),
'allow_new_levels' => 0,
'max_levels' => 3,
),
'entity_count' => array(
'enabled' => 0,
'require_entity' => 0,
'settings' => array(
'count_children' => 0,
'entity_types' => array(),
),
),
// These settings cannot be configured through the UI: they can only be
// overridden through code.
'animation_delay' => 400,
'special_items' => array(),
'render_flat_select' => 0,
),
'#default_value' => '83',
);
Now, let's explain what we see here:
1) We've set the #type property to "hierarchical_select" instead of "select".
2) The #size property is inherited by the selects of the hierarchical select.
You can use it to change a vertical size of the select (i.e. change how many
items are displayed in the select, similar to a form select multiple).
3) There's a new property: #config. This must be an
array. These are the items it can contain:
- module (required)
This will be passed through in the AJAX requests, to let Hierarchical
Select know which module's hooks should be used.
- params (optional, may be necessary for some implementations)
An array of parameters that will also be passed through in every AJAX
request.
e.g. In the case of taxonomy, this is the vocabulary id (vid). In case of
content_taxonomy, there's three parameters: vid, tid and depth (tid allows
one to define a new root, depth allows one to limit the depth of the
displayed hierarchy).
- save_lineage (optional, defaults to 0)
Triggers the lineage saving functionality. If enabled, the selection can
consist of multiple values.
- enforce_deepest (optional, defaults to 0)
Triggers the enforcing of a selection in the deepest level. If enabled, the
selection will always be a single value.
- resizable (optional, defaults to 1)
Makes the hierarchical select resizable.
- level_labels['status'] (optional, defaults to 0)
Whether level labels should be enabled or not. When save_lineage is
enabled, this will result in *empty* level labels.
- level_labels['labels'] (optional)
An array of labels, one per level. The label for the first level should be
the value of key 0.
When enforce_deepest is set to:
- 0, then you can provide n level labels, with n the number of levels
- 1, then you can provide only one level label.
- dropbox['status'] (optional, defaults to 0)
Whether the dropbox is enabled or not (the dropbox allows the user to make
multiple selections).
- dropbox['title'] (optional, defaults to "All selections:")
The title of the dropbox. The dropbox is the area where all selections are
displayed when the dropbox is enabled.
- dropbox['limit'] (optional, defaults to 0, which means "no limit")
Limit the number of selection that can be added to the dropbox. So this
allows you the restrict the number of items that can be selected when
the dropbox has been enabled.
- dropbox['reset_hs'] (optional, defaults to 1, which means "do reset")
Determines what will happen to the hierarchical select when the user has
added a selection to the dropbox.
- dropbox['sort'] (optional, defaults to 1, which means "do sort")
Determines whether the items in the dropbox will be automatically sorted.
- editability['status] (optional, defaults to 0)
Allow the user to create new items in the hierarchy.
- editability['item_types'] (optional, defaults to the empty array)
Only meaningful when editable is set to TRUE.
Set the item type for each level. E.g.: "country" for the first level,
"region" for the second and "city" for the third. When the user then wants
to create a new item, the default label for the new item will be of the
form "new <item type>", e.g. "new region".
- editability['allowed_levels'] (optional, defaults to 1 for each level)
Only meaningful when editable is set to TRUE.
Specify in which levels the user is allowed to create new items. In the
example, the user is only allowed to create new items in the third level.
When a setting for a level is ommitted, it defaults to 1 (i.e. allowed for
that level). This means you only have to specify in which levels the user
is not allowed to create new items.
This only applies to *existing* levels: it does not affect the
allow_new_levels setting (the next setting).
- editability['allow_new_levels'] (optional, defaults to 0)
Only meaningful when editable is set to TRUE.
Allow the user to create new levels, i.e. when a certain item does not yet
have children, the user can create a first child for it (thus thereby
creating a new level).
- editability['max_levels'] (optional, defaults to 3)
Only meaningful when editable_settings['allow_new_levels'] is set to TRUE.
Limits the maximum number of levels. Don't set this too high or you'll end
up with very deep hierarchies. This only affects how deep new levels can be
created, it will not affect the existing hierarchy.
- entity_count['enabled'] (optional, defaults to 0)
Enables the display of entity counts, between parentheses, for each item in
the hierarchy.
- entity_count['require_entity'] (optional, defaults to 0)
Whether an item should only be displayed if it has at least one associated
entity.
- entity_count['settings']['count_children'] (optional, defaults to 0)
Whether the entity count should also count associated children of the entity.
- entity_count['settings']['entity_types'] (optional, defaults to array())
Which types of entities should be counted. This is a list of checkboxes that
allow the user to select entity types by bundles.
- animation_delay (optional, defaults to 400)
The delay of each animation (the drop in left and right animations), in ms.
- special_items (optional, defaults to the empty array)
Through this setting, you can mark each item with special properties it
possesses. There currently are two special properties: 'exclusive' and
'none'.
Note: you should include these items in the hierarchy as if it were a
normal item and then you can mark them as special through this property.
* 'exclusive': Sometimes it's desirable to have exclusive lineages. When
such an option is selected, the user should not be able to
select anything else. This also means that nothing else in
the dropbox can be selected: if the dropbox contains
anything, it will be reset.
Can be applied to multiple items.
e.g. an 'entire_tree' item:
'special_items' => array(
'entire_tree' => array('exclusive'),
)
* 'none': Sometimes you want to replace the default '<none>' option by
something else. This replacement should of course also exist in
the root level.
Can be applied to only one item.
e.g. an 'any' item (used in hs_taxonomy_views):
'special_items' => array(
'any' => array('none', 'exclusive'),
)
And a final example for a better overview:
'special_items' => array(
'entire_tree' => array('exclusive'),
'any' => array('none', 'exclusive'),
)
- render_flat_select (optional, defaults to 0)
Because the hierarchical_select form element consists of multiple form
items, it doesn't work well in GET forms. By enabling this setting, a flat
select will also be rendered, that contains only the selected lineages.
Combine that with Drupal.HierarchicalSelect.prepareGETSubmit in the JS code
(or, alternatively, the 'prepare-GET-submit' event that can be triggered,
see the JavaScript events section for details) and you have a work-around
(which, admittedly, only works when JS is enabled).
3) We *don't* specify a list of options: Hierarchical Select automatically
generates the options for us, thanks to the 'module' and 'params' settings.
Concepts
--------
- Item Unicity: each item in the hierarchy must be *unique*. It doesn't have
to be numerical, it can also be a string.
If your hierarchy does not have unique items by nature or by
design (your items may be unique per level instead), that's
not a problem. You can simply prepend the item's ancestors to
get a unique item.
e.g. you have an item "foobar" at the first, second and third
levels. By prepending the ancestors using the dash as the
separator, you'd get an item "foobar-foobar-foobar" at the
third level.
Also see the "Reserved item values" section.
- #options: it's gone, because it was the inherent cause for scalability
problems: if a hierarchy consists of 10,000 or even 100,000 items,
this results in huge HTML being generated. Huge HTML means more
processing power necessary, and more bandwidth necessary. So where
does Hierarchical Select get its "options"? It uses the hooks that
every implementation has to implement to only get what it needs.
- The General Concept: you should think of Hierarchical Select as an abstract
widget that can represent *any* hierarchy. To be able
to display any hierarchy, you obviously need some
universal way to "browse" a hierarchy.
If you are familiar with C++ or Java iterators, this
should come natural: the hooks you have to implement
is what allows Hierarchical Select to iterate over your
hierarchy. Then the heart of the iterator would be the
root_level() and children() hooks. params() allows you
to define which information is necessary before you can
determine *which* hierarchy or which *part* of the
hierarchy is being browsed. lineage() must return the
lineage, i.e. the item itself and all its ancestors,
this allows a hierarchy to be generated from just one
(selected) item.
Reserved item values
--------------------
- Ensure that your items don't have a "none", "all", "create_new_item" nor
"label_\d+" values (the latter means "label_" followed by one or more
digits). Your values should also not contain a pipe ("|"), since pipes are
used to separate the selection of values that are sent back to the server
in the callbacks.
- Valid 'empty' selections (i.e. if you want to set the #default_value
property of your form item), are -1 and the empty array. The empty string is
also considered valid, because Drupal core's Taxonomy module uses this as
the empty selection.
Developer mode
--------------
When you are writing your implementation of the Hierarchical Select API, you
will often wonder what Hierarchical Select is doing internally with the data
you're feeding it. That's why there's a developer mode: it will show you this
data, even the data generated in AJAX callbacks. It'll also show you the time
it took to generate the lineage, to fill up the levels and to calculate the
child info, to track down badly performing code.
Also, when you're just creating a new HS config and it doesn't quite work
right, it can be helpful to enable the developer mode. It will perform some
basic diagnostics that might help you track down the cause.
To use this, you must have a browser with console.log() support. Install
Firebug Lite (http://getfirebug.com/lite.html) if your browser does not
suport this. Next, go to Hierarchical Select's .module file and set the define
for the HS_DEVELOPER_MODE constant to TRUE.
When you now open Firebug (Firefox) or the Web Inspector (Safari), you'll see
the debug output. New output is added after each callback to the server.
Hierarchical Select implementations: gotcha's
---------------------------------------------
- "warning: Missing argument 1 for drupal_retrieve_form() …"
This implies that your implementation's module weight is heavier than
hierarchical_select.module. In that case, Hierarchical Select will not be
able to detect hierarchical_select form items, preventing it from applying
some magic, and AJAX updates won't work.
Hierarchical Select compatibility: gotcha's
-------------------------------------------
- "Invalid response from server"
This typically means that some functions could not be found when
Hierarchical Select does an AJAX callback to the server, which in turn means
that some code (some PHP file) has not been included, while it should have
been. Instead of using module_load_include() or even require_once, you
should use form_load_include(). This function is new in Drupal 7 and will
ensure that all required PHP files are included automatically.
Hierarchical Select API Tutorial
--------------------------------
Written by Stephen Barker of Digital Frontiers Media
(http://drupal.org/user/106070) and reviewed by Wim Leers:
http://drupal.org/node/532724
Hierarchical Select Small Hierarchy
-----------------------------------
Hierarchical Select includes a Hierarchical Select API implementation that
allows one to use a hardcoded hierarchy. When it becomes to slow, you should
move the hierarchy into the database and write a proper implementation.
Below you can find an example of how to use the hs_smallhierarchy module. Just
change the $hierarchy array to suit your needs and off you go! Look at the
code of hs_smallhierarchy.module for full details, but this code example
should get you started.
$hierarchy = array(
'win' => array(
'label' => 'Windows',
'children' => array(
'xp' => array('label' => 'XP'),
'vista' => array(
'label' => 'Vista',
'children' => array(
'x86' => array('label' => '32-bits'),
'x64' => array('label' => '64-bits'),
),
),
),
),
);
$form['select_some_term'] = array(
'#type' => 'hierarchical_select',
'#title' => t('Select the tag you wish to use.'),
'#size' => 1,
'#config' => array(
'module' => 'hs_smallhierarchy',
'params' => array(
'hierarchy' => $hierarchy,
'id' => 'my-hierarchy-about-windows',
'separator' => '|',
),
'save_lineage' => 0,
'enforce_deepest' => 0,
'resizable' => 1,
'level_labels' => array(
'status' => 0,
'labels' => array(
0 => t('Main category'),
1 => t('Subcategory'),
2 => t('Third level category'),
),
),
'dropbox' => array(
'status' => 0,
'title' => t('All selections'),
'limit' => 0,
'reset_hs' => 1,
'sort' => 1,
),
'editability' => array(
'status' => 0,
'item_types' => array(),
'allowed_levels' => array(
0 => 0,
1 => 0,
2 => 1,
),
'allow_new_levels' => 0,
'max_levels' => 3,
),
'entity_count' => array(
'enabled' => 0,
'require_entity' => 0,
'settings' => array(
'count_children' => 0,
'entity_types' => array(),
),
),
// These settings cannot be configured through the UI: they can only be
// overridden through code.
'animation_delay' => 400,
'exclusive_lineages' => array(),
'render_flat_select' => 0,
),
'#description' => 'Put your description here',
'#default_value' => 'win|xp|x86',
);
Hooks
-----
1) hook_hierarchical_select_params();
Returns an array with the names of all parameters that are necessary for
this implementation to work.
2) hook_hierarchical_select_root_level($params, $dropbox = FALSE);
Returns the root level of the hierarchy: an array of (item, label) pairs.
The $dropbox parameter can is optional and can even ommitted, as it's only
necessary if you need the dropbox to influence your hierarchy.
3) hook_hierarchical_select_children($parent, $params, $dropbox = FALSE);
Gets the children of $parent ($parent is an item in the hierarchy) and
returns them: an array of (item, label) pairs, or the empty array if the
given $parent has no children.
The $dropbox parameter can is optional and can even ommitted, as it's only
necessary if you need the dropbox to influence your hierarchy.
4) hook_hierarchical_select_lineage($item, $params);
Calculates the lineage of $item (array of items, with $item the last) and
returns it. Necessary when the "enforce_deepest" option is enabled.
5) hook_hierarchical_select_valid_item($item, $params);
Validates an item, returns TRUE if valid, FALSE if invalid.
6) hook_hierarchical_select_item_get_label($item, $params);
Given a valid item, returns the label. Is only used for rendering the
selections in the dropbox.
7) hook_hierarchical_select_create_item($label, $parent, $params);
Given a parent item and the label of a new item, create a new item as a
child of the parent item. When $parent == 0, this means a new item is being
created at the root level.
Optional hook. When this hook is not implemented, this functionality will
never be used, even when you configure it that way in code.
8) hook_hierarchical_select_entity_count($item, $params);
Given a item, get the number of entities (most of the time the entity type
is 'node') that are related to the given item. Used for the entity_count
and require_entity settings.
Optional hook. When this hook is not implemented, this functionality will
never be used, even when you configure it that way (i.e. when you enable
the entity_count and require_entity settings).
9) hook_hierarchical_select_implementation_info();
Return metadata about this implementation.
This information is used to generate the implementations overview at
admin/settings/hierarchical_select/implementations. The expected format is:
array(
'hierarchy type' => t('Taxonomy'),
'entity type' => t('Node'),
'entity' => t('Story'),
'context type' => t('Node form'),
'context' => '',
);
another example:
array(
'hierarchy type' => t('Taxonomy'),
'entity type' => t('Node'),
'entity' => '',
'context type' => t('Views exposed filter'),
'context' => t('some view'),
);
10) hook_hierarchical_select_config_info();
Return metadata about each available user-editable configuration for this
implementation.
Optional hook. This information is used to generate the configurations
overview at admin/settings/hierarchical_select/configs. The expected
format is:
$config_info[$config_id] = array(
'config_id' => $config_id,
'hierarchy type' => t('Taxonomy'),
'hierarchy' => t($vocabulary->name),
'entity type' => t('Node'),
'entity' => implode(', ', array_map('t', $entities)),
'edit link' => "admin/content/taxonomy/edit/vocabulary/$vid",
);
Standardized configuration form
-------------------------------
Hierarchical Select 3 comes with a standardized configuration form:
hierarchical_select_common_config_form(). This function accepts a lot of
parameters, which allows you to use names typical to your module's hierarchy
(e.g. 'leaf' instead of 'term' and 'tree' instead of 'vocabulary'). A submit
handler is also provided, of course.
An example:
// I'm not configuring all parameters here. For an example of that, see one
// of the included modules.
$form['foobar_hierarchical_select_config'] = hierarchical_select_common_config_form($module, $params, $config_id, $defaults, $strings, $max_hierarchy_depth, $preview_is_required);
// Add the the submit handler for the Hierarchical Select config form.
$parents = array('foobar_hierarchical_select_config');
$form['#submit'][] = 'hierarchical_select_common_config_form_submit';
$form['#hs_common_config_form_parents'] = $parents;
Configuration management
------------------------
It's now possible to export Hierarchical Select configurations, and there is a
function to set the configuration of a certain Hierarchical Select. Combine
the two and you can manage your Hierarchical Select configurations in code!
An example:
// The exported configuration.
$config = array( … );
$config_id = $config['config_id];
// Apply the configuration.
require_once(drupal_get_path('module', 'hierarchical_select') .'/includes/common.inc');
hierarchical_select_common_config_set($config_id, $config);
JavaScript events
-----------------
The Hierarchical Select module's JavaScript code triggers several events, to
allow for advanced interactions.
You can find all hierarchical_select form items using this selector:
$('.hierarchical-select-wrapper');
You can find a *specific* hierarchical_select form item using this selector:
$('#hierarchical-select-x-wrapper');
where x is a number, or more accurately: a hsid (hierarchical select id).
Retrieving all hsids in the current document can be done like this:
for (var hsid in Drupal.settings.HierarchicalSelect.settings) {
// …
}
Alternatively, you can use one of the transliterated class names. A wrapper
for Hierarchical Select looks like this:
<div class="hierarchical-select-wrapper
hierarchical-select-level-labels-style-none
hierarchical-select-wrapper-for-name-edit-taxonomy-1
hierarchical-select-wrapper-for-config-taxonomy-1
hierarchical-select-wrapper-processed"
id="hierarchical-select-35-wrapper">
</div>
Hence, you could also use selectors such as these, to achieve the same effect,
but with more robust code:
$('.hierarchical-select-wrapper-for-config-taxonomy-1:first')
.trigger('enforce-update');
$('.hierarchical-select-wrapper-for-name-edit-taxonomy-1:first')
.trigger('enforce-update');
The following events are triggered:
- change-hierarchical-select
- update-hierarchical-select
- create-new-item
- cancel-new-item
- add-to-dropbox (check https://www.drupal.org/node/1277068)
- remove-from-dropbox
- enforced-update
- prepared-GET-submit
All events are triggered *after* the animations have completed.
However, it's often useful to do something *before* an event (especially
because all of the above events perform an AJAX request to the server). So,
the equivalent "before" events exist as well:
- before-update-hierarchical-select
- before-create-new-item
- before-cancel-new-item
- before-add-to-dropbox
- before-remove-from-dropbox
- before-enforced-update
There is one exception: when the cache is enabled, the "before update
hierarchical select" event will not be triggered. This makes sense, because
updates from the cache are instantaneous.
An example of binding a function to the 'create-new-item' event of the second
(hsid == 1) hierarchical_select form item on the page:
$('#hierarchical-select-1-wrapper')
.bind('create-new-item', function() {
// …
});
And finally, you can trigger a special event to enforce an update (this can be
useful when you have changed a hierarchy through another form item, or for
live previews, or …). You can then also pass additional information that will
be POSTed. You can even disable normal updates, to manage that completely
yourself via enforced updates. This allows you to write a Hierarchical Select
implementation that gets some of its information ($params) from another form
item!
Suppose you'd like to enforce an update of the first (hsid == 0)
hierarchical_select form item on the page:
$('#hierarchical-select-0-wrapper')
.trigger('enforce-update');
Now let's move on to a more advanced example, in which we will disable normal
updates and let another form item (here a select) provide a part of the
information that will be used to render the Hierarchical Select. Effectively,
this other form item will *influence* the hierarchy that will be presented by
Hierarchical Select!
$(document).ready(function() {
Drupal.settings.specialfilter = {};
// .specialfilter-first: a select form item
// .specialfilter-second: a hierarchical_select form item
update = function() {
var selection = Drupal.settings.specialfilter.currentSelection;
// Send an extra parameter via POST: dynamicParameter. This is the stored
// selection.
$('.specialfilter-second')
.trigger('enforce-update',
[
{ name : 'dynamicParameter', value : selection }
]
);
};
attachHSBindings = function() {
// When a user navigates the hierarchical_select form item, we still want to
// POST the the extra dynamicParameter, or otherwise we will no longer have
// a hierarchy in the hierarchical_select form item that really depends on
// the select.
$('.specialfilter-second .hierarchical-select > select')
.change(function() { update(); });
$('.specialfilter-second')
.unbind('enforced-update').bind('enforced-update', function() { return attachHSBindings(); });
};
// Initialize after 25 ms, because otherwise the event binding of HS will
// not yet be ready, and hence this won't have any effect
setTimeout(function() {
// Get the initial selection (before the user has changed anything).
Drupal.settings.specialfilter.currentSelection = $('.specialfilter-first').attr('value');
// When the select form item changes, we want to *store* that selection, and
// update the hierarchical_select form item.
$('.specialfilter-first')
.change(function() {
// Store the current selection.
Drupal.settings.specialfilter.currentSelection = $(this).attr('value');
update();
});
$('.specialfilter-second')
.trigger('disable-updates');
attachHSBindings();
}, 25);
});
The 'enforced-update' (notice the past tense!) event is triggered upon
completion.
An even more rarely used special event can be triggered to prepare the
hierarchical_select form element for a get submit: the 'prepare GET submit'
event. To use this event, the 'render_flat_select' setting should be enabled
in the config.

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,200 @@
Description
-----------
This module defines the "hierarchical_select" form element, which is a greatly
enhanced way for letting the user select items in a hierarchy.
Hierarchical Select has the ability to save the entire lineage of a selection
or only the "deepest" selection. You can configure it to force the user to
make a selection as deep as possible in the tree, or allow the user to select
an item anywhere in the tree. Levels can be labeled, you can configure limit
the number of items that can be selected, configure a title for the dropbox,
choose a site-wide animation delay, and so on. You can even create new items
and levels through Hierarchical Select!
Integrates with
---------------
* Taxonomy (Drupal core)
Installation
------------
1) Place this module directory in your "modules" folder (this will usually be
"sites/all/modules/"). Don't install your module in Drupal core's "modules"
folder, since that will cause problems and is bad practice in general. If
"sites/all/modules" doesn't exist yet, just create it.
2) Enable the Hierarchical Select and Hierarchical Select Taxonomy modules.
3) If you want to use it for one or more of your vocabularies, go to
admin/structure/types and click the "manage fields" link for a content type on
which you're using a Term reference field. Click the "edit" link for this Term
reference field and then go to the "widget type" tab in the upper right corner.
There, you can choose the "Hierarchical Select" widget type, and when you do,
the entire Hierarchical Select configuration UI will appear: here you'll find
a whole range of Hierarchical Select settings. All settings are explained
there as well!
Troubleshooting
---------------
If you ever have problems, make sure to go through these steps:
1) Go to admin/reports/status (i.e. the Status Report). Ensure that the status
of the Hierarchical Select module is ok.
2) Ensure that the page isn't being served from your browser's cache. Use
CTRL+R in Windows/Linux browsers, CMD+R in Mac OS X browsers to enforce the
browser to reload everything, preventing it from using its cache.
3) When you're getting a JS alert with the following message: "Received an
invalid response from the server.", ensure that the page (of which this
form is a part) is *not* being cached.
4) When Hierarchical Select seems to be misbehaving in a certain use case in
which terms with multiple parents are being used, make sure to enable the
"Save term lineage" setting.
Note: you may have to repeat this for every configuration in which the
vocabulary with terms that have multiple parents are being used. E.g. if
such a vocabulary is called "A", then go to
admin/config/content/hierarchical_select/configs
and edit all configuration that have "A" in the "Hierarchy" column.
In case of problems, don't forget to try a hard refresh in your browser!
Limitations
-----------
- Creating new items in the hierarchy in a multiple parents hierarchy (more
scientifically: a directed acyclic graph) is *not* supported.
- Not the entire scalability problem can be solved by installing this set of
modules; read the maximum scalability section for details.
- The child indicators only work in Firefox. This *cannot* be supported in
Safari or IE. See http://drupal.org/node/180691#comment-1044691.
- The special [save-lineage-termpath] token only works with content_taxonomy
fields as long as you have the "Save option" set to either "Tag" or "Both".
- In hierarchies where items can have multiple parent items and where you have
enabled Hierarchical Select's "save lineage" setting, it is impossible to
remember individual hierarchies, unless the underlying module supports it.
So far, no module supports this. Hierarchical Select is just a form element,
not a system for storing hierarchies.
For example, if you have created a multiple parent vocabulary through the
Taxonomy module, and you have terms like this:
A -> C
A -> D
B -> C
B -> D
If you then save any two lineages in which all four terms exist, all four
lineages will be rendered by Hierarchical Select, because only the four
terms are stored and thus there is no way to recover the originally selected
two lineages.
- You can NOT expect the Hierarchical Select Taxonomy module to automagically
fix all existing nodes when you enable or disable the "save lineage" setting
and neither can you expect it to keep working properly when you reorganize
the term hierarchy. There's nothing I can do about this. Hierarchical Select
is merely a form element, it can't be held responsible for features that
Drupal core lacks or supports poorly.
See the following issues:
* http://drupal.org/node/1023762#comment-4054386
* http://drupal.org/node/976394#comment-4054456
Rendering hierarchy lineages when viewing content
-------------------------------------------------
Hierarchical Select is obviously only used for input. Hence it is only used on
the create/edit forms of content.
Combine that with the fact that Hierarchical Select is the only module capable
of restoring the lineage of saved items (e.g. Taxonomy terms). None of the
Drupal core modules is capable of storing the lineage, but Hierarchical Select
can reconstruct it relatively efficiently. However, this lineage is only
visible when creating/editing content, not when viewing it.
To allow you to display the lineages of stored items, I have provided a
theming function that you can call from within e.g. your node.tpl.php file:
the theme_hierarchical_select_selection_as_lineages($selection, $config)
function.
Sample usage (using Taxonomy and Hierarchical Select Taxonomy):
<?php if ($taxonomy):
require_once(drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc');
$vid = 2; // Vocabulary ID. CHANGE THIS!
$config_id = "taxonomy-$vid"; // Generate the config ID.
$config = hierarchical_select_common_config_get($config_id); // Get the Hierarchical Select configuration through the config ID.
$config['module'] = 'hs_taxonomy'; // Set the module.
$config['params']['vid'] = $vid; // Set the parameters.
?>
<div class="terms"><?php print theme('hierarchical_select_selection_as_lineages', $node->taxonomy, $config); ?></div>
<?php endif; ?>
This will automatically render all lineages for vocabulary 2 (meaning that if
you want to render the lineages of multiple vocabularies, you'll have to clone
this piece of code once for every vocabulary). It will also automatically get
the current Hierarchical Select configuration for that vocabulary.
Alternatively, you could provide the $config array yourself. Only three keys
are required: 1) module, 2) params, 3) save_lineage. For example:
<?php if ($taxonomy):
$vid = 2; // Vocabulary ID. CHANGE THIS!
$config['module'] = 'hs_taxonomy'; // Set the module.
$config['params']['vid'] = $vid; // Set the parameters.
$config['save_lineage'] = 1; // save_lineage setting is enabled. CHANGE THIS!
?>
<div class="terms"><?php print theme('hierarchical_select_selection_as_lineages', $node->taxonomy, $config); ?></div>
<?php endif; ?>
If you don't like how the lineage is displayed, simply override the
theme_hierarchical_select_selection_as_lineages() function from within your
theme, create e.g. garland_hierarchical_select_selection_as_lineages().
It's also worth mentioning that the 'hs_taxonomy_tree' tag was added to the
queries that build the term tree. As a result now you can easily change/filter
the elements that are selected by the module (see hs_taxonomy.module for more
info).
Setting a fixed size
--------------------
When you don't want users to be able to resize a hierarchical select
themselves, you can set a fixed size in advance yourself
Setting #size to >1 does *not* generate #multiple = TRUE selects! And the
opposite is also true. #multiple sets the "multiple" HTML attribute. This
enables the user to select multiple options of a select. #size just controls
the "size" HTML attribute. This increases the vertical size of selects,
thereby showing more options.
See http://www.w3.org/TR/html401/interact/forms.html#adef-size-SELECT.
Sponsors
--------
* Initial development:
Paul Ektov of http://autobin.ru.
* Abstraction, to let other modules than taxonomy hook in:
Etienne Leers of http://creditcalc.biz.
* Support for saving the term lineage:
Paul Ektov of http://autobin.ru.
* Multiple select support:
Marmaladesoul, http://marmaladesoul.com.
* Taxonomy Subscriptions support:
Mr Bidster Inc.
* Ability to create new items/levels:
The Worx Company, http://www.worxco.com.
* Ability to only show items that are associated with at least one entity:
Merge, http://merge.nl.
* Views 2 support:
Merge, http://merge.nl.
* Initial Drupal 7 port + folow-up fixes:
PingV, http://pingv.com.
* Port of "save lineage" functionality to Drupal 7:
Bancard Data Service
Author
------
Wim Leers
* website: http://wimleers.com/
* contact: http://wimleers.com/contact
The author can be contacted for paid development on this module. This can vary
from new features to Hierarchical Select itself, to new implementations (i.e.
support for new kinds of hierarchies).

View File

@@ -0,0 +1,44 @@
HS core:
✓ port: initial port
✓ fix: JS code cleanup (remove hardcoded hacks)
✓ fix: title + description (i.e. something's off with the theme wrapper)
✓ fix: #value_callback may be necessary? (see file.module) OR: ensure #return_value works
✓ fix: #element_validate callback: _hierarchical_select_validate() — verify this still works
✓ port: support multiple HS on the same page
✓ port: admin UI
✓ port: "dropbox" support
✓ upgrade path: delete cache_hierarchical_select
✓ upgrade path: documentation
✓ port: "create new item" support — see http://drupal.org/node/1087620
✓ port: status report
- port: render_flat_select support
- port: client-side caching (use _hierarchical_select_json_convert_hierarchy_to_cache())
- feature: live preview of HS on the common config form
- refactor: use the proper #value_callback -> #process callback -> #after_build callback pipeline as described in the documentation for form_builder() in form.inc
Taxonomy:
✓ port: admin UI
✓ port: "dropbox" support
✓ port: "save lineage" support (i.e. support multiple parents, automatic warning shown through hs_taxonomy_hierarchical_select_root_level())
✓ port: field formatters (from content_taxonomy)
✓ port: taxonomy term (create/edit) form should be altered to include HS
✓ upgrade path: migrate settings (no migration necessary)
✓ upgrade path: documentation (no migration, no docs)
✓ port: "create new item" support — see http://drupal.org/node/1087620
- port: "entity_count" support — see http://drupal.org/node/1068462
- refactor: use the vocabulary machine name internally instead of the vid
- port: token support — see http://drupal.org/node/1248908
- port: forum support
- refactor: optimize HS API implementation: take advantage of improvements in Taxonomy
HS Taxonomy Views:
- everything — see http://drupal.org/node/1170192
Menu:
✓ everything
Flat List:
✓ everything
Small Hierarchy:
✓ everything

View File

@@ -0,0 +1,20 @@
# Upgrading (from Drupal 6 to 7)
1. **BE WARE THAT NOT ALL FUNCTIONALITY HAS BEEN PORTED!**
Make sure that you know if the part of Hierarchical Select's functionality
that you want to use has been ported. Otherwise, you may be in for a
frustrating upgrade experience.
See the included TODO.txt file for details. In a nutshell:
- Taxonomy support is almost complete, only "create new item", "entity count" and token support are missing
- Forum support has **not** yet been ported (but relies on Taxonomy, so this is trivial)
- Taxonomy Views support has **not** yet been ported
- Menu support has **not** yet been ported
2. Upgrade this module just like any other: delete the old module, copy the
files of the new module and run update.php.
For details, see <http://drupal.org/node/570162>.
3. That's it! :)

View File

@@ -0,0 +1,57 @@
/* The hierarchical select. */
.hierarchical-select-wrapper .hierarchical-select .selects {
float: right; /* If a block is floated, it won't consume as much width as
available, only just enough. This allows the grippie to
perfectly scale with the with consumed by the selects. */
}
.hierarchical-select-wrapper .hierarchical-select .selects .grippie {
clear: right; /* clear: left; */
height: 9px;
overflow: hidden;
background: #eee url(images/grippie.png) no-repeat center 2px;
border: 1px solid #ddd;
border-top-width: 0;
cursor: s-resize;
margin-left: 0.5em; /* margin-right: 0.5em; */ /* Give the grippie the same margin as each select. */
min-width: 70px; /* Hack for IE, makes the grip usable, but not yet the same as in other browsers. */
}
.hierarchical-select-wrapper .hierarchical-select select,
.hierarchical-select-wrapper .hierarchical-select .add-to-dropbox,
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
margin-left: .5em;
margin-right: 0; /* Reset ltr style */
float: right;
}
/* The pseudo-modal window for creating a new item or new level. */
.hierarchical-select-wrapper .hierarchical-select .create-new-item-create,
.hierarchical-select-wrapper .hierarchical-select .create-new-item-cancel {
float: left;
margin-right: .4em;
margin-left: 0; /* Reset ltr style */
}
.hierarchical-select-wrapper .hierarchical-select .create-new-item-input {
float: right;
clear: left;
}
/* Child level indicator. */
.hierarchical-select-wrapper .hierarchical-select option.has-children {
background: url(images/arrow-rtl.png) no-repeat left center;
padding-left: 20px;
padding-right: 0;
}
/* Dropbox limit warning.*/
p.hierarchical-select-dropbox-limit-warning {
padding-right: .5em;
padding-left: 0; /* Reset ltr style */
}

View File

@@ -0,0 +1,313 @@
<?php
/**
* @file
* Module settings and configuration administration UI.
*/
/**
* Form definition; admin settings.
*/
function hierarchical_select_admin_settings($form, &$form_state) {
$form['description'] = array(
'#markup' => t('All settings below will be used as site-wide defaults.'),
'#prefix' => '<div>',
'#suffix' => '</div>',
);
$form['hierarchical_select_animation_delay'] = array(
'#type' => 'textfield',
'#title' => t('Animation delay'),
'#description' => t(
'The delay that will be used for the "drop in/out" effect when a
hierarchical select is being updated (in milliseconds).'
),
'#size' => 5,
'#maxlength' => 5,
'#default_value' => variable_get('hierarchical_select_animation_delay', 400),
);
$form['hierarchical_select_level_labels_style'] = array(
'#type' => 'select',
'#title' => t('Level labels style'),
'#description' => t(
'The style that will be used for level labels. This is not supported by
all browsers! If you want a consistent interface, choose to use no
style.'
),
'#options' => array(
'none' => t('No style'),
'bold' => t('Bold'),
'inversed' => t('Inversed'),
'underlined' => t('Underlined'),
),
'#default_value' => variable_get('hierarchical_select_level_labels_style', 'none'),
);
// TODO: port the HS client-side cache system to Drupal 7.
/*
$form['hierarchical_select_js_cache_system'] = array(
'#type' => 'radios',
'#title' => t('Cache in a HTML 5 client-side database'),
'#description' => t(
'This feature only works in browsers that support the
<a href="!spec-url">HTML 5 client-side database storage specification
</a>.</br>
After enabling this, you will notice (in supporting browsers) that
refreshing the hierarchical select will not require a request to the
server when a part is being requested that has been requested before.',
array('!spec-url' => url('http://www.whatwg.org/specs/web-apps/current-work/multipage/section-sql.html'))
),
'#options' => array(
0 => t('Disabled'),
1 => t('Enabled'),
),
'#default_value' => variable_get('hierarchical_select_js_cache_system', 0),
);
*/
return system_settings_form($form);
}
/**
* Menu callback; a table that lists all Hierarchical Select configs.
*/
function hierarchical_select_admin_configs() {
$header = array(t('Hierarchy type'), t('Hierarchy'), t('Entity type'), t('Bundle'), t('Context type'), t('Context'), t('Actions'));
// Retrieve all information items
$info_items = array();
foreach (module_implements('hierarchical_select_config_info') as $module) {
$info_items = array_merge_recursive($info_items, module_invoke($module, 'hierarchical_select_config_info'));
}
// Process the retrieved information into rows.
$rows = array();
foreach ($info_items as $id => $item) {
$config_id = $item['config_id'];
$rows[$id] = array(
$item['hierarchy type'],
$item['hierarchy'],
$item['entity type'],
$item['bundle'],
$item['context type'],
$item['context'],
theme('links', array('links' => array(
array(
'title' => t('Edit'),
'href' => $item['edit link'],
'fragment' => "hierarchical-select-config-form-$config_id",
),
array(
'title' => t('Export'),
'href' => "admin/config/content/hierarchical_select/export/$config_id",
),
array(
'title' => t('Import'),
'href' => "admin/config/content/hierarchical_select/import/$config_id",
),
))),
);
}
return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array(), 'caption' => t('Overview of all Hierarchical Select configurations.')));
}
/**
* Menu callback; a table that lists all Hierarchical Select implementations
* and the features they support.
*/
function hierarchical_select_admin_implementations() {
$output = '';
$header = array(t('Implementation (module)'), t('Hierarchy type'), t('Entity type'), t('Create new items'), t('Entity count'));
// Retrieve all information items
$rows = array();
foreach (module_implements('hierarchical_select_root_level') as $module) {
$filename = db_query("SELECT filename FROM {system} WHERE type = :type AND name = :name", array(':type' => 'module', ':name' => $module))->fetchField();
$module_info = drupal_parse_info_file(dirname($filename) . "/$module.info");
// Try to extract the hierarchy type from the optional hook_hierarchical_select_config_info().
$hierarchy_type = $entity_type = t('unknown');
if (module_hook($module, 'hierarchical_select_implementation_info')) {
$implementation = module_invoke($module, 'hierarchical_select_implementation_info');
$hierarchy_type = $implementation['hierarchy type'];
$entity_type = $implementation['entity type'];
}
$rows[] = array(
$module_info['name'],
$hierarchy_type,
$entity_type,
(module_hook($module, 'hierarchical_select_create_item')) ? t('Yes') : t('No'),
(module_hook($module, 'hierarchical_select_entity_count')) ? t('Yes') : t('No'),
);
}
$output .= '<p>';
$output .= t('
The table below allows you to find out <strong>which Hierarchical Select
features are supported</strong> by the implementations of the Hierarchical
Select API.<br />
It is <strong>not a reflection of some settings</strong>.
');
$output .= '</p>';
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array(), 'caption' => t('Overview of all installed Hierarchical Select implementations.')));
return $output;
}
/**
* Form definition; config export form.
*/
function hierarchical_select_admin_export($form, &$form_state, $config_id) {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
$config = hierarchical_select_common_config_get($config_id);
$code = _hierarchical_select_create_export_code($config);
drupal_add_css(drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css');
drupal_add_js('$(document).ready(function() { $(".hierarchical-select-code").focus(); });', array('type' => 'inline', 'scope' => JS_DEFAULT));
$lines = substr_count($code, "\n") + 1;
$form['config'] = array(
'#type' => 'textarea',
'#title' => t('Hierarchical Select configuration %config_id', array('%config_id' => $config_id)),
'#default_value' => $code,
'#rows' => $lines,
'#attributes' => array('class' => array('hierarchical-select-config-code')),
);
return $form;
}
/**
* Form definition; config import form.
*/
function hierarchical_select_admin_import($form, &$form_state, $config_id) {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
drupal_add_css(drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css');
drupal_add_js('$(document).ready(function() { $(".hierarchical-select-code").focus(); });', array('type' => 'inline', 'scope' => JS_DEFAULT));
$form['config'] = array(
'#type' => 'textarea',
'#title' => t('Import Hierarchical Select configuration code'),
'#cols' => 60,
'#rows' => 15,
'#description' => t('Copy and paste the results of an exported
Hierarchical Select configuration here.<br />This will override the
current Hierarchical Select configuration for %config_id.',
array('%config_id' => $config_id)
),
'#attributes' => array('class' => array('hierarchical-select-config-code')),
);
$form['interpreted_config'] = array('#type' => 'value', '#value' => NULL);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t("Import"),
);
$form_state['#redirect'] = NULL;
return $form;
}
/**
* Validate callback; config import form.
*/
function hierarchical_select_admin_import_validate($form, &$form_state) {
ob_start();
eval($form_state['values']['config']);
ob_end_clean();
form_set_value($form['interpreted_config'], serialize($config), $form_state);
if (empty($form_state['values']['config'])) {
form_error($form['config'], t('You did not enter anything.'));
}
elseif ($config == NULL) {
form_error($form['config'], t('There is a syntax error in the Hierarchical Select configuration you entered.'));
}
elseif (!isset($config['config_id']) || empty($config['config_id'])) {
form_error($form['config'], t('Unable to import this configuration, because no Hierarchical Select <em>config id</em> is set.'));
}
}
/**
* Submit callback; config import form.
*/
function hierarchical_select_admin_import_submit($form, &$form_state) {
$config = unserialize($form_state['values']['interpreted_config']);
$config_id = $config['config_id'];
hierarchical_select_common_config_set($config_id, $config);
drupal_set_message(t('Hierarchical Select configuration for %config_id imported!', array('%config_id' => $config_id)));
}
//----------------------------------------------------------------------------
// Private functions.
/**
* Given a config array, create the export code for it.
*
* @param array $config
* A Hierarchical Select config array, as described in API.txt
* @return string
* The code as it would appear in an editor.
*/
function _hierarchical_select_create_export_code($config) {
$output = _hierarchical_select_create_code_from_array($config);
$output = '$config = ' . $output . ";\n";
return $output;
}
/**
* Given a array, create the export code for it.
*
* This functions is a refactoring of features_var_export() to use with the
* hierarchical select module.
*
* @param mixed $config
* A value to export as code.
* @param string $prefix
* Padding for nested array.
* @param boolean $init
* Indicator of the first level of the export.
* @return string
* The code as it would appear in an editor.
*/
function _hierarchical_select_create_code_from_array($var, $prefix = '', $init = TRUE) {
$output = "";
$type = gettype($var);
switch ($type) {
case 'array':
if (empty($var)) {
$output = "array()";
}
else {
$output = "array(\n";
foreach ($var as $key => $value) {
$value = _hierarchical_select_create_code_from_array($value, ' ', FALSE);
$output .= " '$key' => " . $value . ",\n";
}
$output .= ')';
}
break;
case 'string':
$var = str_replace("\n", "***BREAK***", $var);
$output = var_export($var, TRUE);
break;
case 'boolean':
$var = empty($var) ? 'FALSE' : 'TRUE';
$output = var_export($var, TRUE);
break;
default:
$output = var_export($var, TRUE);
}
if ($prefix) {
$output = str_replace("\n", "\n$prefix", $output);
}
if ($init) {
$output = str_replace("***BREAK***", "\n", $output);
}
return $output;
}

View File

@@ -0,0 +1,226 @@
/* The hierarchical select. */
.hierarchical-select-wrapper .hierarchical-select .selects {
float: left; /* If a block is floated, it won't consume as much width as
available, only just enough. This allows the grippie to
perfectly scale with the with consumed by the selects. */
}
.hierarchical-select-wrapper .hierarchical-select .selects .grippie {
clear: left;
height: 9px;
overflow: hidden;
background: #eee url(images/grippie.png) no-repeat center 2px;
border: 1px solid #ddd;
border-top-width: 0;
cursor: s-resize;
margin-right: 0.5em; /* Give the grippie the same margin as each select. */
min-width: 50px; /* Hack for IE, makes the grip usable, but not yet the same as in other browsers. */
}
.hierarchical-select-wrapper .hierarchical-select select,
.hierarchical-select-wrapper .hierarchical-select .add-to-dropbox,
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
margin: 0;
margin-right: .5em;
margin-bottom: 3px;
float: left;
}
/* The flat select (only used in GET forms). */
.hierarchical-select-wrapper .flat-select {
display: none;
}
/* The pseudo-modal window for creating a new item or new level. */
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
padding: .7em;
border: 2px outset gray;
}
.hierarchical-select-wrapper .hierarchical-select .create-new-item {
width: 11em;
}
.hierarchical-select-wrapper .hierarchical-select .create-new-item-create,
.hierarchical-select-wrapper .hierarchical-select .create-new-item-cancel {
float: right;
margin: 0;
margin-left: .4em;
}
.hierarchical-select-wrapper .hierarchical-select .create-new-item-input {
width: 10.5em;
margin: 0;
margin-bottom: 1em;
float: left;
clear: right;
}
/* Level labels styles. */
.hierarchical-select-level-labels-style-bold .hierarchical-select select option.level-label {
font-weight: bold;
}
.hierarchical-select-level-labels-style-inversed .hierarchical-select select option.level-label {
background-color: #000000;
color: #FFFFFF;
}
.hierarchical-select-level-labels-style-underlined .hierarchical-select select option.level-label {
text-decoration: underline;
}
/* Child level indicator. */
.hierarchical-select-wrapper .hierarchical-select option.has-children {
background: url(images/arrow.png) no-repeat right center;
padding-right: 20px;
}
/* Dropbox limit warning.*/
p.hierarchical-select-dropbox-limit-warning {
padding: 0;
color: #F7A54F;
font-size: 110%;
padding-left: .5em;
}
/* The dropbox table. */
.hierarchical-select-wrapper .dropbox-title {
font-size: 115%;
color: #898989;
margin-bottom: 0.2em;
}
.hierarchical-select-wrapper .dropbox {
display: inline-block;
margin: .5em 0;
}
.hierarchical-select-wrapper .dropbox table {
margin: 0;
width: auto;
max-width: 100%;
min-width: 20em;
color: gray;
font-size: 90%;
border: 1px solid gray;
}
tr.dropbox-entry {
line-height: 1.3em;
padding: .3em .6em;
}
tr.dropbox-entry.even {
background-color: transparent;
border-bottom: 1px solid #CCCCCC;
}
tr.dropbox-entry.odd {
background-color: #EDF5FA;
border-bottom: 1px solid #CCCCCC;
}
tr.dropbox-entry.first {
border-top: 1px solid gray;
}
tr.dropbox-entry.last {
border-bottom: 1px solid gray;
}
.dropbox-selected-item {
font-weight: bold;
}
.hierarchical-select-item-separator {
padding-left: .5em;
padding-right: .5em;
}
td.dropbox-remove *,
td.dropbox-remove a:link,
td.dropbox-remove a:visited {
color: #F7A54F;
text-decoration: none;
}
td.dropbox-remove a:hover {
text-decoration: underline;
}
tr.dropbox-is-empty {
padding: .5em 1em;
}
/* The "Update" button and help text (used when Javascript is disabled). */
.hierarchical-select-wrapper .nojs .update-button {
margin: 0 0 1em;
}
.hierarchical-select-wrapper .nojs .help-text {
font-size: 90%;
color: transparent;
display: block;
border: 1px dotted black;
overflow: hidden;
width: 34em;
height: 1.2em;
padding: .6em;
line-height: normal;
}
.hierarchical-select-wrapper .nojs .help-text:hover {
height: auto;
width: auto;
min-width: 25em;
max-width: 45em;
color: gray;
}
.hierarchical-select-wrapper .nojs .help-text .ask-to-hover {
color: gray;
font-style: italic;
}
.hierarchical-select-wrapper .nojs .help-text:hover .ask-to-hover {
display: none;
}
.hierarchical-select-wrapper .nojs .help-text .highlight {
text-decoration: underline;
}
.hierarchical-select-wrapper .nojs .help-text .warning {
color: red;
}
.hierarchical-select-wrapper .nojs .help-text .solutions {
margin: 0;
padding: 0;
}
/* The 'waiting' class is set dynamically, during a callback to the server. */
.hierarchical-select-wrapper.waiting {
opacity: 0.5;
/* IE doesn't support CSS 2 properly. */
zoom: 1;
filter: alpha(opacity=50);
}
/* Use a monospace font for the import/export config code text areas. */
.hierarchical-select-config-code {
font-family: 'Monaco', 'Lucida Console', 'Consolas', monospace;
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Integration with the features module.
*/
/**
* Implements hook_features_export().
*/
function hierarchical_select_features_export($data, &$export, $module_name) {
// Add hierarchical_select dependency.
$export['dependencies']['hierarchical_select'] = 'hierarchical_select';
// Retrieve dependencies from all information items.
$dependencies = array();
foreach (module_implements('hierarchical_select_config_info') as $module) {
$configs = module_invoke($module, 'hierarchical_select_config_info');
foreach ($configs as $config_id => $config) {
$dependencies[$config_id] = $module;
}
}
// Add features and dependencies.
foreach ($data as $config_id) {
$export['features']['hierarchical_select'][$config_id] = $config_id;
if (isset($dependencies[$config_id])) {
$module = $dependencies[$config_id];
$export['dependencies'][$module] = $module;
}
}
return array();
}
/**
* Implements hook_features_export_options().
*/
function hierarchical_select_features_export_options() {
// Retrieve all information items.
$info_items = array();
foreach (module_implements('hierarchical_select_config_info') as $module) {
$info_items = array_merge_recursive($info_items, module_invoke($module, 'hierarchical_select_config_info'));
}
// Process the retrieved information into options.
$options = array();
foreach ($info_items as $id => $item) {
$config_id = $item['config_id'];
$options[$config_id] = $item['hierarchy type'] . ': ' . $item['hierarchy'] . ' - ' . $item['context type'] . (!empty($item['context']) ? ': ' . $item['context'] : '');
}
return $options;
}
/**
* Implements hook_features_export_render().
*/
function hierarchical_select_features_export_render($module, $data) {
module_load_include('inc', 'hierarchical_select', 'includes/common');
module_load_include('inc', 'hierarchical_select', 'hierarchical_select.admin');
$code = array();
$code[] = '$configs = array();';
foreach ($data as $config_id) {
$config = hierarchical_select_common_config_get($config_id);
$config['config_id'] = $config_id;
$code[] = _hierarchical_select_create_export_code($config);
$code[] = "\$configs['{$config_id}'] = \$config;";
}
$code[] = "return \$configs;";
$code = implode("\n", $code);
return array('hierarchical_select_default_configs' => $code);
}
/**
* Implements hook_features_revert().
*/
function hierarchical_select_features_revert($module) {
hierarchical_select_features_rebuild($module);
}
/**
* Implements hook_features_rebuild().
*/
function hierarchical_select_features_rebuild($module) {
module_load_include('inc', 'hierarchical_select', 'includes/common');
$configs = features_get_default('hierarchical_select', $module);
if (!empty($configs)) {
// Apply the configuration.
require_once(drupal_get_path('module', 'hierarchical_select') .'/includes/common.inc');
foreach ($configs as $config_id => $config) {
hierarchical_select_common_config_set($config_id, $config);
}
}
}

View File

@@ -0,0 +1,14 @@
name = Hierarchical Select
description = Simplifies the selection of one or multiple items in a hierarchical tree.
package = Form Elements
core = 7.x
configure = admin/config/content/hierarchical_select
files[] = tests/internals.test
; Information added by Drupal.org packaging script on 2017-02-15
version = "7.x-3.0-beta8"
core = "7.x"
project = "hierarchical_select"
datestamp = "1487167708"

View File

@@ -0,0 +1,84 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Hierarchical Select module.
*/
/**
* Implements hook_uninstall().
*/
function hierarchical_select_uninstall() {
db_delete('variable')
->condition('name', 'hs_config_%', 'LIKE')
->execute();
db_delete('variable')
->condition('name', 'hierarchical_select_%', 'LIKE')
->execute();
}
//----------------------------------------------------------------------------
// Updates.
/**
* Update Hierarchical Select to Drupal 7. Basically remove a lot of cruft.
*/
function hierarchical_select_update_7001() {
// Drop Hierarchical Select's cache table, which is now obsolete.
db_drop_table('cache_hierarchical_select');
// Undo Hierarchical Select module weight changes, because they're no longer
// necessary.
db_update('system')
->fields(array(
'weight' => 0,
))
->condition('name', 'hierarchical_select')
->execute();
}
/**
* Update Hierarchical Select config to support improved "entity count".
*/
function hierarchical_select_update_7002() {
module_load_include('inc', 'hierarchical_select', 'includes/common');
// Retrieve all information items.
$info_items = array();
foreach (module_implements('hierarchical_select_config_info') as $module) {
$info_items = array_merge_recursive($info_items, module_invoke($module, 'hierarchical_select_config_info'));
}
foreach ($info_items as $info_item) {
// Load config.
$config = hierarchical_select_common_config_get($info_item['config_id']);
// Move old settings to new location.
$config['entity_count'] = array(
'enabled' => $config['entity_count'],
'require_entity' => $config['require_entity'],
);
// Remove old setting.
unset($config['require_entity']);
// Add entity types settings.
$entity_info = entity_get_info();
foreach ($entity_info as $entity => $entity_info) {
if (!empty($entity_info['bundles']) && $entity_info['fieldable'] === TRUE) {
foreach ($entity_info['bundles'] as $bundle => $bundle_info) {
if ($entity == 'node') {
$config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle] = $bundle;
}
else {
$config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle] = 0;
}
}
}
}
// Save new config.
hierarchical_select_common_config_set($info_item['config_id'], $config);
}
}

View File

@@ -0,0 +1,702 @@
(function($) {
Drupal.behaviors.HierarchicalSelect = {
attach: function (context) {
$('.hierarchical-select-wrapper:not(.hierarchical-select-wrapper-processed)', context)
.addClass('hierarchical-select-wrapper-processed').each(function() {
var hsid = $(this).attr('id').replace(/^hierarchical-select-(.+)-wrapper$/, "$1");
Drupal.HierarchicalSelect.initialize(hsid);
});
}
};
Drupal.HierarchicalSelect = {};
Drupal.HierarchicalSelect.state = [];
Drupal.HierarchicalSelect.context = function() {
return $("form .hierarchical-select-wrapper");
};
Drupal.HierarchicalSelect.initialize = function(hsid) {
// Prevent JS errors when Hierarchical Select is loaded dynamically.
if (undefined == Drupal.settings.HierarchicalSelect || undefined == Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]) {
return false;
}
// If you set Drupal.settings.HierarchicalSelect.pretendNoJS to *anything*,
// and as such, Hierarchical Select won't initialize its Javascript! It
// will seem as if your browser had Javascript disabled.
if (undefined != Drupal.settings.HierarchicalSelect.pretendNoJS) {
return false;
}
var form = $('#hierarchical-select-'+ hsid +'-wrapper').parents('form');
// Pressing the 'enter' key on a form that contains an HS widget, depending
// on which browser, usually causes the first submit button to be pressed
// (likely an HS button). This results in unpredictable behaviour. There is
// no way to determine the 'real' submit button, so disable the enter key.
form.find('input').keypress(function(event) {
if (event.keyCode == 13) {
event.preventDefault();
return false;
}
});
// Turn off Firefox' autocomplete feature. This causes Hierarchical Select
// form items to be disabled after a hard refresh.
// See http://drupal.org/node/453048 and
// http://www.ryancramer.com/journal/entries/radio_buttons_firefox/
if (navigator.userAgent.toLowerCase().indexOf('firefox') > -1) {
form.attr('autocomplete', 'off');
}
// Enable *all* submit buttons in this form, as well as all input-related
// elements of the current hierarchical select, in case we reloaded while
// they were disabled.
form.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select')
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select input')
.attr('disabled', false);
if (this.cache != null) {
this.cache.initialize();
}
Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['updatesEnabled'] = true;
if (undefined == Drupal.HierarchicalSelect.state["hs-" + hsid]) {
Drupal.HierarchicalSelect.state["hs-" + hsid] = {};
}
this.transform(hsid);
if (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].resizable) {
this.resizable(hsid);
}
Drupal.HierarchicalSelect.attachBindings(hsid);
if (this.cache != null && this.cache.status()) {
this.cache.load(hsid);
}
Drupal.HierarchicalSelect.log(hsid);
};
Drupal.HierarchicalSelect.log = function(hsid, messages) {
// Only perform logging if logging is enabled.
if (Drupal.settings.HierarchicalSelect.initialLog == undefined || Drupal.settings.HierarchicalSelect.initialLog["hs-" + hsid] == undefined) {
return;
}
else {
Drupal.HierarchicalSelect.state["hs-" + hsid].log = [];
}
// Store the log messages. The first call to this function may not contain a
// message: the initial log included in the initial HTML rendering should be
// used instead..
if (Drupal.HierarchicalSelect.state["hs-" + hsid].log.length == 0) {
Drupal.HierarchicalSelect.state["hs-" + hsid].log.push(Drupal.settings.HierarchicalSelect.initialLog["hs-" + hsid]);
}
else {
Drupal.HierarchicalSelect.state["hs-" + hsid].log.push(messages);
}
// Print the log messages.
console.log("HIERARCHICAL SELECT " + hsid);
var logIndex = Drupal.HierarchicalSelect.state["hs-" + hsid].log.length - 1;
for (var i = 0; i < Drupal.HierarchicalSelect.state["hs-" + hsid].log[logIndex].length; i++) {
console.log(Drupal.HierarchicalSelect.state["hs-" + hsid].log[logIndex][i]);
}
console.log(' ');
};
Drupal.HierarchicalSelect.transform = function(hsid) {
var removeString = $('#hierarchical-select-'+ hsid +'-wrapper .dropbox .dropbox-remove:first', Drupal.HierarchicalSelect.context).text();
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
// Remove the .nojs div.
.find('.nojs').hide().end()
// Find all .dropbox-remove cells in the dropbox table.
.find('.dropbox .dropbox-remove')
// Hide the children of these table cells. We're not removing them because
// we want to continue to use the "Remove" checkboxes.
.find('*').css('display', 'none').end() // We can't use .hide() because of collapse.js: http://drupal.org/node/351458#comment-1258303.
// Put a "Remove" link there instead.
.append('<a href="">'+ removeString +'</a>');
};
Drupal.HierarchicalSelect.resizable = function(hsid) {
var $selectsWrapper = $('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects', Drupal.HierarchicalSelect.context);
// No select wrapper present: the user is creating a new item.
if ($selectsWrapper.length == 0) {
return;
}
// Append the drag handle ("grippie").
$selectsWrapper.append($('<div class="grippie"></div>'));
// jQuery object that contains all selects in the hierarchical select, to
// speed up DOM manipulation during dragging.
var $selects = $selectsWrapper.find('select');
var defaultPadding = parseInt($selects.slice(0, 1).css('padding-top').replace(/^(\d+)px$/, "$1")) + parseInt($selects.slice(0, 1).css('padding-bottom').replace(/^(\d+)px$/, "$1"));
var defaultHeight = Drupal.HierarchicalSelect.state["hs-" + hsid].defaultHeight = $selects.slice(0, 1).height() + defaultPadding;
var defaultSize = Drupal.HierarchicalSelect.state["hs-" + hsid].defaultSize = $selects.slice(0, 1).attr('size');
defaultSize = (defaultSize == 0) ? 1 : defaultSize;
var margin = Drupal.HierarchicalSelect.state["hs-" + hsid].margin = parseInt($selects.slice(0, 1).css('margin-bottom').replace(/^(\d+)px$/, "$1"));
// Bind the drag event.
$('.grippie', $selectsWrapper)
.mousedown(startDrag)
.dblclick(function() {
if (Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight == undefined) {
Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = defaultHeight;
}
var resizedHeight = Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = (Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight > defaultHeight + 2) ? defaultHeight : 4.6 / defaultSize * defaultHeight;
Drupal.HierarchicalSelect.resize($selects, defaultHeight, resizedHeight, defaultSize, margin);
});
function startDrag(e) {
staticOffset = $selects.slice(0, 1).height() - e.pageY;
$selects.css('opacity', 0.25);
$(document).mousemove(performDrag).mouseup(endDrag);
return false;
}
function performDrag(e) {
var resizedHeight = staticOffset + e.pageY;
Drupal.HierarchicalSelect.resize($selects, defaultHeight, resizedHeight, defaultSize, margin);
return false;
}
function endDrag(e) {
var height = $selects.slice(0, 1).height();
$(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
$selects.css('opacity', 1);
if (height != Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight) {
Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight = (height > defaultHeight) ? height : defaultHeight;
}
}
};
Drupal.HierarchicalSelect.resize = function($selects, defaultHeight, resizedHeight, defaultSize, margin) {
if (resizedHeight == undefined) {
resizedHeight = defaultHeight;
}
$selects
.attr('size', (resizedHeight > defaultHeight) ? 2 : defaultSize)
.height(Math.max(defaultHeight + margin, resizedHeight)); // Without the margin component, the height() method would allow the select to be sized to low: defaultHeight - margin.
};
Drupal.HierarchicalSelect.disableForm = function(hsid) {
// Disable *all* submit buttons in this form, as well as all input-related
// elements of the current hierarchical select.
$('form:has(#hierarchical-select-' + hsid +'-wrapper) :submit')
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select')
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :input')
.attr('disabled', true);
// Add the 'waiting' class. Default style: make everything transparent.
$('#hierarchical-select-' + hsid +'-wrapper').addClass('waiting');
// Indicate that the user has to wait.
$('body').css('cursor', 'wait');
};
Drupal.HierarchicalSelect.enableForm = function(hsid) {
// This method undoes everything the disableForm() method did.
$e = $('form:has(#hierarchical-select-' + hsid +'-wrapper) :submit')
.add('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :input:not(:submit)');
// Don't enable the selects again if they've been disabled because the
// dropbox limit was exceeded.
dropboxLimitExceeded = $('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select-dropbox-limit-warning').length > 0;
if (!dropboxLimitExceeded) {
$e = $e.add($('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select'));
}
$e.removeAttr("disabled");
// Don't enable the 'Add' button again if it's been disabled because the
// dropbox limit was exceeded.
if (dropboxLimitExceeded) {
$('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select :submit')
.attr('disabled', true);
}
$('#hierarchical-select-' + hsid +'-wrapper').removeClass('waiting');
$('body').css('cursor', 'auto');
};
Drupal.HierarchicalSelect.throwError = function(hsid, message) {
// Show the error to the user.
alert(message);
// Log the error.
Drupal.HierarchicalSelect.log(hsid, [ message ]);
// Re-enable the form to allow the user to retry, but reset the selection to
// the level label if possible, otherwise the "<none>" option if possible.
var $select = $('#hierarchical-select-' + hsid +'-wrapper .hierarchical-select .selects select:first');
var levelLabelOption = $('option[value^=label_]', $select).val();
if (levelLabelOption !== undefined) {
$select.val(levelLabelOption);
}
else {
var noneOption = $('option[value=none]', $select).val();
if (noneOption !== undefined) {
$select.val(noneOption);
}
}
Drupal.HierarchicalSelect.enableForm(hsid);
};
Drupal.HierarchicalSelect.prepareGETSubmit = function(hsid) {
// Remove the name attributes of all form elements that end up in GET,
// except for the "flat select" form element.
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
.find('input, select')
.not('.flat-select')
.removeAttr('name');
// Update the name attribute of the "flat select" form element
var $flatSelect = $('#hierarchical-select-'+ hsid +'-wrapper .flat-select', Drupal.HierarchicalSelect.context);
var newName = $flatSelect.attr('name').replace(/^([a-zA-Z0-9_\-]*)(?:\[flat_select\]){1}(\[\])?$/, "$1$2");
$flatSelect.attr('name', newName);
Drupal.HierarchicalSelect.triggerEvents(hsid, 'prepared-GET-submit', {});
};
Drupal.HierarchicalSelect.attachBindings = function(hsid) {
var updateOpString = $('#hierarchical-select-'+ hsid +'-wrapper .update-button').val();
var addOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .add-to-dropbox', Drupal.HierarchicalSelect.context).val();
var createNewItemOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-create', Drupal.HierarchicalSelect.context).val();
var cancelNewItemOpString = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-cancel', Drupal.HierarchicalSelect.context).val();
var data = {};
data.hsid = hsid;
$('#hierarchical-select-'+ hsid +'-wrapper', this.context)
// "disable-updates" event
.unbind('disable-updates').bind('disable-updates', data, function(e) {
Drupal.settings.HierarchicalSelect.settings["hs-" + e.data.hsid]['updatesEnabled'] = false;
})
// "enforce-update" event
.unbind('enforce-update').bind('enforce-update', data, function(e, extraPost) {
Drupal.HierarchicalSelect.update(e.data.hsid, 'enforced-update', { opString: updateOpString, extraPost: extraPost });
})
// "prepare-GET-submit" event
.unbind('prepare-GET-submit').bind('prepare-GET-submit', data, function(e) {
Drupal.HierarchicalSelect.prepareGETSubmit(e.data.hsid);
})
// "update-hierarchical-select" event
.find('.hierarchical-select .selects select').unbind().change(function(_hsid) {
return function() {
if (Drupal.settings.HierarchicalSelect.settings["hs-" + _hsid]['updatesEnabled']) {
Drupal.HierarchicalSelect.update(_hsid, 'update-hierarchical-select', { opString: updateOpString, select_id : $(this).attr('id') });
}
};
}(hsid)).end()
// "create-new-item" event
.find('.hierarchical-select .create-new-item .create-new-item-create').unbind().click(function(_hsid) {
return function() {
Drupal.HierarchicalSelect.update(_hsid, 'create-new-item', { opString : createNewItemOpString });
return false; // Prevent the browser from POSTing the page.
};
}(hsid)).end()
// "cancel-new-item" event"
.find('.hierarchical-select .create-new-item .create-new-item-cancel').unbind().click(function(_hsid) {
return function() {
Drupal.HierarchicalSelect.update(_hsid, 'cancel-new-item', { opString : cancelNewItemOpString });
return false; // Prevent the browser from POSTing the page (in case of the "Cancel" button).
};
}(hsid)).end()
// "add-to-dropbox" event
.find('.hierarchical-select .add-to-dropbox').unbind().click(function(_hsid) {
return function() {
Drupal.HierarchicalSelect.update(_hsid, 'add-to-dropbox', { opString : addOpString });
return false; // Prevent the browser from POSTing the page.
};
}(hsid)).end()
// "remove-from-dropbox" event
// (anchors in the .dropbox-remove cells in the .dropbox table)
.find('.dropbox .dropbox-remove a').unbind().click(function(_hsid) {
return function() {
var isDisabled = $('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context).attr('disabled');
// If the hierarchical select is disabled, then ignore this click.
if (isDisabled) {
return false;
}
// Check the (hidden, because JS is enabled) checkbox that marks this
// dropbox entry for removal.
$(this).parent().find('input[type=checkbox]').attr('checked', true);
Drupal.HierarchicalSelect.update(_hsid, 'remove-from-dropbox', { opString: updateOpString });
return false; // Prevent the browser from POSTing the page.
};
}(hsid));
};
Drupal.HierarchicalSelect.preUpdateAnimations = function(hsid, updateType, lastUnchanged, callback) {
switch (updateType) {
case 'update-hierarchical-select':
// Drop out the selects of the levels deeper than the select of the
// level that just changed.
var animationDelay = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['animationDelay'];
var $animatedSelects = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context).slice(lastUnchanged);
if ($animatedSelects.size() > 0) {
$animatedSelects.hide();
for (var i = 0; i < $animatedSelects.size(); i++) {
if (i < $animatedSelects.size() - 1) {
$animatedSelects.slice(i, i + 1).hide("drop", { direction: "left" }, animationDelay);
}
else {
$animatedSelects.slice(i, i + 1).hide("drop", { direction: "left" }, animationDelay, callback);
}
}
}
else if (callback) {
callback();
}
break;
default:
if (callback) {
callback();
}
break;
}
};
Drupal.HierarchicalSelect.postUpdateAnimations = function(hsid, updateType, lastUnchanged, callback) {
if (Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].resizable) {
// Restore the resize.
Drupal.HierarchicalSelect.resize(
$('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context),
Drupal.HierarchicalSelect.state["hs-" + hsid].defaultHeight,
Drupal.HierarchicalSelect.state["hs-" + hsid].resizedHeight,
Drupal.HierarchicalSelect.state["hs-" + hsid].defaultSize,
Drupal.HierarchicalSelect.state["hs-" + hsid].margin
);
}
switch (updateType) {
case 'update-hierarchical-select':
var $createNewItemInput = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .create-new-item-input', Drupal.HierarchicalSelect.context);
// Hide the loaded selects after the one that was just changed, then
// drop them in.
var animationDelay = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['animationDelay'];
var $animatedSelects = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context).slice(lastUnchanged);
if ($animatedSelects.size() > 0) {
$animatedSelects.hide();
for (var i = 0; i < $animatedSelects.size(); i++) {
if (i < $animatedSelects.size() - 1) {
$animatedSelects.slice(i, i + 1).show("drop", { direction: "left" }, animationDelay);
}
else {
$animatedSelects.slice(i, i + 1).show("drop", { direction: "left" }, animationDelay, callback);
}
}
}
else if (callback) {
callback();
}
if ($createNewItemInput.size() == 0) {
// Give focus to the level below the one that has changed, if it
// exists.
setTimeout(
function() {
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
.slice(lastUnchanged, lastUnchanged + 1)
.focus();
},
animationDelay + 100
);
}
else {
// Give focus to the input field of the "create new item/level"
// section, if it exists, and also select the existing text.
$createNewItemInput.focus();
$createNewItemInput[0].select();
}
break;
case 'create-new-item':
// Make sure that other Hierarchical Selects that represent the same
// hierarchy are also updated, to make sure that they have the newly
// created item!
var cacheId = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].cacheId;
for (var otherHsid in Drupal.settings.HierarchicalSelect.settings) {
if (Drupal.settings.HierarchicalSelect.settings[otherHsid].cacheId == cacheId) {
$('#hierarchical-select-'+ otherHsid +'-wrapper')
.trigger('enforce-update');
}
}
// TRICKY: NO BREAK HERE!
case 'cancel-new-item':
// After an item/level has been created/cancelled, reset focus to the
// beginning of the hierarchical select.
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
.slice(0, 1)
.focus();
if (callback) {
callback();
}
break;
default:
if (callback) {
callback();
}
break;
}
};
Drupal.HierarchicalSelect.triggerEvents = function(hsid, updateType, settings) {
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
.trigger(updateType, [ hsid, settings ]);
};
Drupal.HierarchicalSelect.update = function(hsid, updateType, settings) {
var post = $('form:has(#hierarchical-select-' + hsid +'-wrapper)', Drupal.HierarchicalSelect.context).formToArray();
var hs_current_language = Drupal.settings.HierarchicalSelect.hs_current_language;
// Pass the hierarchical_select id via POST.
post.push({ name : 'hsid', value : hsid });
// Send the current language so we can use the same language during the AJAX callback.
post.push({ name : 'hs_current_language', value : hs_current_language});
// Emulate the AJAX data sent normally so that we get the same theme.
post.push({ name : 'ajax_page_state[theme]', value : Drupal.settings.ajaxPageState.theme });
post.push({ name : 'ajax_page_state[theme_token]', value : Drupal.settings.ajaxPageState.theme_token });
// If a cache system is installed, let the server know if it's running
// properly. If it is running properly, the server will send back additional
// information to maintain a lazily-loaded cache.
if (Drupal.HierarchicalSelect.cache != null) {
post.push({ name : 'client_supports_caching', value : Drupal.HierarchicalSelect.cache.status() });
}
// updateType is one of:
// - 'none' (default)
// - 'update-hierarchical-select'
// - 'enforced-update'
// - 'create-new-item'
// - 'cancel-new-item'
// - 'add-to-dropbox'
// - 'remove-from-dropbox'
switch (updateType) {
case 'update-hierarchical-select':
var value = $('#'+ settings.select_id).val();
var lastUnchanged = parseInt(settings.select_id.replace(/^.*-hierarchical-select-selects-(\d+)/, "$1")) + 1;
var optionClass = $('#'+ settings.select_id).find('option[value="'+ value +'"]').attr('class');
// Don't do anything (also no callback to the server!) when the selected
// item is:
// - the '<none>' option and the renderFlatSelect setting is disabled, or
// - a level label, or
// - an option of class 'has-no-children', and
// (the renderFlatSelect setting is disabled or the dropbox is enabled)
// and
// (the createNewLevels setting is disabled).
if ((value == 'none' && Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false)
|| value.match(/^label_\d+$/)
|| (optionClass == 'has-no-children'
&&
(
(Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false
|| $('#hierarchical-select-'+ hsid +'-wrapper .dropbox').length > 0
)
&&
Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['createNewLevels'] == false
)
)
)
{
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, updateType, lastUnchanged, function() {
// Remove the sublevels.
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select', Drupal.HierarchicalSelect.context)
.slice(lastUnchanged)
.remove();
// The selection of this hierarchical select has changed!
Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
});
return;
}
post.push({ name : 'op', value : settings.opString });
break;
case 'enforced-update':
post.push({ name : 'op', value : settings.opString });
post = post.concat(settings.extraPost);
break;
case 'create-new-item':
case 'cancel-new-item':
case 'add-to-dropbox':
case 'remove-from-dropbox':
post.push({ name : 'op', value : settings.opString });
break;
default:
break;
}
// Construct the URL the request should be made to.
var url = Drupal.settings.HierarchicalSelect.settings["hs-" + hsid].ajax_url;
// Construct the object that contains the options for a callback to the
// server. If a client-side cache is found however, it's possible that this
// won't be used.
var ajaxOptions = $.extend({}, Drupal.ajax.prototype, {
url: url,
type: 'POST',
dataType: 'json',
data: post,
effect: 'fade',
wrapper: '#hierarchical-select-' + hsid + '-wrapper',
beforeSend: function() {
Drupal.HierarchicalSelect.triggerEvents(hsid, 'before-' + updateType, settings);
Drupal.HierarchicalSelect.disableForm(hsid);
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
// When invalid HTML is received in Safari, jQuery calls this function.
Drupal.HierarchicalSelect.throwError(hsid, Drupal.t('Received an invalid response from the server.'));
},
success: function(response, status) {
// An invalid response may be returned by the server, in case of a PHP
// error. Detect this and let the user know.
if (response === null || response.length == 0) {
Drupal.HierarchicalSelect.throwError(hsid, Drupal.t('Received an invalid response from the server.'));
return;
}
// Execute all AJAX commands in the response. But pass an additional
// hsid parameter, which is then only used by the commands written
// for Hierarchical Select.
// This is another hack because of the non-Drupal ajax implementation
// of this module, one of the response that can come from a drupal
// ajax command is insert, which expects a Drupal.ajax object as the first
// arguments and assumes that certain functions/settings are available.
// Because we are calling a Drupal.ajax.command but providing the regular
// jQuery ajax object itself, we are allowing Drupal.ajax.prototype.commands
// to misserably fail.
//
// This hack attempts to fix one issue with an insert command,
// @see https://www.drupal.org/node/2393695, allowing it to work properly
// Other hacks might be necessary for other ajax commands if they are added
// by external modules.
this.effect = 'none';
this.getEffect = Drupal.ajax.prototype.getEffect;
for (var i in response) {
if (response[i]['command'] && Drupal.ajax.prototype.commands[response[i]['command']]) {
Drupal.ajax.prototype.commands[response[i]['command']](this, response[i], status, hsid);
}
}
// Attach behaviors. This is just after the HTML has been updated, so
// it's as soon as we can.
Drupal.attachBehaviors($('#hierarchical-select-' + hsid + '-wrapper').parents('div.form-type-hierarchical-select')[0]);
// Transform the hierarchical select and/or dropbox to the JS variant,
// make it resizable again and re-enable the disabled form items.
Drupal.HierarchicalSelect.enableForm(hsid);
Drupal.HierarchicalSelect.postUpdateAnimations(hsid, updateType, lastUnchanged, function() {
// Update the client-side cache when:
// - information for in the cache is provided in the response, and
// - the cache system is available, and
// - the cache system is running.
if (response.cache != null && Drupal.HierarchicalSelect.cache != null && Drupal.HierarchicalSelect.cache.status()) {
Drupal.HierarchicalSelect.cache.sync(hsid, response.cache);
}
if (response.log != undefined) {
Drupal.HierarchicalSelect.log(hsid, response.log);
}
Drupal.HierarchicalSelect.triggerEvents(hsid, updateType, settings);
if (updateType == 'update-hierarchical-select') {
// The selection of this hierarchical select has changed!
Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
}
});
}
});
// Use the client-side cache to update the hierarchical select when:
// - the hierarchical select is being updated (i.e. no add/remove), and
// - the renderFlatSelect setting is disabled, and
// - the createNewItems setting is disabled, and
// - the cache system is available, and
// - the cache system is running.
// Otherwise, perform a normal dynamic form submit.
if (updateType == 'update-hierarchical-select'
&& Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['renderFlatSelect'] == false
&& Drupal.settings.HierarchicalSelect.settings["hs-" + hsid]['createNewItems'] == false
&& Drupal.HierarchicalSelect.cache != null
&& Drupal.HierarchicalSelect.cache.status())
{
Drupal.HierarchicalSelect.cache.updateHierarchicalSelect(hsid, value, settings, lastUnchanged, ajaxOptions);
}
else {
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, updateType, lastUnchanged, function() {
// Adding current theme to prevent conflicts, @see ajax.js
// @TODO, try converting to use Drupal.ajax instead.
// Prevent duplicate HTML ids in the returned markup.
// @see drupal_html_id()
var ids = [];
$('[id]').each(function () {
ids.push(this.id);
});
ajaxOptions.data.push({ name : 'ajax_html_ids[]', value : ids });
ajaxOptions.data.push({ name : 'ajax_page_state[theme]', value : Drupal.settings.ajaxPageState.theme });
ajaxOptions.data.push({ name : 'ajax_page_state[theme_token]', value : Drupal.settings.ajaxPageState.theme_token });
for (var key in Drupal.settings.ajaxPageState.css) {
ajaxOptions.data.push({ name : 'ajax_page_state[css][' + key + ']', value : 1});
}
for (var key in Drupal.settings.ajaxPageState.js) {
ajaxOptions.data.push({ name : 'ajax_page_state[js][' + key + ']', value : 1});
}
// Make it work with jquery update
if (Drupal.settings.ajaxPageState.jquery_version) {
ajaxOptions.data.push({ name : 'ajax_page_state[jquery_version]', value : Drupal.settings.ajaxPageState.jquery_version });
}
$.ajax(ajaxOptions);
});
}
};
Drupal.ajax.prototype.commands.hierarchicalSelectUpdate = function(ajax, response, status, hsid) {
// Replace the old HTML with the (relevant part of) retrieved HTML.
$('#hierarchical-select-'+ hsid +'-wrapper', Drupal.HierarchicalSelect.context)
.parent('.form-item')
.replaceWith($(response.output));
};
Drupal.ajax.prototype.commands.hierarchicalSelectSettingsUpdate = function(ajax, response, status, hsid) {
Drupal.settings.HierarchicalSelect.settings["hs-" + response.hsid] = response.settings;
};
})(jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,229 @@
/**
* @file
* Cache system for Hierarchical Select.
* This cache system takes advantage of the HTML 5 client-side database
* storage specification to reduce the number of queries to the server. A lazy
* loading strategy is used.
*/
/**
* Note: this cache system can be replaced by another one, as long as you
* provide the following methods:
* - initialize()
* - status()
* - load()
* - sync()
* - updateHierarchicalSelect()
*
* TODO: better documentation
*/
(function ($) {
Drupal.HierarchicalSelect.cache = {};
Drupal.HierarchicalSelect.cache.initialize = function() {
try {
if (window.openDatabase) {
this.db = openDatabase("Hierarchical Select", "3.x", "Hierarchical Select cache", 200000);
this.db
// Create the housekeeping table if it doesn't exist yet.
.transaction(function(tx) {
tx.executeSql("SELECT COUNT(*) FROM hierarchical_select", [], null, function(tx, error) {
tx.executeSql("CREATE TABLE hierarchical_select (table_name TEXT UNIQUE, expires REAL)", []);
console.log("Created housekeeping table.");
});
})
// Empty tables that have expired, based on the information in the
// housekeeping table.
.transaction(function(tx) {
tx.executeSql("SELECT table_name FROM hierarchical_select WHERE expires < ?", [ new Date().getTime() ], function(tx, resultSet) {
for (var i = 0; i < resultSet.rows.length; i++) {
var row = resultSet.rows.item(i);
var newExpiresTimestamp = new Date().getTime() + 86400;
tx.executeSql("DELETE * FROM " + row.table_name);
tx.executeSql("UPDATE hierarchical_select SET expires = ? WHERE table_name = ?", [ newExpiresTimestamp, row.table_name ]);
console.log("Table "+ row.table_name +" was expired: emptied it. Will expire again in "+ (newExpiresTimestamp - new Date().getTime()) / 3600 +" hours.");
}
});
});
}
else {
this.db = false;
}
}
catch(err) { }
};
Drupal.HierarchicalSelect.cache.status = function() {
return Drupal.HierarchicalSelect.cache.db !== false;
};
Drupal.HierarchicalSelect.cache.table = function(hsid) {
return Drupal.settings.HierarchicalSelect.settings[hsid].cacheId;
};
Drupal.HierarchicalSelect.cache.load = function(hsid) {
// If necessary, create the cache table for the given Hierarchical Select.
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
var table = Drupal.HierarchicalSelect.cache.table(hsid);
tx.executeSql("SELECT value FROM "+ table, [], function(tx, resultSet) {
console.log("" + resultSet.rows.length + " cached items in the " + table + " table.");
}, function(tx, error) {
var expiresTimestamp = new Date().getTime() + 86400;
tx.executeSql("CREATE TABLE "+ table +" (parent REAL, value REAL UNIQUE, label REAL, weight REAL)");
tx.executeSql("INSERT INTO hierarchical_select (table_name, expires) VALUES (?, ?)", [ table, expiresTimestamp ]);
console.log("Created table "+ table +", will expire in "+ (expiresTimestamp - new Date().getTime()) / 3600 +" hours.");
});
});
};
Drupal.HierarchicalSelect.cache.insertOnDuplicateKeyUpdate = function(table, row) {
// console.log("storing: value: "+ row.value +", label: "+ row.label +", parent: "+ row.parent +", weight: "+ row.weight);
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
tx.executeSql("INSERT INTO "+ table +" (parent, value, label, weight) VALUES (?, ?, ?, ?)", [ row.parent, row.value, row.label, row.weight ], null, function(tx, error) {
// console.log("UPDATING value: "+ row.value +", label: "+ row.label +", parent: "+ row.parent +", weight: "+ row.weight);
tx.executeSql("UPDATE "+ table +" SET parent = ?, label = ?, weight = ? WHERE value = ?", [ row.parent, row.label, row.weight, row.value ], null, function(tx, error) {
// console.log("sql error: " + error.message);
});
});
});
};
Drupal.HierarchicalSelect.cache.sync = function(hsid, info) {
var table = Drupal.HierarchicalSelect.cache.table(hsid);
for (var id in info) {
var closure = function(_info, id) {
Drupal.HierarchicalSelect.cache.insertOnDuplicateKeyUpdate(table, _info[id]);
} (info, id);
}
};
Drupal.HierarchicalSelect.cache.hasChildren = function(hsid, value, successCallback, failCallback) {
var table = Drupal.HierarchicalSelect.cache.table(hsid);
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
tx.executeSql("SELECT * FROM "+ table +" WHERE parent = ?", [ value ], function(tx, resultSet) {
if (resultSet.rows.length > 0) {
successCallback();
}
else {
failCallback();
}
});
});
};
Drupal.HierarchicalSelect.cache.getSubLevels = function(hsid, value, callback, previousSubLevels) {
var table = Drupal.HierarchicalSelect.cache.table(hsid);
var subLevels = new Array();
if (previousSubLevels != undefined) {
subLevels = previousSubLevels;
}
Drupal.HierarchicalSelect.cache.db.transaction(function(tx) {
tx.executeSql("SELECT value, label FROM "+ table +" WHERE parent = ? ORDER BY weight", [ value ], function(tx, resultSet) {
var numChildren = resultSet.rows.length;
// If there's only one child, check if it has the dummy "<value>-has-no-children" value.
if (numChildren == 1) {
var valueOfFirstRow = String(resultSet.rows.item(0).value);
var isDummy = valueOfFirstRow.match(/^.*-has-no-children$/);
}
// Only pass the children if there are any (and not a fake one either).
if (numChildren && !isDummy) {
var level = new Array();
for (var i = 0; i < resultSet.rows.length; i++) {
var row = resultSet.rows.item(i);
level[i] = { 'value' : row.value, 'label' : row.label };
console.log("child of "+ value +": ("+ row.value +", "+ row.label +")");
}
subLevels.push(level);
Drupal.HierarchicalSelect.cache.getSubLevels(hsid, level[0].value, callback, subLevels);
}
else {
if (subLevels.length > 0) {
callback(subLevels);
}
else {
callback(false);
}
}
});
});
};
Drupal.HierarchicalSelect.cache.createAndUpdateSelects = function(hsid, subLevels, lastUnchanged) {
// Remove all levels below the level in which a value was selected, if they
// exist.
// Note: the root level can never change because of this!
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select').slice(lastUnchanged).remove();
// Create the new sublevels, by cloning the root level and then modifying
// that clone.
var $rootSelect = $('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select:first');
for (var depth in subLevels) {
var optionElements = $.map(subLevels[depth], function(item) { return '<option value="'+ item.value +'">'+ item.label +'</option>'; });
var level = parseInt(lastUnchanged) + parseInt(depth);
$('#hierarchical-select-'+ hsid +'-wrapper .hierarchical-select .selects select:last').after(
$rootSelect.clone()
// Update the name attribute.
.attr('name', $rootSelect.attr('name').replace(/(.*)\d+\]$/, "$1"+ level +"]"))
// Update the id attribute.
.attr('id', $rootSelect.attr('id').replace(/(.*-hierarchical-select-selects-)\d+/, "$1"+ level))
// Remove the existing options and set the new ones.
.empty().append(optionElements.join(''))
);
}
};
Drupal.HierarchicalSelect.cache.updateHierarchicalSelect = function(hsid, value, settings, lastUnchanged, ajaxOptions) {
// If the selected value has children
Drupal.HierarchicalSelect.cache.hasChildren(hsid, value, function() {
console.log("Cache hit.");
Drupal.HierarchicalSelect.cache.getSubLevels(hsid, value, function(subLevels) {
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
if (subLevels !== false) {
Drupal.HierarchicalSelect.cache.createAndUpdateSelects(hsid, subLevels, lastUnchanged);
}
else {
// Nothing must happen: the user selected a value that doesn't
// have any subLevels.
$('#hierarchical-select-' + hsid + '-wrapper .hierarchical-select .selects select').slice(lastUnchanged).remove();
}
Drupal.HierarchicalSelect.postUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
// Reattach the bindings.
Drupal.HierarchicalSelect.attachBindings(hsid);
Drupal.HierarchicalSelect.triggerEvents(hsid, 'update-hierarchical-select', settings);
// The selection of this hierarchical select has changed!
Drupal.HierarchicalSelect.triggerEvents(hsid, 'change-hierarchical-select', settings);
});
});
});
}, function() {
// This item was not yet requested before, so we still have to perform
// the dynamic form submit.
console.log("Cache miss. Querying the server.");
Drupal.HierarchicalSelect.preUpdateAnimations(hsid, 'update-hierarchical-select', lastUnchanged, function() {
$.ajax(ajaxOptions);
});
});
};
})(jQuery);

View File

@@ -0,0 +1,95 @@
/**
* @file
* Contains the formToArray method and the method it depends on. Taken from
* jQuery Form Plugin 2.12. (http://www.malsup.com/jquery/form/)
*/
(function ($) {
/**
* formToArray() gathers form element data into an array of objects that can
* be passed to any of the following ajax functions: $.get, $.post, or load.
* Each object in the array has both a 'name' and 'value' property. An example of
* an array for a simple login form might be:
*
* [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
*
* It is this array that is passed to pre-submit callback functions provided to the
* ajaxSubmit() and ajaxForm() methods.
*/
$.fn.formToArray = function(semantic) {
var a = [];
if (this.length == 0) return a;
var form = this[0];
var els = semantic ? form.getElementsByTagName('*') : form.elements;
if (!els) return a;
for(var i=0, max=els.length; i < max; i++) {
var el = els[i];
var n = el.name;
if (!n) continue;
if (semantic && form.clk && el.type == "image") {
// handle image inputs on the fly when semantic == true
if(!el.disabled && form.clk == el)
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
continue;
}
var v = $.fieldValue(el, true);
if (v && v.constructor == Array) {
for(var j=0, jmax=v.length; j < jmax; j++)
a.push({name: n, value: v[j]});
}
else if (v !== null && typeof v != 'undefined')
a.push({name: n, value: v});
}
if (!semantic && form.clk) {
// input type=='image' are not found in elements array! handle them here
var inputs = form.getElementsByTagName("input");
for(var i=0, max=inputs.length; i < max; i++) {
var input = inputs[i];
var n = input.name;
if(n && !input.disabled && input.type == "image" && form.clk == input)
a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
}
}
return a;
};
/**
* Returns the value of the field element.
*/
$.fieldValue = function(el, successful) {
var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
if (typeof successful == 'undefined') successful = true;
if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
(t == 'checkbox' || t == 'radio') && !el.checked ||
(t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
tag == 'select' && el.selectedIndex == -1))
return null;
if (tag == 'select') {
var index = el.selectedIndex;
if (index < 0) return null;
var a = [], ops = el.options;
var one = (t == 'select-one');
var max = (one ? index+1 : ops.length);
for(var i=(one ? index : 0); i < max; i++) {
var op = ops[i];
if (op.selected) {
// extra pain for IE...
var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
if (one) return v;
a.push(v);
}
}
return a;
}
return el.value;
};
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 B

View File

@@ -0,0 +1,441 @@
<?php
/**
* @file
* Functions used by more than one Hierarchical Select implementation.
*/
/**
* Retrieve a config. If certain settings are not yet configured by the user,
* defaults will be set. These defaults can also be overriden. This allows
* modules to provide their own meaningful defaults.
*
* @param $config_id
* A config id, typically of the form "module-someid", e.g.
* "taxonomy-field_name".
* @param $defaults_override
* Optionally override the defaults.
*
* @return array
* An array of default config.
*/
function hierarchical_select_common_config_get($config_id, $defaults_override = array()) {
$config = variable_get('hs_config_' . $config_id, array());
return _hierarchical_select_inherit_default_config($config, $defaults_override);
}
/**
* Set a config.
*
* @param $config_id
* A config id.
* @param $config
* The config to store.
*/
function hierarchical_select_common_config_set($config_id, $config) {
variable_set('hs_config_' . $config_id, $config);
}
/**
* Delete a config.
*
* @param $config_id
* The config id to delete.
*/
function hierarchical_select_common_config_del($config_id) {
variable_del('hs_config_' . $config_id);
}
/**
* Apply a config to a form item.
*
* @param $form_item
* The form item that will be updated.
* @param $config_id
* A config id.
* @param $defaults_override
* Optionally override the defaults.
* @see hierarchical_select_common_config_get()
*/
function hierarchical_select_common_config_apply(&$form_item, $config_id, $defaults_override = array()) {
$config = hierarchical_select_common_config_get($config_id, $defaults_override);
$form_item['#config'] = array_merge((isset($form_item['#config']) && is_array($form_item['#config'])) ? $form_item['#config'] : array(), $config);
}
//----------------------------------------------------------------------------
// Forms API callbacks.
/**
* Form definition of the hierarchical_select_common_config_form form.
*/
function hierarchical_select_common_config_form($module, $params, $config_id, $defaults_override, $strings, $max_hierarchy_depth, $preview_is_required) {
$config = hierarchical_select_common_config_get($config_id, $defaults_override);
$args = array(
'!item' => $strings['item'],
'!items' => $strings['items'],
'!entity' => $strings['entity'],
'!entities' => $strings['entities'],
'!hierarchy' => $strings['hierarchy'],
'!hierarchies' => $strings['hierarchies']
);
$form = array(
'#tree' => TRUE,
'#type' => 'fieldset',
'#title' => t('Hierarchical Select configuration'),
'#attributes' => array(
'class' => array('hierarchical-select-config-form'),
'id' => 'hierarchical-select-config-form-' . $config_id,
),
'#attached' => array(
'css' => array(
drupal_get_path('module', 'hierarchical_select') . '/includes/common_config_form.css'
),
'js' => array(
array(
'type' => 'file',
'data' => drupal_get_path('module', 'hierarchical_select') . '/includes/common_config_form.js',
),
array(
'type' => 'setting',
'data' => array('HierarchicalSelect' => array('configForm' => array($config_id))),
),
),
)
);
$form['config_id'] = array('#type' => 'value', '#value' => $config_id);
// TODO: really make this a *live* preview, i.e. refresh the preview on each
// change in the form. This cannot be done easily in Drupal 5 or 6, so let's
// do so in Drupal 7. See cfg.livePreview in common_config_form.js.
$form['live_preview'] = array(
'#type' => 'fieldset',
'#title' => t('Preview'),
'#description' => t('This is what the Hierarchical Select will look like with your current configuration.'),
'#collapsible' => FALSE,
'#attributes' => array('class' => array('live-preview')),
);
$form['live_preview']['example'] = array(
'#type' => 'hierarchical_select',
'#required' => $preview_is_required,
'#title' => t('Preview'),
'#description' => t('The description.'),
// Skip al validation for this form element: the data collected through it
// is always discarded, it's merely here for illustrative purposes.
'#validated' => TRUE,
);
hierarchical_select_common_config_apply($form['live_preview']['example'], $config_id, array_merge($defaults_override, array('module' => $module, 'params' => $params)));
$form['save_lineage'] = array(
'#type' => 'radios',
'#title' => t('Save lineage'),
'#options' => array(
1 => t('Save !item lineage', $args),
0 => t('Save only the deepest !item', $args),
),
'#default_value' => (isset($config['save_lineage'])) ? $config['save_lineage'] : NULL,
'#description' => t(
'Saving the !item lineage means saving the <em>the !item itself and all
its ancestors</em>.',
$args
),
);
$form['enforce_deepest'] = array(
'#type' => 'radios',
'#title' => t('Level choice'),
'#options' => array(
1 => t('Force the user to choose a !item from a <em>deepest level</em>', $args),
0 => t('Allow the user to choose a !item from <em>any level</em>', $args),
),
'#default_value' => (isset($config['enforce_deepest'])) ? $config['enforce_deepest'] : NULL,
'#description' => t(
'This setting determines from which level in the !hierarchy tree a
user can select a !item.',
$args
),
'#attributes' => array('class' => array('enforce-deepest')),
);
$form['resizable'] = array(
'#type' => 'radios',
'#title' => t('Resizable'),
'#description' => t(
"When enabled, a handle appears below the Hierarchical Select to allow
the user to dynamically resize it. Double clicking will toggle between
the smallest and a sane 'big size'."
),
'#options' => array(
0 => t('Disabled'),
1 => t('Enabled'),
),
'#default_value' => (isset($config['resizable'])) ? $config['resizable'] : NULL,
'#attributes' => array('class' => array('resizable')),
);
$form['level_labels'] = array(
'#tree' => TRUE,
'#type' => 'fieldset',
'#title' => t('Level labels'),
'#description' => t(
'When the user is allowed to choose a !item from any level in the
<em>Level choice</em> setting, you can enter a label for <em>each</em>
level.<br />
However, when the user is only allowed to choose a !item from the
deepest level, then you can only enter a label for the <em>root</em>
level.',
$args
),
'#collapsible' => TRUE,
);
$form['level_labels']['status'] = array(
'#type' => 'checkbox',
'#title' => t('Enable level labels'),
'#default_value' => (isset($config['level_labels']['status'])) ? $config['level_labels']['status'] : NULL,
'#attributes' => array('class' => array('level-labels-status')),
);
for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
$form['level_labels']['labels'][$depth] = array(
'#type' => 'textfield',
'#size' => 20,
'#maxlength' => 255,
'#default_value' => (isset($config['level_labels']['labels'][$depth])) ? $config['level_labels']['labels'][$depth] : NULL,
'#attributes' => array('class' => array('level-label')),
);
}
$form['level_labels']['#theme'] = 'hierarchical_select_common_config_form_level_labels';
$form['level_labels']['#strings'] = $strings;
$form['dropbox'] = array(
'#type' => 'fieldset',
'#title' => t('Dropbox settings'),
'#description' => t('The dropbox allows the user to <strong>make multiple selections</strong>.'),
'#collapsible' => TRUE,
);
$form['dropbox']['status'] = array(
'#type' => 'checkbox',
'#title' => t('Enable the dropbox'),
'#default_value' => (isset($config['dropbox']['status'])) ? $config['dropbox']['status'] : NULL,
'#attributes' => array('class' => array('dropbox-status')),
);
$form['dropbox']['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#description' => t('The title you enter here appears above the dropbox.'),
'#size' => 20,
'#maxlength' => 255,
'#default_value' => (isset($config['dropbox']['title'])) ? $config['dropbox']['title'] : NULL,
'#attributes' => array('class' => array('dropbox-title')),
);
$form['dropbox']['limit'] = array(
'#type' => 'textfield',
'#title' => t('Limit the number of selections'),
'#description' => t(
'Limits the number of selections that can be added to the dropbox.
0 means no limit.
<br />
Note: the "Save !item lineage" option has no effect on this, even if
a lineage consists of 3 !items, this will count as only one selection
in the dropbox.',
$args
),
'#size' => 5,
'#maxlength' => 5,
'#default_value' => (isset($config['dropbox']['limit'])) ? $config['dropbox']['limit'] : NULL,
'#attributes' => array('class' => array('dropbox-limit')),
);
$form['dropbox']['reset_hs'] = array(
'#type' => 'radios',
'#title' => t('Reset selection of hierarchical select'),
'#description' => t(
'This setting determines what will happen to the hierarchical select
when the user has added a selection to the dropbox.'
),
'#options' => array(
0 => t('Disabled'),
1 => t('Enabled'),
),
'#default_value' => (isset($config['dropbox']['reset_hs'])) ? $config['dropbox']['reset_hs'] : NULL,
'#attributes' => array('class' => array('dropbox-reset-hs')),
);
$form['dropbox']['sort'] = array(
'#type' => 'checkbox',
'#title' => t('Sort dropbox items'),
'#description' => t('Automatically sort items added to the dropbox. If unchecked new items will be added to the end of the dropbox list.'),
'#default_value' => (isset($config['dropbox']['sort'])) ? $config['dropbox']['sort'] : 1,
'#attributes' => array('class' => array('dropbox-sort')),
);
if (module_hook($module, 'hierarchical_select_create_item')) {
$form['editability'] = array(
'#type' => 'fieldset',
'#title' => t('Editability settings'),
'#description' => t(
'You can allow the user to <strong>add new !items to this
!hierarchy</strong> <em>through</em> Hierarchical Select.',
$args
),
'#collapsible' => TRUE,
);
$form['editability']['status'] = array(
'#type' => 'checkbox',
'#title' => t('Allow creation of new !items', $args),
'#options' => array(
0 => t('Disabled'),
1 => t('Enabled'),
),
'#default_value' => (isset($config['editability']['status'])) ? $config['editability']['status'] : NULL,
'#attributes' => array('class' => array('editability-status')),
);
for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
$form['editability']['item_types'][$depth] = array(
'#type' => 'textfield',
'#size' => 20,
'#maxlength' => 255,
'#default_value' => (isset($config['editability']['item_types'][$depth])) ? $config['editability']['item_types'][$depth] : NULL,
'#attributes' => array('class' => array('editability-item-type')),
);
}
for ($depth = 0; $depth <= $max_hierarchy_depth; $depth++) {
$form['editability']['allowed_levels'][$depth] = array(
'#type' => 'checkbox',
'#default_value' => (isset($config['editability']['allowed_levels'][$depth])) ? $config['editability']['allowed_levels'][$depth] : 1,
);
}
$form['editability']['allow_new_levels'] = array(
'#type' => 'checkbox',
'#title' => t('Allow creation of new levels'),
'#default_value' => $config['editability']['allow_new_levels'],
'#description' => t(
'Allow the user to create child !items for !items that do not yet have
children.',
$args
),
'#attributes' => array('class' => array('editability-allow-new-levels')),
);
$form['editability']['max_levels'] = array(
'#type' => 'select',
'#title' => t('Maximum number of levels allowed'),
'#options' => array(
0 => t('0 (no limit)'), 1, 2, 3, 4, 5, 6, 7, 8, 9
),
'#default_value' => (isset($config['editability']['max_levels'])) ? $config['editability']['max_levels'] : NULL,
'#description' => t(
'When the user is allowed to create new levels, this option prevents
the user from creating extremely deep !hierarchies.',
$args
),
'#attributes' => array('class' => array('editability-max-levels')),
);
$form['editability']['#theme'] = 'hierarchical_select_common_config_form_editability';
$form['editability']['#strings'] = $strings;
}
if (module_hook($module, 'hierarchical_select_entity_count')) {
$form['entity_count'] = array(
'#type' => 'fieldset',
'#title' => t('Entity Count'),
'#collapsible' => TRUE,
);
$form['entity_count']['enabled'] = array(
'#type' => 'checkbox',
'#title' => t('Display number of entities'),
'#description' => t('Display the number of entities associated with the !item. Do not forget to check which entities should be counted.', $args),
'#default_value' => isset($config['entity_count']['enabled']) ? $config['entity_count']['enabled'] : FALSE,
'#weight' => -1,
'#attributes' => array('class' => array('entity-count-enabled')),
);
$form['entity_count']['require_entity'] = array(
'#type' => 'checkbox',
'#title' => t('Require associated entity'),
'#description' => t('If checked only !items that have at least one entity associated with them will be displayed.', $args),
'#default_value' => (isset($config['entity_count']['require_entity'])) ? $config['entity_count']['require_entity'] : FALSE,
);
$form['entity_count']['settings'] = array(
'#type' => 'fieldset',
'#title' => t('Entity count settings'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
'#weight' => -1,
'#attributes' => array('class' => array('entity-count-settings')),
);
$form['entity_count']['settings']['count_children'] = array(
'#type' => 'checkbox',
'#title' => t('Also count children of !item.', $args),
'#description' => t('If checked this will result in a larger number because the children will be counted also.'),
'#default_value' => isset($config['entity_count']['settings']['count_children']) ? $config['entity_count']['settings']['count_children'] : FALSE,
);
$form['entity_count']['settings']['entity_types'] = array(
'#type' => 'item',
'#title' => t('Select entities that should be counted.'),
'#description' => t('Select entity type or one of it\'s bundles that should be counted'),
);
$entity_info = entity_get_info();
foreach ($entity_info as $entity => $entity_info) {
if (!empty($entity_info['bundles']) && $entity_info['fieldable'] === TRUE) {
$options = array();
$default_values = array();
$form['entity_count']['settings']['entity_types'][$entity] = array(
'#type' => 'fieldset',
'#title' => check_plain($entity_info['label']),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
foreach ($entity_info['bundles'] as $bundle => $bundle_info) {
$options[$bundle] = check_plain($bundle_info['label']);
$default_values[$entity][$bundle] = isset($config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle]) ? $config['entity_count']['settings']['entity_types'][$entity]['count_' . $entity][$bundle] : 0;
}
$form['entity_count']['settings']['entity_types'][$entity]['count_' . $entity] = array(
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $default_values[$entity],
);
}
}
}
return $form;
}
/**
* Submit callback for the hierarchical_select_common_config_form form.
*/
function hierarchical_select_common_config_form_submit($form, &$form_state) {
$config = _hierarchical_select_get_form_item_by_parents($form_state['values'], $form['#hs_common_config_form_parents']);
// Don't include the value of the live preview in the config.
unset($config['live_preview']);
hierarchical_select_common_config_set($config['config_id'], $config);
}
/**
* Get the form element of a form that has a certain lineage of parents.
*
* @param $form
* A structured array for use in the Forms API.
* @param $parents
* An array of parent form element names.
* @return
* The form element that has the specified lineage of parents.
*/
function _hierarchical_select_get_form_item_by_parents($form, $parents) {
if (count($parents)) {
$parent = array_shift($parents);
return _hierarchical_select_get_form_item_by_parents($form[$parent], $parents);
}
else {
return $form;
}
}

View File

@@ -0,0 +1,11 @@
.hierarchical-select-config-form .live-preview {
margin-left: auto;
margin-right: auto;
width: 25em;
float: right;
}
.hierarchical-select-config-form fieldset {
clear: right;
}

View File

@@ -0,0 +1,148 @@
Drupal.HierarchicalSelectConfigForm = {};
(function ($, cfg) {
cfg.context = function(configId) {
if (configId === undefined) {
return $('.hierarchical-select-config-form > *').not('.live-preview');
}
else {
return $('#hierarchical-select-config-form-'+ configId + ' > *').not('.live-preview');
}
};
cfg.levelLabels = function(configId) {
var $status = $('.level-labels-status', cfg.context(configId));
var $enforceDeepest = $('.enforce-deepest input', cfg.context(configId));
var showHide = function(speed) {
$affected = $('.level-labels-settings', cfg.context(configId));
if (!$status.is(':checked')) {
$affected.hide(speed);
}
else {
// For showing/hiding rows, I'm relying on setting the style
// "display: none" and removing it again. jQuery's show()/hide() leave
// "display: block" behind and are thereby messing up the table layout.
if ($enforceDeepest.slice(1, 2).is(':checked')) {
$affected.find('tr').removeAttr('style');
}
else {
// We need to take special measures if sticky headers are enabled, so
// handle the show/hide separately when it's enabled.
if ($affected.find('table.sticky-header').length == 0) {
$affected.find('tr').slice(0, 2).removeAttr('style'); // Show header tr and root level tr.
$affected.find('tr').slice(2).attr('style', 'display: none'); // Hide all other tr's.
}
else {
$affected.find('table').show(speed); // Show both tables (the one with the sticky headers and the one with the actual content).
$affected.find('table').slice(1).find('tr').slice(2).attr('style', 'display: none'); // Show all tr's after the header tr and root level tr of the 2nd table (the one with the actual content).
}
}
// If $status was unchecked previously, the entire div would have been
// hidden!
if ($affected.css('display') == 'none') {
$affected.show(speed);
}
}
};
$status.click(function() { showHide(200); });
$enforceDeepest.click(function() { showHide(200); });
showHide(0);
};
cfg.dropbox = function(configId) {
var $status = $('.dropbox-status', cfg.context(configId));
var showHide = function(speed) {
var $affected = $('.dropbox-title, .dropbox-limit, .dropbox-reset-hs', cfg.context(configId)).parent();
if ($status.is(':checked')) {
$affected.show(speed);
}
else {
$affected.hide(speed);
}
};
$status.click(function() { showHide(200); });
showHide(0);
};
cfg.editability = function(configId) {
var $status = $('.editability-status', cfg.context(configId));
var $allowNewLevels = $('.editability-allow-new-levels', cfg.context(configId));
var showHide = function(speed) {
var $affected = $('.editability-per-level-settings, .form-item:has(.editability-allow-new-levels)', cfg.context(configId));
var $maxLevels = $('.form-item:has(.editability-max-levels)', cfg.context(configId));
if ($status.is(':checked')) {
if ($allowNewLevels.is(':checked')) {
$affected.add($maxLevels).show(speed);
}
else {
$affected.show(speed);
}
}
else {
$affected.add($maxLevels).hide(speed);
}
};
var showHideMaxLevels = function(speed) {
$affected = $('.editability-max-levels', cfg.context(configId)).parent();
if ($allowNewLevels.is(':checked')) {
$affected.show(speed);
}
else {
$affected.hide(speed);
}
};
$status.click(function() { showHide(200); });
$allowNewLevels.click(function() { showHideMaxLevels(200); });
showHideMaxLevels(0);
showHide(0);
};
cfg.entityCount = function(configId) {
var $status = $('.entity-count-enabled', cfg.context(configId));
var showHide = function(speed) {
var $affected = $('.entity-count-settings', cfg.context(configId));
if ($status.is(':checked')) {
$affected.show(speed);
}
else {
$affected.hide(speed);
}
};
$status.click(function() { showHide(200); });
showHide(0);
};
cfg.livePreview = function(configId) {
// React on changes to any input, except the ones in the live preview.
$updateLivePreview = $('input', cfg.context(configId))
.filter(':not(.create-new-item-input):not(.create-new-item-create):not(.create-new-item-cancel)')
.change(function() {
// TODO: Do an AJAX submit of the entire form.
});
};
$(document).ready(function() {
for (var id in Drupal.settings.HierarchicalSelect.configForm) {
var configId = Drupal.settings.HierarchicalSelect.configForm.id;
cfg.levelLabels(configId);
cfg.dropbox(configId);
cfg.editability(configId);
cfg.entityCount(configId);
//cfg.livePreview(configId);
}
});
})(jQuery, Drupal.HierarchicalSelectConfigForm);

View File

@@ -0,0 +1,413 @@
<?php
/**
* @file
* All theme functions for the Hierarchical Select module.
*/
/**
* @ingroup themeable
* @{
*/
/**
* Return a themed Hierarchical Select form element.
*
* @param array $variables
* An associative array containing the properties of the element.
* Properties used: title, description, id, required
*
* @return string
* A string representing the form element.
*
* @ingroup themeable
*/
function theme_hierarchical_select_form_element($variables) {
$element = $variables['element'];
$value = $variables['value'];
$output = '<div class="form-item hierarchical-select-wrapper-wrapper"';
if (!empty($element['#id'])) {
$output .= ' id="' . $element['#id'] . '-wrapper"';
}
$output .= ">\n";
$required = !empty($element['#required']) ? '<span class="form-required" title="' . t('This field is required.') . '">*</span>' : '';
if (!empty($element['#title'])) {
$title = $element['#title'];
if (!empty($element['#id'])) {
$output .= ' <label for="' . $element['#id'] . '">' . t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) . "</label>\n";
}
else {
$output .= ' <label>' . t('!title: !required', array('!title' => filter_xss_admin($title), '!required' => $required)) . "</label>\n";
}
}
$output .= " $value\n";
if (!empty($element['#description'])) {
$output .= ' <div class="description">' . $element['#description'] . "</div>\n";
}
$output .= "</div>\n";
return $output;
}
/**
* Format a hierarchical select.
*
* @param array $variables
* An associative array containing the properties of the element.
* @return string
* A themed HTML string representing the form element.
*/
function theme_hierarchical_select($variables) {
$element = $variables['element'];
$output = '';
// Update $element['#attributes']['class'].
if (!isset($element['#attributes']['class'])) {
$element['#attributes']['class'] = array();
}
$hsid = $element['hsid']['#value'];
$level_labels_style = variable_get('hierarchical_select_level_labels_style', 'none');
$classes = array(
'hierarchical-select-wrapper',
"hierarchical-select-level-labels-style-$level_labels_style",
// Classes that make it possible to override the styling of specific
// instances of Hierarchical Select, based on either the ID of the form
// element or the config that it uses.
'hierarchical-select-wrapper-for-name-' . $element['#id'],
(isset($element['#config']['config_id'])) ? 'hierarchical-select-wrapper-for-config-' . $element['#config']['config_id'] : NULL,
);
$element['#attributes']['class'] = array_merge($element['#attributes']['class'], $classes);
$element['#attributes']['id'] = "hierarchical-select-$hsid-wrapper";
$element['#id'] = "hierarchical-select-$hsid-wrapper"; // This ensures the label's for attribute is correct.
return '<div ' . drupal_attributes($element['#attributes']) . '>' . drupal_render_children($element) . '</div>';
}
/**
* Format the container for all selects in the hierarchical select.
*
* @param array $variables
* An associative array containing the properties of the element.
* @return string
* A themed HTML string representing the form element.
*/
function theme_hierarchical_select_selects_container($variables) {
$element = $variables['element'];
$output = '';
$output .= '<div class="hierarchical-select clearfix">';
$output .= drupal_render_children($element);
$output .= '</div>';
return $output;
}
/**
* Format a select in the .hierarchial-select div: prevent it from being
* wrapped in a div. This simplifies the CSS and JS code.
*
* @param array $variables
* An associative array containing the properties of the element.
* @return string
* A themed HTML string representing the form element.
*/
function theme_hierarchical_select_select($variables) {
$element = $variables['element'];
element_set_attributes($element, array('id', 'name', 'size'));
_form_set_class($element, array('form-select'));
return '<select' . drupal_attributes($element['#attributes']) . '>' . _hierarchical_select_options($element) . '</select>';
}
/**
* Format an item separator (for use in a lineage).
*/
function theme_hierarchical_select_item_separator($variables) {
$output = '';
$output .= '<span class="hierarchical-select-item-separator">';
$output .= '';
$output .= '</span>';
return $output;
}
/**
* Format a special option in a Hierarchical Select select. For example the
* "none" option or the "create new item" option. This theme function allows
* you to change how a special option is indicated textually.
*
* @param array $variables
* A special option.
* @return string
* A textually indicated special option.
*/
function theme_hierarchical_select_special_option($variables) {
$option = $variables['option'];
return '<' . $option . '>';
}
/**
* Forms API theming callback for the dropbox. Renders the dropbox as a table.
*
* @param array $variables
* An element for which the #theme property was set to this function.
* @return string
* A themed HTML string.
*/
function theme_hierarchical_select_dropbox_table($variables) {
$element = $variables['element'];
$output = '';
$class = 'dropbox';
if (form_get_error($element) === '') {
$class .= ' error';
}
$title = $element['title']['#value'];
$separator = $element['separator']['#value'];
$is_empty = $element['is_empty']['#value'];
$separator_html = '<span class="hierarchical-select-item-separator">' . $separator . '</span>';
$output .= '<div class="' . $class . '">';
$output .= '<table>';
$output .= '<caption class="dropbox-title">' . $title . '</caption>';
$output .= '<tbody>';
if (!$is_empty) {
// Each lineage in the dropbox corresponds to an entry in the dropbox table.
$lineage_count = count(element_children($element['lineages']));
for ($x = 0; $x < $lineage_count; $x++) {
$db_entry = $element['lineages']["lineage-$x"];
$zebra = $db_entry['#zebra'];
$first = $db_entry['#first'];
$last = $db_entry['#last'];
// The deepest level is the number of child levels minus one. This "one"
// is the element for the "Remove" checkbox.
$deepest_level = count(element_children($db_entry)) - 1;
$output .= '<tr class="dropbox-entry ' . $first . ' ' . $last . ' ' . $zebra . '">';
$output .= '<td>';
// Each item in a lineage is separated by the separator string.
for ($depth = 0; $depth < $deepest_level; $depth++) {
$output .= drupal_render($db_entry[$depth]);
if ($depth < $deepest_level - 1) {
$output .= $separator_html;
}
}
$output .= '</td>';
$output .= '<td class="dropbox-remove">' . drupal_render($db_entry['remove']) . '</td>';
$output .= '</tr>';
}
}
else {
$output .= '<tr class="dropbox-entry first last dropbox-is-empty"><td>';
$output .= t('Nothing has been selected.');
$output .= '</td></tr>';
}
$output .= '</tbody>';
$output .= '</table>';
$output .= '</div>';
return $output;
}
/**
* Themeing function to render the level_labels settings as a table.
*/
// TODO: rename $form to $element for consistency (and update hook_theme() after that), make the comment consistent.
/**
* @todo Please document this function.
* @see http://drupal.org/node/1354
*/
function theme_hierarchical_select_common_config_form_level_labels($variables) {
$form = $variables['form'];
// Recover the stored strings.
$strings = $form['#strings'];
$output = '';
$header = array(t('Level'), t('Label'));
$rows = array();
$output .= drupal_render($form['status']);
$output .= '<div class="level-labels-settings">';
if (isset($form['labels']) && count(element_children($form['labels']))) {
foreach (element_children($form['labels']) as $depth) {
$row = array();
$row[]['data'] = ($depth == 0) ? t('Root level') : t('Sublevel !depth', array('!depth' => $depth));
$row[]['data'] = drupal_render($form['labels'][$depth]);
$rows[] = $row;
}
// Render the table.
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;')));
}
else {
// No levels exist yet in the hierarchy!
$output .= '<p><strong>';
$output .= t('There are no levels yet in this !hierarchy!', array('!hierarchy' => $strings['hierarchy']));
$output .= '</strong></p>';
}
$output .= '</div>';
// Render the remaining form items.
$output .= drupal_render_children($form);
return $output;
}
/**
* Themeing function to render the per-level editability settings as a table,
* (these are the item_types and allowed_levels settings).
*/
// TODO: rename $form to $element for consistency (and update hook_theme() after that), make the comment consistent.
/**
* @todo Please document this function.
* @see http://drupal.org/node/1354
*/
function theme_hierarchical_select_common_config_form_editability($variables) {
$form = $variables['form'];
// Recover the stored strings.
$strings = $form['#strings'];
$output = '';
$header = array(t('Level'), t('Allow'), t('!item_type', array('!item_type' => drupal_ucfirst($strings['item_type']))));
$rows = array();
$output .= drupal_render($form['status']);
$output .= '<div class="editability-per-level-settings">';
if (isset($form['item_types']) && count(element_children($form['item_types']))) {
foreach (element_children($form['item_types']) as $depth) {
$row = array();
$row[]['data'] = ($depth == 0) ? t('Root level') : t('Sublevel !depth', array('!depth' => $depth));
$row[]['data'] = drupal_render($form['allowed_levels'][$depth]);
$row[]['data'] = drupal_render($form['item_types'][$depth]);
$rows[] = $row;
}
// Render the table and description.
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto;'), 'caption' => '<em>' . t('Per-level settings for creating new !items.', array('!items' => $strings['items']))));
$output .= '<div class="description">';
$output .= t(
'The %item_type you enter for each level is what will be used in
each level to replace a "&lt;create new item&gt;" option with a
"&lt;create new %item_type&gt;" option, which is often more
intuitive.',
array(
'%item_type' => $strings['item_type'],
)
);
$output .= '</div>';
}
else {
// No levels exist yet in the hierarchy!
$output .= '<p><strong>';
$output .= t('There are no levels yet in this !hierarchy!', array('!hierarchy' => $strings['hierarchy']));
$output .= '</strong></p>';
}
$output .= '</div>';
// Render the remaining form items.
$output .= drupal_render_children($form);
return $output;
}
/**
* Themeing function to render a selection (of items) according to a given
* Hierarchical Select configuration as one or more lineages.
*
* @param $selection
* A selection of items of a hierarchy.
* @param $config
* A config array with at least the following settings:
* - module
* - save_lineage
* - params
*/
function theme_hierarchical_select_selection_as_lineages($variables) {
$selection = $variables['selection'];
$config = $variables['config'];
$output = '';
$selection = (!is_array($selection)) ? array($selection) : $selection;
// Generate a dropbox out of the selection. This will automatically
// calculate all lineages for us.
$selection = array_keys($selection);
$dropbox = _hierarchical_select_dropbox_generate($config, $selection);
// Actual formatting.
foreach ($dropbox->lineages as $id => $lineage) {
if ($id > 0) {
$output .= '<br />';
}
$items = array();
foreach ($lineage as $level => $item) {
$items[] = $item['label'];
}
$output .= implode('<span class="hierarchical-select-item-separator"></span>', $items);
}
// Add the CSS.
drupal_add_css(drupal_get_path('module', 'hierarchical_select') . '/hierarchical_select.css');
return $output;
}
/**
* @} End of "ingroup themeable".
*/
//----------------------------------------------------------------------------
// Private functions.
/**
* This is an altered clone of form_select_options(). The reason: I need to be
* able to set a class on an option element if it contains a level label, to
* allow for level label styles.
* TODO: rename to _hierarchical_select_select_options().
*/
function _hierarchical_select_options($element) {
if (!isset($choices)) {
$choices = $element['#options'];
}
// array_key_exists() accommodates the rare event where $element['#value'] is NULL.
// isset() fails in this situation.
$value_valid = isset($element['#value']) || array_key_exists('#value', $element);
$value_is_array = isset($element['#value']) && is_array($element['#value']);
$options = '';
foreach ($choices as $key => $choice) {
$key = (string) $key;
if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) {
$selected = ' selected="selected"';
}
else {
$selected = '';
}
// If an option DOES NOT have child info, then it's a special option:
// - label_\d+ (level label)
// - none ("<none>")
// - create_new_item ("<create new item>")
// Only when it's a level label, we have to add a class to this option.
if (!isset($element['#childinfo'][$key])) {
$class = (preg_match('/label_\d+/', $key)) ? ' level-label' : '';
}
else {
$class = ($element['#childinfo'][$key] == 0) ? 'has-no-children' : 'has-children';
}
$options .= '<option value="' . check_plain($key) . '" class="' . $class . '"' . $selected . '>' . check_plain($choice) . '</option>';
}
return $options;
}

View File

@@ -0,0 +1,29 @@
/**
* @file
* Make Hierarchical Select work in Views' exposed filters form.
*
* Views' exposed filters form is a GET form, but since Hierarchical Select
* really is a combination of various form items, this will result in a very
* ugly and unnecessarily long GET URL, which also breaks the exposed filters.
* This piece of JavaScript is a necessity to make it work again, but it will
* of course only work when JavaScript is enabled!
*/
if (Drupal.jsEnabled) {
$(document).ready(function(){
$('.view-filters form').submit(function() {
// Remove the Hierarchical Select form build id and the form id, to
// prevent them from ending up in the GET URL.
$('#edit-hs-form-build-id').remove();
// Prepare the hierarchical select form elements that are used as
// exposed filters for a GET submit.
$('.view-filters form')
.find('.hierarchical-select-wrapper')
.trigger('prepare-GET-submit');
});
});
}

View File

@@ -0,0 +1,12 @@
name = Hierarchical Select Flat List
description = Allows Hierarchical Select's dropbox to be used for selecting multiple items in a flat list of options.
dependencies[] = hierarchical_select
package = Form Elements
core = 7.x
; Information added by Drupal.org packaging script on 2017-02-15
version = "7.x-3.0-beta8"
core = "7.x"
project = "hierarchical_select"
datestamp = "1487167708"

View File

@@ -0,0 +1,66 @@
<?php
/**
* @file
* Implementation of the Hierarchical Select API that allows one to use
* Hierarchical Select's dropbox for selecting multiple items of a flat list.
*/
//----------------------------------------------------------------------------
// Hierarchical Select hooks.
/**
* Implements hook_hierarchical_select_params().
*/
function hs_flatlist_hierarchical_select_params() {
$params = array(
'options',
);
return $params;
}
/**
* Implements hook_hierarchical_select_root_level().
*/
function hs_flatlist_hierarchical_select_root_level($params) {
return $params['options'];
}
/**
* Implements hook_hierarchical_select_children().
*/
function hs_flatlist_hierarchical_select_children($parent, $params) {
return array(); // A flat list doesn't have any children, ever.
}
/**
* Implements hook_hierarchical_select_lineage().
*/
function hs_flatlist_hierarchical_select_lineage($item, $params) {
return array($item); // No hierarchies exist in flat lists.
}
/**
* Implements hook_hierarchical_select_valid_item().
*/
function hs_flatlist_hierarchical_select_valid_item($item, $params) {
return (in_array($item, array_keys($params['options'])));
}
/**
* Implements hook_hierarchical_select_item_get_label().
*/
function hs_flatlist_hierarchical_select_item_get_label($item, $params) {
return $params['options'][$item];
}
/**
* Implements hook_hierarchical_select_implementation_info().
*/
function hs_flatlist_hierarchical_select_implementation_info() {
return array(
'hierarchy type' => t('None: flat list'),
'entity type' => t('N/A'),
);
}

View File

@@ -0,0 +1,13 @@
name = Hierarchical Select Menu
description = Use Hierarchical Select for menu parent selection.
dependencies[] = hierarchical_select
dependencies[] = menu
package = Form Elements
core = 7.x
; Information added by Drupal.org packaging script on 2017-02-15
version = "7.x-3.0-beta8"
core = "7.x"
project = "hierarchical_select"
datestamp = "1487167708"

View File

@@ -0,0 +1,27 @@
<?php
/**
* @file
* Install file for the Hierarchical Select Menu module.
*/
/**
* Implements hook_install().
*/
function hs_menu_install() {
// Ensure hs_menu runs after Core's menu.
db_update('system')
->fields(array('weight' => 1))
->condition('name', 'hs_menu')
->execute();
}
/**
* Implementats hook_uninstall().
*/
function hs_menu_uninstall() {
db_delete('variable')
->condition('name', 'hs_menu_%', 'LIKE')
->execute();
}

View File

@@ -0,0 +1,326 @@
<?php
/**
* @file
* Implementation of the Hierarchical Select API for the Menu module.
*/
//----------------------------------------------------------------------------
// Drupal core hooks.
/**
* Implements of hook_menu().
*/
function hs_menu_menu() {
$items['admin/config/content/hierarchical_select/menu'] = array(
'title' => 'Menu',
'description' => 'Hierarchical Select configuration for Menu',
'access arguments' => array('administer site configuration'),
'page callback' => 'drupal_get_form',
'page arguments' => array('hs_menu_admin_settings'),
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implements hook_form_FORMID_alter().
*
* Alter the node form's menu form.
*/
function hs_menu_form_node_form_alter(&$form, &$form_state) {
$active_types = array_filter(variable_get('hs_menu_content_types', array()));
$active = empty($active_types) || in_array($form_state['node']->type, $active_types);
if ($active && isset($form['menu']['link']['parent']) && isset($form['menu']['#access']) && $form['menu']['#access']) {
unset($form['menu']['link']['parent']['#options']);
$form['menu']['link']['parent']['#type'] = 'hierarchical_select';
// Get menu name, needed to exclude current node.
$menu_name = explode(':', $form['menu']['link']['parent']['#default_value']);
_hs_menu_apply_config($form['menu']['link']['parent'], array(
0 => $menu_name[0],
1 => $form['menu']['link']['mlid']['#value'],
'type' => $form['type']['#value'],
));
// Set custom submit callback.
array_unshift($form['#submit'], 'hs_menu_node_form_submit');
// Change the loaded default value into an array so we can populate the
// Hierarchical Select element.
$form['menu']['link']['parent']['#default_value'] = array($form['menu']['link']['parent']['#default_value']);
}
}
/**
* Implements hook_form_BASE_FORMID_alter().
*
* Alter the widget type form; dynamically add the Hierarchical Select
* Configuration form when it is needed.
*/
function hs_menu_form_menu_edit_item_alter(&$form, &$form_state) {
unset($form['parent']['#options']);
$original_item = $form['original_item']['#value'];
$form['parent']['#type'] = 'hierarchical_select';
_hs_menu_apply_config($form['parent'], array('exclude' => array(
$original_item['menu_name'],
$original_item['mlid'],
)));
// Set custom submit callback.
array_unshift($form['#submit'], 'hs_menu_menu_edit_item_form_submit');
}
//----------------------------------------------------------------------------
// Form API callbacks.
/**
* Submit callback; menu edit item form.
*/
function hs_menu_menu_edit_item_form_submit(&$form, &$form_state) {
// Don't return an array, but a single item.
$form_state['values']['parent'] = $form_state['values']['parent'][0];
}
/**
* Submit callback; node edit form.
*/
function hs_menu_node_form_submit(&$form, &$form_state) {
// Don't return an array, but a single item.
$form_state['values']['menu']['parent'] = $form_state['values']['menu']['parent'][0];
}
//----------------------------------------------------------------------------
// Menu callbacks.
/**
* Form definition; admin settings.
*/
function hs_menu_admin_settings() {
$form['hs_menu_resizable'] = array(
'#type' => 'radios',
'#title' => t('Resizable'),
'#description' => t(
"When enabled, a handle appears below the Hierarchical Select to allow
the user to dynamically resize it. Double clicking will toggle between
the smallest and a sane 'big size'."
),
'#options' => array(
0 => t('Disabled'),
1 => t('Enabled'),
),
'#default_value' => variable_get('hs_menu_resizable', 1),
);
$form['hs_menu_content_types'] = array(
'#type' => 'checkboxes',
'#title' => t('Content types'),
'#description' => t("Select the content types to use Hierarchical Select Menu on. If no content types are selected, then it will apply to all content types."),
'#options' => node_type_get_names(),
'#default_value' => variable_get('hs_menu_content_types', array()),
);
return system_settings_form($form);
}
//----------------------------------------------------------------------------
// Hierarchical Select hooks.
/**
* Implements hook_hierarchical_select_params().
*/
function hs_menu_hierarchical_select_params() {
$params = array(
'exclude', // The menu_name and mlid (in an array) of a menu link that should be excluded from the hierarchy.
);
return $params;
}
/**
* Implements hook_hierarchical_select_root_level().
*/
function hs_menu_hierarchical_select_root_level($params) {
$menus = array();
$result = db_query("SELECT menu_name, title FROM {menu_custom} ORDER BY title");
// If the type is set, respect the core menu options setting.
if (isset($params['type'])) {
$type_menus = variable_get('menu_options_' . $params['type'], array('main-menu' => 'main-menu'));
while ($menu = $result->fetchObject()) {
if (in_array($menu->menu_name, $type_menus)) {
$menus[$menu->menu_name . ':0'] = $menu->title;
}
}
}
// Fall back to the legacy approach, show all menu's.
else {
while ($menu = $result->fetchObject()) {
$menus[$menu->menu_name . ':0'] = $menu->title;
}
}
return $menus;
}
/**
* Implements hook_hierarchical_select_children().
*/
function hs_menu_hierarchical_select_children($parent, $params) {
$children = array();
list($menu_name, $plid) = explode(':', $parent);
$tree = menu_tree_all_data($menu_name, NULL);
return _hs_menu_children($tree, $menu_name, $plid, $params['exclude']);
}
/**
* Implements hook_hierarchical_select_lineage().
*/
function hs_menu_hierarchical_select_lineage($item, $params) {
$lineage = array($item);
list($menu_name, $mlid) = explode(':', $item);
// If the initial mlid is zero, then this is the root level, so we don't
// have to get the lineage.
if ($mlid > 0) {
// Prepend each parent mlid (i.e. plid) to the lineage.
do {
$plid = db_query("SELECT plid FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchField();
array_unshift($lineage, "$menu_name:$plid");
if ($mlid == $plid) {
// Somehow we have an infinite loop situation. Bail out of the loop.
break;
}
$mlid = $plid;
} while ($plid > 0);
}
return $lineage;
}
/**
* Implements hook_hierarchical_select_valid_item().
*/
function hs_menu_hierarchical_select_valid_item($item, $params) {
$parts = explode(':', $item);
$valid = TRUE;
// Validate menu name.
$valid = (array_key_exists($parts[0], menu_get_menus()));
// Validate hierarchy of mlids.
for ($i = 1; $valid && $i < count($parts); $i++) {
$valid = $valid && is_numeric($parts[$i]);
}
// Ensure that this isn't the excluded menu link.
$valid = $valid && $item != $params['exclude'][0] . $params['exclude'][1];
return $valid;
}
/**
* Implements hook_hierarchical_select_item_get_label().
*/
function hs_menu_hierarchical_select_item_get_label($item, $params) {
static $labels = array();
$parts = explode(':', $item);
if (count($parts) == 1) { // Get the menu name.
$menu_name = $parts[0];
$labels[$item] = db_query("SELECT title FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField();
}
else { // Get the menu link title.
$mlid = end($parts);
$menu_link = menu_link_load($mlid);
$labels[$item] = $menu_link['title'];
}
return $labels[$item];
}
/**
* Implements hook_hierarchical_select_implementation_info().
*/
function hs_menu_hierarchical_select_implementation_info() {
return array(
'hierarchy type' => t('Menu'),
'entity type' => t('N/A'),
);
}
//----------------------------------------------------------------------------
// Private functions.
/**
* Recursive helper function for hs_menu_hierarchical_select_children().
*/
function _hs_menu_children($tree, $menu_name, $plid = 0, $exclude = FALSE) {
$children = array();
foreach ($tree as $data) {
if ($data['link']['plid'] == $plid && $data['link']['hidden'] >= 0) {
if ($exclude && $data['link']['menu_name'] === $exclude[0] && $data['link']['mlid'] == $exclude[1]) {
continue;
}
$title = truncate_utf8($data['link']['title'], 30, TRUE, FALSE);
if ($data['link']['hidden']) {
$title .= ' (' . t('disabled') . ')';
}
$children[$menu_name . ':' . $data['link']['mlid']] = $title;
if ($data['below']) {
$children += _hs_menu_children($data['below'], $menu_name, $plid, $exclude);
}
}
elseif ($data['below']) {
$children += _hs_menu_children($data['below'], $menu_name, $plid, $exclude);
}
}
return $children;
}
/**
* Helper function to apply the HS config to a form item.
*/
function _hs_menu_apply_config(&$form, $params) {
// The following is to ensure via javascript self is not listed.
if (!empty($params['exclude'])) {
$params['exclude'] = $params['exclude'][0] .':'. $params['exclude'][1];
drupal_add_js('jQuery(document).ready(function () {
jQuery("[value*=\"' . $params['exclude'] . '\"]").hide();
});', 'inline');
}
$form['#config'] = array(
'module' => 'hs_menu',
'params' => array(
'exclude' => isset($params['exclude']) ? $params['exclude'] : NULL,
'type' => isset($params['type']) ? $params['type'] : NULL,
),
'save_lineage' => 0,
'enforce_deepest' => 0,
'resizable' => variable_get('hs_menu_resizable', 1),
'level_labels' => array(
'status' => 0,
),
'dropbox' => array(
'status' => 0,
),
'editability' => array(
'status' => 0,
),
'entity_count' => array(
'enabled' => 0,
'require_entity' => 0,
'settings' => array(
'count_children' => 0,
'entity_types' => array(),
),
),
'render_flat_select' => 0,
);
}

View File

@@ -0,0 +1,12 @@
name = Hierarchical Select Small Hierarchy
description = Allows Hierarchical Select to be used for a hardcoded hierarchy. When it becomes to slow, you should move the hierarchy into the database and write a proper implementation.
dependencies[] = hierarchical_select
package = Form Elements
core = 7.x
; Information added by Drupal.org packaging script on 2017-02-15
version = "7.x-3.0-beta8"
core = "7.x"
project = "hierarchical_select"
datestamp = "1487167708"

View File

@@ -0,0 +1,214 @@
<?php
/**
* @file
* Implementation of the Hierarchical Select API that allows one to use a
* hardcoded hierarchy. When it becomes to slow, you should move the hierarchy
* into the database and write a proper implementation.
*/
//----------------------------------------------------------------------------
// Hierarchical Select hooks.
/**
* Implementation of hook_hierarchical_select_params().
*/
function hs_smallhierarchy_hierarchical_select_params() {
$params = array(
'hierarchy',
'id',
'separator',
);
return $params;
}
/**
* Implementation of hook_hierarchical_select_root_level().
*/
function hs_smallhierarchy_hierarchical_select_root_level($params) {
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
$children = $hierarchy['root']['children'];
$level = array();
foreach ($children as $item) {
$level[$item] = $hierarchy[$item]['label'];
}
return $level;
}
/**
* Implementation of hook_hierarchical_select_children().
*/
function hs_smallhierarchy_hierarchical_select_children($parent, $params) {
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
$children = (isset($hierarchy[$parent]['children'])) ? $hierarchy[$parent]['children'] : array();
$level = array();
foreach ($children as $item) {
$level[$item] = $hierarchy[$item]['label'];
}
return $level;
}
/**
* Implementation of hook_hierarchical_select_lineage().
*/
function hs_smallhierarchy_hierarchical_select_lineage($item, $params) {
$parts = explode($params['separator'], $item);
$lineage = array();
for ($i = 0; $i < count($parts); $i++) {
$lineage[$i] = implode($params['separator'], array_slice($parts, 0, $i + 1));
}
return $lineage;
}
/**
* Implementation of hook_hierarchical_select_valid_item().
*/
function hs_smallhierarchy_hierarchical_select_valid_item($item, $params) {
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
// All valid items are in the keys of the $hierarchy array. Only the fake
// "root" item is not a valid item.
$items = array_keys($hierarchy);
unset($items['root']);
return (in_array($item, $items));
}
/**
* Implementation of hook_hierarchical_select_item_get_label().
*/
function hs_smallhierarchy_hierarchical_select_item_get_label($item, $params) {
$hierarchy = _hs_smallhierarchy_transform($params['hierarchy'], $params['id'], $params['separator']);
return $hierarchy[$item]['label'];
}
/**
* Implementation of hook_hierarchical_select_implementation_info().
*/
function hs_smallhierarchy_hierarchical_select_implementation_info() {
return array(
'hierarchy type' => t('Custom'),
'entity type' => t('N/A'),
);
}
//----------------------------------------------------------------------------
// Private functions.
/**
* Automatically transform a given hierarchy with this format:
* array(
* 'win' => array(
* 'label' => 'Windows',
* 'children' => array(
* 'xp' => array('label' => 'XP'),
* 'vista' => array(
* 'label' => 'Vista',
* 'children' => array(
* 'x86' => array('label' => '32-bits'),
* 'x64' => array('label' => '64-bits'),
* ),
* ),
* ),
* ),
* )
*
* to one with this format:
* array(
* 'root' => array(
* 'children' => array(
* 'xp',
* 'vista',
* ),
* ),
* 'win' => array(
* 'label' => 'Windows',
* 'children' => array(
* 'win|xp',
* 'win|vista',
* ),
* ),
* 'win|xp' => array(
* 'label' => 'XP',
* ),
* 'win|vista' => array(
* 'label' => 'Vista',
* 'children' => array(
* 'win|vista|x86',
* 'win|vista|x64',
* ),
* ),
* 'win|vista|x86' => array(
* 'label' => '32-bits',
* ),
* 'win|vista|x64' => array(
* 'label' => '64-bits',
* ),
* )
*
* This new format:
* - ensures unique identifiers for each item
* - makes it very easy to find the parent of a given item.
* - makes it very easy to find the label and children of a given item.
*
* @params $hierarchy
* The hierarchy.
* @params $id
* A unique identifier for the hierarchy, for caching purposes.
* @params $separator
* The separator to use.
*/
function _hs_smallhierarchy_transform($hierarchy, $id, $separator = '|') {
// Make sure each hierarchy is only transformed once.
if (!isset($hs_hierarchy[$id])) {
$hs_hierarchy[$id] = array();
// Build the root level.
foreach ($hierarchy as $item => $children) {
$hs_hierarchy[$id]['root']['children'][] = $item;
$hs_hierarchy[$id][$item]['label'] = $children['label'];
// Build the subsequent levels.
if (isset($children['children'])) {
_hs_smallhierarchy_transform_recurse($item, $hs_hierarchy[$id], $children['children'], $separator);
}
}
}
return $hs_hierarchy[$id];
}
/**
* Helper function for _hs_smallhierarchy_transform().
*
* @params $parent
* The parent item of the current level.
* @params $hs_hierarchy
* The HS hierarchy.
* @params $relative_hierarchy
* The hierarchy relative to the current level.
* @params $separator
* The separator to use.
*/
function _hs_smallhierarchy_transform_recurse($parent, &$hs_hierarchy, $relative_hierarchy, $separator = '|') {
foreach ($relative_hierarchy as $item => $children) {
$generated_item = $parent . $separator . $item;
$hs_hierarchy[$parent]['children'][] = $generated_item;
$hs_hierarchy[$generated_item]['label'] = $children['label'];
// Build the subsequent levels.
if (isset($children['children'])) {
_hs_smallhierarchy_transform_recurse($generated_item, $hs_hierarchy, $children['children'], $separator);
}
}
}

View File

@@ -0,0 +1,14 @@
name = Hierarchical Select Taxonomy
description = Use Hierarchical Select for Taxonomy.
dependencies[] = hierarchical_select
dependencies[] = taxonomy
package = Form Elements
core = 7.x
; Information added by Drupal.org packaging script on 2017-02-15
version = "7.x-3.0-beta8"
core = "7.x"
project = "hierarchical_select"
datestamp = "1487167708"

View File

@@ -0,0 +1,123 @@
<?php
/**
* @file
* Install file for the Hierarchical Select Taxonomy module.
*/
/**
* Implementation of hook_uninstall().
*/
function hs_taxonomy_uninstall() {
db_delete('variable')
->condition('name', 'taxonomy_hierarchical_select_%', 'LIKE')
->execute();
variable_del('taxonomy_override_selector');
}
/**
* Implementation of hook_enable().
*/
function hs_taxonomy_enable() {
variable_set('taxonomy_override_selector', TRUE);
drupal_set_message(t("Drupal core's taxonomy selects are now overridden on the
Taxonomy Term form. They've been replaced by
Hierarchical Selects for better scalability.<br />
You can <a href=\"!configure-url\">configure</a> it to
be used on node forms too!",
array(
'!configure-url' => url('admin/config/content/hierarchical_select/configs'),
)));
}
/**
* Implementation of hook_disable().
*/
function hs_taxonomy_disable() {
variable_set('taxonomy_override_selector', FALSE);
drupal_set_message(t("Drupal core's taxonomy selects are now restored.
Please remember that they're not scalable!."),
'warning');
}
//----------------------------------------------------------------------------
// Schema updates.
/**
* Upgrade path from Drupal 6 to Drupal 7 version of Hierarchical Select:
* - delete the taxonomy_override_selector variable if it exists.
*/
function hs_taxonomy_update_7300() {
variable_del('taxonomy_override_selector');
}
/**
* Apparently, taxonomy_override_selector still exists in *one* location in
* Drupal 7 core: on the form_taxonomy_form_term form (where you can create or
* edit a Taxonomy term).
*/
function hs_taxonomy_update_7301() {
variable_set('taxonomy_override_selector', TRUE);
}
/**
* Convert Taxonomy vocabulary config IDs to use machine name instead of serial
* vocabulary ID.
*/
function hs_taxonomy_update_7302() {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
$vocabularies = taxonomy_vocabulary_get_names();
foreach ($vocabularies as $machine_name => $vocabulary) {
$old_config_id = "taxonomy-{$vocabulary->vid}";
$new_config_id = "taxonomy-{$machine_name}";
$old_config = variable_get('hs_config_' . $old_config_id, NULL);
if (!empty($old_config)) {
hierarchical_select_common_config_set($new_config_id, $old_config);
hierarchical_select_common_config_del($old_config_id);
}
}
}
/**
* Convert Taxonomy vocabulary config IDs to use field name.
*/
function hs_taxonomy_update_7303() {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
foreach (field_info_instances() as $entity_type => $bundles) {
foreach ($bundles as $bundle => $field_list) {
foreach ($field_list as $field_name => $instance) {
if ($instance['widget']['type'] == 'taxonomy_hs') {
$field_info = field_info_field($field_name);
$allowed_value = $field_info['settings']['allowed_values'][0];
$vocabulary_name = $allowed_value['vocabulary'];
$old_config_id = "taxonomy-{$vocabulary_name}";
$new_config_id = "taxonomy-{$field_name}";
$old_config = hierarchical_select_common_config_get($old_config_id);
if (!empty($old_config)) {
hierarchical_select_common_config_set($new_config_id, $old_config);
}
}
}
}
}
}
/**
* Cleanup old-named configs.
*/
function hs_taxonomy_update_7304() {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'hierarchical_select') . '/includes/common.inc';
$vocabularies = taxonomy_get_vocabularies();
foreach ($vocabularies as $vid => $vocabulary) {
$old_config_id = "taxonomy-{$vocabulary->machine_name}";
hierarchical_select_common_config_del($old_config_id);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
This module allows you to use hierarchical_select (version 7.x-3.x) as a widget
for a taxonomy-based entityreference field.
To use it, create an entityreference field, with the Hierarchical Select widget,
select "Taxonomy term" as the target type, and select one vocabulary (you must
choose exactly one) as the target bundle.
Credit: John Morahan, iO1 and iVillage.

View File

@@ -0,0 +1,18 @@
name = Hierarchical Select Entity Reference
description = Use the hierarchical select widget for entity reference fields, using taxonomy to provide hierarchy if appropriate, otherwise flat.
package = Form Elements
core = 7.x
dependencies[] = hierarchical_select
dependencies[] = hs_taxonomy
dependencies[] = entityreference
dependencies[] = entity
dependencies[] = ctools
dependencies[] = options
dependencies[] = field
; Information added by Drupal.org packaging script on 2017-02-15
version = "7.x-3.0-beta8"
core = "7.x"
project = "hierarchical_select"
datestamp = "1487167708"

View File

@@ -0,0 +1,132 @@
<?php
/**
* @file
* Allows hierarchical select to be used with entity reference fields.
*/
/**
* Implements hook_field_widget_info().
*/
function hser_field_widget_info() {
return array(
'hser_hierarchy' => array(
'label' => t('Hierarchical Select'),
'field types' => array('entityreference'),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
),
'settings' => array(
'editable' => FALSE,
),
),
);
}
/**
* Implements hook_field_widget_settings_form().
*/
function hser_field_widget_settings_form($field, $instance) {
$widget = $instance['widget'];
$settings = $widget['settings'] + field_info_widget_settings($widget['type']);
$form = array();
if ($widget['type'] == 'hser_hierarchy') {
$form['editable'] = array(
'#type' => 'checkbox',
'#title' => t('Editable'),
'#default_value' => $settings['editable'],
'#description' => t('Select this to allow users to use the hierarchical select widget to create new terms in the selected vocabulary.'),
);
}
return $form;
}
/**
* Implements hook_field_widget_form().
*/
function hser_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
if ($field['settings']['target_type'] == 'taxonomy_term') {
$vocabularies = $field['settings']['handler_settings']['target_bundles'];
if ((count($vocabularies) == 1) && ($vocabulary = taxonomy_vocabulary_machine_name_load(reset($vocabularies)))) {
$default_value = array();
foreach ($items as $item) {
$default_value[] = $item['target_id'];
}
$element += array(
'#type' => 'hierarchical_select',
'#size' => 1,
'#default_value' => $default_value,
'#element_validate' => array('_hser_element_validate'),
'#required' => $instance['required'],
'#config' => array(
'module' => 'hs_taxonomy',
'params' => array(
'vid' => $vocabulary->vid,
),
'save_lineage' => FALSE,
'enforce_deepest' => FALSE,
'resizable' => FALSE,
'level_labels' => array('status' => FALSE),
'dropbox' => array(
'status' => ($field['cardinality'] != 1),
'limit' => $field['cardinality'],
),
'editability' => array(
'status' => $instance['widget']['settings']['editable'],
'allow_new_levels' => TRUE,
'max_levels' => 0,
),
'entity_count' => array(
'enabled' => 0,
'require_entity' => 0,
'settings' => array(
'count_children' => 0,
'entity_types' => array(),
),
),
'render_flat_select' => FALSE,
),
);
return $element;
}
}
// If we reach this point, we decided that hierarchical_select would not be
// appropriate for some reason (not taxonomy, no vocabulary selected, etc).
// So instead we fall back to a normal select widget.
$instance['widget']['type'] = 'options_select';
return options_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element);
}
/**
* Element validation callback for field widget hierarchical select element.
*/
function _hser_element_validate($element, &$form_state, $form) {
$value = array();
foreach ($element['#value'] as $delta => $target_id) {
$value[$delta]['target_id'] = $target_id;
}
form_set_value($element, $value, $form_state);
if ($element['#required'] && (!isset($form_state['submit_handlers'][0]) || $form_state['submit_handlers'][0] !== 'hierarchical_select_ajax_update_submit')) {
if (!count($element['#value']) || (is_string($element['#value']) && strlen(trim($element['#value'])) == 0) || (array_key_exists(0, $element['#value'])) && !$element['#value'][0]) {
form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
_hierarchical_select_form_set_error_class($element);
}
}
}
/**
* Implements hook_node_validate().
*
* Temporary workaround for https://drupal.org/node/1293166 - remove when that
* bug is fixed.
*/
function hser_node_validate($node, $form, &$form_state) {
if (arg(0) == 'hierarchical_select_ajax') {
form_set_error('');
}
}

View File

@@ -0,0 +1,433 @@
<?php
/**
* @file
* This file contains the unit tests of the internals.
*/
//file_put_contents('test.html', print_r(array($hierarchy, $reference), TRUE));
define('SEP', '|');
define('EURO', 'europe');
define('EURO_BE', 'belgium');
define('EURO_BE_BRU', 'brussels');
define('EURO_BE_HAS', 'hasselt');
define('EURO_FR', 'france');
define('ASIA', 'asia');
define('ASIA_CH', 'china');
define('ASIA_JP', 'japan');
define('ASIA_JP_TOK', 'tokyo');
define('LABEL_EURO', 'Europe');
define('LABEL_EURO_BE', 'Belgium');
define('LABEL_EURO_BE_BRU', 'Brussels');
define('LABEL_EURO_BE_HAS', 'Hasselt');
define('LABEL_EURO_FR', 'France');
define('LABEL_ASIA', 'Asia');
define('LABEL_ASIA_CH', 'China');
define('LABEL_ASIA_JP', 'Japan');
define('LABEL_ASIA_JP_TOK', 'tokyo');
define('LINEAGE_EURO', implode(SEP, array(EURO)));
define('LINEAGE_EURO_BE', implode(SEP, array(EURO, EURO_BE)));
define('LINEAGE_EURO_BE_BRU', implode(SEP, array(EURO, EURO_BE, EURO_BE_BRU)));
define('LINEAGE_EURO_BE_HAS', implode(SEP, array(EURO, EURO_BE, EURO_BE_HAS)));
define('LINEAGE_EURO_FR', implode(SEP, array(EURO, EURO_FR)));
define('LINEAGE_ASIA', implode(SEP, array(ASIA)));
define('LINEAGE_ASIA_CH', implode(SEP, array(ASIA, ASIA_CH)));
define('LINEAGE_ASIA_JP', implode(SEP, array(ASIA, ASIA_JP)));
define('LINEAGE_ASIA_JP_TOK', implode(SEP, array(ASIA, ASIA_JP, ASIA_JP_TOK)));
class HierarchicalSelectInternals extends DrupalWebTestCase {
private $small_hierarchy = array(
EURO => array(
'label' => LABEL_EURO,
'children' => array(
EURO_BE => array(
'label' => LABEL_EURO_BE,
'children' => array(
EURO_BE_BRU => array(
'label' => LABEL_EURO_BE_BRU,
),
EURO_BE_HAS => array(
'label' => LABEL_EURO_BE_HAS,
),
),
),
EURO_FR => array(
'label' => LABEL_EURO_FR,
),
),
),
ASIA => array(
'label' => LABEL_ASIA,
'children' => array(
ASIA_CH => array(
'label' => LABEL_ASIA_CH,
),
ASIA_JP => array(
'label' => LABEL_ASIA_JP,
'children' => array(
ASIA_JP_TOK => array(
'label' => LABEL_ASIA_JP_TOK,
),
),
),
),
),
);
/**
* Implementation of getInfo().
*/
public function getInfo() {
return array(
'name' => 'Internals',
'description' => 'Checks whether all internals are working: the
building of the hierarchy and dropbox objects.',
'group' => 'Hierarchical Select',
);
}
/**
* Implementation of setUp().
*/
public function setUp() {
parent::setUp('hierarchical_select', 'hs_smallhierarchy');
}
// In this test, all settings are disabled.
public function testAllSettingsOff() {
// Generate form item.
$form_item = array(
'#required' => FALSE,
'#config' => array(
'module' => 'hs_smallhierarchy',
'params' => array(
'hierarchy' => $this->small_hierarchy,
'id' => 'driverpack_platforms',
'separator' => '|',
),
'save_lineage' => 0,
'enforce_deepest' => 0,
'resizable' => 1,
'level_labels' => array(
'status' => 0,
),
'dropbox' => array(
'status' => 0,
'limit' => 0,
'reset_hs' => 1,
),
'editability' => array(
'status' => 0,
'item_types' => array(),
'allowed_levels' => array(),
'allow_new_levels' => 0,
'max_levels' => 3,
),
'entity_count' => array(
'enabled' => 0,
'require_entity' => 0,
'settings' => array(
'count_children' => 0,
'entity_types' => array(),
),
),
'animation_delay' => 400,
'exclusive_lineages' => array(),
'render_flat_select' => 0,
),
);
// No selection
list($hierarchy, $dropbox) = $this->generate($form_item, array(), array());
$reference = new stdClass();
$reference->lineage = array(
0 => 'none',
);
$reference->levels = array(
0 => array(
'none' => '<none>',
LINEAGE_EURO => LABEL_EURO,
LINEAGE_ASIA => LABEL_ASIA,
),
);
$reference->childinfo = array(
0 => array(
LINEAGE_EURO => 2,
LINEAGE_ASIA => 2,
),
);
$this->assertHierarchy($hierarchy, $reference);
// Europe
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO), array());
$reference->lineage = array(
0 => LINEAGE_EURO,
1 => 'label_1',
);
$reference->levels[1] = array(
'label_1' => '',
LINEAGE_EURO_BE => LABEL_EURO_BE,
LINEAGE_EURO_FR => LABEL_EURO_FR,
);
$reference->childinfo[1] = array(
LINEAGE_EURO_BE => 2,
LINEAGE_EURO_FR => 0,
);
$this->assertHierarchy($hierarchy, $reference);
// Europe > France
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_FR), array());
$reference->lineage = array(
0 => LINEAGE_EURO,
1 => LINEAGE_EURO_FR,
);
$this->assertHierarchy($hierarchy, $reference);
// Europe > Belgium
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_BE), array());
$reference->lineage = array(
0 => LINEAGE_EURO,
1 => LINEAGE_EURO_BE,
2 => 'label_2',
);
$reference->levels[1] = array(
'label_1' => '',
LINEAGE_EURO_BE => LABEL_EURO_BE,
LINEAGE_EURO_FR => LABEL_EURO_FR,
);
$reference->levels[2] = array(
'label_2' => '',
LINEAGE_EURO_BE_BRU => LABEL_EURO_BE_BRU,
LINEAGE_EURO_BE_HAS => LABEL_EURO_BE_HAS,
);
$reference->childinfo[1] = array(
LINEAGE_EURO_BE => 2,
LINEAGE_EURO_FR => 0,
);
$reference->childinfo[2] = array(
LINEAGE_EURO_BE_BRU => 0,
LINEAGE_EURO_BE_HAS => 0,
);
$this->assertHierarchy($hierarchy, $reference);
// Asia
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA), array());
$reference->lineage = array(
0 => LINEAGE_ASIA,
1 => 'label_1',
);
$reference->levels[1] = array(
'label_1' => '',
LINEAGE_ASIA_CH => LABEL_ASIA_CH,
LINEAGE_ASIA_JP => LABEL_ASIA_JP,
);
unset($reference->levels[2]);
$reference->childinfo[1] = array(
LINEAGE_ASIA_CH => 0,
LINEAGE_ASIA_JP => 1,
);
unset($reference->childinfo[2]);
$this->assertHierarchy($hierarchy, $reference);
// Asia > Japan > Tokyo
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA_JP_TOK), array());
$reference->lineage = array(
0 => LINEAGE_ASIA,
1 => LINEAGE_ASIA_JP,
2 => LINEAGE_ASIA_JP_TOK,
);
$reference->levels[2] = array(
'label_2' => '',
LINEAGE_ASIA_JP_TOK => LABEL_ASIA_JP_TOK,
);
$reference->childinfo[2] = array(
LINEAGE_ASIA_JP_TOK => 0,
);
$this->assertHierarchy($hierarchy, $reference);
}
// In this test, only enforce_deepest enabled.
public function testEnforceDeepest() {
// Generate form item.
$form_item = array(
'#required' => FALSE,
'#config' => array(
'module' => 'hs_smallhierarchy',
'params' => array(
'hierarchy' => $this->small_hierarchy,
'id' => 'driverpack_platforms',
'separator' => '|',
),
'save_lineage' => 0,
'enforce_deepest' => 1,
'resizable' => 1,
'level_labels' => array(
'status' => 0,
),
'dropbox' => array(
'status' => 0,
'limit' => 0,
'reset_hs' => 1,
),
'editability' => array(
'status' => 0,
'item_types' => array(),
'allowed_levels' => array(),
'allow_new_levels' => 0,
'max_levels' => 3,
),
'entity_count' => array(
'enabled' => 0,
'require_entity' => 0,
'settings' => array(
'count_children' => 0,
'entity_types' => array(),
),
),
'animation_delay' => 400,
'exclusive_lineages' => array(),
'render_flat_select' => 0,
),
);
// No selection
list($hierarchy, $dropbox) = $this->generate($form_item, array(), array());
$reference = new stdClass();
$reference->lineage = array(
0 => 'label_0',
);
$reference->levels = array(
0 => array(
'none' => '<none>',
LINEAGE_EURO => LABEL_EURO,
LINEAGE_ASIA => LABEL_ASIA,
),
);
$reference->childinfo = array(
0 => array(
LINEAGE_EURO => 2,
LINEAGE_ASIA => 2,
),
);
$this->assertHierarchy($hierarchy, $reference);
// Europe
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO), array());
$reference->lineage = array(
0 => LINEAGE_EURO,
1 => LINEAGE_EURO_BE,
2 => LINEAGE_EURO_BE_BRU,
);
$reference->levels[1] = array(
LINEAGE_EURO_BE => LABEL_EURO_BE,
LINEAGE_EURO_FR => LABEL_EURO_FR,
);
$reference->levels[2] = array(
LINEAGE_EURO_BE_BRU => LABEL_EURO_BE_BRU,
LINEAGE_EURO_BE_HAS => LABEL_EURO_BE_HAS,
);
$reference->childinfo[1] = array(
LINEAGE_EURO_BE => 2,
LINEAGE_EURO_FR => 0,
);
$reference->childinfo[2] = array(
LINEAGE_EURO_BE_BRU => 0,
LINEAGE_EURO_BE_HAS => 0,
);
$this->assertHierarchy($hierarchy, $reference);
// Europe > France
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_FR), array());
$reference->lineage = array(
0 => LINEAGE_EURO,
1 => LINEAGE_EURO_FR,
);
unset($reference->levels[2]);
unset($reference->childinfo[2]);
$this->assertHierarchy($hierarchy, $reference);
// Europe > Belgium
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_EURO_BE), array());
$reference->lineage = array(
0 => LINEAGE_EURO,
1 => LINEAGE_EURO_BE,
2 => LINEAGE_EURO_BE_BRU,
);
$reference->levels[1] = array(
LINEAGE_EURO_BE => LABEL_EURO_BE,
LINEAGE_EURO_FR => LABEL_EURO_FR,
);
$reference->levels[2] = array(
LINEAGE_EURO_BE_BRU => LABEL_EURO_BE_BRU,
LINEAGE_EURO_BE_HAS => LABEL_EURO_BE_HAS,
);
$reference->childinfo[1] = array(
LINEAGE_EURO_BE => 2,
LINEAGE_EURO_FR => 0,
);
$reference->childinfo[2] = array(
LINEAGE_EURO_BE_BRU => 0,
LINEAGE_EURO_BE_HAS => 0,
);
$this->assertHierarchy($hierarchy, $reference);
// Asia
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA), array());
$reference->lineage = array(
0 => LINEAGE_ASIA,
1 => LINEAGE_ASIA_CH,
);
$reference->levels[1] = array(
LINEAGE_ASIA_CH => LABEL_ASIA_CH,
LINEAGE_ASIA_JP => LABEL_ASIA_JP,
);
unset($reference->levels[2]);
$reference->childinfo[1] = array(
LINEAGE_ASIA_CH => 0,
LINEAGE_ASIA_JP => 1,
);
unset($reference->childinfo[2]);
$this->assertHierarchy($hierarchy, $reference);
// Asia > Japan > Tokyo
list($hierarchy, $dropbox) = $this->generate($form_item, array(LINEAGE_ASIA_JP_TOK), array());
$reference->lineage = array(
0 => LINEAGE_ASIA,
1 => LINEAGE_ASIA_JP,
2 => LINEAGE_ASIA_JP_TOK,
);
$reference->levels[2] = array(
LINEAGE_ASIA_JP_TOK => LABEL_ASIA_JP_TOK,
);
$reference->childinfo[2] = array(
LINEAGE_ASIA_JP_TOK => 0,
);
$this->assertHierarchy($hierarchy, $reference);
}
//--------------------------------------------------------------------------
// Private methods.
private function generate($element, $hs_selection, $db_selection, $op = 'Update') {
$config = $element['#config'];
// Generate the $hierarchy and $dropbox objects using the selections that
// were just calculated.
$dropbox = (!$config['dropbox']['status']) ? FALSE : _hierarchical_select_dropbox_generate($config, $db_selection);
$hierarchy = _hierarchical_select_hierarchy_generate($config, $hs_selection, $element['#required'], $dropbox);
return array($hierarchy, $dropbox);
}
private function assertHierarchy($hierarchy, $reference) {
$this->assertIdentical($hierarchy->lineage, $reference->lineage, 'Hierarchy lineage is correct.');
$this->assertIdentical($hierarchy->levels, $reference->levels, 'Hierarchy levels is correct.');
$this->assertIdentical($hierarchy->childinfo, $reference->childinfo, 'Hierarchy child info is correct.');
}
}

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,23 @@
PREPOPULATE MODULE
==================
By ea.Farris, based on an idea from chx.
Maintained by Addison Berry (add1sun).
Prepopulate is an attempt to solve the problem that resulted from
the discussion at http://www.drupal.org/node/27155 where the $node object,
it was (correctly, I believe) decided, should
not be prefilled from the $_GET variables, and instead, the power of the
FormsAPI should be used to modify the #default_value of the form
elements themselves.
This functionality will make things like bookmarklets easier to write,
since it basically allows forms to be prefilled from the URL, using a
syntax like:
http://www.example.com/node/add/blog?edit[title]=this is the title&edit[body]=body goes here
Refer to the online handbook at http://drupal.org/node/228167 for more examples.
Please report any bugs or feature requests to the Prepopulate issue queue:
http://drupal.org/project/issues/prepopulate

View File

@@ -0,0 +1,10 @@
name = Prepopulate
description = Allows form elements to be prepopulated from the URL.
core = 7.x
; Information added by Drupal.org packaging script on 2016-03-02
version = "7.x-2.1"
core = "7.x"
project = "prepopulate"
datestamp = "1456898940"

View File

@@ -0,0 +1,12 @@
<?php
/**
* Implementation of hook_install().
*/
function prepopulate_install() {
$ret = array();
// Ensure that prepopulate sinks to the bottom during hook calls
// there should be a UI for this at some point.
$ret[] = db_query("UPDATE {system} SET weight = 10 WHERE name = 'prepopulate'");
return $ret;
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* @file
* Fill form elements with data from GET or POST values.
*
* Originally written by ea. Farris <eafarris@gmail.com>
* Based on an idea from chx, from the conversation at
* http://www.drupal.org/node/27155.
*/
/**
* Implements hook_help().
*/
function prepopulate_help($path, $arg) {
switch ($path) {
case 'admin/modules#description':
return t('Pre-populates forms with HTTP GET or POST data');
}
}
/**
* Implements hook_form_alter().
*/
function prepopulate_form_alter(&$form, $form_state, $form_id) {
// If this is a subsequent step of a multi-step form, the prepopulate values
// have done their work, and the user may have modified them: bail.
if (!empty($form_state['rebuild'])) {
return;
}
if (isset($_REQUEST['edit'])) {
$form['#after_build'][] = 'prepopulate_after_build';
}
}
/**
* An #after_build function to set the values prepopulated in the request.
*/
function prepopulate_after_build($form, &$form_state) {
if (isset($_REQUEST['edit'])) {
$request = (array) $_REQUEST['edit'];
_prepopulate_request_walk($form, $request);
}
return $form;
}
/**
* Internal helper to set element values from the $_REQUEST variable.
*
* @param array &$form
* A form element.
* @param mixed &$request_slice
* String or array. Value(s) to be applied to the element.
*/
function _prepopulate_request_walk(&$form, &$request_slice) {
$limited_types = array(
'actions',
'button',
'container',
'token',
'value',
'hidden',
'image_button',
'password',
'password_confirm',
'text_format',
'markup',
);
if (is_array($request_slice)) {
foreach (array_keys($request_slice) as $request_variable) {
if (element_child($request_variable) && !empty($form[$request_variable]) &&
(!isset($form[$request_variable]['#type']) || !in_array($form[$request_variable]['#type'], $limited_types))) {
if (!isset($form[$request_variable]['#access']) || $form[$request_variable]['#access'] != FALSE) {
_prepopulate_request_walk($form[$request_variable], $request_slice[$request_variable]);
}
}
}
if (!empty($form['#default_value']) && is_array($form['#default_value'])) {
$form['#default_value'] = array_merge($form['#default_value'], $request_slice);
}
}
else {
if ($form['#type'] == 'markup' || empty($form['#type'])) {
$form['#value'] = check_plain($request_slice);
}
else {
$form['#value'] = $request_slice;
}
if ($form['#type'] == 'checkboxes' || $form['#type'] == 'checkbox') {
if (!empty($form['#value'])) {
$form['#checked'] = TRUE;
}
else {
$form['#checked'] = FALSE;
}
}
}
}