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

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View File

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

View File

@@ -0,0 +1,53 @@
-- SUMMARY --
The Synonyms module extends the Drupal core Taxonomy features. Currently
the module provides this additional functionality:
* support of synonyms through Field API. Any field, for which synonyms extractor
is created, attached to a term can be enabled as source of synonyms.
* synonym-friendly autocomplete widget for taxonomy_term_reference fields
* integration with Drupal search functionality enabling searching content by
synonyms of the terms that the content references
-- REQUIREMENTS --
The Synonyms module requires the following modules:
* Taxonomy module
* Text module
-- SYNONYMS EXTRACTORS, SUPPORTED FIELD TYPES --
Module ships with ability to extract synonyms from the following field types:
* Text
* Taxonomy Term Reference
* Entity Reference
* Number
* Float
* Decimal
If you want to implement your own synonyms extractor that would enable support
for any other field type, please, refer to synonyms.api.php file for
instructions on how to do it, or file an issue against Synonyms module. We will
try to implement support for your field type too. If you have written your
synonyms extractor, please share by opening an issue, and it will be included
into this module.
-- INSTALLATION --
* Install as usual
-- CONFIGURATION --
* The module itself does not provide any configuration as of the moment.
Although during creation/editing of a Taxonomy vocabulary you will be able
to enable/disable for that particular vocabulary the additional functionality
this module provides, you will find additional fieldset at the bottom of
vocabulary edit page.
-- FUTURE DEVELOPMENT --
* If you are interested into converting this module from synonyms for Taxonomy
terms into synonyms for any entity types, please go to this issue
http://drupal.org/node/1194802 and leave a comment. Once we see some demand for
this great feature and the Synonyms module gets a little more mature, we will
try to make it happen.

View File

@@ -0,0 +1,112 @@
<?php
/**
* @file
* Define interface required for extracting synonyms from field types.
*/
abstract class AbstractSynonymsExtractor {
/**
* Return array of supported field types for synonyms extraction.
*
* @return array
* Array of Field API field types from which this class is able to extract
* synonyms
*/
static public abstract function fieldTypesSupported();
/**
* Extract synonyms from a field attached to an entity.
*
* We try to pass as many info about context as possible, however, normally
* you will only need $items to extract the synonyms.
*
* @param array $items
* Array of items
* @param array $field
* Array of field definition according to Field API
* @param array $instance
* Array of instance definition according to Field API
* @param object $entity
* Fully loaded entity object to which the $field and $instance with $item
* values is attached to
* @param string $entity_type
* Type of the entity $entity according to Field API definition of entity
* types
*
* @return array
* Array of synonyms extracted from $items
*/
static public abstract function synonymsExtract($items, $field, $instance, $entity, $entity_type);
/**
* Allow you to hook in during autocomplete suggestions generation.
*
* Allow you to include entities for autocomplete suggestion that are possible
* candidates based on your field as a source of synonyms. This method is
* void, however, you have to alter and add your condition to $query
* parameter.
*
* @param string $tag
* What user has typed in into autocomplete widget. Normally you would
* run LIKE '%$tag%' on your column
* @param EntityFieldQuery $query
* EntityFieldQuery object where you should put your conditions to
* @param array $field
* Array of field definition according to Field API
* @param array $instance
* Array of instance definition according to Field API
*/
static public abstract function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance);
/**
* Add an entity as a synonym into a field of another entity.
*
* Basically this method should be called when you want to add some entity
* as a synonym to another entity (for example when you merge one entity
* into another and besides merging want to add synonym of the merging
* entity into the trunk entity). You should extract synonym value (according
* to what value is expected in this field) and return it. We try to provide
* you with as much of context as possible, but normally you would only need
* $synonym_entity and $synonym_entity_type parameters. Return an empty array
* if entity of type $synonym_entity_type cannot be converted into a format
* expected by $field.
*
* @param array $items
* Array items that already exist in the field into which new synonyms is to
* be added
* @param array $field
* Field array definition according to Field API of the field into which new
* synonym is to be added
* @param array $instance
* Instance array definition according to Field API of the instance into
* which new synonym is to be added
* @param object $synonym_entity
* Fully loaded entity object which has to be added as synonym
* @param string $synonym_entity_type
* Entity type of $synonym_entity
*
* @return array
* Array of extra items to be merged into the items that already exist in
* field values
*/
static public abstract function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type);
/**
* Supportive method.
*
* Set such a condition on $query that it will always yield no results. Should
* be called from $this->processEntityFieldQuery() when for whatever reason
* the object can't alter $query to include matched synonyms. As a fallback
* it should call this method to make sure it filtered everything out.
*
* @param EntityFieldQuery $query
* Query object passed to $this->processEntityFieldQuery() method
* @param array $field
* Field array passed to $this->processEntityFieldQuery() method
*/
static protected function emptyResultsCondition(EntityFieldQuery $query) {
$query->entityCondition('entity_id', -1);
}
}

View File

@@ -0,0 +1,71 @@
<?php
/**
* @file
* Enables Entity Reference field type to be source of synonyms.
*/
class EntityReferenceSynonymsExtractor extends AbstractSynonymsExtractor {
static public function fieldTypesSupported() {
return array('entityreference');
}
static public function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
$synonyms = array();
// For speading up loading all the entities at once.
$target_tids = array();
foreach ($items as $item) {
$target_tids[] = $item['target_id'];
}
$entities = entity_load($field['settings']['target_type'], $target_tids);
foreach ($entities as $entity) {
$synonyms[] = entity_label($field['settings']['target_type'], $entity);
}
return $synonyms;
}
static public function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
// Unfortunately EntityFieldQuery does not currently support INNER JOINing
// referenced entities via any field type.
// Thus, we use an ugly solution -- going through all entities that exist
// in such entity type trying to see if there is a match by entity's label.
$efq = new EntityFieldQuery();
$efq->entityCondition('entity_type', $field['settings']['target_type']);
// Additionally we have to figure out which column in the entity table
// represents entity label.
$entity_info = entity_get_info($field['settings']['target_type']);
if (!isset($entity_info['entity keys']['label'])) {
// We can't get any matches if we do not know what column to query
// against. So we add a condition to $query which will 100% yield empty
// results.
self::emptyResultsCondition($query);
return;
}
$efq->propertyCondition($entity_info['entity keys']['label'], '%' . $tag . '%', 'LIKE');
$result = $efq->execute();
if (!isset($result[$field['settings']['target_type']]) || !is_array($result[$field['settings']['target_type']])) {
self::emptyResultsCondition($query);
return;
}
$result = $result[$field['settings']['target_type']];
$query->fieldCondition($field, 'target_id', array_keys($result));
}
static public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
// Firstly validating that this entity refernce is able to reference to that
// type of entity.
$expected_synonym_entity_type = $field['settings']['target_type'];
if ($expected_synonym_entity_type != $synonym_entity_type) {
return array();
}
$synonym_entity_id = entity_id($synonym_entity_type, $synonym_entity);
return array(array(
'target_id' => $synonym_entity_id,
));
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* @file
* Default Synonyms Extractor class that ships together with the Synonym module.
*/
class SynonymsSynonymsExtractor extends AbstractSynonymsExtractor {
static public function fieldTypesSupported() {
return array('text', 'number_integer', 'number_float', 'number_decimal');
}
static public function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
$synonyms = array();
foreach ($items as $item) {
$synonyms[] = $item['value'];
}
return $synonyms;
}
static public function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
$query->fieldCondition($field, 'value', '%' . $tag . '%', 'LIKE');
}
static public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
$synonym = entity_label($synonym_entity_type, $synonym_entity);
switch ($field['type']) {
case 'text':
break;
// We add synonyms for numbers only if $synonym is a number.
case 'number_integer':
case 'number_float':
case 'number_decimal':
if (!is_numeric($synonym)) {
return array();
}
break;
}
return array(array(
'value' => $synonym,
));
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* @file
* Enables Taxonomy Term Reference field types to be source of synonyms.
*/
class TaxonomySynonymsExtractor extends AbstractSynonymsExtractor {
static public function fieldTypesSupported() {
return array('taxonomy_term_reference');
}
static public function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
$synonyms = array();
$terms = array();
foreach ($items as $item) {
$terms[] = $item['tid'];
}
$terms = taxonomy_term_load_multiple($terms);
foreach ($terms as $term) {
$synonyms[] = entity_label('taxonomy_term', $term);
}
return $synonyms;
}
static public function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
// Unfortunately EntityFieldQuery does not currently support INNER JOINing
// term entity that is referenced via taxonomy_term_reference field type.
// Thus, we use an ugly solution -- going through all terms that exist in
// vocabulary and trying to see if there is a match by term's name.
$tids = array();
foreach ($field['settings']['allowed_values'] as $settings) {
$efd = new EntityFieldQuery();
$efd->entityCondition('bundle', $settings['vocabulary'])
->entityCondition('entity_type', 'taxonomy_term')
->propertyCondition('name', '%' . $tag . '%', 'LIKE');
$result = $efd->execute();
if (isset($result['taxonomy_term'])) {
foreach ($result['taxonomy_term'] as $tid) {
$tids[] = $tid->tid;
}
}
}
// Now we have tids of terms from the referenced vocabulary which names
// LIKE %$tag%, suggested are the terms that refer to any of these $tids.
if (empty($tids)) {
// No possible suggestions were found. We have to make sure $query yields
// no results.
self::emptyResultsCondition($query);
return;
}
$query->fieldCondition($field, 'tid', $tids);
}
static public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
// Taxonomy term reference supports only referencing of entity types
// 'taxonomy_term'.. duh.
if ($synonym_entity_type != 'taxonomy_term') {
return array();
}
// Checking that $field is configured to reference the vocabulary of
// $synonym_entity term.
$is_allowed = FALSE;
foreach ($field['settings']['allowed_values'] as $setting) {
if ($synonym_entity->vocabulary_machine_name == $setting['vocabulary']) {
if ($setting['parent'] == 0) {
// No need to check parent property as there is no limitation on it.
$is_allowed = TRUE;
break;
}
else {
foreach (taxonomy_get_parents_all($synonym_entity->tid) as $parent) {
if ($parent->tid == $setting['parent']) {
$is_allowed = TRUE;
break(2);
}
}
}
}
}
if (!$is_allowed) {
// Synonym term is from a vocabulary that is not expected by this field,
// or under unexpected parent.
return array();
}
return array(array(
'tid' => $synonym_entity->tid,
));
}
}

View File

@@ -0,0 +1,154 @@
<?php
/**
* @file
* Documentation for Synonyms module.
*/
/**
* Provide Synonyms module with names of synonyms extractor classes.
*
* Provide Synonyms module with names of classes that are able to extract
* synonyms from fields. Each of the provided classes should extend
* AbstractSynonymsExtractor base class.
*
* @return array
* Array of strings, where each value is a name of synonyms extractor class
*/
function hook_synonyms_extractor_info() {
return array(
// Please see below the defintion of ApiSynonymsSynonymsExtractor class
// for your reference.
'ApiSynonymsSynonymsExtractor',
);
}
/**
* Dummy synonyms extractor class for documentation purposes.
*
* This is a copy of SynonymsSynonymsExtractor class providing an example of
* how to write your own synonyms extractor class. See the definition of
* AbstractSynonymsExtractor for reference and incode comments. For more
* complicated examples take a look at EntityReferenceSynonymsExtractor class.
*/
class ApiSynonymsSynonymsExtractor extends AbstractSynonymsExtractor {
/**
* Return array of supported field types for synonyms extraction.
*
* @return array
* Array of Field API field types from which this class is able to extract
* synonyms
*/
static public function fieldTypesSupported() {
return array('text', 'number_integer', 'number_float', 'number_decimal');
}
/**
* Extract synonyms from a field attached to an entity.
*
* We try to pass as many info about context as possible, however, normally
* you will only need $items to extract the synonyms.
*
* @param array $items
* Array of items
* @param array $field
* Array of field definition according to Field API
* @param array $instance
* Array of instance definition according to Field API
* @param object $entity
* Fully loaded entity object to which the $field and $instance with $item
* values is attached to
* @param string $entity_type
* Type of the entity $entity according to Field API definition of entity
* types
*
* @return array
* Array of synonyms extracted from $items
*/
static public function synonymsExtract($items, $field, $instance, $entity, $entity_type) {
$synonyms = array();
foreach ($items as $item) {
$synonyms[] = $item['value'];
}
return $synonyms;
}
/**
* Allow you to hook in during autocomplete suggestions generation.
*
* Allow you to include entities for autocomplete suggestion that are possible
* candidates based on your field as a source of synonyms. This method is
* void, however, you have to alter and add your condition to $query
* parameter.
*
* @param string $tag
* What user has typed in into autocomplete widget. Normally you would
* run LIKE '%$tag%' on your column
* @param EntityFieldQuery $query
* EntityFieldQuery object where you should add your conditions to
* @param array $field
* Array of field definition according to Field API, autocomplete on which
* is fired
* @param array $instance
* Array of instance definition according to Field API, autocomplete on
* which is fired
*/
static public function processEntityFieldQuery($tag, EntityFieldQuery $query, $field, $instance) {
$query->fieldCondition($field, 'value', '%' . $tag . '%', 'LIKE');
}
/**
* Add an entity as a synonym into a field of another entity.
*
* Basically this method should be called when you want to add some entity
* as a synonym to another entity (for example when you merge one entity
* into another and besides merging want to add synonym of the merging
* entity into the trunk entity). You should extract synonym value (according
* to what value is expected in this field) and return it. We try to provide
* you with as much context as possible, but normally you would only need
* $synonym_entity and $synonym_entity_type parameters. Return an empty array
* if entity of type $synonym_entity_type cannot be converted into a format
* expected by $field.
*
* @param array $items
* Array items that already exist in the field into which new synonyms is to
* be added
* @param array $field
* Field array definition according to Field API of the field into which new
* synonym is to be added
* @param array $instance
* Instance array definition according to Field API of the instance into
* which new synonym is to be added
* @param object $synonym_entity
* Fully loaded entity object which has to be added as synonym
* @param string $synonym_entity_type
* Entity type of $synonym_entity
*
* @return array
* Array of extra items to be merged into the items that already exist in
* field values
*/
static public function mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type) {
$synonym = entity_label($synonym_entity_type, $synonym_entity);
switch ($field['type']) {
case 'text':
break;
// We add synonyms for numbers only if $synonym is a number.
case 'number_integer':
case 'number_float':
case 'number_decimal':
if (!is_numeric($synonym)) {
return array();
}
break;
}
return array(array(
'value' => $synonym,
));
}
}

View File

@@ -0,0 +1,19 @@
name = Synonyms
description = "Provides synonyms feature for working with Drupal Taxonomy"
package = Taxonomy
core = 7.x
dependencies[] = taxonomy
dependencies[] = text
files[] = synonyms.test
files[] = includes/AbstractSynonymsExtractor.class.inc
files[] = includes/SynonymsSynonymsExtractor.class.inc
files[] = includes/TaxonomySynonymsExtractor.class.inc
files[] = includes/EntityReferenceSynonymsExtractor.class.inc
; Information added by drupal.org packaging script on 2013-01-15
version = "7.x-1.1"
core = "7.x"
project = "synonyms"
datestamp = "1358220389"

View File

@@ -0,0 +1,46 @@
<?php
/**
* @file
* Install, update, and uninstall functions for the Synonyms module.
*/
/**
* Implements hook_uninstall().
*/
function synonyms_uninstall() {
// We rely on a constant defined in the main module's file, so we include it.
drupal_load('module', 'synonyms');
field_delete_field(SYNONYMS_DEFAULT_FIELD_NAME);
// Cleaning all configure variables.
$results = db_select('variable', 'var')
->fields('var', array('name'))
->condition('var.name', db_like('synonyms_') . '%', 'LIKE')
->execute();
foreach ($results as $var) {
variable_del($var->name);
}
}
/**
* Implements hook_update_N().
*
* Update to version 7.x-1.1 of Synonyms module.
*/
function synonyms_update_7101() {
$result = db_select('variable', 'var')
->fields('var', array('name'))
->condition('var.name', db_like('synonyms_settings_') . '%', 'LIKE')
->execute();
foreach ($result as $var) {
$settings = variable_get($var->name);
// Term merging has been deprecated in favor of Term Merge module.
unset($settings['term_merge']);
// Enabled synonyms now stored as field names, since the field independency
// has been introduced. See issue http://drupal.org/node/1850748.
drupal_load('module', 'synonyms');
$settings['synonyms'] = $settings['synonyms'] ? array(SYNONYMS_DEFAULT_FIELD_NAME) : array();
variable_set($var->name, $settings);
}
return t('Updated settings of synonyms.');
}

View File

@@ -0,0 +1,797 @@
<?php
/**
* @file
* Provide synonyms feature for Drupal Taxonomy.
*/
/**
* The default field name to be used as a source of synonyms for a term.
*/
define('SYNONYMS_DEFAULT_FIELD_NAME', 'synonyms_synonyms');
/**
* Implements hook_menu().
*/
function synonyms_menu() {
$items = array();
$items['synonyms/autocomplete'] = array(
'title' => 'Autocomplete Synonyms',
'page callback' => 'synonyms_autocomplete',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_synonyms_extractor_info().
*/
function synonyms_synonyms_extractor_info() {
// Here we provide synonyms extractors that come along with Synonyms module.
return array(
'SynonymsSynonymsExtractor',
'TaxonomySynonymsExtractor',
'EntityReferenceSynonymsExtractor',
);
}
/**
* Public function.
*
* Provide info about what class reports ability to extract synonyms from
* which field type. The output information of this function is collected via
* hook_synonyms_extractor_info().
*
* @param string $field_type
* Optionally you may specify to get a class responsible for a specific field
* type only. If nothing is supplied, array of field_type => class_extractor
* relation is returned
* @param bool $reset
* Whether collect all the info again from hooks, or cached info is fine
*
* @return array
* Key of this array denote the field type while value of the array denotes
* which class reports ability to extract synonyms from such field type.
*/
function synonyms_extractor_info($field_type = NULL, $reset = FALSE) {
$cache = &drupal_static(__FUNCTION__);
// Trying static cache.
if (!is_array($cache) || $reset) {
// Trying Drupal DB cache layer.
$cid = 'synonyms_extractor_info';
$cache = cache_get($cid);
if (!isset($cache->data) || $reset) {
// No cache has been found at all. So we call hooks and collect data.
$info = array();
$extractor_classes = module_invoke_all('synonyms_extractor_info');
if (is_array($extractor_classes)) {
foreach ($extractor_classes as $class) {
if (class_exists($class) && is_subclass_of($class, 'AbstractSynonymsExtractor')) {
foreach ($class::fieldTypesSupported() as $_field_type) {
$info[$_field_type] = $class;
}
}
}
}
// Letting whoever wants to implement any changes after preprocessing the
// data.
drupal_alter('synonyms_extractor_info', $info);
// Validating that any changes made to $info do not break class hierarchy.
foreach ($info as $k => $v) {
if (!class_exists($v) || !is_subclass_of($v, 'AbstractSynonymsExtractor')) {
unset($info[$k]);
}
}
$cache = $info;
cache_set($cid, $cache, 'cache', CACHE_TEMPORARY);
}
else {
$cache = $cache->data;
}
}
if (!is_null($field_type) && isset($cache[$field_type])) {
return $cache[$field_type];
}
return $cache;
}
/**
* Implements hook_node_update_index().
*/
function synonyms_node_update_index($node) {
$output = '';
foreach (field_info_instances('node', $node->type) as $instance) {
// We go a field by field looking for taxonomy term reference and
// if that vocabulary has enabled synonyms, we add term's synonyms
// to the search index.
$field_info = field_info_field($instance['field_name']);
if ($field_info['type'] == 'taxonomy_term_reference') {
// For each term referenced in this node we have to add synonyms.
$_terms = field_get_items('node', $node, $instance['field_name']);
if (is_array($_terms) && !empty($_terms)) {
$terms = array();
foreach ($_terms as $v) {
$terms[] = $v['tid'];
}
$terms = taxonomy_term_load_multiple($terms);
foreach ($terms as $term) {
$synonyms = synonyms_get_term_synonyms($term);
if (!empty($synonyms)) {
$safe_synonyms = array();
foreach ($synonyms as $synonym) {
$safe_synonyms[] = $synonym['safe_value'];
}
$output .= '<strong>' . implode(', ', $safe_synonyms) . '</strong>';
}
}
}
}
}
return $output;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function synonyms_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) {
if (isset($form_state['confirm_delete']) && $form_state['confirm_delete']) {
return;
}
$form['synonyms'] = array(
'#type' => 'fieldset',
'#title' => t('Synonyms'),
'#collapsible' => TRUE,
'#tree' => TRUE,
);
$options = array(
SYNONYMS_DEFAULT_FIELD_NAME => t('Default synonyms field'),
);
if (isset($form['#vocabulary']->vid)) {
$instances = synonyms_instances_extract_applicapable($form['#vocabulary']);
foreach ($instances as $instance) {
// Here we prefer some informative text for the default synonyms field
// rather its label.
if ($instance['field_name'] != SYNONYMS_DEFAULT_FIELD_NAME) {
$options[$instance['field_name']] = $instance['label'];
}
}
}
$form['synonyms']['synonyms'] = array(
'#type' => 'checkboxes',
'#title' => t('Synonyms Fields'),
'#options' => $options,
'#description' => t('<p>This option allows you to assign synonym fields for each term of the vocabulary, allowing to reduce the amount of duplicates.</p><p><b>Important note:</b> unticking %default_field on a production website will result in loss of your synonyms.</p>', array('%default_field' => $options[SYNONYMS_DEFAULT_FIELD_NAME])),
'#default_value' => synonyms_synonyms_fields($form['#vocabulary']),
);
// Adding out own submit handler.
$form['#submit'][] = 'synonyms_taxonomy_form_vocabulary_submit';
}
/**
* Implements hook_field_widget_info().
*/
function synonyms_field_widget_info() {
return array(
'synonyms_autocomplete' => array(
'label' => t('Synonyms friendly autocomplete term widget'),
'field types' => array('taxonomy_term_reference'),
'settings' => array(
'size' => 60,
'autocomplete_path' => 'synonyms/autocomplete',
),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
),
),
);
}
/**
* Implements hook_field_widget_settings_form().
*/
function synonyms_field_widget_settings_form($field, $instance) {
$widget = $instance['widget'];
$settings = $widget['settings'];
$form = array();
$form['auto_creation'] = array(
'#type' => 'checkbox',
'#title' => t('Allow auto-creation?'),
'#description' => t('Whether users may create a new term by typing in a non-existing name into this field.'),
'#default_value' => isset($settings['auto_creation']) ? $settings['auto_creation'] : 0,
);
return $form;
}
/**
* Implements hook_field_widget_form().
*/
function synonyms_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
$tags = array();
foreach ($items as $item) {
$tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']);
}
$element += array(
'#type' => 'textfield',
'#default_value' => taxonomy_implode_tags($tags),
'#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $field['field_name'],
'#size' => $instance['widget']['settings']['size'],
'#maxlength' => 1024,
'#element_validate' => array('taxonomy_autocomplete_validate', 'synonyms_autocomplete_validate'),
'#auto_creation' => isset($instance['widget']['settings']['auto_creation']) ? $instance['widget']['settings']['auto_creation'] : 0,
);
return $element;
}
/**
* Implements hook_field_widget_error().
*/
function synonyms_field_widget_error($element, $error, $form, &$form_state) {
form_error($element, $error['message']);
}
/**
* Form element validate handler.
*
* Handle validation for taxonomy term synonym-friendly autocomplete element.
*/
function synonyms_autocomplete_validate($element, &$form_state) {
// After taxonomy_autocomplete_validate() has finished its job
// it might left terms in the format for autocreation. Since our field
// supports auto creation as a configurable option, we have to make sure
// auto creation terms are allowed.
$value = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
if (!$element['#auto_creation']) {
// Deleting all the terms meant to be auto-created.
foreach ($value as $delta => $term) {
if ($term['tid'] == 'autocreate') {
unset($value[$delta]);
}
}
$value = array_values($value);
}
form_set_value($element, $value, $form_state);
}
/**
* Submit hanlder for Taxonomy vocabulary edit form.
*
* Store extra values attached to form in this module.
*/
function synonyms_taxonomy_form_vocabulary_submit($form, &$form_state) {
$values = $form_state['values'];
if ($values['op'] == $form['actions']['submit']['#value']) {
if (isset($form['#vocabulary']->vid)) {
$vocabulary = taxonomy_vocabulary_load($form['#vocabulary']->vid);
}
else {
// As a fallback, if this is a just created vocabulary, we try to pull it
// up by the just submitted machine name.
$vocabulary = taxonomy_vocabulary_machine_name_load($values['machine_name']);
}
// Preprocessing values keeping only field names of the checked fields.
foreach ($values['synonyms']['synonyms'] as $k => $v) {
if (!$v) {
unset($values['synonyms']['synonyms'][$k]);
}
}
$values['synonyms']['synonyms'] = array_values($values['synonyms']['synonyms']);
$settings = synonyms_vocabulary_settings($vocabulary);
$settings = $values['synonyms'] + $settings;
synonyms_vocabulary_settings_save($vocabulary, $settings);
}
}
/**
* Page callback: Outputs JSON for taxonomy autocomplete suggestions.
*
* This callback outputs term name suggestions in response to Ajax requests
* made by the synonyms autocomplete widget for taxonomy term reference
* fields. The output is a JSON object of plain-text term suggestions,
* keyed by the user-entered value with the completed term name appended.
* Term names containing commas are wrapped in quotes. The search is made
* with consideration of synonyms.
*
* @param string $field_name
* The name of the term reference field.
* @param string $tags_typed
* (optional) A comma-separated list of term names entered in the
* autocomplete form element. Only the last term is used for autocompletion.
* Defaults to '' (an empty string).
*/
function synonyms_autocomplete($field_name, $tags_typed = '') {
// How many suggestions maximum we are able to output.
$max_suggestions = 10;
// If the request has a '/' in the search text, then the menu system will have
// split it into multiple arguments, recover the intended $tags_typed.
$args = func_get_args();
// Shift off the $field_name argument.
array_shift($args);
$tags_typed = implode('/', $args);
// Make sure the field exists and is a taxonomy field.
if (!($field = field_info_field($field_name)) || $field['type'] != 'taxonomy_term_reference') {
// Error string. The JavaScript handler will realize this is not JSON and
// will display it as debugging information.
print t('Taxonomy field @field_name not found.', array('@field_name' => $field_name));
exit;
}
// The user enters a comma-separated list of tags. We only autocomplete the
// last tag.
$tags_typed = drupal_explode_tags($tags_typed);
$tag_last = drupal_strtolower(array_pop($tags_typed));
$term_matches = array();
if ($tag_last != '') {
// Part of the criteria for the query come from the field's own settings.
$vocabularies = array();
$tmp = taxonomy_vocabulary_get_names();
foreach ($field['settings']['allowed_values'] as $tree) {
$vocabularies[$tmp[$tree['vocabulary']]->vid] = $tree['vocabulary'];
}
$vocabularies = taxonomy_vocabulary_load_multiple(array_keys($vocabularies));
// Firstly getting a list of tids that match by $term->name.
$query = db_select('taxonomy_term_data', 't');
$query->addTag('translatable');
$query->addTag('term_access');
// Do not select already entered terms.
if (!empty($tags_typed)) {
$query->condition('t.name', $tags_typed, 'NOT IN');
}
// Select rows that match by term name.
$tags_return = $query
->fields('t', array('tid', 'name'))
->condition('t.vid', array_keys($vocabularies))
->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE')
->range(0, $max_suggestions)
->execute()
->fetchAllKeyed();
// Converting results into another format.
foreach ($tags_return as $tid => $name) {
$tags_return[$tid] = array('name' => $name);
}
$synonym_tids = array();
// Now we go vocabulary by vocabulary looking through synonym fields.
foreach ($vocabularies as $vocabulary) {
// Now we go a synonym field by synonym field gathering suggestions.
// Since officially EntityFieldQuery doesn't support OR conditions
// we are enforced to go a field by field querying multiple times the DB.
$bundle = field_extract_bundle('taxonomy_term', $vocabulary);
foreach (synonyms_synonyms_fields($vocabulary) as $field) {
$field = field_info_field($field);
$instance = field_info_instance('taxonomy_term', $field['field_name'], $bundle);
if (count($tags_return) + count($synonym_tids) > $max_suggestions) {
// We have collected enough suggestions and the ongoing queries
// will be just a waste of time.
break;
}
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'taxonomy_term')
->entityCondition('bundle', $vocabulary->machine_name);
// We let the class that defines this field type as a source of synonyms
// filter our and provide its suggestions based on this field.
$class = synonyms_extractor_info($field['type']);
$class::processEntityFieldQuery($tag_last, $query, $field, $instance);
if (!empty($tags_typed)) {
$query->propertyCondition('name', $tags_typed, 'NOT IN');
}
if (!empty($tags_return)) {
// We don't want to search among the terms already found by term name.
$query->entityCondition('entity_id', array_keys($tags_return), 'NOT IN');
}
if (!empty($synonym_tids)) {
// Nor we want to search among the terms already mached by previous
// synonym fields.
$query->entityCondition('entity_id', $synonym_tids, 'NOT IN');
}
$tmp = $query->execute();
if (!empty($tmp)) {
// Merging the results.
$tmp = array_keys($tmp['taxonomy_term']);
$synonym_tids = array_merge($synonym_tids, $tmp);
}
}
}
if (!empty($synonym_tids)) {
$synonym_tids = array_slice($synonym_tids, 0, $max_suggestions - count($tags_return));
foreach (taxonomy_term_load_multiple($synonym_tids) as $synonym_term) {
$tags_return[$synonym_term->tid] = array('name' => $synonym_term->name);
// Additionally we have to find out which synonym triggered inclusion
// of this term.
$synonyms = synonyms_get_term_synonyms($synonym_term);
foreach ($synonyms as $item) {
if (strpos(mb_strtoupper($item['value'], 'UTF-8'), mb_strtoupper($tag_last, 'UTF-8')) !== FALSE) {
$tags_return[$synonym_term->tid]['synonym'] = $item['value'];
break;
}
}
}
}
$prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : '';
// Now formatting the results.
foreach ($tags_return as $tid => $info) {
$n = $info['name'];
// Term names containing commas or quotes must be wrapped in quotes.
if (strpos($info['name'], ',') !== FALSE || strpos($info['name'], '"') !== FALSE) {
$n = '"' . str_replace('"', '""', $info['name']) . '"';
}
if (isset($info['synonym'])) {
$display_name = t('@synonym, synonym of %term', array('@synonym' => $info['synonym'], '%term' => $info['name']));
}
else {
$display_name = check_plain($info['name']);
}
$term_matches[$prefix . $n] = $display_name;
}
}
drupal_json_output($term_matches);
}
/**
* Try to find a term by its name or synonym.
*
* To maximize the match trimming and case-insensetive comparison is used.
*
* @param string $name
* The string to be searched for its {taxonomy_term}.tid
* @param object $vocabulary
* Fully loaded vocabulary object in which you wish to search
* @param int $parent
* Optional. In case you want to narrow your search scope, this parameter
* takes in the {taxonomy_term}.tid of the parent term, letting you search
* only among its children
*
* @return int
* If the look up was successfull returns the {taxonomy_term}.tid of the
* found term, otherwise returns 0
*/
function synonyms_get_term_by_synonym($name, $vocabulary, $parent = 0) {
$name = mb_strtoupper(trim($name), 'UTF-8');
$tree = taxonomy_get_tree($vocabulary->vid, $parent, NULL, TRUE);
foreach ($tree as $term) {
if (mb_strtoupper($term->name, 'UTF-8') == $name) {
return $term->tid;
}
// We additionally scan through the synonyms.
$synonyms = synonyms_get_term_synonyms($term);
foreach ($synonyms as $item) {
if (mb_strtoupper($item['safe_value'], 'UTF-8') == $name) {
return $term->tid;
}
}
}
// If we have reached down here, this means we haven't got any match
// as fallback we return 0.
return 0;
}
/**
* Look up a term considering synonyms and if nothing found add one.
*
* This function is useful for automated creation of new terms
* as it won't generate the same terms over and over again.
*
* @param string $name
* The string to be searched for its {taxonomy_term}.tid
* @param object $vocabulary
* Fully loaded vocabulary object in which you wish to search
* @param int $parent
* Optional. In case you want to narrow your search scope, this parameter
* takes in the {taxonomy_term}.tid of the parent term, letting you search
* only among its children
*
* @return int
* If a term already exists, its {taxonomy_term}.tid is returned,
* otherwise it creates a new term and returns its {taxonomy_term}.tid
*/
function synonyms_add_term_by_synonym($name, $vocabulary, $parent = 0) {
$tid = synonyms_get_term_by_synonym($name, $vocabulary, $parent);
if ($tid) {
// We found some term, returning its tid.
return $tid;
}
// We haven't found any term, so we create one.
$term = (object) array(
'name' => $name,
'vid' => $vocabulary->vid,
'parent' => array($parent),
);
taxonomy_term_save($term);
if (isset($term->tid)) {
return $term->tid;
}
// Normally we shouldn't reach up to here, because a term would have got
// created and the just created tid would have been returned. Nevertheless,
// as a fallback in case of any error we return 0.
return 0;
}
/**
* Public function for retrieving synonyms of a taxonomy term.
*
* @param object $term
* Fully loaded taxonomy term for which the synonyms are desired
*
* @return array
* Array of synonyms, if synonyms are disabled for the taxonomy term's
* vocabulary, an empty array is returned. Each synonyms array consists of the
* following keys:
* value - the value of a synonym as it was input by user
* safe_value - a sanitized value of a synonym
*/
function synonyms_get_term_synonyms($term) {
$synonyms = array();
$vocabulary = taxonomy_vocabulary_load($term->vid);
foreach (synonyms_synonyms_fields($vocabulary) as $field) {
$bundle = field_extract_bundle('taxonomy_term', $vocabulary);
$instance = field_info_instance('taxonomy_term', $field, $bundle);
$field = field_info_field($field);
$items = field_get_items('taxonomy_term', $term, $field['field_name']);
if (is_array($items) && !empty($items)) {
$class = synonyms_extractor_info($field['type']);
$synonyms = array_merge($synonyms, $class::synonymsExtract($items, $field, $instance, $term, 'taxonomy_term'));
}
}
// Applying sanitization to the extracted synonyms.
foreach ($synonyms as $k => $v) {
$synonyms[$k] = array(
'value' => $v,
'safe_value' => check_plain($v),
);
}
return $synonyms;
}
/**
* Allow to merge $synonym_entity as a synonym into $trunk_entity.
*
* Helpful function during various merging operations. It allows you to add a
* synonym (where possible) into one entity, which will represent another entity
* in the format expected by the field in which the synonym is being added.
* Important note: if the cardinality limit of the field into which you are
* adding synonym has been reached, calling to this function will take no
* effect.
*
* @param object $trunk_entity
* Fully loaded entity object in which the synonym is being added
* @param string $trunk_entity_type
* Entity type of $trunk_entity
* @param string $field
* Field name that should exist in $trunk_entity and be enabled as a synonym
* source. Into this field synonym will be added
* @param object $synonym_entity
* Fully loaded entity object which will be added as a synonym
* @param string $synonym_entity_type
* Entity type of $synonym_entity
*
* @return bool
* Whether synonym has been successfully added
*/
function synonyms_add_entity_as_synonym($trunk_entity, $trunk_entity_type, $field, $synonym_entity, $synonym_entity_type) {
if ($trunk_entity_type != 'taxonomy_term') {
// Currently synonyms module only operates on taxonomy terms.
return FALSE;
}
if (!in_array($field, synonyms_synonyms_fields(taxonomy_vocabulary_load($trunk_entity->vid)))) {
// $field either doesn't exist in the $trunk_entity or it's not enabled as
// a source of synonyms.
return FALSE;
}
// Preparing arguments for calling a method of Extractor class.
$field = field_info_field($field);
$extractor = synonyms_extractor_info($field['type']);
$items = field_get_items($trunk_entity_type, $trunk_entity, $field['field_name']);
$items = is_array($items) ? $items : array();
$trunk_entity_ids = entity_extract_ids($trunk_entity_type, $trunk_entity);
$instance = field_info_instance($trunk_entity_type, $field['field_name'], $trunk_entity_ids[2]);
$extra_items = $extractor::mergeEntityAsSynonym($items, $field, $instance, $synonym_entity, $synonym_entity_type);
// Merging extracted synonym items into the values of the field that already
// exist.
// @todo: Currently we hardcode to LANGUAGE_NONE, but in future it would be
// nice to have multilanguage support.
$items = array_merge($items, $extra_items);
$trunk_entity->{$field['field_name']}[LANGUAGE_NONE] = $items;
// In future if this module eventually becomes a gateway for synonyms for any
// entity types, we'll substitute it with entity_save().
taxonomy_term_save($trunk_entity);
return TRUE;
}
/**
* Return array of field names that are sources of synonyms.
*
* Return array of field names that are currently enabled as source of
* synonyms in the supplied vocabulary.
*
* @param object $vocabulary
* Fully loaded taxonomy vocabulary object
*
* @return array
* Array of field names
*/
function synonyms_synonyms_fields($vocabulary) {
$settings = synonyms_vocabulary_settings($vocabulary);
if (!isset($settings['synonyms']) || !is_array($settings['synonyms'])) {
// Wierd as normally we expect to see here at least an empty array but
// no problem. We simply initialize it.
$settings['synonyms'] = array();
}
// It's not just as easy as pulling up already saved value. After this
// we have to make sure that all the fields are still present and have not
// been deleted from the vocabulary.
$bundle = field_extract_bundle('taxonomy_term', $vocabulary);
$instances = field_info_instances('taxonomy_term', $bundle);
$settings['synonyms'] = array_intersect($settings['synonyms'], array_keys($instances));
return $settings['synonyms'];
}
/**
* Enforce the setting "synonyms".
*
* @param object $vocabulary
* Fully loaded entity of a taxonomy vocabulary
* @param array $fields
* Array of fields that are enabled as a source of synonyms
*/
function synonyms_synonyms_enforce($vocabulary, $fields) {
$bundle = field_extract_bundle('taxonomy_term', $vocabulary);
// Normally all the fields already exist, we just need to assure that
// default synonyms field exists if it is enabled as a source of synonyms.
// Otherwise we make sure we delete instance of the default field in the
// vocabulary.
$instance = field_info_instance('taxonomy_term', SYNONYMS_DEFAULT_FIELD_NAME, $bundle);
if (in_array(SYNONYMS_DEFAULT_FIELD_NAME, $fields)) {
if (is_null($instance)) {
// Make sure the field exists.
synonyms_default_field_ensure();
field_create_instance(array(
'field_name' => SYNONYMS_DEFAULT_FIELD_NAME,
'entity_type' => 'taxonomy_term',
'bundle' => $bundle,
'label' => t('Synonyms'),
'description' => t('Please, enter the synonyms which should be related to this term.'),
));
}
}
elseif (!is_null($instance)) {
// Deleting the instance, we will delete the field separately when the
// module gets uninstalled.
field_delete_instance($instance, FALSE);
}
}
/**
* Return the current settings for the supplied $vocabulary.
*
* @param object $vocabulary
* Fully loaded entity of a taxonomy vocabulary
*
* @return array
* Array of current synonyms settings for the supplied vocabulary.
* Should include the following keys:
* synonyms - array of field names that are enabled as source of synonyms
*/
function synonyms_vocabulary_settings($vocabulary) {
$settings = array();
if (isset($vocabulary->vid)) {
$settings = variable_get('synonyms_settings_' . $vocabulary->vid, array());
}
return $settings;
}
/**
* Save the current settings for the supplied $vocabulary.
*
* @param object $vocabulary
* Fully loaded entity of a taxonomy vocabulary
* @param array $settings
* Settings the have to be stored. The structure of this array has to be
* identical to the output array of the function
* synonyms_vocabulary_settings()
*/
function synonyms_vocabulary_settings_save($vocabulary, $settings) {
variable_set('synonyms_settings_' . $vocabulary->vid, $settings);
// Now enforcing each setting.
foreach ($settings as $k => $v) {
switch ($k) {
case 'synonyms':
synonyms_synonyms_enforce($vocabulary, $v);
break;
}
}
}
/**
* Make sure the default synonyms field exists.
*
* If the field doesn't exist function creates one, if the field exists,
* the function does nothing.
*/
function synonyms_default_field_ensure() {
$field = field_info_field(SYNONYMS_DEFAULT_FIELD_NAME);
if (is_null($field)) {
$field = array(
'field_name' => SYNONYMS_DEFAULT_FIELD_NAME,
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
'locked' => TRUE,
'type' => 'text',
);
field_create_field($field);
}
}
/**
* Extract instances that are applicapable for being source of synonyms.
*
* Walk through all instances of a vocabulary and extract only valid candidates
* for becoming a source of synonyms for the vocabulary terms.
*
* @param object $vocabulary
* Fully loaded vocabulary object
*
* @return array
* Array of instance arrays
*/
function synonyms_instances_extract_applicapable($vocabulary) {
$applicapable_field_types = array_keys(synonyms_extractor_info());
$applicapable = array();
$bundle = field_extract_bundle('taxonomy_term', $vocabulary);
foreach (field_info_instances('taxonomy_term', $bundle) as $instance) {
$field = field_info_field($instance['field_name']);
if (in_array($field['type'], $applicapable_field_types)) {
$applicapable[] = $instance;
}
}
return $applicapable;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,392 @@
TAXONOMY CSV IMPORT/EXPORT
==========================
== CHANGELOG ==
===========
Taxonomy csv import/export 7.x-5.10, 2012-02-22
-----------------------------------------------
#1410784 by Daniel Berthereau: Added import/export of multivalued fields.
By Daniel Berthereau: Simplified import UI: only three generic import formats.
By Daniel Berthereau: Removed old code from 6.x in order to use only Field API.
By Daniel Berthereau: Clarified import of translations.
By Daniel Berthereau: Added export of translations.
Taxonomy csv import/export 7.x-5.10, 2012-02-22
Taxonomy csv import/export 6.x-5.10, 2012-02-22
-----------------------------------------------
By Daniel Berthereau: Updated documentation and checked code with Coder.
Taxonomy csv import/export 6.x-5.10, 2012-02-22
-----------------------------------------------
By Daniel Berthereau: Added a full export format compatible with Taxonomy CSV 7.
Taxonomy csv import/export 7.x-5.9, 2012-02-04
----------------------------------------------
By Daniel Berthereau: Added import custom fields (by tid and for any fields).
By Daniel Berthereau: Added import of translations by tid.
By Daniel Berthereau: Added export of fields.
Taxonomy csv import/export 7.x-5.9, 2012-02-04
Taxonomy csv import/export 6.x-5.9, 2012-02-04
----------------------------------------------
#969750 by Daniel Berthereau: Fixed check of Javascript, presumed true.
By Daniel Berthereau: Improved internal documentation and code.
By Daniel Berthereau: Added new export format: tid and name.
Taxonomy csv import/export 7.x-5.8, 2012-01-28
----------------------------------------------
#1395090 by Alex Oroshchuk: Added import/export by vocabulary machine name.
By Daniel Berthereau: Added option to set the format of the description.
By Daniel Berthereau: Removed compatibily layer between Taxonomy CSV 6 and 7.
By Daniel Berthereau: Improved import speed and memory usage.
By Daniel Berthereau: Added internationalization for Taxonomy CSV import.
#1413922 By Daniel Berthereau: Added support for number and text custom fields.
Taxonomy csv import/export 7.x-5.8, 2012-01-28
Taxonomy csv import/export 6.x-5.8, 2012-01-28
----------------------------------------------
#1359260 by Daniel Berthereau: Added a warning when terms count is invalid.
By Daniel Berthereau: Added pipe delimiter and quote enclosure option.
By Daniel Berthereau: Check line breaks in descriptions and escape or set them.
#1287210 by Èric Palau: Added permissions to import and export by CSV.
#1312244 by Daniel Berthereau: Added drush support to use module with CLI.
By Daniel Berthereau: Simplified use of update/replace/ignore existing items.
#1308420 by Jordim: Fixed 'Full definitions and links' import.
By Daniel Berthereau: Cleaned some cosmetic items (Coder review).
#1395090 by Alex Oroshchuk: Fixed check options with Drush 4.5.
#1006440 by Daniel Berthereau: Changed default export enclosure to « " ».
By Daniel Berthereau: Fixed Full definitions import with duplicates names.
#1410628 by Daniel Berthereau: Added select to export multiple vocabularies.
Taxonomy csv import/export 7.x-5.7, 2011-10-03
----------------------------------------------
By Daniel Berthereau: Added autocreation of custom fields.
By Daniel Berthereau: Improved related terms import.
#1027068 by Carsten Müller and Roberto Mariani: Added support for custom fields.
#1040894 by Carsten Müller: Added support for file extension .csv and .txt.
#1179568 by TommyChris: Fixed DB double prefix error.
Taxonomy csv import/export 7.x-5.7, 2011-10-03
Taxonomy csv import/export 6.x-5.7, 2011-10-03
----------------------------------------------
By Daniel Berthereau: Released new versions.
By Daniel Berthereau: Fixed some UI bugs and updated help.
By Daniel Berthereau: Simplified full definition and links import.
By Daniel Berthereau: Completed keep order of terms option.
By Daniel Berthereau: Improved user interface.
#1006466 by Daniel Berthereau: Fixed import full definitions in existing.
#1208010 by Daniel Berthereau: Added hierarchical tree export format.
#1237068 by Ted A Gifford: Removed a "by reference" warning in PHP strict mode.
#1244498 by Brad Erickson: Added link to first term causing export failure.
#1256628 by Daniel Berthereau: Fixed Microsoft DOS end line export format.
#1267026 by Daniel Berthereau: Fixed non-ascii filename of exported vocabulary.
#1270168 by Daniel Berthereau: Fixed Taxonomy manager export format.
#1287210 by Daniel Berthereau: Added option to delete terms before import.
Taxonomy csv import/export 6.x-5.7, 2011-10-03
----------------------------------------------
#1123914 by Andrey parashutiki: Fixed use of UI with keys up and down.
#1248926 by Daniel Berthereau: Added infos about weight limit of imported terms.
Taxonomy csv import/export 7.x-5.6, 2011-01-07
Taxonomy csv import/export 6.x-5.6, 2011-01-07
----------------------------------------------
#1017708 by EliteMonk: Fixed query with database prefix.
Taxonomy csv import/export 7.x-5.5, 2011-01-07
Taxonomy csv import/export 6.x-5.5, 2011-01-07
----------------------------------------------
By Daniel Berthereau: Added soft tab (two spaces or more) delimiter.
By Daniel Berthereau: Added option to keep order of terms by specifying weight.
#1006436 by John Voskuilen: Fixed export with Microsoft DOS end of line.
#1006466 by John Voskuilen: Harmonized import of Full term definition and links.
Taxonomy csv import/export 6.x-5.4, 2010-09-14
----------------------------------------------
#910788 by Brad Bulger: Fixed error when vocabulary isn't checked after import.
Taxonomy csv import/export 7.x-5.4-dev, 2010-09-13
--------------------------------------------------
#896132 by Matthias Hutterer: Fixed parents & children names broken by D7 HEAD.
Taxonomy csv import/export 6.x-5.3, 2010-09-13
----------------------------------------------
By Daniel Berthereau: Reimplemented utf8 conversion for obsolete Windows users.
By Daniel Berthereau: Simplified format for geotaxonomy import/export.
Taxonomy csv import/export 7.x-5.3, 2010-08-20
Taxonomy csv import/export 6.x-5.3-dev, 2010-08-20
--------------------------------------------------
By Daniel Berthereau: Enabled plug-ins for specific formats.
By Daniel Berthereau: Implemented geotaxonomy import/export format.
By Daniel Berthereau: Simplified and standardized import process.
Taxonomy csv import/export 7.x-5.1, 2010-08-16
Taxonomy csv import/export 6.x-5.2, 2010-08-16
----------------------------------------------
By Daniel Berthereau: Fixed error of commit to cvs and linked bugs.
By Daniel Berthereau: Forget taxonomy_csv.css and taxonomy_csv.js (Drupal 6).
By Daniel Berthereau: Merged all taxonomy-csv 6.x branches except 6.x-1.
By Daniel Berthereau: Doxygened some help.
Taxonomy csv import/export 7.x-5.0, 2010-08-13
Taxonomy csv import/export 6.x-5.0, 2010-08-13
----------------------------------------------
By Daniel Berthereau: Changed cvs HEAD from 6.x to 7.x release.
By Daniel Berthereau: Merged all taxonomy-csv 7.x branches except 7.x-1.
By Daniel Berthereau: Modularized and simplified code and reintegrated line api.
By Daniel Berthereau: Use of new features of Drupal 7 (UI and Field).
By Daniel Berthereau: Implemented synonyms and related terms in 7.x release.
By Daniel Berthereau: Cleaned structure of user interface.
By Daniel Berthereau: Improved speed of tree and polyhierarchical import.
By Daniel Berthereau: Implemented quick import mode: no check of existing terms.
By Daniel Berthereau: Set hierarchy check disabled by default in user interface.
By Daniel Berthereau: Removed special permissions and use 'administer taxonomy'.
By Daniel Berthereau: Removed internal cache and use only core taxonomy ones.
By Daniel Berthereau: Removed utf8 conversion.
#872366 by vipzhicheng: Better support of non-Ascii characters.
#843526 by iAugur: Fixed field name prefix.
#686328 by Alexander N: Fixed double display of PathAuto warning.
Taxonomy csv import/export 7.x-4.11, 2010-05-29
Taxonomy csv import/export 6.x-4.11, 2010-05-29
-----------------------------------------------
By Daniel Berthereau: Fixed and updated release to prepare Drupal 7 release.
By Daniel Berthereau: Small changed 6.x release to keep it close to the 7.x one.
By Daniel Berthereau: Added check of maximum length of names to 255 characters.
Taxonomy csv import/export 6.x-4.10, 2010-05-10
-----------------------------------------------
#765964 by Daniel Berthereau: Fixed import duplicate terms with full term mode.
Taxonomy csv import/export 6.x-4.9, 2010-01-13
----------------------------------------------
#666820 by Jeremy Rasmussen: Fixed url of exported taxonomy file.
taxonomy_csv 5.x-1.5, 2009-12-11
--------------------------------
#333159 by Katrina Messenger: Added weight import.
Taxonomy csv import/export 6.x-4.8, 2009-12-10
Taxonomy csv line api 6.x-4.8, 2009-12-10
----------------------------------------------
By Daniel Berthereau: Cleaned code with SimpleTest/TestBed suggestions.
#633348 by Jens Beltofte: Fixed field name when database uses a prefix.
#495548 and #447852 by Daniel Berthereau: Warned if use of incompatible modules.
Taxonomy csv import/export 7.x-4.7, 2009-10-20
Taxonomy csv import/export 6.x-4.7, 2009-10-20
----------------------------------------------
By Daniel Berthereau: Added export of terms and vocabularies.
Taxonomy csv 7.x-4.6, 2009-10-05
Taxonomy csv 6.x-4.6, 2009-10-05
--------------------------------
#590206 by Daniel Berthereau: Added import of full term definitions and links.
By Daniel Berthereau: Added import in multiple vocabularies with 'fields_links'.
By Daniel Berthereau: Cleaned code with some Zend Studio suggestions.
By Daniel Berthereau: Added table of content in help.
#576042 by Daniel Berthereau: Fixed use of previous choices save.
Taxonomy csv line api 6.x-4.5, 2009-09-23
-----------------------------------------
By Daniel Berthereau: Released light api: taxonomy_csv.line.api.inc in CVS HEAD.
Taxonomy csv 7.x-4.5, 2009-09-22
--------------------------------
By Daniel Berthereau: Released public version of taxonomy_csv for Drupal 7.
Taxonomy csv 7.x-4.5, 2009-09-22
Taxonomy csv 6.x-4.5, 2009-09-22
--------------------------------
By Daniel Berthereau: Added Polyhierarchical structure import.
By Daniel Berthereau: Added Fields (name, weight, description, synonyms) import.
By Daniel Berthereau: Added check of root level term.
By Daniel Berthereau: Added check when an imported term unchanges existing ones.
By Daniel Berthereau: Cleaned some UI items and help.
By Daniel Berthereau: Fixed compatibility of url import with all browsers.
By Daniel Berthereau: Fixed sensitive case update of a name.
Taxonomy csv 7.x-4.4, 2009-09-13
Taxonomy csv 6.x-4.4, 2009-09-13
--------------------------------
#575236 by Daniel Berthereau: Added Children import.
By Daniel Berthereau: Added Parents import.
Taxonomy csv 7.x-4.3, 2009-09-12
Taxonomy csv 6.x-4.3, 2009-09-12
--------------------------------
By Daniel Berthereau: Created API to import csv line or batch from any module.
By Daniel Berthereau: Added any size file import (full line by line approach).
By Daniel Berthereau: Added tweaks to allow import even with low memory.
By Daniel Berthereau: Removed multistep autodivide import.
By Daniel Berthereau: Added Taxonomy manager export format.
By Daniel Berthereau: Cleaned comments and help.
By Daniel Berthereau: Fixed form display bugs.
Taxonomy csv 7.x-4.2, 2009-08-18
Taxonomy csv 6.x-4.2, 2009-08-18
--------------------------------
#450900 by Daniel Berthereau: Fixed duplicate parent term query.
By Daniel Berthereau: Fixed batch cache and statistics.
By Daniel Berthereau: Fixed error display when batch error (only useful infos).
By Daniel Berthereau: Added option to reduce log level and memory usage.
By Daniel Berthereau: Cleaned release.
Taxonomy csv 7.x-4.1, 2009-08-11
Taxonomy csv 6.x-4.1, 2009-08-11
--------------------------------
#540916 by Daniel Berthereau: Added warning to disable Pathauto before import.
Taxonomy csv 7.x-4.0, 2009-08-10
--------------------------------
By Daniel Berthereau: Converted Taxonomy csv module from Drupal 6.x to Drupal 7.
Taxonomy csv 6.x-4.0, 2009-08-09
--------------------------------
By Daniel Berthereau: Added multistep import for big files (avoid memory error).
By Daniel Berthereau: Added term enclosure option.
By Daniel Berthereau: Reviewed user interface code for lisibility (js & css).
By Daniel Berthereau: Reviewed batch code in order to decrease memory usage.
Taxonomy csv 6.x-3.1, 2009-04-08
--------------------------------
#427076 by Daniel Berthereau: Fixed Microsoft Windows pass-by-reference error.
Taxonomy csv 6.x-3.0, 2009-04-03
--------------------------------
By Daniel Berthereau: Redesigned UI with dynamic options and examples (AHAH).
By Daniel Berthereau: Added import by a text area.
By Daniel Berthereau: Added hook_perm "administer taxonomy by csv".
By Daniel Berthereau: Added memorize user settings.
By Daniel Berthereau: Added test on a duplicate of an existing vocabulary.
By Daniel Berthereau: Added tabulation delimiter option.
By Daniel Berthereau: Added import terms as flat vocabulary.
By Daniel Berthereau: Renamed "children import" to "structure import".
By Daniel Berthereau: Disabled option relations to all vocabularies (not in D6).
By Daniel Berthereau: Extracted advanced help to alone file.
By Daniel Berthereau: Simplified stats import code.
Taxonomy csv 6.x-2.4, 2009-03-10
--------------------------------
By Daniel Berthereau: Released 6.x.2.4.
Taxonomy csv 6.x-2.3, 2009-03-09
--------------------------------
By Daniel Berthereau: Added subrelations import.
By Daniel Berthereau: Added option to make relations into all vocabularies.
By Daniel Berthereau: Fixed related terms import: remove related when replace.
By Daniel Berthereau: Fixed vocabulary level check for flat vocabulary.
Taxonomy csv 6.x-2.2, 2009-03-06
--------------------------------
By Daniel Berthereau: Restructured module to display warning informations.
By Daniel Berthereau: Added options to display import result.
By Daniel Berthereau: Added options to choose log level.
By Daniel Berthereau: Added one term by line structure import.
By Daniel Berthereau: Added auto create vocabulary.
By Daniel Berthereau: Added vocabulary level check.
By Daniel Berthereau: Extended help about one term structure array import.
By Daniel Berthereau: Fixed translate in install hook.
By Daniel Berthereau: Fixed related terms import: keep old even if Ignore.
Taxonomy csv 6.x-2.1, 2009-02-28
--------------------------------
By Daniel Berthereau: Fixed package release.
By Daniel Berthereau: Fixed define.
Taxonomy csv 6.x-2.0, 2009-02-27
--------------------------------
By Daniel Berthereau: Rewrited module. Cleaned release.
taxonomy_csv 6.x-1.5, 2008-04-19
taxonomy_csv 5.x-1.4, 2008-04-19
taxonomy_csv 4.7.x-1.4, 2008-04-19
----------------------------------
By naquah: Various bug fixes regarding file encoding, line endings, and
delimiters.
taxonomy_csv 6.x-1.4, 2008-04-10
--------------------------------
By naquah: Update for Drupal 6.2 compatibility.
taxonomy_csv 6.x-1.3, 2008-02-23
taxonomy_csv 5.x-1.3, 2008-02-23
taxonomy_csv 4.7.x-1.3, 2008-02-23
----------------------------------
By naquah: Improved documentation.
By naquah: Allow import of term description and term synonyms.
By naquah: Various bug fixes.
taxonomy_csv 6.x-1.x-dev, 2008-01-27
------------------------------------
Fully compatible with Drupal 6 and utilizing the new Batch API that will
accomodate in importing larger files.
taxonomy_csv 5.x-1.2, 2008-01-27
taxonomy_csv 4.7.x-1.2, 2008-01-27
----------------------------------
By naquah: Added a README.txt file.
No new features, no bug fixes and no known bugs.
taxonomy_csv 5.x-1.1, 2007-11-16
taxonomy_csv 4.7.x-1.1, 2007-11-16
----------------------------------
By naquah: Fixed a bug where items with the same name though different parents
are not imported.
taxonomy_csv 5.x-1.0, 2007-10-26
taxonomy_csv 4.7.x-1.0, 2007-10-26
----------------------------------
By naquah: Make sure to read the disclaimer on the project page!
taxonomy_csv 5.x-1.x-dev, 2007-10-21
taxonomy_csv 4.7.x-1.x-dev, 2007-10-21
--------------------------------------
By naquah: Use at your own risk!

View File

@@ -0,0 +1,64 @@
TAXONOMY CSV IMPORT/EXPORT
==========================
-- REQUIREMENTS --
--------------
* Drupal default taxonomy module.
* Optional: i18n Taxonomy, submodule of i18n (internationalization)
(see http://drupal.org/project/i18n).
-- INSTALLATION --
--------------
* Install as usual, see http://drupal.org/node/70151 for further information.
1) Extract and copy the taxonomy_csv directory into the appropriate modules
directory in your Drupal installation (recommended: sites/all/modules).
2) Adjust permissions of directory and files (read access).
3) Go to Administer > Modules and enable the
"Taxonomy CSV import/export" module in the "Taxonomy" section.
Drupal "Taxonomy" core module is automatically enabled.
4) Adjust user permissions in Administer > People > Permissions:
* import taxonomy by CSV
* export taxonomy by CSV
-- UNINSTALLATION --
----------------
* As Taxonomy csv save user last used options, you can clean Drupal with the
"uninstall" option.
1) Go to Administer > Modules and disable "Taxonomy CSV import/export".
2) Go to Administer > Modules > Uninstall,
then check "taxonomy csv import/export" and click Uninstall button.
3) Remove the taxonomy_csv directory in the directory where you install it.
-- USAGE --
-------
* Follow instructions in README.txt, on the main page or in advanced
help (Help > Taxonomy CSV import/export).
* Main page access with Drupal 6:
- Go to: Administer > Content management > Taxonomy and click on
"CSV import" tab or "CSV export" tab.
- Direct access to CSV import at:
http://www.example.com/?q=admin/content/taxonomy/csv_import
- Direct access to CSV export at:
http://www.example.com/?q=admin/content/taxonomy/csv_export
* Main page access With Drupal 7:
- Go to: Administer > Structure > Taxonomy and click on the "CSV import" tab
or on the "CSV export" tab.
- Direct access to CSV import at:
http://www.example.com/?q=admin/structure/taxonomy/csv_import
- Direct access to CSV export at:
http://www.example.com/?q=admin/structure/taxonomy/csv_export

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,104 @@
TAXONOMY CSV IMPORT/EXPORT
==========================
-- SUMMARY --
---------
This module allows to import or export taxonomy from or to a CSV
(comma-separated values) local or distant file or a copy-and-paste text.
When you want to quick import a non-standardized vocabulary, for example an
old thesaurus or a simple list of children, synonyms, related terms,
descriptions or weights of a set of terms, Taxonomy CSV is simpler to use. It
can manage internationalized vocabularies with i18n_taxonomy, a submodule of
i18n module (see http://drupal.org/project/i18n).
Some other modules allow to import and export taxonomies:
* Taxonomy XML (http://drupal.org/project/taxonomy_xml) is perfect for
standardized taxonomies and vocabularies importation. Despite its name, it can
import csv files too, but only if they are ISO 2788 formatted.
* Migration modules, see http://groups.drupal.org/node/21338 and
http://groups.drupal.org/soc-2006-import-export-api.
Taxonomy CSV is a more specialised tool which allows more precise tuning.
It's designed to be used when the website is building. After that, it's
recommended to disable it.
For a full description of the module, visit the project page:
http://drupal.org/project/taxonomy_csv
To submit bug reports and feature suggestions, or to track changes:
http://drupal.org/project/issues/taxonomy_csv
See Advanced help in Help > Taxonomy CSV import/export for updated help.
-- WARNING --
---------
Use at your risk. Even if many informations are displayed, taxonomy_csv module
does not tell you what it's gonna do before it's doing it, so make sure you have
a backup so you can roll back if necessary.
It's recommended to use the "autocreate" or the "duplicate vocabulary" choices
to check your csv file before import in a true vocabulary.
-- TROUBLESHOOTING --
-----------------
See online issue at http://drupal.org/project/issues/taxonomy_csv.
-- FAQ --
-----
See Advanced help in Help > Taxonomy CSV import/export.
See technical infos in TECHINFO.txt.
-- LICENCE --
---------
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.
-- CONTACT --
---------
Current maintainers:
* Daniel Berthereau (Daniel_KM) => http://drupal.org/user/428555
* Dennis Stevense (naquah) => http://drupal.org/user/26342
First version has been written by Dennis Stevense (naquah).
Major rewrite 6.x-2.0 and subsequents by Daniel Berthereau (Daniel_KM).
First version of the project has been sponsored by
* The Site Mechanic (http://www.thesitemechanics.com)
-- COPYRIGHT --
---------
Copyright © 2007-2009 Dennis Stevense (naquah)
Copyright © 2009-2012 Daniel Berthereau <daniel.drupal@berthereau.net>

View File

@@ -0,0 +1,237 @@
TAXONOMY CSV IMPORT/EXPORT TECHNICAL INFOS
==========================================
@todo To be updated for >= 7.x.5.8
Taxonomy csv import/export module has a lot of files, but majority of them are
used for user interface, to check the input and to manage big taxonomies import.
Furthermore, the process of duplicate terms is complex and code can be largely
simplified if these terms are not allowed - what is advised - and, therefore,
not to be managed.
True import/export process is done by some main functions only.
- Files of module
- Detail of files
- Use as an api
- Logical structure
- Add a new scheme
-- FILES OF MODULE --
-----------------
Taxonomy csv module is divided in files in order to minimize memory and to use
it as an api. Here an explanation of structure of these files.
Default Drupal files
.info
.install
.module
|
------------------+------------------
| |
V V
User Interface use -> .import.admin.inc .export.admin.inc
| |
------------------+------------------
|
V
.api.inc
*.format.inc
|
------------------+------------------
| |
V V
API / Drush use -----> .import.api.inc .export.api.inc
.import.parser.api.inc |
.import.line.api.inc |
| |
------------------+------------------
|
V
.api.inc
*.format.inc
.vocabulary.api.inc |
.term.api.inc
|
Only if user wants result infos and stats |
------------------+------------------
| |
V V
.import.result.inc .export.result.inc
| |
------------------+------------------
|
V
.result.inc
-- DETAILS OF FILES --
------------------
- Default Drupal files
- .info : info on module
- .install : install/uninstall
- .module : manage help, permissions and menus
- Central file
- .api.inc : manage variables and features of module. It is
invoked by below files.
- User interface files
- .import.admin.inc : create import form and validate user input
- .export.admin.inc : create export form and validate user input
- Api files
- .import.api.inc : validate import options and manage import process
- .import.parser.inc : Check a line of imported terms: duplicate, format...
Can be excluded if user doesn't want to check lines.
- .import.line.inc : process import of a csv line, i.e. of a term or a
list of terms
- .export.api.inc : validate export options, manage and process export
of a vocabulary (no need of a check)
- .vocabulary.api.inc : prepare and manage vocabularies
- .term.api.inc : find and get full or detail term definitions, and
save term
- Result files
- .result.inc : manages common messages on results of process
- .import.result.inc : manage infos and stats about import
- .export.result.inc : manage infos and stats about export
- Format files
- *.format.inc : contain full functions to import/export a vocabulary
-- USE AS AN API --
---------------
- Taxonomy_csv module doesn't need to be enabled. If it is not enabled, it need
to be invoked directly as this (example for import):
// Invoke taxonomy_csv import api.
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/import/taxonomy_csv.import.api.inc");
Other needed files are automaticaly invoked.
- If you choose to copy needed taxonomy_csv files in your module, they need to
be invoked by your module.info or directly with require_once. To include api
such this is possible, but not recommended, because some changes may be done
on taxonomy_csv files : each path of "require_once" should be modified.
- If Drupal core taxonomy module is not activated, main files of this module
should be invoked in your module as this:
// Invoke taxonomy core api.
$taxonomy_path = drupal_get_path('module', 'taxonomy');
require_once("$taxonomy_path/taxonomy.module");
require_once("$taxonomy_path/taxonomy.admin.inc");
require_once("$taxonomy_path/taxonomy.api.php"); // Drupal 7 only.
- Example (import of three lines in a new vocabulary with internal invocation):
// Invoke taxonomy_csv.api if not included in module.info or enabled.
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/import/taxonomy_csv.import.api.inc");
$csv_lines = '"Europe", "France", "Paris"';
$csv_lines .= "\n". ',, "Lyon"';
$csv_lines .= "\n". ',"United Kingdom", "London"';
$csv_lines .= "\n". ',"Portugal", "Lisbonne"';
$result = taxonomy_csv_import(
array(
'text' => $csv_lines,
'import_format' => 'tree_structure',
'existing_items' => 'update_replace',
));
- Others functions of api can be used too (line_import, export, vocabulary...).
-- LOGICAL STRUCTURE --
-------------------
Functions sets:
1a. Prepare and import a vocabulary : taxonomy_csv_vocabulary_import
1b. Prepare and export a vocabulary : taxonomy_csv_vocabulary_export
2a. Prepare and import a line : taxonomy_csv_line_import
2b. Prepare and export a term : taxonomy_csv_term_export
3a. Prepare and import a term : taxonomy_csv_term_import
3b. Prepare and export a line : taxonomy_csv_line_export
4. Errors helpers
5. Infos and log messages
Structure of import Api:
1. Batch prepare import of file or text
2. Process import structure (line by line import from a batch set)
1. Validate line if wanted
1. Clean input line
2. Check line items
2. Prepare to process items matching import type
3. Process import
1. Find previous or existing term, switch case:
- in part of vocabulary if structure import (parent)
- in whole vocabulary
- in all vocabularies if external term referenced
2. Update or create term
4. Check import and save messages if wanted
3. Evaluate vocabulary and finish process
Structure of export Api:
1. Batch prepare of vocabulary
2. Export depending on format
-- ADD A NEW SCHEME --
------------------
You can add a new csv scheme either by include it in module, either as plug-in.
To include it in module, you need:
- a define without space,
- features items in _taxonomy_csv_values (.api.inc file),
- items in taxonomy_csv.js and taxonomy_csv.css (Drupal 6 only),
- a description in appropriate forms (.module, .admin.inc files),
- an advanced help (.help.html),
- a case in _taxonomy_csv_check_items(),
- a case in taxonomy_csv_import_line_items(),
- eventually specific options.
- a case in taxonomy_csv_export_line_items() if possible.
To include it as plug-in, you need to add a formatted inc file in "formats" sub
directory. File must be named with format NAME followed by '.format.inc'. This
file should contain some functions. Only the first is required, others are
needed to process an import or an export or when there are specific fields.
- taxonomy_csv_format_NAME describing format and available functions.
- 'format' : format name
- 'name' : name displayed
- 'needed_module' : required module to use if there are non standard fields
- 'import_format' : name displayed if available, else nothing
- 'export_format' : name displayed if available, else nothing
- 'import_allowed': options to use for existing terms when importing terms
- 'import_previous': TRUE or FALSE if format uses specific previous items
- 'specific_fields': TRUE or FALSE if format use specific fields
- 'description' : short description of format
- 'description_format': oredred list of fields
- 'description_example': a working example of the format
- 'description_long': long description of format
- 'import_options_help': explanation of options for existing terms
- _vocabulary_check_NAME to check if vocabulary has specific fields.
- _import_vocabulary_prepare_NAME if special fields are needed.
- _line_import_check_NAME to check quality of line items.
- _line_import_NAME to process true import.
- _term_import_NAME if format has specific fields.
- _line_export_NAME if format provides export schema.
- _term_export_NAME if format provides export schema.
- _term_load_NAME to get full term.
- _term_load_NAME to get full term.
- _term_get_full_NAME to complete a term with specific fields.
- _term_get_NAME to get only specific fields.
Furthermore, you need:
- items in taxonomy_csv.js and taxonomy_csv.css (Drupal 6 only).
- items in main help taxonomy_csv.help.html.
- translations.
See 'geotaxonomy' and 'taxonomy_manager' examples.

View File

@@ -0,0 +1,140 @@
TAXONOMY CSV IMPORT/EXPORT
==========================
== T O D O ==
=========
(no particular order)
o = TODO
- = Skipped/postponed
x = DONE
R = Removed
nnn mmm = Introduced in release nnn for Drupal 6 and mmm for Drupal 7
--- --- = Removed or skipped
=====================
User interface
x 622 740 display result of import
x 622 740 display imported terms
x 622 740 notice warnings and errors of import file
x 622 740 choose level of message
x 650 750 cumulate important messages with use Drupal's log
x 622 740 auto create vocabulary
x 630 740 test on an existing vocabulary (duplicating it)
x 630 740 dynamic options and examples (existing items matching source content)
x 630 740 import by a text area. See taxonomy_batch_operations
o text area to modify line when warning
x 620 740 add link to use module after installation
x 630 740 remind previous options values
x 630 740 do a uninstall hook if remind previous options values is done
x 622 740 warn remove when there is only one column and update is chosen
- --- --- reset button (useless)
x 630 740 recommended default values button
o inform about changes during import (ex: relations set then unset)
o two steps import (validate and display warnings then process)
o option to choose one or two steps
o five step wizard for non javascript users
x 630 740 notice exceed file size
o option to import in original vocabulary after import in a duplicate
o 645 745 no warning on last empty lines
x 643 743 ahah ui for specific parameters
x 643 743 disable utf8 option if textarea import
x 640 740 add a csv enclosure option (so >= php 4.3)
- --- --- use Drupal themes
- --- --- add a menu link to taxonomy csv in list terms
x 643 743 use drupal_set_message 'warning' and not only 'status' and 'error'.
- --- --- batch process without Javascript
Functions
o import of structured alphabetic terms list (thesaurus)
o import of iso 2788 as in taxonomy_xml (but KISS by line approach)
x 623 740 option to import sub-relations and not only relations to first term
x 623 740 option to make relations to terms of all vocabularies.
x 630 740 import of ancestors structure and not children (change name)
x 644 744 import of first level parents
x 644 744 import of first level children
x 622 740 use of parents of previous line for children without ancestors
("one term by line structure import")
x 645 745 parent merge (polyhierarchy)
x 620 740 distinction between description and synonyms
o multiple files import
- --- --- option sensitive or not to uppercase/lowercase (neither in D6 nor D7)
o add notice when sensitive duplicate items are found in line check
o integrate or reference to Taxonomy manager module
o remove csv terms import choice
o test before import (autocreate a vocabulary) (see UI)
o remove ignore existing terms choice (see dynamic options above)
- --- --- import relateds in other vocabulary before current one (not in D6)
o autodetect good source content and vocabulary with a command line key
o with autodetect, import multiple type with one file
o convert and export vocabulary to xml (especially to compare with xml)
R 643 743 multistep import: autodivide big taxonomies to avoid memory error
o 759 import by term identifiant (tid)
x 643 743 import from url (#529480)
o allow direct duplicates with polyhierarchy
o option to automatically disable and re-enable non compatible modules
x 647 747 full term definitions and links export in order to import in Drupal 7
o quick light basic import with no check release 7.x-1.y as 4/5/6.x-1.y
o 758 internationalization
Code / security
o remove query and use only Drupal core ones and php filters (useful?)
o create dirty sql load and save functions for quick find and import
x --- 740 see if there are Drupal functions to import only synonyms etc.
x 622 740 manage and inform on input error
o factorize code and use more php functions (import type callback...)
x 622 740 redo a cache
x 750 remove cache
x 622 740 clean the input line at the beginning of process and never after
o use taxonomy_parser?
x 622 740 change the type of vocabulary (flat, monohierarchy, polyhierarchy)
x --- --- add hook_perm
o add hook_info
x 630 740 replace _taxonomy_csv_parse_size by a Drupal function
o no order to import files
x 650 750 use Drupal management of errors (mysql log)
x 622 740 change function name to internal, except some
o vocabulary full fine check (after and before)
o vocabulary relations integrety check and attribute
o --- use management of test in Drupal 7.
x 643 743 time out control
x 645 745 fix: keep multiple parents when children import
x 622 740 fix: if ignore existing items, search in imported terms cache before
x 643 743 fix: result statistic import (sometime new terms number is wrong)
- --- --- remove useless define
- --- --- use a global messages variable or sandbox to avoid multiple copy
o relations with other vocabularies (unsupported in Drupal 6 and 7)
o extract all helps of forms to help.html in order to remove duplicates
x 640 740 simplify batch set (all $options aren't needed)
- --- --- use $form_state['storage'] for batch operations? Useless.
x 643 743 remove base64 temporary and work only with utf8
- --- --- use a smarter regexp delimiter than fgetcsv or explode
x 642 742 fix: when a file is set, avoid UI bug when first option changes
x 645 745 change Warning empty line by a simple info except if problem
o 653 753 modularize code to simplify addition of new schemes
o modularize code to simplify integration of Taxonomy Builder API
o create a UI for continuous synchro with a database
x 643 743 create API
R --- --- fix: make multistep import compatible with one term by line import
R --- --- allow cancel multistep when import in an existing vocabulary
R --- --- fix: allow only users with taxonomy rights (useless: roles)
R --- --- fix: display main form when error occurs in multistep process
x 643 743 direct read of lines without prepare batch for file import
o --- fix: created unchanged terms are counted and merged with created ones
Documentation
o make it simpler
o more examples
o merge advanced help and dynamic examples (cf. modularize code)
x 620 740 image view and presentation text for project page
x 620 740 translations
Drupal version / Taxonomy csv version
Drupal 7 : 7.x-4.0 => current
Drupal 6 : 6.x-1.3 => current
Drupal 5 : 5.x-1.0 => 5x-1.5
Drupal 4 : 4.7-1.0 => 4.7-1.4

View File

@@ -0,0 +1,458 @@
<?php
/**
* @file
* Create taxonomy csv export form and validate user input.
*/
/**
* Invoke associated files.
*/
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once($module_dir . '/taxonomy_csv.api.inc');
/**
* Generates the taxonomy CSV export form.
*
* Form contain three fieldsets:
* - 1. Which vocabulary to export ?
* - 2. How to export (format of csv file) ?
* - 1. Csv delimiter
* - 2. Csv enclosure
* - 3. Csv end of line
* - 3. Order and specific options
*
* @ingroup forms
* @see taxonomy_csv_export_form_validate()
* @see taxonomy_csv_export_form_submit()
* @see taxonomy_csv_export_form_default_values_validate()
* @see taxonomy_csv_export_form_default_values_submit()
*/
function taxonomy_csv_export_form($form, &$form_state) {
// Remember previous values to use in particular when reloading form.
// If not reloading form, use saved values if exist, else recommended ones.
$list_recommended_values = _taxonomy_csv_values('export_default_ui');
$list_previous_values = array();
foreach ($list_recommended_values as $key => $value) {
$list_previous_values[$key] = isset($form_state['values'][$key]) ?
$form_state['values'][$key] :
variable_get("taxonomy_csv_{$key}", $value);
}
$list_export_format = _taxonomy_csv_values('export_format');
$list_export_delimiter = array(
'comma' => t('« , » (Comma)'),
'semicolon' => t('« ; » (Semicolon)'),
'tabulation' => t('« » (Tabulation)'),
'pipe' => t('« | » (Pipe)'),
'space' => t('« » (Space)'),
'currency_sign' => t('« ¤ » (Currency sign)'),
'custom_delimiter' => t('Custom delimiter'),
);
$list_export_enclosure = array(
'none' => t('None'),
'quotation' => t('« " » (Quotation mark)'),
'quote' => t("« ' » (Quote)"),
'custom_enclosure' => t('Custom enclosure'),
);
$list_export_line_ending = array(
'Unix' => t('Unix / Linux'),
'Mac' => t('Apple Mac'),
'MS-DOS' => t('Microsoft DOS'),
);
$list_export_order = array(
'name' => t('Alphabetic order'),
'weight' => t('Weight'),
'tid' => t('Internal order (tid)'),
);
// Build form.
$form = array();
$list_vocabularies = taxonomy_vocabulary_get_names();
if (count($list_vocabularies) == 0) {
$form['info'] = array(
'#type' => 'item',
'#markup' => t("As there isn't any vocabulary, nothing can be exported..."),
);
return $form;
}
// Else there are vocabularies.
$form['tab'] = array(
'#type' => 'vertical_tabs',
'#default_tab' => 'content',
);
$form['tab']['content'] = array(
'#type' => 'fieldset',
'#title' => t('1. What do you want to export?'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['tab']['content']['export_format'] = array(
'#type' => 'radios',
'#title' => 'CSV format',
'#options' => $list_export_format,
'#default_value' => $list_previous_values['export_format'],
);
$form['tab']['content']['info'] = array(
'#type' => 'item',
'#description' => t('See <a href="!more_help_link">advanced help</a> for informations about formats.', array('!more_help_link' => url('admin/help/taxonomy_csv'))) . '<br />'
. t('In all cases, you will be notified if a duplicate is found.'),
);
$form['tab']['content']['export_vocabulary_id'] = array(
'#type' => 'select',
'#title' => t('Vocabularies to export'),
'#options' => array(
0 => t('All vocabularies'),
),
'#multiple' => TRUE,
'#default_value' => $list_previous_values['export_vocabulary_id'],
'#description' => t('The vocabularies you want to export.'),
'#size' => min(12, count($list_vocabularies)) + 1,
);
foreach ($list_vocabularies as $vocabulary) {
$form['tab']['content']['export_vocabulary_id']['#options'][$vocabulary->vid] = $vocabulary->name;
}
$form['tab']['csv_format'] = array(
'#type' => 'fieldset',
'#title' => t('2. How do you want to format your CSV file?'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['tab']['csv_format']['export_delimiter'] = array(
'#type' => 'select',
'#title' => t('CSV value delimiter'),
'#options' => $list_export_delimiter,
'#default_value' => $list_previous_values['export_delimiter'],
'#description' => t('Choose the delimiter to use in the CSV file.'),
);
$form['tab']['csv_format']['export_delimiter_custom'] = array(
'#type' => 'textfield',
'#title' => 'Custom delimiter',
'#default_value' => $list_previous_values['export_delimiter_custom'],
'#size' => 2,
'#maxlength' => 1,
'#description' => t('Specify your custom delimiter.'),
'#states' => array(
'visible' => array(
':input[name=export_delimiter]' => array('value' => 'custom_delimiter'),
),
),
);
$form['tab']['csv_format']['export_enclosure'] = array(
'#type' => 'select',
'#title' => t('CSV value enclosure'),
'#options' => $list_export_enclosure,
'#default_value' => $list_previous_values['export_enclosure'],
'#description' => t('Choose the enclosure used in the CSV file you want to export. Warning: enclosure should not be used in term definitions, specially in descriptions. Furthermore, an enclosure is needed if a field contains a line ending character. Export process will stop in case of problem.'),
);
$form['tab']['csv_format']['export_enclosure_custom'] = array(
'#type' => 'textfield',
'#title' => 'Custom enclosure',
'#default_value' => $list_previous_values['export_enclosure_custom'],
'#size' => 2,
'#maxlength' => 1,
'#description' => t('Specify your custom enclosure.'),
'#states' => array(
'visible' => array(
':input[name=export_enclosure]' => array('value' => 'custom_enclosure'),
),
),
);
$form['tab']['csv_format']['export_line_ending'] = array(
'#type' => 'select',
'#title' => t('Line ending'),
'#options' => $list_export_line_ending,
'#default_value' => $list_previous_values['export_line_ending'],
'#description' => t('Choose the end of line to use.'),
);
$form['tab']['advanced_options'] = array(
'#type' => 'fieldset',
'#title' => t('3. Advanced and specific options'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['tab']['advanced_options']['export_order'] = array(
'#type' => 'select',
'#title' => t('Terms order'),
'#options' => $list_export_order,
'#default_value' => $list_previous_values['export_order'],
'#description' => t('Choose order of exported terms.'),
'#states' => array(
'invisible' => array(
':input[name=export_format]' => array('value' => TAXONOMY_CSV_FORMAT_TREE),
),
),
);
$form['tab']['advanced_options']['info'] = array(
'#type' => 'item',
'#description' => t('Specific options are shown only if suitable.'),
);
$form['tab']['advanced_options']['result_duplicates'] = array(
'#type' => 'checkbox',
'#title' => t('Show duplicate terms after export'),
'#default_value' => $list_previous_values['result_duplicates'],
'#description' => '',
);
$form['export_submit'] = array(
'#type' => 'submit',
'#value' => t('Export'),
);
$form['export_default_values'] = array(
'#type' => 'submit',
'#value' => t('Reset to defaults'),
'#validate' => array('taxonomy_csv_export_form_default_values_validate'),
'#submit' => array('taxonomy_csv_export_form_default_values_submit'),
);
return $form;
}
/**
* Handles CSV export form validation.
*
* @see taxonomy_csv_export_form()
*/
function taxonomy_csv_export_form_validate($form, &$form_state) {
// Invoke taxonomy_csv api (defines and functions).
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once($module_dir . '/export/taxonomy_csv.export.api.inc');
$options = &$form_state['values'];
// 1. Presave a file in order to check it.
// Define vocabulary id (use simple api name of it).
$options['vocabulary_id'] = $options['export_vocabulary_id'];
$messages = _taxonomy_csv_export_output_presave($options);
// 2, Simplify values to be compatible with api checks.
// Define true delimiter.
$delimiter = array(
'comma' => ',',
'semicolon' => ';',
'tabulation' => "\t",
'pipe' => '|',
'space' => ' ',
'currency_sign' => '¤',
'custom_delimiter' => $options['export_delimiter_custom'],
);
$options['delimiter'] = $delimiter[$options['export_delimiter']];
// Define true enclosure.
$enclosure = array(
'none' => '',
'quotation' => '"',
'quote' => "'",
'custom_enclosure' => $options['export_enclosure_custom'],
);
$options['enclosure'] = $enclosure[$options['export_enclosure']];
// Define true line ending and order.
$options['line_ending'] = $options['export_line_ending'];
$options['order'] = $options['export_order'];
// 3, Make api checks and eventually update options by reference.
$messages = array_merge($messages, _taxonomy_csv_export_check_options($options));
// Use form set error for api errors.
foreach (array(
'vocabulary_id' => 'export_vocabulary_id',
'delimiter' => 'export_delimiter',
'enclosure' => 'export_enclosure',
'line_ending' => 'export_line_ending',
'order' => 'export_order',
) as $key => $value) {
if (isset($message[$key])) {
$message[$value] = $message[$key];
unset($message[$key]);
}
}
// 4. Make non api checks.
if (($options['export_delimiter'] == 'custom_delimiter')
&& (empty($options['export_delimiter_custom']))) {
$messages['export_delimiter_custom'] = t('You choose to use a custom delimiter, but your delimiter is empty.');
}
if (($options['export_enclosure'] == 'custom_enclosure')
&& (empty($options['export_enclosure_custom']))) {
$messages['export_enclosure_custom'] = t('You choose to use a custom enclosure, but your enclosure is empty.');
}
if (($options['export_delimiter'] == 'custom_delimiter')
&& (drupal_strlen($options['export_delimiter_custom']) > 1)) {
$messages['export_delimiter_custom'] = t('Delimiter should have only one character.');
}
if (($options['export_enclosure'] == 'custom_enclosure')
&& (drupal_strlen($options['export_enclosure_custom']) > 1)) {
$messages['export_enclosure_custom'] = t('Enclosure should have only zero or one character.');
}
// 5. Finish validatation of form.
foreach ($messages as $item => $message) {
form_set_error(check_plain($item), filter_xss($message));
}
}
/**
* Validate options of exported vocabulary.
*
* @param $options
* An associative array of options.
*
* @return
* Array of messages errors if any.
* By reference options are cleaned and completed.
*/
function _taxonomy_csv_export_check_options(&$options) {
$messages = array();
if ($options['export_format'] == TAXONOMY_CSV_FORMAT_TRANSLATE && !module_exists('i18n_taxonomy')) {
$messages['export_format'] = t('You cannot use Translations if i18n_taxonomy is not enabled.');
}
$list_vocabularies = taxonomy_get_vocabularies();
if (!$list_vocabularies) {
$messages['vocabulary_id'] = t('No vocabulary to export.');
}
elseif ($options['vocabulary_id']) {
// Replace machine name with vocabulary id.
if (!(is_numeric($options['vocabulary_id']) || is_array($options['vocabulary_id']))) {
$vocabulary = taxonomy_vocabulary_machine_name_load($options['vocabulary_id']);
$options['vocabulary_id'] = array($vocabulary->vid);
}
// Replace vocabulary_id by an array if only one item is selected.
elseif (!is_array($options['vocabulary_id'])) {
$options['vocabulary_id'] = array($options['vocabulary_id']);
}
if ((count($options['vocabulary_id']) > 1) && in_array(0, $options['vocabulary_id'])) {
$messages['vocabulary_id'] = t('You choose to export all vocabularies, but you select some individual vocabularies too.');
}
foreach ($options['vocabulary_id'] as $item) {
if (($item != 0) && !isset($list_vocabularies[$item])) {
$messages['vocabulary_id'] = t("You choose to export a vocabulary, but it doesn't exist.");
}
}
}
// Delimiter and enclosure greater than one character are forbidden.
if (drupal_strlen($options['delimiter']) != 1) {
$messages['delimiter'] = t('Delimiter should be a one character string.');
}
if (drupal_strlen($options['enclosure']) > 1) {
$messages['enclosure'] = t('Enclosure lenght cannot be greater than one character.');
}
if ($options['delimiter'] == $options['enclosure']) {
$messages['delimiter'] = t('Delimiter and enclosure cannot be same character.');
}
if (!in_array($options['line_ending'], array(
'Unix',
'Mac',
'MS-DOS',
))) {
$messages['line_ending'] = t('Line ending should be "Unix", "Mac" or "MS-DOS".');
}
if (!in_array($options['order'], array(
'name',
'tid',
'weight',
))) {
$messages['order'] = t('Order should be "name", "tid" or "weight".');
}
// Calculates number of terms to be exported.
$options['total_terms'] = taxonomy_csv_vocabulary_count_terms($options['vocabulary_id']);
if (!$options['total_terms']) {
$messages['vocabulary_id'] = t('Vocabulary has no term to export. Export finished.');
}
return $messages;
}
/**
* Handles CSV export form submission and launch batch set.
*
* @see taxonomy_csv_export_form()
*/
function taxonomy_csv_export_form_submit($form, &$form_state) {
// Remember last preferences and prepare only options to be sent to api.
foreach (array(
'export_format',
'export_vocabulary_id',
'export_delimiter',
'export_delimiter_custom',
'export_enclosure',
'export_enclosure_custom',
'export_line_ending',
'export_order',
'result_duplicates',
) as $option) {
variable_set('taxonomy_csv_' . $option, $form_state['values'][$option]);
$options[$option] = $form_state['values'][$option];
}
// Finish to prepare $options. Unset useless options for api.
unset($options['export_vocabulary_id']);
unset($options['export_delimiter']);
unset($options['export_delimiter_custom']);
unset($options['export_enclosure']);
unset($options['export_enclosure_custom']);
unset($options['export_line_ending']);
unset($options['export_order']);
$options['delimiter'] = $form_state['values']['delimiter'];
$options['enclosure'] = $form_state['values']['enclosure'];
$options['line_ending'] = $form_state['values']['line_ending'];
$options['order'] = $form_state['values']['order'];
$options['file'] = $form_state['values']['file'];
$options['vocabulary_id'] = $form_state['values']['vocabulary_id'];
$options['check_options'] = FALSE; // Already done.
$options['result_display'] = TRUE;
// Prepares process batch (will be automatically processed when returns).
taxonomy_csv_vocabulary_export($options);
}
/**
* Restore recommended default values in the export form. Empty validate hook.
*/
function taxonomy_csv_export_form_default_values_validate($form, &$form_state) {
}
/**
* Restore recommended default values in the export form.
*/
function taxonomy_csv_export_form_default_values_submit($form, &$form_state) {
foreach (_taxonomy_csv_values('export_default_ui') as $option => $value) {
variable_set("taxonomy_csv_$option", $value);
}
unset($form_state['values']);
unset($form_state['storage']);
drupal_set_message(t('Export options have been reset to default.'));
}

View File

@@ -0,0 +1,705 @@
<?php
/**
* @file
* Validate export options and manage export process.
*/
/**
* Invoke associated include file.
*/
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once($module_dir . '/taxonomy_csv.api.inc');
require_once($module_dir . '/taxonomy_csv.term.api.inc');
require_once($module_dir . '/taxonomy_csv.vocabulary.api.inc');
/**
* Process the export of a vocabulary.
*
* If not used in a form, don't forget to use batch_process().
*
* @param $options
* An associative array of options:
* - export_format : see format of the csv line (see taxonomy.api.inc)
* - vocabulary_id : vid or machine_name (or array of them) of the vocabulary
* to export (default: 0, which means all)
* - delimiter : one character csv delimiter (default: ',')
* - enclosure : zero or one character csv enclosure (default: '"')
* - line_ending : 'Unix' (default), 'Mac' or 'MS-DOS'
* - order : order of terms: 'name' (default), 'tid' or 'weight'
* // Level of result process infos:
* - check_options : boolean. check or not (default) this array of options
* - result_display: boolean. display or not (default). Only used with UI
* - result_duplicates: boolean. display or not (default) duplicate terms
* All options have default values.
* Warning: default values are different with UI.
*
* @return
* Array of errors or file object to download (need to execute batch process;
* result is logged in watchdog).
*/
function taxonomy_csv_export($options) {
// Complete $options with default values if needed.
// Default api and UI options are different.
$options += _taxonomy_csv_values('export_default_api');
// Presave a file in order to check access to temporary folder.
$messages = _taxonomy_csv_export_output_presave($options);
if (count($messages)) {
return $messages;
}
// Process export.
$messages = taxonomy_csv_vocabulary_export($options);
// Return errors if any.
if (count($messages)) {
return $messages;
}
// Else return file infos.
else {
return $options['file'];
}
}
/**
* Presave output.
*
* Check if there is write access and prepare file.
*
* @param $options
* Array. Same as taxonomy_csv_export.
*
* @return
* Array of messages errors if any.
* By reference options are cleaned and completed.
*/
function _taxonomy_csv_export_output_presave(&$options) {
$messages = array();
// Check if there is write access and prepare file.
$filename = file_unmanaged_save_data(
'',
'public://' . 'taxocsv.csv',
'FILE_EXISTS_RENAME');
if (!$filename) {
$messages['file'] = t('Check access rights to temp directory: export needs permission to write and to read in it. Export failed.');
}
else {
$options['file'] = (object) array(
'filename' => basename($filename),
'filepath' => drupal_realpath($filename),
'filesize' => filesize($filename),
);
}
return $messages;
}
/**
* Prepare the export of a vocabulary.
*
* @note
* If not used in a form, don't forget to use batch_process().
*
* @param $options
* Array. Same as taxonomy_csv_export.
*
* @return
* Array of errors or nothing (batch process to execute).
*/
function taxonomy_csv_vocabulary_export($options) {
// Check options and return array of messages in case of errors.
if ($options['check_options']) {
// Invoke export admin file.
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/export/taxonomy_csv.export.admin.inc");
$result = _taxonomy_csv_export_check_options($options);
if (count($result)) {
return $result;
}
}
// Complete $options with some csv variables.
$options['separator'] = $options['enclosure'] . $options['delimiter'] . $options['enclosure'];
$line_ending = array(
'Unix' => "\n",
'Mac' => "\r",
'MS-DOS' => "\r\n",
);
$options['end_of_line'] = $line_ending[$options['line_ending']];
// Calculates number of terms to be exported.
$options['total_terms'] = taxonomy_csv_vocabulary_count_terms($options['vocabulary_id']);
// Get infos about fields of vocabulary.
$options['vocabulary'] = array();
foreach ($options['vocabulary_id'] as $vocabulary_id) {
$options['vocabulary'][$vocabulary_id] = taxonomy_vocabulary_load($vocabulary_id);
$vocabulary = &$options['vocabulary'][$vocabulary_id];
$vocabulary->instances = field_info_instances('taxonomy_term', $vocabulary->machine_name);
// Prepare list of fields to be exported.
$vocabulary->fields = array();
// Not included, because referenced terms are exported by name.
// $vocabulary->fields['tid'] = array('cardinality' => 1);
$vocabulary->fields['name'] = array('cardinality' => 1);
// Not included, because there is already 'vocabulary_machine_name'.
// $vocabulary->fields['vid'] = array('cardinality' => 1);
$vocabulary->fields['vocabulary_machine_name'] = array('cardinality' => 1);
$vocabulary->fields['description'] = array('cardinality' => 1);
$vocabulary->fields['format'] = array('cardinality' => 1);
$vocabulary->fields['weight'] = array('cardinality' => 1);
$vocabulary->fields['parent'] = array('cardinality' => -1);
if (module_exists('i18n_taxonomy')) {
switch ($vocabulary->i18n_mode) {
case I18N_MODE_LANGUAGE:
case I18N_MODE_LOCALIZE:
$vocabulary->fields['language'] = array('cardinality' => 1);
break;
case I18N_MODE_TRANSLATE:
case I18N_MODE_MULTIPLE:
$vocabulary->fields['language'] = array('cardinality' => 1);
$vocabulary->fields['i18n_tsid'] = array('cardinality' => 1);
break;
}
}
// @todo
// $vocabulary->fields['guid'] = array('cardinality' => 1);
// Prepare list of unlimited fields to be exported.
$vocabulary->fields_unlimited = array();
$vocabulary->fields_unlimited['parent'] = 1;
if (is_array($vocabulary->instances)) {
foreach ($vocabulary->instances as $field_name => $value) {
$vocabulary->fields[$field_name] = field_info_field($field_name);
// Get the list of fields with an unlimited number of values to avoid
// the loop of check (used with custom fields export).
// The minimum is set to one to avoid zero value.
if ($vocabulary->fields[$field_name]['cardinality'] == -1) {
$vocabulary->fields_unlimited[$field_name] = 1;
}
}
}
}
// Prepare export batch.
$batch = array(
'title' => t('Exporting !total_terms terms to CSV file...', array('!total_terms' => $options['total_terms'])),
'init_message' => t('Starting downloading of datas...') . '<br />'
. t('Wait some seconds for pre-processing...'),
'progress_message' => '',
'error_message' => t('An error occurred during the export.'),
'finished' => '_taxonomy_csv_vocabulary_export_finished',
'file' => drupal_get_path('module', 'taxonomy_csv') . '/export/taxonomy_csv.export.api.inc',
'progressive' => TRUE,
'operations' => array(
0 => array('_taxonomy_csv_vocabulary_export_process', array($options)),
),
);
batch_set($batch);
}
/**
* Batch process of vocabulary export.
*
* @todo Check if direct query and only tid save is really less memory
* intensive (with core taxonomy cache or not).
* @todo Don't remember terms, but load them one by one in order to decrease
* memory usage.
*
* @param $options
* Array of batch options.
* @param &$context
* Batch context to keep results and messages.
*
* @return
* NULL because use of &$context.
*/
function _taxonomy_csv_vocabulary_export_process($options, &$context) {
// First callback.
if (empty($context['sandbox'])) {
// Remember options as batch_set can't use form_storage.
// It allows too that first line in result is numbered 1 and not 0.
$context['results'][0] = $options;
// Initialize some variables.
$context['results'][0]['current_term'] = 0;
$context['results'][0]['current_name'] = '';
$context['results'][0]['worst_tid'] = 0;
$context['results'][0]['worst_term'] = 0;
$context['results'][0]['worst_name'] = '';
$context['results'][0]['worst_message'] = 799;
// No pointer because new line is appended to file.
$context['results'][0]['handle'] = fopen($options['file']->filepath, 'a+');
// Fetch terms to be exported and order them (by tree or by specific order).
// Prepare a hierarchical tree of terms.
if (in_array($options['export_format'], array(
TAXONOMY_CSV_FORMAT_STRUCTURE,
TAXONOMY_CSV_FORMAT_TREE,
TAXONOMY_CSV_FORMAT_POLYHIERARCHY,
))) {
$context['sandbox']['terms'] = array();
// Export only selected vocabularies.
if ($options['vocabulary_id'] && ($options['vocabulary_id'] != array(0))) {
$vocabularies = array();
foreach ($options['vocabulary_id'] as $vocabulary) {
$vocabularies[] = taxonomy_vocabulary_load($vocabulary);
}
}
// Export all vocabularies.
else {
$vocabularies = taxonomy_vocabulary_get_names();
}
foreach ($vocabularies as $vocabulary) {
$context['sandbox']['terms'] = array_merge($context['sandbox']['terms'], taxonomy_get_tree($vocabulary->vid));
}
}
// Prepare a list of normal terms.
// @todo Prepare a simple list of term names or definitions without links in
// order to reduce memory usage for some formats.
// @todo Use a one term approach to reduce memory usage (anyway, all terms
// will be load)?
else {
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'taxonomy_term');
if ($options['vocabulary_id'] && ($options['vocabulary_id'] != array(0))) {
$query->propertyCondition('vid', $options['vocabulary_id']);
}
$query->propertyOrderBy($options['order'], 'ASC');
$terms = $query->execute();
if ($terms['taxonomy_term'] === NULL) {
$terms['taxonomy_term'] = array();
}
$context['sandbox']['terms'] = taxonomy_term_load_multiple(array_keys($terms['taxonomy_term']));
}
// Get max number of values for fields with a undefined number of values.
if ($options['export_format'] == TAXONOMY_CSV_FORMAT_FIELDS) {
// Currently, manage only undefined language.
$language = 'und';
foreach ($context['sandbox']['terms'] as $term) {
foreach (array_keys($options['vocabulary'][$term->vid]->fields_unlimited) as $field_name) {
if (isset($term->{$field_name})) {
// Remember the number of items if this term field has got more
// items than previous one.
if ($field_name == 'parent'
&& (count($term->parent) > $options['vocabulary'][$term->vid]->fields_unlimited['parent'])
) {
$options['vocabulary'][$term->vid]->fields_unlimited['parent'] = count($term->parent);
}
elseif (count($term->{$field_name}[$language]) > $options['vocabulary'][$term->vid]->fields_unlimited[$field_name]) {
$options['vocabulary'][$term->vid]->fields_unlimited[$field_name] = count($term->{$field_name}[$language]);
}
}
}
}
// Keep updated the list of vocabulary with undefined fields.
$context['results'][0]['vocabulary'] = $options['vocabulary'];
$context['sandbox']['vocabulary'] = $options['vocabulary'];
}
// Display a warning if terms count in taxonomy_term_data table is different
// of terms counted with Drupal taxonomy_term_load_multiple() function.
// See http://drupal.org/node/1359260.
if ($options['total_terms'] != count($context['sandbox']['terms'])) {
// Memorize the first wrong term.
$first_wrong_term = array_shift(array_diff_key($terms['taxonomy_term'], taxonomy_term_load_multiple(array_keys($terms['taxonomy_term']))));
$context['results'][0]['worst_tid'] = $first_wrong_term->tid;
$context['results'][0]['worst_term'] = '';
$context['results'][0]['worst_name'] = 'tid:' . ' ' . $first_wrong_term->tid;
$context['results'][0]['worst_message'] = 408; // Warning not a good term.
}
// Clean memory.
if (isset($terms)) {
unset($terms);
}
// Duplicate names will be searched after export process.
$context['results'][0]['duplicate_terms'] = array();
}
elseif (!is_resource($context['results'][0]['handle'])) {
// Reopen file in case of memory out.
$context['results'][0]['handle'] = fopen($options['file']->filepath, 'a+');
}
// Get list of unlimited multivalued fields.
// @todo Currently, these fields are not saved in batch.
if ($options['export_format'] == TAXONOMY_CSV_FORMAT_FIELDS) {
$options['vocabulary'] = $context['sandbox']['vocabulary'];
}
// To simplify use of variables.
$worst_tid = &$context['results'][0]['worst_tid'];
$worst_term = &$context['results'][0]['worst_term'];
$worst_name = &$context['results'][0]['worst_name'];
$worst_message = &$context['results'][0]['worst_message'];
$handle = &$context['results'][0]['handle'];
$duplicate_terms = &$context['results'][0]['duplicate_terms'];
$term_number = &$context['results'][0]['current_term'];
$current_name = &$context['results'][0]['current_name'];
$terms_list = &$context['sandbox']['terms'];
$terms_list_ids = array_keys($terms_list);
// Process one term until end of vocabulary.
if ($term_number < count($context['sandbox']['terms'])) {
$term = $terms_list[$terms_list_ids[$term_number]];
++$term_number;
// Remember current name/tid in case of error.
$current_name = $term->name;
$current_tid = $term->tid;
// Process export of current term.
$result = taxonomy_csv_term_export($term, $options, $terms_list, $duplicate_terms);
// If result is empty, there is nothing to export (with Translation export,
// terms can be skipped and exported with another term).
$result['msg'] = taxonomy_csv_line_export($result['line'], $options, $handle, $result['msg']);
// Remember worst message of exported terms.
$worst_message_new = _taxonomy_csv_worst_message($result['msg']);
if ($worst_message > $worst_message_new) {
$worst_tid = $current_tid;
$worst_term = $term_number;
$worst_name = $current_name;
$worst_message = $worst_message_new;
};
// Remember messages. Currently useless because there isn't any warning or
// notice message (only error). A result level can be added here if needed.
if (count($result['msg'])) {
$context['results'][$term_number] = $result['msg'];
}
// Inform about progress.
$context['message'] = t('Term !term_number of !total_terms processed: %term', array(
'!term_number' => $term_number,
'!total_terms' => $options['total_terms'],
'%term' => $current_name,
));
// Check worst message of exported terms and update progress.
if ($worst_message >= TAXONOMY_CSV_PROCESS_WARNING) {
// Count should be <= 0.99 to avoid batch stop before end (Drupal 7 bug).
$context['finished'] = floor($term_number / count($context['sandbox']['terms']) * 100) / 100;
}
else {
$context['finished'] = 1;
}
}
}
/**
* Callback for finished batch export and display result informations.
*/
function _taxonomy_csv_vocabulary_export_finished($success, $results, $operations) {
$options = &$results[0];
unset($results[0]);
// Close exported file.
if ($options['handle']) {
fclose($options['handle']);
}
// Invoke export stats file if user wants to display results.
if ($options['result_display']) {
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/export/taxonomy_csv.export.result.inc");
}
// Short summary information is different if batch succeeded or not.
if ($success) {
if (!empty($options['worst_tid'])) {
$worst_path = drupal_get_path_alias('taxonomy/term/' . $options['worst_tid']);
$options['worst_name'] = l($options['worst_name'], $worst_path);
}
$variables = array(
'!total_terms' => $options['total_terms'],
'!worst_count' => $options['worst_term'],
'!worst_name' => $options['worst_name'],
'!worst_msg' => $options['result_display'] ?
_taxonomy_csv_message_text($options['worst_message']) :
t('Message code') . ' = ' . $options['worst_message'],
);
$messages = array(
WATCHDOG_DEBUG => t('No error, warnings or notices have been reported during export process of !total_terms terms.', $variables),
WATCHDOG_INFO => t('No error, warnings or notices have been reported during export process of !total_terms terms.', $variables),
WATCHDOG_NOTICE => t('Notices have been reported during export process (bad formatted or empty terms). !total_terms terms processed. First notice occurred on term !worst_count (!worst_name) [!worst_msg].', $variables),
WATCHDOG_WARNING => t('Warnings have been reported during export process (bad formatted terms). !total_terms terms processed. First term skipped is term !worst_count (!worst_name) [!worst_msg].', $variables),
WATCHDOG_ERROR => t('Errors have been reported during export process. Process failed at term !worst_count (!worst_name) of a total of !total_terms [!worst_msg].', $variables),
);
$worst_level = intval($options['worst_message'] / 100);
$message = $messages[$worst_level];
}
else {
$message = t('Exportation failed. Export process was successful until the term !term_count (!term_name) of a total of !total_terms.', array(
'!term_count' => $options['current_term'],
'!term_name' => $options['current_name'],
'!total_terms' => $options['total_terms'],
)) . '<br />'
. t('This issue is related to export process and may be caused by a memory overrun of the database. If not, you can reinstall module from a fresh release or submit an issue on <a href="!link">Taxonomy CSV import/export module</a>.', array(
'!link' => url('http://drupal.org/project/issues/taxonomy_csv/'),
));
$worst_level = WATCHDOG_ERROR;
}
// Set result message in watchdog and eventually in user interface.
// Use of a $message variable is unrecommended, but simpler and working.
// See http://drupal.org/node/323101
watchdog('taxonomy_csv', $message, NULL, $worst_level);
if ($options['result_display']) {
_taxonomy_csv_export_result($options, $worst_level, $message, $results);
}
}
/**
* Export a line.
*
* @param $line
* Array to be exported to a line.
* @param $options
* An associative array of export options:
* - 'separator' : string separator (formatted delimiter and enclosure).
* - 'enclosure' : item enclosure.
* - 'end_of_line': end of line string.
* @param $handle
* Handle of the open file where to save line.
* @param $result_message
* (Optional) Array of messages.
*
* @return
* Result array of messages.
*/
function taxonomy_csv_line_export($line, $options, &$handle, $result = array()) {
if (!$line) {
return $result;
}
// Check if separator, enclosure or line ending exist in line.
$check_line = implode('', $line);
if ((strpos($check_line, $options['separator']) !== FALSE)
|| (($options['enclosure'] != '')
&& (strpos($check_line, $options['enclosure']) !== FALSE))
|| (($options['enclosure'] == '')
&& (strpos($check_line, $options['end_of_line']) !== FALSE))) {
$result[] = 313; // Error delimiter or enclosure.
}
// Skip a term with warning.
elseif (_taxonomy_csv_worst_message($result) < TAXONOMY_CSV_PROCESS_NOTICE) {
}
else {
// Save line to file.
$line = $options['enclosure'] . implode($options['separator'], $line) . $options['enclosure'] . $options['end_of_line'];
if (fwrite($handle, $line) === FALSE) {
$result[] = 312; // Unable to write to file.
}
}
return $result;
}
/**
* Export a term to a line matching the options.
*
* @param $term
* Full term object to export.
* @param $options
* An associative array of export options:
* - export_format : format of the csv line (see taxonomy.api.inc)
* @param $terms_list
* (Optional) Array of all term objects to export, used to avoid to repeat
* fetch of terms.
* @param $duplicate_terms
* (Optional) Array of duplicate terms names indexed by tid.
*
* @return
* Result array with:
* - 'line' => array of exported items,
* - 'msg' => array of messages arrays.
*/
function taxonomy_csv_term_export($term, $options, &$terms_list = array(), $duplicate_terms = array()) {
// Define default values.
$result = array(
'line' => array(),
'msg' => array(),
);
// Only count check because term and options are already checked.
if (count($term)) {
switch ($options['export_format']) {
case TAXONOMY_CSV_FORMAT_FLAT:
$result['line'][] = $term->name;
break;
case TAXONOMY_CSV_FORMAT_STRUCTURE:
case TAXONOMY_CSV_FORMAT_TREE:
$terms = taxonomy_csv_term_get_first_path($term, $terms_list);
foreach ($terms as $parent) {
$result['line'][] = $parent->name;
}
$result['line'][] = $term->name;
break;
case TAXONOMY_CSV_FORMAT_POLYHIERARCHY:
// @todo
// Warning : taxonomy_csv_term_get_first_path() returns only first path.
break;
case TAXONOMY_CSV_FORMAT_FIELDS:
// Currently, manage only undefined language.
$language = 'und';
// Use of field_get_items is slower.
foreach ($options['vocabulary'][$term->vid]->fields as $field_name => $field) {
$count = 0;
// Item is a Field.
if (isset($field['type'])) {
if (isset($term->{$field_name}[$language])) {
// For taxonomy term reference, use name instead of value.
if ($field['type'] == 'taxonomy_term_reference') {
foreach ($term->{$field_name}[$language] as &$item) {
$result['line'][] = isset($terms_list[$item['tid']]) ?
$terms_list[$item['tid']]->name :
taxonomy_term_load($item['tid'])->name;
}
}
// For long text, need to escape the value.
elseif ($field['type'] == 'text_long'
|| $field['type'] == 'text_with_summary'
) {
foreach ($term->{$field_name}[$language] as &$item) {
$result['line'][] = _taxonomy_csv_escape_line_break($item['value']);
}
}
else {
// Key is generally 'value' but it can be something else.
reset($field['columns']);
foreach ($term->{$field_name}[$language] as &$item) {
$result['line'][] = $item[key($field['columns'])];
}
}
$count = count($term->{$field_name}[$language]);
}
}
// Item is a database term field (name, tid, language...).
else {
// For taxonomy term parent, use name instead of value.
if ($field_name == 'parent') {
if (isset($term->parent)) {
foreach ($term->parent as $tid) {
$result['line'][] = isset($terms_list[$tid]) ?
$terms_list[$tid]->name :
taxonomy_term_load($tid)->name;
}
$count = count($term->parent);
}
}
elseif ($field_name == 'description') {
$result['line'][] = _taxonomy_csv_escape_line_break($term->description);
$count = 1;
}
else {
$result['line'][] = $term->{$field_name};
$count = 1;
}
}
// Add empty value the max number of values in the vocabulary times.
if (isset($options['vocabulary'][$term->vid]->fields_unlimited[$field_name])) {
if ($count < $options['vocabulary'][$term->vid]->fields_unlimited[$field_name]) {
$result['line'] = array_merge($result['line'], array_fill(0, $options['vocabulary'][$term->vid]->fields_unlimited[$field_name] - $count, ''));
}
}
elseif ($count < $field['cardinality']) {
$result['line'] = array_merge($result['line'], array_fill(0, $field['cardinality'] - $count, ''));
}
}
break;
case TAXONOMY_CSV_FORMAT_TRANSLATE:
if (!module_exists('i18n_taxonomy')) {
$result['msg'][] = 360; // Translation error.
break;
}
switch ($options['vocabulary'][$term->vid]->i18n_mode) {
case I18N_MODE_NONE:
case I18N_MODE_LANGUAGE:
$result['line'][] = $term->name;
break;
case I18N_MODE_LOCALIZE:
$result['line'][] = $term->name;
$languages = locale_language_list('name');
unset($languages[language_default('language')]);
foreach ($languages as $language => $value) {
$translation = i18n_string_translate(
array('taxonomy', 'term', $term->tid, 'name'),
$term->name,
array('langcode' => $language)
);
$result['line'][] = $translation ?
$translation :
'';
}
break;
case I18N_MODE_TRANSLATE:
case I18N_MODE_MULTIPLE:
$languages = array(
'und' => t('Language neutral'),
);
$languages += locale_language_list('name');
$languages = array_flip(array_keys($languages));
$result['line'] = array_fill(0, count($languages), '');
if ($term->i18n_tsid == 0) {
$result['line'][$languages[$term->language]] = $term->name;
}
else {
$translation_set = i18n_translation_set_load($term->i18n_tsid);
$translations = $translation_set->get_translations();
$language_min = $languages[$term->language];
foreach ($translations as $language => $translated_term) {
$result['line'][$languages[$language]] = $translated_term->name;
// Check if this term is already exported (when there is a term
// with a language before the current one).
if ($languages[$language] < $language_min) {
$language_min = $languages[$language];
}
}
// This term is already exported or will be exported with another
// term.
if ($language_min < $languages[$term->language]) {
$result['line'] = array();
}
}
break;
}
break;
default:
$result['msg'][] = 307; // Error unknown export format.
}
}
else {
$result['msg'][] = 385; // Error no term to process.
}
// Clean result.
$result['msg'] = array_unique($result['msg']);
sort($result['msg']);
return $result;
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* @file
* Show export result messages.
*/
/**
* Invoke associated include file.
*/
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once($module_dir . '/taxonomy_csv.result.inc');
/**
* Display result messages of export process.
*
* @param $options
* Array. Same as taxonomy_csv_export.
* @param $worst_level
* Integer. Worst watchdog level of the process.
* @param $summary
* Translated string. Short summary of informations on process.
* @param $results
* Array. Batch process results.
*
* @return
* Nothing.
*/
function _taxonomy_csv_export_result($options, $worst_level, $summary, $results) {
// Prepare info and warning messages.
$messages = array();
// Summary message.
$messages[] = array($worst_level => $summary);
// Prepare batch result message.
if ($options['total_terms'] == 0) {
$message = t('Notice: Chosen vocabulary has no term to export.');
$messages[] = array(WATCHDOG_NOTICE => $message);
}
// Only summary is displayed in case of error.
elseif ($worst_level >= WATCHDOG_WARNING) {
$list_format = _taxonomy_csv_values('export_format');
// General infos.
$message = t('!term_count / !total_terms terms of chosen vocabularies have been exported to file !file (!filesize KB) with the format "!format". Click on link to view it or right click to download it.', array(
'!term_count' => $options['current_term'],
'!total_terms' => $options['total_terms'],
'!file' => l($options['file']->filename, file_create_url('public://' . $options['file']->filename)),
'!filesize' => number_format(filesize($options['file']->filepath) / 1000, 1),
'!format' => $list_format[$options['export_format']],
));
// Main check of end process.
$watchdog_level = (($options['current_term'] == $options['total_terms']) && ($options['current_term'] > 0)) ?
WATCHDOG_INFO :
WATCHDOG_ERROR;
$messages[] = array($watchdog_level => $message);
// Infos on duplicate term names.
$options['duplicate_terms'] = array();
foreach ($options['vocabulary_id'] as $item) {
$options['duplicate_terms'] += taxonomy_csv_term_find_duplicate($item);
}
$duplicate_count = count($options['duplicate_terms']);
// If there are duplicate term names, display them if user chooses it.
if ($duplicate_count) {
$message = t('!count duplicate term names have been found in all vocabularies. Error can occur when you will import this list, except if you choose "fields and links" export format or import option "ignore existing items".', array('!count' => $duplicate_count));
if ($options['result_duplicates']) {
$message .= '<br />' . t('List of duplicated term names:')
. ' "' . implode('", "', array_keys(array_flip($options['duplicate_terms']))) . '".';
}
$watchdog_level = WATCHDOG_NOTICE;
}
else {
// No duplicate term names.
$message = t('No duplicate term name has been found.');
$watchdog_level = WATCHDOG_INFO;
}
$messages[] = array($watchdog_level => $message);
// Info on specific format.
foreach ($options['vocabulary'] as $vocabulary) {
switch ($options['export_format']) {
case TAXONOMY_CSV_FORMAT_TREE:
if ($vocabulary->hierarchy == 2) {
$message = t('Vocabulary "@vocabulary" is polyhierarchical. With the selected format (tree), only the first parent of each term was exported.', array(
'@vocabulary' => $vocabulary->name,
));
$messages[] = array(WATCHDOG_NOTICE => $message);
}
break;
case TAXONOMY_CSV_FORMAT_FIELDS:
$format = array();
foreach ($vocabulary->fields as $field_name => &$field) {
if (isset($vocabulary->fields_unlimited[$field_name])) {
$format = array_merge($format, array_fill(0, $vocabulary->fields_unlimited[$field_name], $field_name));
}
else {
$format = array_merge($format, array_fill(0, $field['cardinality'], $field_name));
}
}
$message = t('CSV format of vocabulary "@vocabulary" is:', array(
'@vocabulary' => $vocabulary->name,
));
$message .= '<br /><pre>' . implode(', ', $format) . '</pre>';
$messages[] = array(WATCHDOG_INFO => $message);
break;
case TAXONOMY_CSV_FORMAT_TRANSLATE:
$format = array();
switch ($vocabulary->i18n_mode) {
case I18N_MODE_NONE:
$format[] = 'und';
break;
case I18N_MODE_LANGUAGE:
$format[] = $vocabulary->language;
break;
case I18N_MODE_LOCALIZE:
$format = locale_language_list('name');
unset($format[language_default('language')]);
$format = array_flip($format);
array_unshift($format, language_default('language'));
break;
case I18N_MODE_TRANSLATE:
case I18N_MODE_MULTIPLE:
$format = locale_language_list('name');
$format = array_flip($format);
array_unshift($format, 'und');
break;
}
$message = t('CSV format of vocabulary "@vocabulary" is:', array(
'@vocabulary' => $vocabulary->name,
));
$message .= '<br /><pre>' . implode(', ', $format) . '</pre>';
$messages[] = array(WATCHDOG_INFO => $message);
break;
}
}
}
_taxonomy_csv_message_result($messages);
}

View File

@@ -0,0 +1,943 @@
<?php
/**
* @file
* Validate import options and manage import process.
*/
/**
* Invoke associated include file.
*/
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once($module_dir . '/taxonomy_csv.api.inc');
require_once($module_dir . '/import/taxonomy_csv.import.line.api.inc');
require_once($module_dir . '/taxonomy_csv.vocabulary.api.inc');
require_once($module_dir . '/taxonomy_csv.term.api.inc');
/**
* Process the import of an input.
*
* If not used in a form, don't forget to use batch_process().
*
* @param $options
* An associative array of options:
* - file : object file if file is already uploaded
* - text : csv text to import
* - url : url to distant or local csv file
* - path : path to local csv file
* - import_format : see _taxonomy_csv_values (default: 'flat')
* - fields_format : array. list of machinename fields (default: 0 => 'name')
* - translate_by : string. name (default) or tid
* - translate_languages: array. List of languages for i18n (default: empty)
* - keep_order : boolean. keep order of imported terms or not (default)
* - delimiter : 1 character csv delimiter (default: ',')
* - enclosure : 0 or 1 character csv enclosure (default: none or '"')
* - filter_format : string. description field format (default: 'plain_text')
* - filter_format_custom: string. custom fields format (default: 'none')
* - language : string. terms' default language (default: neutral 'und')
* - check_line : boolean. check or not (default) format of lines
* - check_utf8 : boolean. check or not (default) utf8 format
* - locale_custom : string. specific locale of imported file
* - vocabulary_target: 'autocreate' (default), 'duplicate' or 'existing'
* - vocabulary_id : vid or machine_name of the vocabulary to import into
* - i18n_mode : integer. internationalization mode of autocreated
* vocabulary (default: 0 (I18N_MODE_NONE))
* - vocabulary_language: string. language code of autocreated vocabulary
* (default: 'und' (undefined))
* - fields_custom : array. custom fields to add or create (default: array())
* - delete_terms : delete all terms before import (default: FALSE)
* - check_hierarchy: boolean. check (default) or not vocabulary hierarchy
* - set_hierarchy : if hierarchy isn't checked, set it (0, 1 or 2 (default))
* - update_or_ignore : depends on import_format: 'update' (default) or 'ignore'
* - check_options : boolean. check or not (default) this array of options
* - result_display : boolean. display or not (default)
* - result_stats : boolean. display or not (default) stats
* - result_terms : boolean. display or not (default) list of imported terms
* - result_level : log: 'first' (default), 'warnings', 'notices' or 'infos'
* - result_type : display log 'by_message' (default) or 'by_line'
* Only one option is required: file or text or url or path. Other options
* have default values. Warning: default values are little different with UI.
*
* @return
* Array of errors or nothing (need to execute batch process; result is logged
* in watchdog).
*/
function taxonomy_csv_import($options) {
// Complete $options with default values if needed.
// Default api and UI options are different.
$options += _taxonomy_csv_values('import_default_api');
// Preload text or file in order to check access to temporary folder.
$messages = _taxonomy_csv_import_input_preload($options);
if (count($messages)) {
return $messages;
}
// Process import.
return taxonomy_csv_vocabulary_import($options);
}
/**
* Preload input.
*
* Automatically determinate source choice (file, text, path or url).
* Then check if there is write access and prepare file.
* To use a file is not optimal with array and text input, but this solution
* unifies input and avoids some memory crashes.
*
* @todo Add an option to use only memory.
*
* @param $options
* Array of options.
*
* @return
* Array of messages errors if any.
* By reference options are cleaned and completed.
*/
function _taxonomy_csv_import_input_preload(&$options) {
$messages = array();
// File import. Used with UI.
if (isset($options['file']) && is_object($options['file'])) {
// File ready. No more check here.
}
// Text import.
elseif (isset($options['text']) && $options['text'] != '') {
// Prepare import by text: save text as a temp file to simplify process.
$filename = file_unmanaged_save_data(
$options['text'],
'public://taxocsv.csv',
'FILE_EXISTS_RENAME');
$options['file'] = (object) array(
'filename' => basename($filename),
'filepath' => drupal_realpath($filename),
'filesize' => filesize($filename)
);
}
// Url import.
elseif (isset($options['url']) && $options['url'] != '') {
$filename = file_unmanaged_save_data(
file_get_contents($options['url']),
'public://taxocsv.csv',
'FILE_EXISTS_RENAME');
$options['file'] = (object) array(
'filename' => basename($filename),
'filepath' => drupal_realpath($filename),
'filesize' => filesize($filename),
);
}
// Path import. With UI, path is already uploaded as a file.
elseif (isset($options['path']) && $options['path'] != '') {
// Load source local file. No check for extension with API.
$file = file_save_upload($options['path'], array('file_validate_extensions' => array('csv txt')));
// fopen and fseek need a real path.
if (!empty($file)) {
$file->filepath = drupal_realpath($file->uri);
if (!empty($file->filepath)) {
$options['file'] = $file;
}
}
}
else {
$messages['source_choice'] = t('Source choice should be a text, an url or a file path and source content should not be empty.');
return $messages;
}
// Check file.
if (!is_object($options['file'])
|| !$options['file']->filesize) {
$messages['file'] = t('Import failed.') . '<br />'
. t('- Check size of your input : it cannot be null.') . '<br />'
. t('- Check your server configuration and your rights : server needs permission to access and to read file.') . '<br />'
. t('- Check access rights to temp directory : import needs permission to write and to read in it.');
return $messages;
}
// Check if input format is utf8, if file has no bom and convert it if needed.
if (!_taxonomy_csv_import_clean_utf8($options['file'], $options['check_utf8'])) {
$messages['file'] = t("Your file is not utf-8 formatted. Possible solutions below.") . '<br />'
. t('- Convert your file to utf-8.') . '<br />'
. t('- Install iconv, GNU recode or mbstring for PHP.') . '<br />'
. t('- Disable "Check utf-8" option.');
return $messages;
}
return $messages;
}
/**
* Helper function to check if file is utf8 encoded and to remove bom if needed.
*
* See http://drupal.org/node/364832.
* This function remove utf8 byte order mark if needed.
*
* @param $file
* By reference file object to check.
*
* @param $check_utf8
* Boolean. Check utf8 format of the file (default) or not.
*
* @return
* TRUE if input is utf8 formatted or FALSE else. File is updated if needed.
*/
function _taxonomy_csv_import_clean_utf8(&$file, $check_utf8 = TRUE) {
$content = file_get_contents($file->filepath, TRUE);
$flag = FALSE;
// Check encoding.
if ($check_utf8) {
if (!function_exists('mb_detect_encoding')) {
return FALSE;
}
$enc = mb_detect_encoding($content, 'UTF-8, ISO-8859-1, ISO-8859-15', TRUE);
if ($enc != 'UTF-8') {
$content = drupal_convert_to_utf8($content, $enc);
if (!$content) {
$messages[] = 320; // Error convert.
return FALSE;
}
$flag = TRUE;
}
}
// Skip eventual UTF-8 byte order mark.
if (strncmp($content, "\xEF\xBB\xBF", 3) === 0) {
$content = substr($content, 3);
$flag = TRUE;
}
// Update content in file if needed.
if ($flag) {
$filename = file_unmanaged_save_data(
$content,
$file->uri,
'FILE_EXISTS_REPLACE');
$file = (object) array(
'filename' => basename($filename),
'filepath' => drupal_realpath($filename),
'filesize' => filesize($filename),
);
}
return TRUE;
}
/**
* Helper to check if delimiter is a soft tab and to prepare file if needed.
*
* @param $file
* By reference file object to check.
*
* @param $delimiter
* String. Soft tab delimiter to switch with a true tab.
*
* @return
* TRUE if delimiter isn't a soft tab or if file has been updated.
* FALSE if file can't be updated with a true tab (currently not used).
*/
function _taxonomy_csv_import_soft_tab(&$file, $delimiter) {
if (drupal_strlen($delimiter) > 1) {
$content = file_get_contents($file->filepath, TRUE);
// Switch soft tab delimiter with a true tab.
$content = str_replace($delimiter, "\t", $content);
// Save updated file.
$filename = file_unmanaged_save_data(
$content,
$file->uri,
'FILE_EXISTS_REPLACE');
$file = (object) array(
'filename' => basename($filename),
'filepath' => drupal_realpath($filename),
'filesize' => filesize($filename),
);
}
return TRUE;
}
/**
* Prepare the batch to import the vocabulary.
*
* @note
* If not used in a form, don't forget to use batch_process().
*
* @param $options
* Array. Same as taxonomy_csv_import. See above.
*
* @return
* Array of errors or nothing (batch process to execute).
*/
function taxonomy_csv_vocabulary_import($options) {
// Check options and return array of messages in case of errors.
if ($options['check_options']) {
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/import/taxonomy_csv.import.admin.inc");
$result = _taxonomy_csv_import_check_options($options);
if (count($result)) {
return $result;
}
}
// Complete $options.
// Switch soft tab delimiter with a true one if needed.
if (drupal_strlen($options['delimiter']) > 1) {
$result = _taxonomy_csv_import_soft_tab($options['file'], $options['delimiter']);
$options['delimiter'] = "\t";
}
// Calculates number of lines to be imported. File is already checked.
$options['total_lines'] = count(file($options['file']->filepath));
// Prepare vocabularies. Options are passed by-reference and can be updated.
$options['vocabulary'] = _taxonomy_csv_import_vocabulary_prepare($options);
// Get infos about fields of vocabulary.
if ($options['import_format'] == TAXONOMY_CSV_FORMAT_FIELDS) {
$options['instances'] = field_info_instances('taxonomy_term', $options['vocabulary']->machine_name);
$options['fields'] = array();
if (is_array($options['instances'])) {
foreach ($options['instances'] as $key => $value) {
$options['fields'][$key] = field_info_field($key);
}
}
}
// Set locale if needed.
// See http://drupal.org/node/872366
$options['locale_previous'] = setlocale(LC_CTYPE, 0);
if ($options['locale_custom']) {
setlocale(LC_CTYPE, $options['locale_custom']);
}
// Prepare import batch.
// Use a one step batch in order to avoid memory crash in case of big import.
$batch = array(
'title' => ($options['source_choice'] == 'text') ?
t('Importing !total_lines lines from text...', array(
'!total_lines' => $options['total_lines'])) :
t('Importing !total_lines lines from CSV file "%filename"...', array(
'%filename' => $options['vocabulary']->name,
'!total_lines' => $options['total_lines'])),
'init_message' => t('Starting uploading of datas...') . '<br />'
. t('Wait some seconds for pre-processing...'),
'progress_message' => '',
'error_message' => t('An error occurred during the import.'),
'finished' => '_taxonomy_csv_vocabulary_import_finished',
'file' => drupal_get_path('module', 'taxonomy_csv') . '/import/taxonomy_csv.import.api.inc',
'progressive' => TRUE,
'operations' => array(
0 => array('_taxonomy_csv_vocabulary_import_process', array($options)),
),
);
batch_set($batch);
}
/**
* Batch process of vocabulary import.
*
* @param $options
* Array of batch options.
* @param &$context
* Batch context to keep results and messages.
*
* @return
* NULL because use of &$context.
*/
function _taxonomy_csv_vocabulary_import_process($options, &$context) {
// Session or batch context are needed, because with batch process, static
// and $GLOBALS don't work for large import.
// First callback to prepare batch context.
if (empty($context['sandbox'])) {
// Remember options as batch_set can't use form_storage.
// It allows too that first line in result is numbered 1 and not 0.
$context['results'][0] = $options;
// Automatically detect line endings.
ini_set('auto_detect_line_endings', '1');
// Initialize some variables.
$context['results'][0]['current_line'] = 0;
$context['results'][0]['worst_line'] = 0;
$context['results'][0]['worst_message'] = 799;
$context['results'][0]['terms_count'] = 0;
$context['results'][0]['handle'] = fopen($options['file']->filepath, 'r');
$context['sandbox']['handle_pointer'] = 0;
$context['sandbox']['previous_items'] = array(
'name' => array(),
'tid' => array(),
);
}
elseif (!is_resource($context['results'][0]['handle'])) {
// Recall file and set pointer in case of memory or time out.
$context['results'][0]['handle'] = fopen($options['file']->filepath, 'r');
fseek($context['results'][0]['handle'], $context['sandbox']['handle_pointer']);
}
// Load and process one line.
$line_number = &$context['results'][0]['current_line'];
$worst_line = &$context['results'][0]['worst_line'];
$worst_message = &$context['results'][0]['worst_message'];
$terms_count = &$context['results'][0]['terms_count'];
$handle = &$context['results'][0]['handle'];
$previous_items = &$context['sandbox']['previous_items'];
// To set locale is needed with fgetcsv and it's needed each time this
// function is called. See http:/php.net/manual/en/function.fgetcsv.php.
// See http://drupal.org/node/872366
// @todo Use of preg_split?
if ($options['locale_custom']) {
setlocale(LC_CTYPE, $options['locale_custom']);
}
$line = fgetcsv($handle, 4096, $options['delimiter'], $options['enclosure']);
if ($line) {
++$line_number;
// Remember pointer in case of memory or time out.
$context['sandbox']['handle_pointer'] = ftell($handle);
// Process import of current line.
$result = taxonomy_csv_line_import_process($line, $options, $previous_items, $terms_count);
// Remember processed line.
$previous_items['name'] = $result['name'];
$previous_items['tid'] = $result['tid'];
$terms_count = $result['terms_count'];
// Remember first worst message of imported lines.
$worst_message_new = _taxonomy_csv_worst_message($result['msg']);
if ($worst_message > $worst_message_new) {
$worst_message = $worst_message_new;
$worst_line = $line_number;
};
// Remember only wanted messages.
if (($options['result_stats'] || $options['result_terms'])
&& $result['tid']
) {
// Some formats use $result to save other infos, so it needs to be cleaned
// before to be saved.
if (in_array($format['import_format'], array(
TAXONOMY_CSV_FORMAT_STRUCTURE,
TAXONOMY_CSV_FORMAT_TREE,
TAXONOMY_CSV_FORMAT_POLYHIERARCHY,
))) {
// Don't remember previous previous items.
$context['results'][0]['imported_terms'][0] = array_values($result['tid']);
}
else {
$context['results'][0]['imported_terms'][$line_number] = $result['tid'];
}
}
if ($options['result_level'] != 'first') {
$list_messages = array();
switch ($options['result_level']) {
// case 'first':
// break;
case 'warnings':
foreach ($result['msg'] as $msg) {
if ($msg < TAXONOMY_CSV_PROCESS_NOTICE) {
$list_messages[] = $msg;
}
}
break;
case 'notices':
foreach ($result['msg'] as $msg) {
if ($msg < TAXONOMY_CSV_PROCESS_INFO) {
$list_messages[] = $msg;
}
}
break;
case 'infos':
$list_messages = $result['msg'];
break;
}
if (count($list_messages)) {
$context['results'][$line_number] = $list_messages;
}
}
// Inform about progress.
$context['message'] = t('Line !line_number of !total_lines processed: !line', array(
'!line_number' => $line_number,
'!total_lines' => $options['total_lines'],
'!line' => '"' . implode('", "', $line) . '"',
));
// Check worst message of imported lines and update progress.
if ($worst_message >= TAXONOMY_CSV_PROCESS_WARNING) {
// Count should be <= 0.99 to avoid batch stop before end (Drupal 7 bug).
$context['finished'] = floor($line_number / $options['total_lines'] * 100) / 100;
}
else {
$context['finished'] = 1;
}
}
}
/**
* Callback for finished batch import and display result informations.
*/
function _taxonomy_csv_vocabulary_import_finished($success, $results, $operations) {
// $results[0] is used to save options and some infos (imported terms), as
// batch process can't use $form_state.
$options = &$results[0];
unset($results[0]);
// Close imported file.
if ($options['handle']) {
fclose($options['handle']);
}
// Set previous locale if needed.
if ($options['locale_custom']) {
setlocale(LC_CTYPE, $options['locale_previous']);
}
// Clean Session.
unset($_SESSION['taxonomy_csv_import']);
// Invoke import stats file if user wants to display texts for result.
if ($options['result_display']) {
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once("$module_dir/import/taxonomy_csv.import.result.inc");
}
// Short summary information is different if batch succeeded or not.
if ($success) {
$variables = array(
'!line' => $options['worst_line'],
'!total_lines' => $options['total_lines'],
'!worst_msg' => $options['result_display'] ?
_taxonomy_csv_message_text($options['worst_message']) :
t('Message code') . ' = ' . $options['worst_message'],
);
$messages = array(
WATCHDOG_DEBUG => t('No error, warnings or notices have been reported during import process of !total_lines lines.', $variables),
WATCHDOG_INFO => t('No error, warnings or notices have been reported during import process of !total_lines lines.', $variables),
WATCHDOG_NOTICE => t('Notices have been reported during import process (bad formatted or empty lines). !total_lines lines processed. First notice occurred on line !line [!worst_msg].', $variables),
WATCHDOG_WARNING => t('Warnings have been reported during import process (bad formatted lines). !total_lines lines processed. First line skipped is line !line [!worst_msg].', $variables),
WATCHDOG_ERROR => t('Errors have been reported during import process. Process failed at line !line of a total of !total_lines [!worst_msg].', $variables),
);
$worst_level = intval($options['worst_message'] / 100);
$message = $messages[$worst_level];
}
else {
$message = t('Importation failed. Import process was successful until the line !line_count of a total of !total_lines. You can first check your file on this line and check file uploading.', array(
'!line_count' => $options['current_line'],
'!total_lines' => $options['total_lines'],
)) . '<br />'
. t('This issue is related to import process or to size import and probably not to content. You can disable hierarchy check and reduce log level. You can divide your import file into lighter files. You can increase php and sql memory. If problem does not disappear, you can reinstall module from a fresh release or submit an issue on <a href="!link">Taxonomy CSV import/export module</a>.', array(
'!link' => url('http://drupal.org/project/issues/taxonomy_csv/'),
));
$worst_level = WATCHDOG_ERROR;
}
// Set result message in watchdog and eventually in user interface.
// Use of a $message variable is unrecommended, but simpler and working.
// See http://drupal.org/node/323101
watchdog('taxonomy_csv', $message, NULL, $worst_level);
if ($options['result_display']) {
_taxonomy_csv_import_result($options, $worst_level, $message, $results);
}
}
/**
* Prepare a vocabulary for import.
*
* @param $options
* Array of batch options.
* @param $check
* (Optional) Boolean used to determine if some options are checked or not.
*
* @return
* Prepared vocabulary object. $options can be updated.
*/
function _taxonomy_csv_import_vocabulary_prepare(&$options, $check = TRUE) {
$name = '';
if ($check) {
// Use name of file or url as vocabulary name.
if (isset($options['url']) && $options['url'] != '') {
$name = basename($options['url']);
}
elseif (isset($options['text']) && $options['text'] != '') {
$name = '';
// Remove useless option, because text is now saved.
$options['text'] = '';
}
elseif (isset($options['file']->filename) && $options['file']->filename != '') {
$name = $options['file']->filename;
}
}
// Create, duplicate or use an existing vocabulary.
switch ($options['vocabulary_target']) {
case 'autocreate':
$vocabulary = taxonomy_csv_vocabulary_create($name);
$options['vocabulary_id'] = $vocabulary->vid;
// Update vocabulary with language options.
if (module_exists('i18n_taxonomy')) {
$vocabulary->i18n_mode = $options['i18n_mode'];
$vocabulary->language = $options['vocabulary_language'];
$result = taxonomy_vocabulary_save($vocabulary);
}
break;
case 'existing':
$vocabulary = taxonomy_vocabulary_load($options['vocabulary_id']);
// Optional deletion of terms.
if ($options['delete_terms']) {
$tids = taxonomy_csv_vocabulary_get_tids($vocabulary->vid);
$result = taxonomy_csv_term_delete_multiple($tids);
}
break;
}
// Synchronize vocabulary internationalization options.
if (module_exists('i18n_taxonomy')) {
$options['i18n_mode'] = $vocabulary->i18n_mode;
$options['vocabulary_language'] = $vocabulary->language;
if ($vocabulary->i18n_mode == I18N_MODE_LOCALIZE) {
$options['filter_format'] = 'plain_text';
}
}
// Add or create custom fields if needed.
if ($options['import_format'] == TAXONOMY_CSV_FORMAT_FIELDS) {
$fields_custom = array_flip($options['fields_format']);
// Set default format to items of the custom format.
foreach ($fields_custom as $key => $value) {
if (in_array($key, array(
'tid',
'vid',
'name',
'description',
'format',
'weight',
'language',
'i18n_tsid',
'guid',
'vocabulary_machine_name',
'parent',
))) {
unset($fields_custom[$key]);
}
else {
$fields_custom[$key] = 'text';
}
}
// Add items of the custom fields. Allowed types are already checked.
foreach ($options['fields_custom'] as $key => $value) {
$field_name = (strpos($value, '|') === FALSE) ?
$value :
trim(drupal_substr($value, 0, strpos($value, '|')));
$field_type = (strpos($value, '|') === FALSE) ?
'text' :
trim(drupal_substr($value, strpos($value, '|') + 1));
$fields_custom[$field_name] = $field_type;
}
// Add or create each custom fields.
foreach ($fields_custom as $field_name => $field_type) {
// Set default field type.
$field = array(
'field_name' => $field_name,
'label' => $field_name,
'description' => '',
'type' => $field_type,
'cardinality' => FIELD_CARDINALITY_UNLIMITED,
// Currently, translatable term reference fields are not supported.
'translatable' => FALSE,
);
switch ($field_type) {
case 'taxonomy_term_reference':
$field += array(
'settings' => array('allowed_values' => array(0 => array(
'vocabulary' => $vocabulary->machine_name,
'parent' => 0,
))));
break;
case 'list_boolean':
$field += array(
'settings' => array('allowed_values' => array(
'0' => 'FALSE',
'1' => 'TRUE',
)));
break;
}
$result = taxonomy_csv_vocabulary_field_attach($vocabulary->machine_name, $field);
}
}
return $vocabulary;
}
/**
* Import a line that contains a term and other items matching the options.
*
* @param $line
* Array which contains items of a csv line.
* @param $options
* An associative array of import options:
* - import_format : format of the csv line (see taxonomy.api.inc)
* - keep_order : boolean. keep order of imported terms or not (default)
* - vocabulary_id : vocabulary id to import into
* - update_or_ignore: indicates what will become existing terms, if any.
* - check_line : boolean. Tweak to check (default) or not format of lines
* - check_utf8 : boolean. Tweak to check (default) or not utf8 format
* @param $previous_items
* (Optional) Cleaned and checked previous imported line names and tids array.
* Needed with some contents as one term array structure.
* @param $terms_count
* (Optional integer) Total of imported terms (duplicate included) is needed
* to set weight of terms and to keep order of items, if wished.
*
* @return
* Result array:
* - 'name' => array of imported terms names,
* - 'tid' => array of imported terms tids,
* - 'msg' => messages arrays:
* term position => array of status messages of term,
* 'line' => array of status messages of line,
* - 'terms_count' => total of imported terms.
*/
function taxonomy_csv_line_import_process($line, $options, $previous_items = array(), $terms_count = 0) {
// Define default values.
$line_messages = array();
$items_messages = array();
$result = array(
'name' => array(),
'tid' => array(),
'msg' => array(),
'terms_count' => $terms_count,
);
// 1. Validate and clean line.
if ($options['check_line']) {
$line = _taxonomy_csv_line_import_clean(
$line,
$line_messages);
// 2. Check items of line if no error occurs and if line is not empty.
if ((_taxonomy_csv_worst_message($line_messages) >= TAXONOMY_CSV_PROCESS_NOTICE)
&& ((count($line_messages) == 0) || ($line_messages[0] != 696))) {
$line = _taxonomy_csv_line_import_check(
$line,
$options,
$previous_items,
$items_messages);
$line_messages = array_merge($line_messages, $items_messages);
// 3. Process import items with full checked line.
if (_taxonomy_csv_worst_message($items_messages) >= TAXONOMY_CSV_PROCESS_NOTICE) {
$result = taxonomy_csv_line_import(
$line,
$options,
$previous_items,
$terms_count);
// Add line level message of bad or successful import.
$line_messages[] = (_taxonomy_csv_worst_message($result['msg']) >= TAXONOMY_CSV_PROCESS_NOTICE) ? 699 : 499;
}
}
}
else {
// No checks, so directly import line after a simple trim.
$result = taxonomy_csv_line_import(
array_values(array_map('trim', $line)),
$options,
$previous_items,
$terms_count);
// Add line level message of bad or successful import.
$line_messages[] = (_taxonomy_csv_worst_message($result['msg']) >= TAXONOMY_CSV_PROCESS_NOTICE) ? 699 : 499;
}
// Keep previous items in case of an empty or an unprocessed line.
if (count($result['name']) == 0) {
$result['name'] = $previous_items['name'];
$result['tid'] = $previous_items['tid'];
}
// Add line level messages and clean result.
$result['msg'] = array_unique(array_merge($result['msg'], $line_messages));
sort($result['msg']);
return $result;
}
/**
* Helper function to clean an imported line.
*
* @todo To be simplified.
*
* @param $line
* Array of items to be processed.
* @param &$messages
* (Optional) By reference array of messages codes to be returned.
*
* @return
* Array of cleaned imported line.
*/
function _taxonomy_csv_line_import_clean($line, &$messages = array()) {
$cleaned_line = array();
// Example: string "Term 1".
if (!is_array($line)) {
$messages[] = 310; // Error not a line array.
}
// Example: " " or unrecognized line.
elseif ((count($line) == 0)
|| empty($line)
|| ((count($line) == 1) && (trim($line[0]) == ''))) {
$messages[] = 696; // Info empty line.
}
else {
$cleaned_line = $line;
if (!drupal_validate_utf8(implode(',', $line))) {
$messages[] = 321; // Error validate.
}
// Trim and check empty line: useful for some non-Ascii lines.
$line = array_map('trim', $cleaned_line);
// @todo To simplify.
// Example: " , , ".
$test_line = array_unique($line);
if (count($test_line) == 1 && in_array('', $test_line)) {
$messages[] = 491; // Warning no item.
}
else {
$cleaned_line = array_values($line);
}
}
return $cleaned_line;
}
/**
* Check a line to find duplicate items, empty items...
*
* @param $line
* Array of items from a cleaned line.
* @param $options
* Array of available options. See taxonomy_csv_line_import_process.
* @param $previous_items
* (Optional) Cleaned and checked previous imported line names and tids array.
* Needed with some contents as one term array structure.
* @param &$messages
* (Optional) By reference array of messages codes to be returned.
*
* @return
* Array of checked items of imported line.
*/
function _taxonomy_csv_line_import_check($line, $options, $previous_items = array(), &$messages = array()) {
$checked_items = array();
// Simplify used options.
$update_or_ignore = &$options['update_or_ignore'];
// No input check because line and previous line are already checked.
// @todo A php callback function may be used to simplify checking.
// @todo To be factorized and simplified.
switch ($options['import_format']) {
case TAXONOMY_CSV_FORMAT_FLAT:
if (count($line) == 0) {
$messages[] = 491; // Warning no item.
break;
}
$checked_items = array_unique(array_filter($line));
if (count($checked_items) < count($line)) {
$messages[] = 531; // Notice duplicates, which are removed.
}
foreach ($checked_items as $name) {
if (drupal_strlen($name) > 255) {
$messages[] = 454; // Warning too long.
break;
}
}
break;
case TAXONOMY_CSV_FORMAT_STRUCTURE:
case TAXONOMY_CSV_FORMAT_TREE:
case TAXONOMY_CSV_FORMAT_POLYHIERARCHY:
// Check last empty column before first item with previous imported items.
for ($first_non_empty = 0; ($first_non_empty < count($line)) && (empty($line[$first_non_empty])); $first_non_empty++) {
}
// Example: Previous line("Term 1,Item 2") ; Current line(",,,Item4")
if ($first_non_empty && (!isset($previous_items['name'][$first_non_empty - 1]))) {
$messages[] = 410; // Warning impossible to get parent.
break;
}
// Example: Previous line("Term 1,Item 2") ; Current line(",,,Item4")
// "0" value are lost, but that is not important for a taxonomy.
$imported_items = array_filter(array_slice($line, $first_non_empty));
if (count($imported_items) == 0) {
$messages[] = 491; // Warning no item.
break;
}
if (count($imported_items) < (count($line) - $first_non_empty)) {
$messages[] = 510; // Notice empty items.
}
if (count(array_unique($imported_items)) < count($imported_items)) {
$messages[] = 632; // Info duplicates (not removed).
}
if ($first_non_empty == 0) {
$checked_items = $imported_items;
}
else {
$checked_items = array_merge(array_fill(0, $first_non_empty, ''), $imported_items);
}
foreach ($checked_items as $name) {
if (drupal_strlen($name) > 255) {
$messages[] = 454; // Warning too long.
break;
}
}
break;
case TAXONOMY_CSV_FORMAT_FIELDS:
// Get number of items.
$number_items = count($options['fields_format']);
if (count($line) < count($options['fields_format'])) {
$messages[] = 570; // Notice too little items.
}
elseif (count($line) > count($options['fields_format'])) {
$messages[] = 564; // Notice too many items.
}
if (count(array_keys($options['fields_format'], '')) >= 1) {
$messages[] = 513; // Notice empty items.
}
// @todo Add format check: required or not, type of datas...
// Currently, check only if the first field is empty or not.
if (empty($line[0])) {
$messages[] = 464; // Warning no name.
break;
}
$checked_items = $line;
break;
case TAXONOMY_CSV_FORMAT_TRANSLATE:
if (empty($line[0])) {
$messages[] = 484; // Warning no first/second column.
break;
}
if (count($line) < 2) {
$messages[] = 484; // Warning no first/second column.
break;
}
$checked_items = $line;
break;
default:
$messages[] = 306; // Error unknown source content.
}
return array_values($checked_items);
}

View File

@@ -0,0 +1,303 @@
<?php
/**
* @file
* Show export result messages.
*/
/**
* Invoke associated include file.
*/
$module_dir = drupal_get_path('module', 'taxonomy_csv');
require_once($module_dir . '/taxonomy_csv.result.inc');
/**
* Display result messages of import process.
*
* @param $options
* Array. Same as taxonomy_csv_import.
* @param $worst_level
* Integer. Worst watchdog level of the process.
* @param $summary
* Translated string. Short summary of informations on process.
* @param $results
* Array. Batch process results.
*
* @return
* Nothing.
*/
function _taxonomy_csv_import_result($options, $worst_level, $summary, $results) {
// Prepare info and warning messages.
$messages = array();
// Summary message.
$messages[] = array($worst_level => $summary);
// Prepare batch result message.
if ($options['total_lines'] == 0) {
$message = t('Notice: Input has no term to import.');
$messages[] = array(WATCHDOG_NOTICE => $message);
}
// Only summary is displayed in case of error.
elseif ($worst_level >= WATCHDOG_WARNING) {
// General infos.
$messages[] = array(WATCHDOG_INFO => _taxonomy_csv_info_chosen_options($options));
// Check and info on used or created vocabularies.
$messages[] = array(WATCHDOG_INFO => _taxonomy_csv_info_vocabulary($options));
if (!$options['check_line']) {
$messages[] = array(WATCHDOG_NOTICE => t('Line checks have been disabled. Some warnings and notices may have not been reported.'));
}
// Display stats and eventually lists about imported terms.
if ($options['result_stats']
|| $options['result_terms']) {
$messages[] = array(WATCHDOG_INFO => _taxonomy_csv_info_terms($options));
}
// Display messages about line process.
if ($options['result_level'] == 'first') {
$messages[] = array(WATCHDOG_NOTICE => t('No more information: choice is to report only first warning or notice if any.'));
}
else {
// Display detailled result of import. First info was already displayed.
$messages = array_merge($messages, _taxonomy_csv_info_lines($options, $results));
}
}
_taxonomy_csv_message_result($messages);
}
/**
* Return formatted list of chosen options.
*/
function _taxonomy_csv_info_chosen_options($options) {
if (!isset($options['source_choice'])) {
$options['source_choice'] = 'file';
}
// Local file, distant file or text.
switch ($options['source_choice']) {
case 'file':
$message = t('File "%file" uploaded.', array(
'%file' => $options['file']->filename));
break;
case 'text':
$message = t('Import terms from text.');
break;
case 'url':
$message = t('File "<a href="!url">%file</a>" uploaded from an url.', array(
'!url' => $options['url'],
'%file' => basename($options['url'])));
break;
default:
$message = t('Import from Api.');
}
// Content.
$message .= '<br />' . t('Source content: "!import_format".', array(
'!import_format' => $options['import_format'],
)) . '<br />';
// Existing items.
$message .= t('Existing terms choice: "!import_items".', array(
'!import_items' => $options['update_or_ignore'],
)) . '<br />';
// Internationalization.
if ($options['import_format'] != TAXONOMY_CSV_FORMAT_TRANSLATE) {
$message .= t('Language of terms: "!language".', array(
'!language' => $options['language'],
)) . '<br />';
}
// Custom locale.
if ($options['locale_custom']) {
$message .= t('Custom locale "!locale" has been used.', array(
'!locale' => $options['locale_custom'],
)) . '<br />';
}
return $message;
}
/**
* Check created vocabularies and return formatted info on them.
*/
function _taxonomy_csv_info_vocabulary($options) {
$messages = array();
// General info on used or created vocabularies.
$vocabularies = array($options['vocabulary_id']);
$messages[] = t('%count used or created.', array(
'%count' => format_plural(count($vocabularies), 'A vocabulary has been', '@count vocabularies have been', array())
));
foreach ($vocabularies as $vocabulary_id) {
$message = '';
// Check or update hierarchy of vocabularies.
$vocabulary = taxonomy_vocabulary_load($vocabulary_id);
if ($options['check_hierarchy']) {
$result = taxonomy_check_vocabulary_hierarchy($vocabulary, array('tid' => 0));
}
// Set vocabulary hierarchy only if needed.
else {
if ($options['set_hierarchy'] != $vocabulary->hierarchy) {
$vocabulary->hierarchy = $options['set_hierarchy'];
$result = taxonomy_vocabulary_save($vocabulary);
}
$result = $options['set_hierarchy'];
}
$new_hierarchy[$vocabulary_id] = $result;
// Display general info about vocabulary.
$message .= _taxonomy_csv_info_vocabulary_destination($vocabulary, $options['vocabulary_target']);
$message .= '<br />';
if (isset($vocabulary->language) && module_exists('i18n_taxonomy')) {
$message .= t('Language of vocabulary: "!language".', array(
'!language' => $vocabulary->language,
)) . '<br />';
$language_mode = array(
I18N_MODE_NONE => t('None'),
I18N_MODE_LOCALIZE => t('Localize'),
I18N_MODE_TRANSLATE => t('Translate'),
I18N_MODE_MULTIPLE => t('Multiple'),
I18N_MODE_LANGUAGE => t('Fixed language'),
);
$message .= t('Internationalization mode of vocabulary: "!language".', array(
'!language' => $language_mode[$vocabulary->i18n_mode],
)) . '<br />';
if ($options['language'] != $vocabulary->language) {
$message .= '<strong>' . t('Warning') . '</strong>: ' . t('Language of imported terms is not the language of the vocabulary. You may check multilingual options to see terms.') . '<br />';
}
}
if ($options['delete_terms']) {
$message .= t('Existing terms of the vocabulary have been deleted.') . '<br />';
}
if (!$options['check_hierarchy']) {
$message .= t('Hierarchy level has been manually set.') . '<br />';
}
$message .= _taxonomy_csv_info_vocabulary_result($vocabulary, $options['vocabulary_target'], $new_hierarchy[$vocabulary_id]) . '<br />';
$messages[] = $message;
}
return implode('<br />', $messages);
}
/**
* Return informations about destination vocabulary.
*/
function _taxonomy_csv_info_vocabulary_destination($vocabulary, $vocabulary_target) {
// Destination vocabulary.
$list_messages = array(
'autocreate' => t('New vocabulary "%vocabulary_name" has been created.', array(
'%vocabulary_name' => $vocabulary->name,
)),
'duplicate' => t('Duplicate vocabulary "%vocabulary_name" has been created.', array(
'%vocabulary_name' => $vocabulary->name,
)),
'existing' => t('Terms are imported into existing vocabulary "%vocabulary_name".', array(
'%vocabulary_name' => $vocabulary->name,
)),
);
return $list_messages[$vocabulary_target];
}
/**
* Return informations about result vocabulary.
*/
function _taxonomy_csv_info_vocabulary_result($vocabulary, $vocabulary_target, $new_hierarchy) {
$message = '';
// Level of vocabulary.
$hierarchy_text = array(
0 => t('no parent (flat)'),
1 => t('single parent (tree)'),
2 => t('multiple parents (polyhierarchy)'),
);
if ($vocabulary_target != 'autocreate') {
$message .= t('Old hierarchy level was !level (!level_text).', array(
'!level' => $vocabulary->hierarchy,
'!level_text' => $hierarchy_text[$vocabulary->hierarchy],
)) . '<br />';
}
$message .= t('Hierarchy level is !level (!level_text).', array(
'!level' => $new_hierarchy,
'!level_text' => $hierarchy_text[$new_hierarchy],
)) . '<br />';
// Direct links to edit vocabulary and view terms.
$message .= t('Properties can be edited at <a href="!vocabulary_edit_link">Administer > Structure > Taxonomy > edit vocabulary</a>.', array(
'!vocabulary_edit_link' => url("admin/structure/taxonomy/$vocabulary->machine_name/edit"),
)) . '<br />';
$message .= t('You can view terms at <a href="!vocabulary_list">Administer > Structure > Taxonomy > list terms</a>.', array(
'!vocabulary_list' => url("admin/structure/taxonomy/$vocabulary->machine_name"),
));
return $message;
}
/**
* Display stats and eventually lists of terms about imported terms.
*
* Currently, display only total and list of imported terms.
*/
function _taxonomy_csv_info_terms($options) {
if (isset($options['imported_terms'])) {
$tids = array();
foreach ($options['imported_terms'] as $imported_tids) {
$tids = array_unique(array_merge($tids, $imported_tids));
}
$message = '';
// Display basic stats.
if ($options['result_stats']) {
// Display total of imported terms.
$message .= t('Total created or updated terms : !count.', array('!count' => count($tids))) . '<br />';
}
// Display list of terms.
if ($options['result_terms']) {
$terms = taxonomy_term_load_multiple($tids);
$term_list = array();
foreach ($terms as $term) {
$term_list[] = $term->name;
}
$message .= '"' . implode('", "', $term_list) . '".';
}
}
else {
$message = t('No term was imported.');
}
return $message;
}
/**
* Display detailled result of import.
*/
function _taxonomy_csv_info_lines($options, $results) {
$messages = array();
if (count($results)) {
$messages[] = array(WATCHDOG_INFO => t('Available informations about lines import.'));
switch ($options['result_type']) {
case 'by_line':
$messages = array_merge($messages, _taxonomy_csv_message_result_by_line($results));
break;
case 'by_message':
$messages = array_merge($messages, _taxonomy_csv_message_result_by_message($results));
break;
}
}
else {
$messages[] = array(WATCHDOG_INFO => t('No more information reported about lines import.'));
}
return $messages;
}

View File

@@ -0,0 +1,245 @@
<?php
/**
* @file
* Manage variables and features of module.
*
* More infos (schemas of file, use as an api, etc.) in TECHINFO.txt.
*/
/**
* Available internal import/export formats.
*/
define('TAXONOMY_CSV_FORMAT_STRUCTURE', 'structure');
define('TAXONOMY_CSV_FORMAT_FLAT', 'flat');
define('TAXONOMY_CSV_FORMAT_TREE', 'tree');
define('TAXONOMY_CSV_FORMAT_POLYHIERARCHY', 'polyhierarchy');
define('TAXONOMY_CSV_FORMAT_FIELDS', 'fields');
define('TAXONOMY_CSV_FORMAT_TRANSLATE', 'translate');
/**
* Available import options.
*/
define('TAXONOMY_CSV_EXISTING_UPDATE', 'update');
define('TAXONOMY_CSV_EXISTING_IGNORE', 'ignore');
// Internal use only.
define('TAXONOMY_CSV_EXISTING_IGNORE_PREVIOUS', 'ignore_previous');
/**
* List of process levels matching watchdog levels.
*
* See _taxonomy_csv_message_watchdog_type and _taxonomy_csv_message_text.
*/
define('TAXONOMY_CSV_PROCESS_ERROR', 300); // Stop import process.
define('TAXONOMY_CSV_PROCESS_WARNING', 400); // Stop line process and go next.
define('TAXONOMY_CSV_PROCESS_NOTICE', 500); // Continue current line process.
define('TAXONOMY_CSV_PROCESS_INFO', 600); // Successfully processed.
define('TAXONOMY_CSV_PROCESS_DEBUG', 700); // Internal use only.
/**
* Information about import process.
*
* Use too default Drupal constants:
* - SAVED_NEW = 1
* - SAVED_UPDATED = 2
* Possibly use of:
* - SAVED_DELETED = 3
*/
define('TAXONOMY_CSV_ERROR', 0);
define('TAXONOMY_CSV_NEW_UPDATED', 4);
define('TAXONOMY_CSV_UNCHANGED', 5);
/**
* Helper to remember some items and to describe features.
*
* @param $list
* A string matching list to be returned:
* - 'import_format' : available formats for import.
* - 'export_format' : available formats for export.
* - 'import_default_ui' : default options to import by user interface.
* - 'import_default_api': default options to import by api.
* - 'export_default_ui' : default options to export by user interface.
* - 'export_default_api': default options to export by api.
*
* @return
* Array of wanted content.
*/
function _taxonomy_csv_values($list) {
switch ($list) {
case 'import_format':
return array(
TAXONOMY_CSV_FORMAT_STRUCTURE => t('Structure'),
TAXONOMY_CSV_FORMAT_FIELDS => t('Fields'),
TAXONOMY_CSV_FORMAT_TRANSLATE => t('Translation'),
);
case 'export_format':
return array(
TAXONOMY_CSV_FORMAT_FLAT => t('Term names'),
TAXONOMY_CSV_FORMAT_TREE => t('Hierarchical tree structure'),
TAXONOMY_CSV_FORMAT_FIELDS => t('Fields'),
TAXONOMY_CSV_FORMAT_TRANSLATE => t('Translation'),
);
case 'import_default_ui':
return array(
'import_format' => TAXONOMY_CSV_FORMAT_STRUCTURE,
'structure_type' => TAXONOMY_CSV_FORMAT_FLAT,
'import_fields_format' => 'name',
'translate_by' => 'name',
'translate_languages' => '',
'keep_order' => FALSE,
'source_choice' => 'text',
'import_delimiter' => 'comma',
'import_delimiter_soft_tab_width' => '2',
'import_delimiter_custom' => '',
'import_enclosure' => 'none',
'import_enclosure_custom' => '',
'filter_format' => 'plain_text',
'filter_format_custom' => 'none',
'import_language' => 'und', // Undefined.
'check_line' => TRUE,
'check_utf8' => TRUE,
'locale_custom' => '',
'vocabulary_target' => 'autocreate',
'vocabulary_id' => 'choose_vocabulary',
'i18n_mode' => 0, // I18N_MODE_NONE.
'language' => 'und', // Undefined.
'import_fields_custom' => '',
'delete_terms' => FALSE,
'check_hierarchy' => TRUE,
'set_hierarchy' => 2, // Polyhierarchy.
'update_or_ignore' => TAXONOMY_CSV_EXISTING_UPDATE,
// General options.
'result_stats' => 'result_stats',
'result_terms' => 'result_terms',
'result_level' => 'notices',
'result_type' => 'by_message',
);
case 'import_default_api':
return array(
'import_format' => TAXONOMY_CSV_FORMAT_FLAT,
'fields_format' => array(0 => 'name'),
'translate_by' => 'name',
'translate_languages' => '',
'keep_order' => FALSE,
'delimiter' => ',',
'enclosure' => '',
'filter_format' => 'plain_text',
'filter_format_custom' => 'none',
// Warning: in API, language is option for terms.
'language' => 'und', // Undefined.
'check_line' => FALSE,
'check_utf8' => FALSE,
'locale_custom' => '',
'vocabulary_target' => 'autocreate',
'vocabulary_id' => 'choose_vocabulary',
'i18n_mode' => 0, // I18N_MODE_NONE.
'vocabulary_language' => 'und', // Undefined.
'fields_custom' => array(),
'delete_terms' => FALSE,
'check_hierarchy' => TRUE,
'set_hierarchy' => 2, // Polyhierarchy.
'update_or_ignore' => TAXONOMY_CSV_EXISTING_UPDATE,
// General options.
'check_options' => FALSE,
'result_display' => FALSE,
'result_stats' => FALSE,
'result_terms' => FALSE,
'result_level' => 'first',
'result_type' => 'by_message',
);
case 'export_default_ui':
return array(
'export_format' => TAXONOMY_CSV_FORMAT_FLAT,
'export_fields_format' => 'name',
'export_vocabulary_id' => 0,
'export_delimiter' => 'comma',
'export_delimiter_custom' => '',
'export_enclosure' => 'quotation',
'export_enclosure_custom' => '',
'export_line_ending' => 'Unix',
// Default options of specific imports.
'def_links_terms_ids' => 'name_if_needed',
'def_links_vocabularies_ids' => 'none',
// General options.
'export_order' => 'name',
'result_duplicates' => TRUE,
);
case 'export_default_api':
return array(
'export_format' => TAXONOMY_CSV_FORMAT_FLAT,
'fields_format' => array(0 => 'name'),
'vocabulary_id' => 0,
'delimiter' => ',',
'enclosure' => '"',
'line_ending' => 'Unix',
'order' => 'name',
// Default options of specific imports.
'def_links_terms_ids' => 'name_if_needed',
'def_links_vocabularies_ids' => 'none',
// General options.
'result_duplicates' => FALSE,
'check_options' => FALSE,
'result_display' => FALSE,
);
}
}
/**
* Returns worst message of a set of message codes.
*
* @todo Move into another included file or remove it.
*
* @param $messages
* Array of message code (000 to 799).
*
* @return
* Worst message code.
*/
function _taxonomy_csv_worst_message($messages) {
return count($messages) ?
min($messages) :
799;
}
/**
* Escapes carriage return and linefeed.
*
* This function is used for description field of terms and allows to get only
* one csv line for one term.
*
* @param $string
* String to update.
*
* @return
* Updated string.
*/
function _taxonomy_csv_escape_line_break($string) {
return str_replace(
array("\r", "\n"),
array('\r', '\n'),
$string);
}
/**
* Remove escapes carriage return and linefeed.
*
* This function is used for description field of terms and allows to import a
* multiline text.
*
* @param $string
* String to update.
*
* @return
* Updated string.
*/
function _taxonomy_csv_set_line_break($string) {
return str_replace(
array('\r', '\n'),
array("\r", "\n"),
$string);
}

View File

@@ -0,0 +1,307 @@
<?php
/**
* @file
* Drush commands for taxonomy CSV import/export.
*/
/**
* Implements hook_drush_command().
*/
function taxonomy_csv_drush_command() {
$items = array();
$items['taxocsv-import'] = array(
'aliases' => array('vocimp'),
'callback' => 'drush_taxonomy_csv_import',
'description' => 'Import taxonomies and hierarchical structures with CSV file.',
'examples' => array(
'drush taxocsv-import my_file flat' => 'Import my_file using the default settings.',
'drush taxocsv-import my_file tree --keep_order --delimiter=";" --check_line --vocabulary_target=existing --vocabulary_id=3 --update_or_ignore=update --result_terms' => 'Import my_file as a tree structure into vocabulary with vid 2, a semicolon for delimiter and default settings for other options, and display imported terms after process.',
),
'arguments' => array(
'file_path' => 'Required. The full path or url to CSV file to import',
'import_format' => "Optional. CSV format: 'flat' (default), 'tree', 'polyhierarchy', 'fields', 'translate'",
),
'options' => array(
'fields_format' => "List of comma separated fields (default: 'name')",
'translate_by' => "First item ('name' or 'tid') of a translation import (default: 'name')",
'translate_languages' => "List of comma separated languages of terms (default: '')",
'keep_order' => 'Keep order of imported terms',
'delimiter' => 'CSV delimiter (default: ",")',
'enclosure' => "CSV enclosure (default: none or '\"')",
'filter_format' => 'Default format of the description (default: "plain_text")',
'filter_format_custom' => 'Default format of custom fields (default: "none", as fixed plain text)',
'language' => 'Default language of terms (default: "und" (undefined))',
'check_line' => 'Check format of lines',
'check_utf8' => 'Check utf8 format',
'locale_custom' => 'Specific locale of imported file',
'vocabulary_target' => "'autocreate' (default), 'duplicate' or 'existing'",
'vocabulary_id' => 'vid or machine_name of the vocabulary to duplicate or to import into',
'i18n_mode' => 'Internationalization mode of autocreated voc (default: 0 (none))',
'vocabulary_language' => 'Language of autocreated voc (default: "und" (undefined))',
'fields_custom' => 'Custom comma separated fields to add or create',
'delete_terms' => 'Delete all terms before import',
'check_hierarchy' => 'Check vocabulary hierarchy',
'set_hierarchy' => "If hierarchy isn't checked, set it (0, 1 or 2 (default))",
'update_or_ignore' => "What to do with existing items: 'update' (default) or 'ignore'",
// Level of result process infos.
'result_stats' => 'Display import stats',
'result_terms' => 'Display list of imported terms',
'result_level' => "Level of displayed messages: 'first' (default), 'warnings', 'notices' or 'infos'",
'result_type' => "Group infos 'by_message' (default) or 'by_line'",
),
);
$items['taxocsv-export'] = array(
'aliases' => array('vocexp'),
'callback' => 'drush_taxonomy_csv_export',
'description' => 'Export terms and properties to a CSV file.',
'examples' => array(
'drush taxocsv-export 2 flat' => "Export all terms names of the vocabulary with vid 2, using the default settings.",
'drush taxocsv-export 2 tree --delimiter=";" --order=name' => 'Export vocabulary with vid 2 as a CSV tree structure ordered by name, with a semicolon for delimiter, and default settings for other options.',
),
'arguments' => array(
'vocabulary_id' => 'Required. vid or machine_name of the vocabulary to export (0 means all)',
'export_format' => "Optional. CSV format: 'flat' (default), 'tree', 'fields', 'fields_first'",
'file_path' => "Optional. Full path or filename of exported csv file (default: [current directory]/taxocsv.csv)",
),
'options' => array(
'fields_format' => "List of comma separated fields (default: 'name')",
'delimiter' => 'One character csv delimiter (default: ",")',
'enclosure' => "Zero or one character csv enclosure (default: '\"')",
'line_ending' => "'Unix' (default), 'Mac' or 'MS-DOS'",
'order' => "Order of terms: 'name' (default), 'tid' or 'weight'",
// Level of result process infos.
'result_duplicates' => "Display duplicate terms",
),
);
return $items;
}
/**
* Implements hook_drush_help().
*/
function taxonomy_csv_drush_help($section) {
switch ($section) {
case 'drush:taxocsv-import':
return dt('Import taxonomies and hierarchical structures with CSV file.');
case 'drush:taxocsv-export':
return dt('Export terms and properties to a CSV file.');
}
}
/**
* Process the import of an input.
*
* @see drush_invoke()
* @see drush.api.php
*/
function drush_taxonomy_csv_import($file_path, $import_format = 'alone_terms') {
// Start process.
drush_print('Checking options...');
if (!$file_path || !file_exists($file_path)) {
drush_log('You need to set the correct path or url of the file to import.', 'error');
return;
}
require_once(drupal_get_path('module', 'taxonomy_csv') . '/import/taxonomy_csv.import.api.inc');
// Set arguments.
$options = array();
$options['url'] = $file_path;
$options['import_format'] = $import_format;
// Get the defaults options and update them with user ones.
$options += _taxonomy_csv_values('import_default_api');
// Set simple options.
foreach (array(
'delimiter',
'enclosure',
'locale_custom',
'vocabulary_target',
'vocabulary_id',
'set_hierarchy',
'update_or_ignore',
// Level of result process infos.
'result_level',
'result_type',
) as $value) {
$option = drush_get_option($value);
if ($option !== NULL) {
$options[$value] = $option;
}
}
// Set boolean options.
foreach (array(
'keep_order',
'check_line',
'check_utf8',
'delete_terms',
'check_hierarchy',
'set_hierarchy',
// Level of result process infos.
'result_stats',
'result_terms',
) as $value) {
$options[$value] = (drush_get_option($value) === NULL) ? FALSE : TRUE;
}
// Set array options.
foreach (array(
'translate_languages',
'fields_format',
'fields_custom',
) as $value) {
$option = drush_get_option_list($value);
if ($option !== array()) {
$options[$value] = $option;
}
}
// Set general options.
$options['check_options'] = TRUE;
$options['result_display'] = TRUE;
// Prepare import.
$messages = taxonomy_csv_import($options);
// Last check: unknown options.
$other_options = array_diff(array_keys(drush_get_merged_options()), array_keys($options));
// Compatibility with drush 4.5. See https://drupal.org/node/1395090.
if (($i = array_search('drush-make-update-default-url', $other_options)) !== FALSE) {
unset($other_options[$i]);
}
if ($other_options) {
$messages[] = 'Unknown options:' . ' ' . implode(', ', $other_options) . '.';
}
if (count($messages)) {
foreach ($messages as $message) {
$msg = str_replace('<br />', " \n", $message);
drush_log('- ' . $msg, 'error');
}
return;
}
// Process the batch.
drush_log('Options are good.', 'status');
drush_print('Launch import process. Please wait...');
$batch =& batch_get();
$batch['progressive'] = FALSE;
drush_backend_batch_process();
// End of drush process.
drush_print('End of drush import batch.');
}
/**
* Process the export of a vocabulary.
*
* @see drush_invoke()
* @see drush.api.php
*/
function drush_taxonomy_csv_export($vocabulary_id, $export_format = 'alone_terms', $file_path = '') {
// Start process.
drush_print('Checking options...');
if ($vocabulary_id === NULL) {
drush_log('You need to set a vocabulary id (0 means all).', 'error');
return;
}
// Set the destination file path.
if ($file_path == '') {
$file_path = getcwd() . '/taxocsv.csv';
}
require_once(drupal_get_path('module', 'taxonomy_csv') . '/export/taxonomy_csv.export.api.inc');
// Set arguments.
$options = array();
$options['vocabulary_id'] = $vocabulary_id;
$options['export_format'] = $export_format;
// Get the defaults options and update them with user ones.
$options += _taxonomy_csv_values('export_default_api');
// Set simple options.
foreach (array(
'delimiter',
'enclosure',
'line_ending',
'order',
// Level of result process infos.
) as $value) {
$option = drush_get_option($value);
if ($option !== NULL) {
$options[$value] = $option;
}
}
// Set boolean options.
foreach (array(
'result_duplicates',
) as $value) {
$options[$value] = (drush_get_option($value) === NULL) ? FALSE : TRUE;
}
// Set array options.
foreach (array(
) as $value) {
$option = drush_get_option_list($value);
if ($option !== array()) {
$options[$value] = $option;
}
}
// Set general options.
$options['check_options'] = TRUE;
$options['result_display'] = TRUE;
// Prepare export.
$messages = taxonomy_csv_export($options);
$file = '';
if (is_object($messages)) {
$file = $messages;
$messages = array();
}
// Last check: unknown options.
$other_options = array_diff(array_keys(drush_get_merged_options()), array_keys($options));
// Compatibility with drush 4.5. See https://drupal.org/node/1395090.
if (($i = array_search('drush-make-update-default-url', $other_options)) !== FALSE) {
unset($other_options[$i]);
}
if ($other_options) {
$messages[] = 'Unknown options:' . ' ' . implode(', ', $other_options) . '.';
}
if (count($messages)) {
foreach ($messages as $message) {
$msg = str_replace('<br />', " \n", $message);
drush_log('- ' . $msg, 'error');
}
return;
}
// Process the batch.
drush_log('Options are good.', 'status');
drush_print('Launch export process. Please wait...');
$batch =& batch_get();
$batch['progressive'] = FALSE;
drush_backend_batch_process();
// Move file to chosen directory if needed.
if (file_exists($file->filepath)) {
rename($file->filepath, $file_path);
drush_log("The vocabulary has been exported to $file_path.", 'status');
}
// End of drush process.
drush_print('End of drush export batch.');
}

View File

@@ -0,0 +1,427 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Taxonomy CSV import/export help</title>
<style type="text/css"><!--
h3 {
font: bold 120% sans-serif;
}
h4 {
font: bold 100% sans-serif;
margin-top: 24px;
margin-left: 18px;
}
h5 {
font: italic 100% sans-serif;
margin-top: 8px;
margin-left: 36px;
}
table {
width: 80%;
}
table,
div.codeblock {
border: 1px solid rgb(204, 204, 204);
padding: 4px;
background-color: rgb(238, 238, 238);
margin: 6px 48px 12px 24px;
}
code {
white-space: pre;
}
--></style>
</head>
<body>
<p>This module allows to import/export taxonomies, structures or simple lists of terms into/from a vocabulary from/to a <a href="http://en.wikipedia.org/wiki/Comma-separated_values" title="Wikipedia definition">CSV</a> file, a url or a copy-and-paste text.</p>
<p>CSV format is a simple list of values separated by a delimiter, often comma (<strong><code>,</code></strong>) or semicolon (<strong><code>;</code></strong>), and enclosures, often quotation marks (<strong><code>"</code></strong>). If you are unsure how to create a CSV file, you might want to use <a href="http://www.libreoffice.org" title="LibreOffice.org">LibreOffice Calc</a> or another spreadsheet application to export your data into a CSV file.</p>
<p>Specific import or export formats can be added simply by a plug-in mecanism (or ask author!).</p>
<p>After enabling the module, permissions "import taxonomy by csv" and "export taxonomy by csv" need to be set. Main "administer taxonomy" permission is not needed to use module, but this is recommended.</p>
<p>Internationalization is supported, except for attached Fields [Taxonomy CSV pour Drupal 7 only]. So you can import a list or a structure of terms, then translate them.</p>
<p>This module supports <a href="https://drupal.org/project/drush">drush</a> and allows to import or to export taxonomies by csv with command line interface.</p>
<p>Taxonomy CSV for Drupal 7 has got a simpler UI compared to the version for Drupal 6, because taxonomy has been reworked with fields in Drupal 7. Help for Drupal 6 version is available in <a href="https://drupalcode.org/project/taxonomy_csv.git/blob_plain/refs/heads/6.x-5.x:/taxonomy_csv.help.html">English</a> or in <a href="https://drupalcode.org/project/taxonomy_csv.git/blob_plain/refs/heads/6.x-5.x:/taxonomy_csv.help.fr.html">French</a>.</p>
<div class="toc">
<h2 class="nonum"><a id="contents" name="contents">Table of Contents</a></h2>
<ol class="toc">
<li>
<a href="#formats">Formats</a>
<ol class="toc">
<li>
<a href="#structure">Structure</a>
<ul>
<li>
<a href="#flat">Terms (flat vocabulary)</a>
</li>
<li>
<a href="#tree">Hierarchical tree structure or one term by line structure</a>
</li>
<li>
<a href="#polyhierarchy">Polyhierarchical structure</a>
</li>
</ul>
</li>
<li>
<a href="#fields">Fields</a>
<ul>
<li>
<a href="#fields_format">Fields CSV format</a>
</li>
<li>
<a href="#fields_custom">Autocreation of Fields</a>
</li>
</ul>
</li>
<li>
<a href="#translate">Term translation</a>
</li>
</ol>
</li>
<li>
<a href="#import">Import</a>
<ol class="toc">
<li>
<a href="#import_content">What to import (content of the source)?</a>
</li>
<li>
<a href="#source">Where are terms to import?</a>
</li>
<li>
<a href="#file_format">How is formatted your source?</a>
</li>
<li>
<a href="#destination">Which vocabulary do you want to import into?</a>
</li>
<li>
<a href="#import_options">When a term exists, what to do with it?</a>
</li>
<li>
<a href="#info_process">Informations on process and big taxonomies import</a>
</li>
</ol>
</li>
<li>
<a href="#export">Export</a>
</li>
<li>
<a href="#import_api">Drush and Taxonomy csv import API</a>
</li>
<li>
<a href="#advanced_options">Advanced settings and hints</a>
<ol class="toc">
<li>
<a href="#permissions">Permissions</a>
</li>
<li>
<a href="#other_hints">Other hints</a>
</li>
</ol>
</li>
</ol>
</div>
<h3 id="formats"><a href="#contents">1. Formats</a></h3>
<p>Multiple formats can be used to import or export vocabulary. Except with some formats, the first column contains the term name. You can specify what you want to export and how additional columns should be imported.<br />
<p>To import a complex taxonomy, it's recommended to import terms in this order:</p>
<ul>
<li>Structure (flat, tree or polyhierarchical)</li>
<li>Fields (name, weight, description, parent and what you want)</li>
<li>Translation (if <a href="https://drupal.org/project/i18n">i18n</a> is installed)</li>
</ul>
<p>That's what you can choose in the first tab.</p>
<h4 id="structure"><a href="#contents">1. Structure</a></h4>
<h5 id="flat"><a href="#contents">1. Terms (flat vocabulary)</a></h5>
<p>Use this option to import a lot of terms in order to create a flat vocabulary. All items in your file will be imported as terms. Example:</p>
<div class="codeblock"><ul>
<li><code>Clothes, Trees, Houses</code></li>
<li><code>Paris, Drupal</code></li>
</ul></div>
<h5 id="tree"><a href="#contents">2. Hierarchical tree structure or one term by line structure</a></h5>
<p>Use this option to create a tree structure of a vocabulary (geography, classification...). To import a hierarchy with multiple parents as a genealogical one, it's advised to use "Polyhierarchy", "First level children" or "First level parents" imports.</p>
Your file can be written with two schemes and a mixed one.<br />
<h6 id="tree_structure_full_line"><a href="#contents">First scheme: Full ancestors of a term</a></h6>
In the first scheme, you need to set all ancestors to each term. The second column will be imported as the name of a child term of the term defined by the first column. The third column will be imported as a child of the second column, and so on. Lines can have any order. Example:<br />
<div class="codeblock"><ul>
<li><code>Animal, Mammal, Whale</code></li>
<li><code>Animal, Mammal, Monkey</code></li>
</ul></div>
Be careful: when a child is added or updated, line should contain all its ancestors. So a third line may be:<br />
<div class="codeblock"><ul>
<li><code>Animal, Mammal, Human</code></li>
</ul></div>
<strong>but not</strong>:
<div class="codeblock"><ul>
<li><code>Mammal, Human</code></li>
</ul></div>
because in this second case, &lt; <code>Mammal</code> &gt; is imported as a first level term and not as a &lt; <code>Animal</code> &gt; term child as in previous line.
<h6 id="tree_structure_partial_line"><a href="#contents">Second scheme: One term by line</a></h6>
In the "one term by line structure" scheme, you can import terms without duplicate all its ancestor if previous term has ancestors. It is very useful with a spreadsheet application. It allows to easy build a structure and to upload a less heavy file. So your hierarchy can be:<br />
<code><table border="1">
<tr><td>World</td><td></td><td></td><td></td></tr>
<tr><td></td><td>Asia</td><td></td><td></td></tr>
<tr><td></td><td></td><td>Japan</td><td></td></tr>
<tr><td></td><td></td><td></td><td>Tokyo</td></tr>
<tr><td></td><td></td><td>Korea</td><td></td></tr>
<tr><td></td><td></td><td></td><td>Seoul</td></tr>
</table></code>
<br />
So, first lines of your csv file can be:
<div class="codeblock"><ul>
<li><code>World</code></li>
<li><code>,Asia</code></li>
<li><code>,,Japan</code></li>
<li><code>,,,Tokyo</code></li>
<li><code>,,Korea</code></li>
<li><code>,,,Seoul</code></li>
</ul></div>
<div class="codeblock"><ul>
<li><code>,Europe</code></li>
<li><code>,,France</code></li>
<li><code>,,,Paris</code></li>
</ul></div>
<div class="codeblock"><ul>
<li><code>,,Italia,Abruzzo,Chieti,Chieti</code></li>
<li><code>,,,,,Civitaluparella</code></li>
</ul></div>
&lt; <code>Paris</code> &gt; will be automatically added as a child of &lt; <code>France</code> &gt; and so on.
<h6 id="tree_structure_mixed_line"><a href="#contents">Mixed scheme</a></h6>
<p>Partial lines are allowed, so a next line can be:</p>
<div class="codeblock"><ul>
<li><code>,,Switzerland,Bern</code></li>
</ul></div>
&lt; <code>Switzerland</code> &gt; will be added as a child of &lt; <code>Europe</code> &gt; and of course &lt; <code>Bern</code> &gt; as a child of &lt; <code>Switzerland</code> &gt;.
<p>In same way, above lines can be simplified to:</p>
<div class="codeblock"><ul>
<li><code>World, Asia, Japan, Tokyo</code></li>
<li><code>,, Korea, Seoul</code></li>
<li><code>World, Europe, France, Paris</code></li>
</ul></div>
Full lines, partial and one term lines can be mixed, but order is important two by two lines, except when there are only full lines. In this example, if fifth and sixth lines are shift, &lt; <code>Seoul</code> &gt; will become a child of &lt; <code>Japan</code> &gt;.
<h5 id="polyhierarchy"><a href="#contents">3. Polyhierarchical structure</a></h5>
<p>Use this option to create a a polyhierarchical structure, as a genealogy.<br />
Format is the same than tree structure: each term is the child of previous item: parent, child, sub-child... and so on.<br />
There are four differences. First, the first item doesn't need to be a root. Second, duplicate terms are always merged, except when one is the direct parent of the other one, because it's forbidden in Drupal. So, if the vocabulary is monohierarchical and without non-direct duplicate terms, as in the previous geographical example, result is the same than with previous option. Third, lines can be partial too, but in some case of duplicates, result may differ. Last, polyhierarchy can be recursive.<br />
For example, lines may be:</p>
<div class="codeblock"><ul>
<li><code>Grand-Mother, Mother, Daughter</code></li>
<li><code>Grand-Father, Mother, Son</code></li>
<li><code>Grand-Mother 2, Father, Daughter</code></li>
<li><code>Grand-Father 2, Father, Son</code></li>
<li><code> , , Son 2</code></li>
<li><code> , Uncle</code></li>
<li><code>Grand-Mother 2, Uncle</code></li>
<li><code>Father, Son 3</code></li>
</ul></div>
<h4 id="fields"><a href="#contents">2. Fields</a></h4>
<p>Terms are imported with a csv scheme provided by the user.</p>
<h5 id="fields_format"><a href="#contents">1. Fields CSV format</a></h5>
<p>The csv scheme should contain each column header of the csv input. The column header is the name (not the label) of the field where to import items into. It can be a default header (name, description, weight, vid, vocabulary_machine_name, guid) or a custom field. The first item is always the name or the tid of a term.</p>
<p>For example, you want to import a list of car makers, and you would like each car maker to have custom fields indicating nationality and date of origine(<a href="https://drupal.org/node/1027068#comment-4152194">origine of example, fictional</a>):</p>
<div class="codeblock"><ul>
<li><code>[Taxonomy Term] - [Custom Field] - [Custom Field]</code></li>
<li><code>Car Maker - Country - Year Started</code></li>
<li><code>-------------------------------------------------</code></li>
<li><code>Ford - US - 1900</code></li>
<li><code>Renault - France - 1901</code></li>
<li><code>Toyota - Japan - 1902</code></li>
<li><code>Nissan - Japan - 1903</code></li>
<li><code>Daimler Benz - Germany - 1904</code></li>
</ul></div>
<p>So, with 'Fields' format, you can set your format:</p>
<div class="codeblock">
<code>name, field_country, field_year_started</code>
</div>
<p>or more generically:</p>
<div class="codeblock">
<code>name, field_mycustomfield_1_machinename, field_mycustomfield_2_machinename...</code>
</div>
<p>Items can be repeated for multivalued fields.</p>
<div class="codeblock">
<code>name, description, weight, parent, synonym, synonym, synonym, related_term, related_term, related_term, related_term</code>
</div>
<p>The module supports all field types as long as they have a 'value' in their definition. These fields has been checked:
<ul>
<li><code>text</code></li>
<li><code>text_long</code></li>
<li><code>text_with_summary</code></li>
<li><code>number_decimal</code></li>
<li><code>number_integer</code></li>
<li><code>number_float</code></li>
<li><code>list_boolean</code></li>
<li><code>taxonomy_term_reference</code> <em>[Note: if value is a number, the field will be linked to the term with that tid; if value is a string, the field will be linked to an existing or a new term with that name.]</em></li>
<li><code>file</code></li>
</ul></p>
<h5 id="fields_custom"><a href="#contents">2. Autocreation of Fields</a></h5>
<p>Custom fields are automatically created if they don't exist and then attached to the vocabulary. By default, a 'text' field is created when the field doesn't exist. If you want to create another type of field, you have to set it with <code>"|"</code> symbol in the vocabulary section of the form. The field is not created or modified if it exists.</p>
<p>For exemple, you want to import these items (<a href="https://drupal.org/node/1027068#comment-4194716">origine of example</a>):</p>
<div class="codeblock"><ul>
<li><code>name 1, tax gtin name 1, description 1, /home/robertom/file1.pdf, status 1, related term 1, related term 2, related term 3</code></li>
<li><code>name 2, tax gtin name 2, description 2, /home/robertom/file2.pdf, status 2, related term 1, related term 4</code></li>
<li><code>name 3, tax gtin name 3, description 3, /home/robertom/file3.pdf, status 3</code></li>
</ul></div>
<p>Your custom format will be:</p>
<div class="codeblock">
<code>name, field_internal_name, description, field_file, field_status, related_term, related_term, related_term</code>
</div>
<p>Your custom fields will be:</p>
<div class="codeblock">
<code>field_file|file, related_term|taxonomy_term_reference</code>
</div>
<h4 id="translate"><a href="#contents">3. Term translation</a></h4>
<p>Translation is available only if <a href="https://drupal.org/project/i18n">i18n</a> is installed and the submodule i18n Taxonomy is enabled. Furthermore, the i18n mode of the vocabulary should be "Translate" or "Localize".</p>
<p><strong>Warning</strong>: If you have a tree structure, you should import it before fields or translations, and for each translation if you use a full Translate mode.</p>
<p>Format: The term is in the first column followed by its translations. If the i18n mode is Localize, then description and translated descriptions can be added.</p>
<p>For vocabulary with Translate mode:</p>
<div class="codeblock">
<code>term name/id, first translated term name, second translated term name...</code>
</div>
<p>For vocabulary with Localize mode:</p>
<div class="codeblock">
<code>term name/id, first translation of term name... , description, first translation of description...</code>
</div>
<p>Examples:</p>
<div class="codeblock"><ul>
<li><code>foo, bar</code></li>
<li><code>"United Kingdom", "Royaume-Uni", "Vereinigte Königreich"</code></li>
<li><code>"Germany", "Allemagne", "A European country", "Un pays européen"</code> <em>[vocabulary with Localize mode only]</em></li>
</ul></div>
<p><strong>Notes</strong></p>
<ul>
<li>With a vocabulary in Translate mode, a term with an undefined language cannot be translated, so do not forget to choose a language when you import original terms.</li>
<li>With a vocabulary in Localize mode, only terms with a undefined language can be translated, so do not set a language when you import original terms.</li>
<li>All languages should have been enabled in Regional and language settings before import.</li>
<li><strong>Important</strong>: If your taxonomy is structured, you should import the structure <em>before</em>. Furthermore, if the i18n mode is Translate, you should import the structure for each translation before. The translate process only creates translation sets for terms, but does not recreate any structure.</li>
<li>With Translate mode, terms with an undefined language (<code>"und"</code>) cannot be translated.</li>
<li>With Localize mode, only terms with an undefined language can be translated, so the first language should be "und" and the default language of the site cannot be used.</li>
</ul>
<h3 id="import"><a href="#contents">2. Import</a></h3>
<p>Taxonomy CSV allows to import structure and properties of terms.</p>
<h4 id="import_content"><a href="#contents">1. What to import (content of the source)?</a></h4>
<p>Source can be configured with the first field set. See <a href='#formats'>formats</a>.</p>
<h4 id="source"><a href="#contents">2. Where are terms to import?</a></h4>
<p>You can import your terms from a file or with a text area. Simply check your choice. File can be a local file path or a url.</p>
<h4 id="file_format"><a href="#contents">3. How is formatted your source?</a></h4>
<p>Import need to be utf-8 formatted, without byte order mark in preference.</p>
<p>This group of settings allows to set non standard delimiter and enclosure and specific locales, such as "fr_FR.utf8".</p>
<ul>
<li>
Delimiters (comma "<strong><code>,</code></strong>" by default, semicolon "<strong><code>;</code></strong>" or tabulation) between terms can be chosen in Advanced settings in the second fieldset. You can choose a custom delimiter too. Delimiter is a one character string. Example with delimiter &lt; <strong><code>¤</code></strong> &gt;:<br />
<div class="codeblock"><ul>
<li><code>term 1¤This field has commas, a semicolon (;), a quotation mark (") and a tabulation, but it will be correctly imported.</code></li>
</ul></div>
</li>
<li>
It is recommended to protect terms with enclosure characters as quotation marks (<strong><code>"</code></strong>), specialy if they contain non-ASCII letters or if imported items, in particular descriptions, contain the chosen delimiter. Example:<br />
<div class="codeblock"><ul>
<li><code>"term 1","This field has a comma, but it will be correctly imported."</code></li>
</ul></div>
You can choose a custom enclosure character in Advanced settings in the second fieldset. Enclosure can have only one character or be null. Quotation marks (<strong><code>"</code></strong>) are automatically managed.
</li>
<li>
Warning: when you export a vocabulary, delimiter and enclosure should not be used in term definitions, specially in descriptions. Export process will stop in case of problem.
</li>
</ul>
<h4 id="destination"><a href="#contents">4. Which vocabulary do you want to import into?</a></h4>
<p>You can import your terms in a existing vocabulary or create a new one. You can import your terms too in an existing vocabulary.</p>
<p>When you want to import a new taxonomy into an existing one, it is recommended to process in two steps in order to check import.</p>
<ul>
<li>First, check the import file with the &lt; <em>Autocreate a new vocabulary</em> &gt; option. Repeat this step while there are warnings and notices.</li>
<li>Second, you can import your file in the true vocabulary with the &lt; <em>Import in an existing vocabulary</em> &gt; option. This allows you to keep old links between existing terms and nodes.</li>
</ul>
<p>If you only want to create a new vocabulary, the first choice is sufficient, unless when you have multiple files for one vocabulary.</p>
<h4 id="import_options"><a href="#contents">5. When a term exists, what to do with it?</a></h4>
<p>Destination can be configured with the next field set. You can specify what will become existing terms when you import a term with the same name.</p>
<ul>
<li>
<h5>Update terms and merge existing (avoid duplicate terms)</h5>
<p>
Update current terms when name matches with imported ones and merge existing descriptions, parents, etc. Duplicates are removed. This choice is recommended.
Note: Duplicate terms in trees are fully supported.
</p>
</li>
<li>
<h5>Ignore current terms and create new ones (may create duplicate terms)</h5>
<p>
Let current terms as they are and create a new term for the first column term.
<br />
Warning: This can create duplicate terms. It is recommended to use this option only if you are sure that imported taxonomy contains only new terms or if your vocabulary allows multiple parents.
</p>
</li>
</ul>
<h4 id="info_process"><a href="#contents">6. Informations on process and big taxonomies import</a></h4>
<p>This group of options allows to choose informations displayed at end of process.</p>
<p>To import big taxonomies (from 1000 or 10000 lines depending on the server) when access to time and memory settings of server are forbidden, it is recommended first to disable some other taxonomy related modules as "pathauto" before import. Settings aren't lost when you disable a module - and not uninstall it -. After import process, modules can be reactivated.<br />
Next, you can use these tweaks (in groups of options).</p>
<ul>
<li>
<h5>Reduce log level</h5>
<p>Stats, terms and notices or warnings displayed at the end of process are memory consomming. So, you can reduce or disable them.</p>
</li>
<li>
<h5>Manually set vocabulary hierarchy</h5>
<p>As to calculate vocabulary hierarchy is memory intensive, this option allows to set hierarchy manually without verify it.</p>
</li>
<li>
<h5>Disable line checks</h5>
<p>If you are sure that vocabulary to import is well formatted (utf8, order of items...), you can disable checks. This option increases import speed by 5%.</p>
</li>
</ul>
<h3 id="export"><a href="#contents">3. Export</a></h3>
<p>Taxonomy CSV allows to export structures and properties of terms of one or all vocabularies.</p>
<p>Simply choose what you want to export (see <a href='#formats'>formats</a>) and how to export. Some formats may be unavailable.</p>
<h3 id="import_api"><a href="#contents">4. Drush and Taxonomy csv import API</a></h3>
<p>This module supports <a href="https://drupal.org/project/drush">drush</a>: you can import/export taxonomies from the command line interface with <code>drush taxocsv-import</code> and <code>drush taxocsv-export</code>. See command line help for more information.</p>
<p>More generally, this module can be used as an API. You can use the full module as a dependance or directly in your module. Import is run as this:</p>
<div class="codeblock"><code>
$csv_lines = '"Europe", "France", "Paris"';<br />
$csv_lines .= "\n". ',, "Lyon"';<br />
$csv_lines .= "\n". ',"United Kingdom", "London"';<br />
$csv_lines .= "\n". ',"Portugal", "Lisbonne"';<br />
$result = taxonomy_csv_import(<br />
array(<br />
'text' => $csv_lines,<br />
'import_format' => 'tree',<br />
'update_or_ignore' => 'update',<br />
));<br />
</code></div>
<p>Or as this (line level import):</p>
<div class="codeblock"><code>
$result = taxonomy_csv_line_import(<br />
array("Europe", "France", "Paris"),<br />
array(<br />
'import_format' => 'tree',<br /ta>
'vocabulary_id' => 2,<br />
'update_or_ignore' => 'update',<br />
));<br />
</code></div>
<p>Possible formats are explained in comments or <a href='#formats'>above</a>. Some may be unavailable.</p>
<p><a href="https://drupal.org/project/taxonomy_builder" title="Taxonomy Builder API">Taxonomy Builder API</a> can be more convenient in some cases. Choice depends on your needs. Taxonomy CSV is designed as a run-once module with checks and multiple formats, while Taxonomy Builder is a permanent, light and quick module. A full explanation of the differences in design can be found here.</p>
<h3 id="advanced_options"><a href="#contents">5. Advanced settings and hints</a></h3>
<h4 id="permissions"><a href="#contents">1. Permissions</a></h4>
<p>To import/export terms, user needs 'Import taxonomy by CSV' and 'Export taxonomy by CSV' rights.<br />
Furthermore, user needs general <a href="?q=admin/user/permissions#module-taxonomy" title="configure taxonomy permissions">taxonomy permissions (Drupal 6)</a> or <a href="?q=admin/people/permissions#module-taxonomy" title="configure taxonomy permissions">taxonomy permissions (Drupal 7)</a>. These permissions are often associated with access rights for administration pages.</p>
<h4 id="other_hints"><a href="#contents">2. Other hints</a></h4>
<ul>
<li>
It's recommended to use utf8 encoded file in order to avoid problems with non-ASCII terms.
</li>
<li>
Some memory or compatibility issues have been reported with some modules as <em>Pathauto</em>, <em>taxonomy_vtn</em> and <em>taxonomynode</em> (see <a href="https://drupal.org/node/495548">#495548</a>, <a href="https://drupal.org/node/447852">#447852</a> and <a href="https://drupal.org/node/540916">#540916</a>). It's advised to increase server and php memory temporary (no problem reported with 256 MB) or to disable these modules manually (settings aren't lost when you disable a module - and not uninstall it -). After import process, you can decrease memory and reactivate modules.
</li>
</ul>
<p>Another Drupal module allows CSV import too, despite its name: <a href="https://drupal.org/project/taxonomy_xml" title="Taxonomy XML module">taxonomy XML</a>. Its approach is different: it uses one file compliant to thesauri standard ISO 2788, i.e. a three columns csv file: <code>term, type of link (relation, description, synonym...), item</code>, or, for specialists, <code>subject, predicate, object</code>. Additional fields are managed as the third one.</p>
<p><a href="https://drupal.org/project/taxonomy_manager" title="Taxonomy manager">Taxonomy manager</a> can be used too.</p>
<p>For export, you can use Taxonomy XML too or one of backup modules. Taxonomy CSV is a more specialised tool which allows more precise tuning.</p>
<br />
</body>
</html>

View File

@@ -0,0 +1,27 @@
name = "Taxonomy CSV import/export"
description = "Export and import complete taxonomies, hierarchical structure or simple lists of terms and fields to or from a CSV file, url or text."
core = 7.x
dependencies[] = taxonomy
package = 'Taxonomy'
project ='taxonomy_csv'
version = '7.x-5.x-dev'
files[] = taxonomy_csv.install
files[] = taxonomy_csv.module
files[] = taxonomy_csv.api.inc
files[] = taxonomy_csv.vocabulary.api.inc
files[] = taxonomy_csv.term.api.inc
files[] = taxonomy_csv.result.inc
files[] = import/taxonomy_csv.import.admin.inc
files[] = import/taxonomy_csv.import.api.inc
files[] = import/taxonomy_csv.import.line.api.inc
files[] = import/taxonomy_csv.import.result.inc
files[] = export/taxonomy_csv.export.admin.inc
files[] = export/taxonomy_csv.export.api.inc
files[] = export/taxonomy_csv.export.result.inc
; Information added by drupal.org packaging script on 2012-02-22
version = "7.x-5.10"
core = "7.x"
project = "taxonomy_csv"
datestamp = "1329924048"

View File

@@ -0,0 +1,55 @@
<?php
/**
* @file
* Install, update and uninstall functions for the taxonomy_csv module.
*/
/**
* Implements hook_install().
*/
function taxonomy_csv_install() {
// Make uploads easy for csv files.
variable_set('upload_extensions_default', variable_get('upload_extensions_default', '') . ' csv');
drupal_set_message(filter_xss(st('Taxonomy CSV import/export has been installed. You can now import and export taxonomies, structures or lists of terms under <a href="!link_import">Administer > Structure > Taxonomy > CSV import</a> and <a href="!link_export">CSV export</a>. More information is available under <a href="!link_help">Administer > Help > Taxonomy CSV import/export</a>.<br /> Your comments are welcomed on the <a href=\"!link_module\">Taxonomy CSV import/export</a> module page.', array(
'!link_import' => url('admin/structure/taxonomy/csv_import'),
'!link_export' => url('admin/structure/taxonomy/csv_export'),
'!link_help' => url('admin/help/taxonomy_csv'),
'!link_module' => url('https://drupal.org/project/taxonomy_csv'),
))));
}
/**
* Implements hook_uninstall().
*/
function taxonomy_csv_uninstall() {
// Simple DB query to get the names of the variables of the module.
$results = db_select('variable', 'v')
->fields('v', array('name'))
->condition('name', 'taxonomy_csv_%', 'LIKE')
->execute();
foreach ($results as $result) {
variable_del($result->name);
}
drupal_set_message(st('Taxonomy csv import/export: All user preferences have been removed. Thanks for using this module!<br />
Your comments are welcomed on <a href="!link">Taxonomy CSV import/export</a> module page.', array(
'!link' => url('https://drupal.org/project/taxonomy_csv'),
)));
}
/**
* Implements hook_requirements().
*/
function taxonomy_csv_requirements($phase) {
$requirements = array();
$requirements['taxonomy_csv'] = array(
'title' => 'Taxonomy CSV import/export is enabled',
'value' => 'Taxonomy CSV is designed as a run-once setup or migration module. You may disable it once your imports and exports are processed.',
'severity' => REQUIREMENT_INFO,
);
return $requirements;
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* taxonomy_csv module for Drupal
*
* Copyright (c) 2007-2008 Dennis Stevense, see LICENSE.txt for more information
* Copyright (c) 2009-2012 Daniel Berthereau <daniel.drupal@berthereau.net>
*
* 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.
*/
/**
* @file
* Quick export and import of taxonomies, structure or lists of terms to or from
* a csv local or distant file or a text area.
*
* Automatically exports or imports a list or structure of terms from or into a
* vocabulary with a simple csv file. General infos can be found in README.txt.
* Technical infos can be found in TECHINFO.txt.
*
* taxonomy_csv.module manage general hooks of module.
*/
/**
* Implements hook_help().
*/
function taxonomy_csv_help($path, $arg) {
global $language;
switch ($path) {
case 'admin/structure/taxonomy/csv_import':
$output = '<p>' . t('Use this form to import a taxonomy, a structure or a list of terms into a vocabulary from a simple <a href="!link" title="Wikipedia definition">CSV</a> file, a url or a copy-and-paste text.', array(
'!link' => url('http://en.wikipedia.org/wiki/Comma-separated_values'),
)) . '</p>'
. '<ul>'
. '<li>' . t('For performance reasons, it is recommended to disable some other taxonomy related modules before import of big taxonomies and to reactivate them after process.') . '</li>'
. '<li>' . t('For a better user experience, it is recommended to avoid duplicate terms. This module can manage them efficiently, but hidden errors can occur when a complex vocabulary with duplicates is updated by the administrator or by the module.') . '</li>'
. '<li>' . '<strong>' . t('Warning') . '</strong>' . ': ' . t('If you want to update an existing vocabulary, make sure you have a backup before you proceed so you can roll back, if necessary.') . '</li>'
. '</ul>'
. theme('more_help_link', array('url' => 'admin/help/taxonomy_csv')) . '<br />';
return $output;
case 'admin/structure/taxonomy/csv_export':
$output = '<p>' . t('Use this form to export a taxonomy, a structure or a list of terms to a simple <a href="!link" title="Wikipedia definition">CSV</a> file.', array(
'!link' => url('http://en.wikipedia.org/wiki/Comma-separated_values'),
)) . '</p>'
. '<p>' . t('Set vocabulary to export in first tab, format to use in second tab and order of terms in third tab.') . '</p>'
. theme('more_help_link', array('url' => 'admin/help/taxonomy_csv')) . '<br />';
return $output;
case 'admin/help#taxonomy_csv':
$check = drupal_realpath(drupal_get_path('module', 'taxonomy_csv') . '/taxonomy_csv.help.' . $language->prefix . '.html');
$output = file_get_contents($check ? $check : drupal_realpath(drupal_get_path('module', 'taxonomy_csv') . '/taxonomy_csv.help.html'));
return $output;
}
}
/**
* Implements hook_permission().
*/
function taxonomy_csv_permission() {
return array(
'import taxonomy by csv' => array(
'title' => t('Import taxonomy by CSV'),
),
'export taxonomy by csv' => array(
'title' => t('Export taxonomy by CSV'),
),
);
}
/**
* Implements hook_menu().
*/
function taxonomy_csv_menu() {
$items = array();
$items['admin/structure/taxonomy/csv_import'] = array(
'title' => 'CSV import',
'description' => 'Import taxonomies, hierarchical structure or simple lists of terms and properties with CSV file or text.',
'page callback' => 'drupal_get_form',
'page arguments' => array('taxonomy_csv_import_form'),
'access arguments' => array('import taxonomy by csv'),
'weight' => 12,
'type' => MENU_LOCAL_TASK,
'file' => 'import/taxonomy_csv.import.admin.inc',
);
$items['admin/structure/taxonomy/csv_export'] = array(
'title' => 'CSV export',
'description' => 'Export terms and properties to a CSV file.',
'page callback' => 'drupal_get_form',
'page arguments' => array('taxonomy_csv_export_form'),
'access arguments' => array('export taxonomy by csv'),
'weight' => 13,
'type' => MENU_LOCAL_TASK,
'file' => 'export/taxonomy_csv.export.admin.inc',
);
return $items;
}

View File

@@ -0,0 +1,282 @@
<?php
/**
* @file
* Manage messages on results of import or export process.
*/
/**
* Helper to determine drupal_set_message type level from Drupal watchdog level.
*
* As drupal_set_message uses only three types, a mapping is needing when we
* have only a watchdog level.
* @see bootstrap.inc
*
* @param $watchdog_level
* Integer. Watchdog level as defined in bootstrap.inc.
*
* @return
* drupal_set_message type level.
*/
function _taxonomy_csv_message_watchdog_type($watchdog_level) {
$mapping = array(
WATCHDOG_ERROR => 'error', // Stop import process.
WATCHDOG_WARNING => 'error', // Stop line process and go next.
WATCHDOG_NOTICE => 'warning', // Continue current line process.
WATCHDOG_INFO => 'status', // Successfully processed.
WATCHDOG_DEBUG => 'status', // Internal use only.
);
return $mapping[$watchdog_level];
}
/**
* Helper to determine error level of a message code.
*
* @param $message_code
* Single message code (000 to 799).
*
* @return
* Level (0 to 7).
*/
function _taxonomy_csv_message_get_level($message_code) {
$result = intval($message_code / 100);
return ($result >= WATCHDOG_ERROR && $result <= WATCHDOG_DEBUG) ?
$result :
WATCHDOG_ERROR;
}
/**
* Helper to display result messages.
*
* @param $messages
* Array of messages array. A message array contains a watchdog level and a
* message code. A message code is an integer between 000 and 799.
*
* @return
* Nothing.
*/
function _taxonomy_csv_message_result($messages) {
foreach ($messages as $message) {
$type = key($message);
drupal_set_message($message[$type], _taxonomy_csv_message_watchdog_type($type));
}
}
/**
* Helper to display by line list of result messages.
*
* @param $messages_set
* Array of message codes by line.
* Message code is an integer between 000 and 799.
*
* @return
* Array of messages.
*/
function _taxonomy_csv_message_result_by_line($messages_set) {
$messages = array();
if (is_array($messages_set) && $messages_set) {
foreach ($messages_set as $line_number => $message_codes) {
$processed_message_level = _taxonomy_csv_message_get_level(_taxonomy_csv_worst_message($message_codes));
$processed_message_text = t('Line #!line_number:', array('!line_number' => $line_number)) . '<br />';
foreach ($message_codes as $message_code) {
$processed_message_text .= _taxonomy_csv_message_text($message_code) . '<br />';
}
$messages[] = array($processed_message_level => $processed_message_text);
}
}
return $messages;
}
/**
* Helper to display compact list of result messages.
*
* @param $messages_set
* Array of operation array of codes, except 691, 692 and 695.
* Message code is an integer between 000 and 799.
*
* @return
* Array of messages.
*/
function _taxonomy_csv_message_result_by_message($messages_set) {
$messages = array();
if (is_array($messages_set) && $messages_set) {
// Convert [line number][message codes]
// to [message codes][line number].
foreach ($messages_set as $line_number => $operation) {
foreach ($operation as $message_code) {
$list_messages[$message_code][] = $line_number;
}
}
ksort($list_messages);
foreach ($list_messages as $message_code => $line_numbers) {
// Only show line level message with created, updated and unchanged terms.
if (($message_code != 691) && ($message_code != 692) && ($message_code != 695)) {
$processed_message_level = _taxonomy_csv_message_get_level($message_code);
$processed_message_text = _taxonomy_csv_message_text($message_code) . ' ' . t('Lines:') . '<br />' . implode(', ', $line_numbers) . '.';
$messages[] = array($processed_message_level => $processed_message_text);
}
}
}
return $messages;
}
/**
* Helper to get text of a message with a message code.
*
* @param $message_code
* Message codes are integer between 000 and 799.
*
* @return
* Message text string.
*/
function _taxonomy_csv_message_text($message_code) {
// Static used only for performance reason.
static $watchdog_messages;
if (!isset($watchdog_messages)) {
// These variables are used to simplify strings management and translation.
$error = ' ' . t("Import process is stopped.");
$warning = ' ' . t("This line won't be processed.");
$notice = ' ' . t("Line is processed.");
$watchdog_messages = array(
// Format: Level, Type of import, Serial.
300 => t('ERROR'),
301 => t("Wrong code. Module is not installed correctly. Please reinstall module from a fresh release or submit an issue."),
302 => t('Unreferenced code. Please reinstall module from a fresh release or submit an issue.'),
305 => t('Error in options.'),
306 => t('Unknown import format. Change "Source content" option.') . $error,
307 => t('Unknown export format. Change "Export format" option.') . $error,
310 => t("You choose to import a taxonomy by a file, but you don't set its name or its size is greater than the server's limit.") . $error,
311 => t('Size of your file is null.') . $error,
312 => t('Unable to write to file.') . $error,
313 => t('Current line is multiline or contains delimiter, enclosure or unprotected line ending. Change the content of the term, or set appropriate format settings in second tab.'),
320 => t("Your file can't be converted to utf-8. Please install iconv, GNU recode or mbstring for PHP or convert your file to utf-8 and disable 'Enable utf-8 conversion' option.") . $error,
321 => t('Despite your option, your file is not recognize as an UTF-8 encoded one. Convert it before submit it.') . $error,
340 => t("Vocabulary doesn't exist."),
341 => t("Field doesn't exist in this vocabulary.") . $error,
352 => t("A parent tid is given, but it hasn't been imported in a previous line.") . $error,
360 => t('An error occurs during translation of term.') . $error,
361 => t('You cannot import translated terms into a vocabulary without language or with a fixed language.') . $error,
385 => t('No term to export.'),
390 => t('No line to import. Please first check your file and file uploading, else reinstall module from a fresh release or submit an issue.') . $error,
391 => t('No term to import. Please first check your file and file uploading, else reinstall module from a fresh release or submit an issue.') . $error,
392 => t('Problem when caching imported term. Please first check your file and file uploading, else reinstall module from a fresh release or submit an issue.') . $error,
400 => t('WARNING'),
402 => t('Unable to import a field term.') . $warning,
405 => t('Unmanaged option.') . $warning,
408 => t('Number of terms is invalid. Check if all your terms are attached to an existing vocabulary. The module exports only good terms and skips the others automatically.'),
410 => t("Impossible to get parent of first item, because previous line has less parent(s). You may add one or more parents to current line or change lines order.") . $warning,
430 => t('Line contains empty items.') . $warning,
431 => t('Line contains duplicate items.') . $warning,
432 => t('Line contains an empty term. Only items before this one have been imported.'),
433 => t("Line contains items matching term name. A term can't be synonym, related, parent or child of itself.") . $warning,
434 => t('Line contains too many items.') . $warning,
435 => t('Line contains a translation for an empty description.') . $warning,
440 => t("Vocabulary doesn't exist. When you duplicate or import into an existing vocabulary, option 'vocabulary_id' should contains true vocabulary id.") . $warning,
450 => t('Weight in second column is not an allowed number.') . $warning,
451 => t('One or more items are not numbers.') . $warning,
452 => t('A term cannot be a parent of itself.') . $warning,
453 => t('A term has no semantic field parent.'),
454 => t('Term name or id is longer than 255 characters. Check field and delimiter.') . $warning,
455 => t('Unable to make a reference to term "0".') . $warning,
460 => t('Unknown predicate.') . $warning,
461 => t('No subject.') . $warning,
462 => t('No predicate.') . $warning,
463 => t('No object.') . $warning,
464 => t('No name.') . $warning,
465 => t('More than three items.') . $warning,
466 => t('Unmanaged predicate.') . $warning,
467 => t('Each semantic field should be imported as "vocabulary" or "root_term".') . $warning,
480 => t('No first column term to import. Empty first column is allowed only with structure or multiple terms import.') . $warning,
481 => t('No item in second or third column.') . $warning,
482 => t("Some items aren't present.") . $warning,
483 => t('Term without name. Name is the only needed field.') . $warning,
484 => t('No first or second column. They are required.') . $warning,
490 => t('No line to import.') . $warning,
491 => t('No item to import.') . $warning,
492 => t('Nothing to import.') . $warning,
493 => t('Empty line.') . $warning,
499 => t('Warnings have been reported on this line.'),
500 => t('Notice'),
501 => t('Too many items. Second and next columns will be ignored, as import choice is to ignore them.'),
510 => t('Line contains empty items. They are ignored.'),
511 => t('To change vocabulary of a term is not recommended.'),
512 => t("Line contains items matching first column term. A term can't be related to itself and a synonym may be different to it.") . ' ' . t('Duplicates will be ignored.'),
513 => t('Line contains empty items.'),
520 => t('Unable to load the file attached to the term.'),
530 => t('Line contains empty items. They will be ignored.'),
531 => t('Line contains duplicate items or repeated soft tabs.') . ' ' . t('Duplicates or repeated soft tabs will be ignored.'),
532 => t('Line contains duplicate items.') . ' ' . t('Duplicates are allowed.'),
533 => t("Line contains items matching term name. A term can't be related, parent, child or synonym to itself.") . ' ' . t('Duplicates will be ignored.'),
535 => t('Line contains duplicate synonyms.') . ' ' . t('Duplicates will be ignored.'),
536 => t('Line contains duplicate parents.') . ' ' . t('Duplicates will be ignored.'),
537 => t('Line contains duplicate children.') . ' ' . t('Duplicates will be ignored.'),
538 => t('Line contains duplicate related terms.') . ' ' . t('Duplicates will be ignored.'),
541 => t('Too many items. Third and next columns will be ignored, as a term gets only one description and one weight.'),
542 => t('Too many items. Fifth and next columns will be ignored.'),
550 => t("No first column, but line can be processed."),
551 => t('No parent, so it is a root term.'),
552 => t('Term has been already imported in a previous line.'),
553 => t('A semantic field cannot be related to terms.'),
554 => t('A term has many parents and one is a semantic field, what is impossible.'),
555 => t('A root term has a parent.') . $notice,
557 => t('A term cannot be related to a semantic field'),
558 => t('A semantic field cannot be a term synonym.'),
559 => t('A term can have only one semantic field.'),
561 => t('No subject.') . $notice,
562 => t('No predicate.') . $notice,
563 => t('No object.') . $notice,
564 => t('Too many items on the line for the selected format. These items will be ignored.') . $notice,
565 => t('Some items are empty. Previous items will be used.') . $notice,
570 => t("Line doesn't contain all needed items.") . $notice,
580 => t('Be careful. This line has only one term and import choice is to replace existing items. So these items will be removed.'),
600 => t('Info'),
605 => t('No error in options.'),
610 => t('New vocabulary has been created.'),
611 => t('A vocabulary has been duplicated.'),
632 => t('Line contains duplicate items.') . ' ' . t('Duplicates are allowed.'),
639 => t('Line contains empty vocabulary for related terms. They will be created in vocabulary of main term.'),
640 => t('Vocabulary checked.'),
662 => t('No predicate.'),
683 => t('Use of a previous line term.'),
685 => t('No term to process.'),
691 => t('Saved new term.'),
692 => t('Updated term.'),
693 => t('Removed existing term.'),
694 => t('Updated new term.'),
695 => t('Unchanged term.'),
696 => t('Empty line.'),
697 => t('Command line.'),
698 => t('Comment line.'),
699 => t('Items of the line have been successfully imported.'),
700 => t('Debug.'),
799 => t('No message.'),
);
}
if (is_int($message_code) && ($message_code >= 0) && ($message_code <= 799)) {
// Good and referenced code.
if (isset($watchdog_messages[$message_code])) {
$message_title = $watchdog_messages[intval($message_code / 100) * 100];
return $message_title . ' : ' . $watchdog_messages[$message_code];
}
// Else unreferenced code.
return $watchdog_messages[300] . ' : (' . $message_code . ') : ' . $watchdog_messages[302];
}
// Else it's a wrong code.
return $watchdog_messages[300] . ' : (' . $message_code . ') : ' . $watchdog_messages[301];
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* @file
* Find, get and set full or detail term items.
*/
/**
* Find a term by its name and load it. This function manages duplicates.
*
* If the term has got duplicates, only first one (lower tid) will be returned.
*
* @note
* Need to maintain a specific function and a direct query, because
* taxonomy_term_load_multiple doesn't manage parents and duplicates.
* db_query() is prefered, because it's four times faster than db_select(),
* EntityFieldQuery and taxonomy_get_term_by_name() (these last two don't manage
* parents'). Anyway terms are loaded to get all fields, specialy parents.
*
* @param $term
* The term object to find. It's not necessarily a standard term object. It's
* an object which needs only a name and eventually a vid, a parent id and a
* language. Of course, if tid is set, the found term is the existing one.
* @param $all_vocabularies
* (Optional) Boolean. Search in all vocabularies or only in $term->vid
* vocabulary (default), which need to be set.
* @param $parent_tid
* (Optional) The direct parent term id where to restrict search.
* Used for structure import. Default to NULL (no parent restriction).
*
* @return
* Formatted found term object, or FALSE if not found or error.
*/
function taxonomy_csv_term_find($term, $all_vocabularies = FALSE, $parent_tid = NULL) {
if (isset($term->tid) && $term->tid) {
return taxonomy_term_load($term->tid);
}
static $flag_i18n = NULL;
if (is_NULL($flag_i18n)) {
$flag_i18n = module_exists('i18n_taxonomy');
}
if (isset($term->name)) {
$name = drupal_strtolower(trim($term->name));
// Only term id is selected, because taxonomy_term_load is used next in
// order to take advantage of taxonomy cache.
$sql = '
SELECT t.tid
FROM {taxonomy_term_data} t
INNER JOIN {taxonomy_term_hierarchy} h ON t.tid = h.tid
WHERE :name LIKE LOWER(t.name)
';
$args = array();
$args[':name'] = $name;
if (isset($term->vid)
&& $term->vid
&& !$all_vocabularies) {
$sql .= ' AND t.vid = :vid';
$args[':vid'] = $term->vid;
}
if ($flag_i18n && isset($term->language)) {
$sql .= ' AND t.language = :language';
$args[':language'] = $term->language;
}
if ($parent_tid) {
$sql .= ' AND h.parent = :parent';
$args[':parent'] = $parent_tid;
}
$sql .= ' ORDER BY t.tid ASC LIMIT 1';
$result = db_query($sql, $args)->fetchField();
if ($result) {
return taxonomy_term_load($result);
}
}
// Not found or error.
return FALSE;
}
/**
* Find duplicate terms in a vocabulary or in all vocabularies.
*
* @todo
* Use taxonomy_term_load_multiple or regular Drupal 7 query.
*
* @param $vid
* (Optional) Vocabulary to check in.
*
* @return
* An array of term names, indexed by tid.
*/
function taxonomy_csv_term_find_duplicate($vid = 0) {
$terms = array();
$sql = '
SELECT t1.tid, t1.name
FROM {taxonomy_term_data} t1
LEFT OUTER JOIN {taxonomy_term_data} t2 ON t1.tid != t2.tid AND LOWER(t1.name) = LOWER(t2.name)
WHERE t2.tid IS NOT NULL
';
$args = array();
if ($vid) {
$sql .= ' AND t1.vid = :vid AND t2.vid = :vid ';
$args[':vid'] = $vid;
}
$sql .= ' ORDER BY t1.tid ASC ';
$result = db_query($sql, $args)->fetchAllKeyed();
return $result;
}
/**
* Return the first path to the root of a term.
*
* @note
* Drupal and taxonomy_csv use 'parent' property, but taxonomy_get_tree() uses
* 'parents'.
*
* @param $term
* A term object with 'parent' property.
* @param $tree
* A tree array as obtained with taxonomy_get_tree().
*
* @return
* Array of term objects matching to the path of a term to its root term.
* If a term is a root term, return an empty array.
*/
function taxonomy_csv_term_get_first_path($term, &$tree) {
$path = array();
// Items need to be ordered from 0 to get first parent easy.
if (isset($term->parent)) {
$term->parent = array_values($term->parent);
}
// Sometime, taxonomy_term_load() return a 'parents', not a 'parent'.
elseif (isset($term->parents)) {
$term->parent = $term->parents;
unset($term->parents);
}
// To use a counter prevents infinite loop when the hierarchy is inconsistent.
$i = 0;
while ($i < 100
// A term root has no parent.
&& isset($term->parent)
&& !empty($term->parent)
&& $term->parent[0] <> 0
) {
$tid = $term->parent[0];
if ($tid === 0) {
break;
}
// Get the full term from the tree.
foreach ($tree as $parent) {
if ($parent->tid == $tid) {
break;
}
}
if (isset($parent->parents)) {
$parent->parent = array_values($parent->parents);
unset($parent->parents);
}
$path[] = $term = $parent;
$i++;
}
// The path is reversed in order to begin with root term.
return array_reverse($path);
}
/**
* Delete multiple terms.
*
* @param $tids
* An array of taxonomy term IDs.
*
* @return
* TRUE.
*/
function taxonomy_csv_term_delete_multiple($tids) {
if (!is_array($tids)) {
return FALSE;
}
foreach ($tids as $tid) {
taxonomy_term_delete($tid);
}
return TRUE;
}

View File

@@ -0,0 +1,226 @@
<?php
/**
* @file
* Prepare and manage vocabularies.
*/
/**
* Creates vocabulary by its name and returns vocabulary object.
*
* @param $name
* (Optional) Name of vocabulary to create.
*
* @return
* Created vocabulary object.
*/
function taxonomy_csv_vocabulary_create($name = '') {
$name = _taxonomy_csv_vocabulary_name_create($name);
// Create an empty vocabulary with default Drupal 7 fields.
// Hierarchy is updated later.
$vocabulary = (object) array(
'name' => $name,
'machine_name' => taxonomy_csv_vocabulary_machine_name_create($name),
'description' => t('Vocabulary created automatically by Taxonomy csv import/export module'),
'hierarchy' => 2,
'module' => 'taxonomy',
'weight' => 0,
);
$result = taxonomy_vocabulary_save($vocabulary);
return $vocabulary;
}
/**
* Helper to create an unused vocabulary name from a string.
*/
function _taxonomy_csv_vocabulary_name_create($name = '') {
$name = preg_replace('/.csv$/', '', trim(basename(strval($name))));
$name = (drupal_strlen($name) == 0) ?
t('Auto created vocabulary') :
// Limit to 250 characters.
drupal_substr($name, 0, 250);
// Invent a unused vocabulary name.
if (taxonomy_csv_vocabulary_name_check($name)
|| taxonomy_csv_vocabulary_machine_name_check(taxonomy_csv_vocabulary_machine_name_create($name))) {
for (
$i = 2;
(taxonomy_csv_vocabulary_name_check("$name $i"))
|| taxonomy_csv_vocabulary_machine_name_check(taxonomy_csv_vocabulary_machine_name_create("$name $i"));
$i++) {
}
$name = "$name $i";
}
return $name;
}
/**
* Creates a machine name from a string.
*
* The name is created by replacing non alphanumeric character by an underscore.
* Machine name is defined as first 16 cleaned characters of name and a random
* five characters serial. Fields module prepends 'taxonomy_' to name and check
* if total lenght is 21 characters max.
*
* @param $name
* The string to process.
*
* @return
* The processed string.
*/
function taxonomy_csv_vocabulary_machine_name_create($name) {
// Get last vid.
$vid = 1 + db_query('SELECT max(vid) FROM {taxonomy_vocabulary}')->fetchField();
$machine_name = drupal_substr(preg_replace('/_+/i', '_', preg_replace('/[^a-z0-9\\_]/i', '_', drupal_strtolower(trim(strval($name))))), 0, 16) . $vid . '_' . strval(rand(10000, 99999));
return drupal_substr($machine_name, 0, 21);
}
/**
* Checks if a name is a vocabulary machine_name.
*/
function taxonomy_csv_vocabulary_machine_name_check($name) {
return (taxonomy_vocabulary_machine_name_load($name) != FALSE);
}
/**
* Check if a name is a vocabulary name.
*/
function taxonomy_csv_vocabulary_name_check($name) {
return (taxonomy_vocabulary_load_multiple(FALSE, array('name' => $name)) != FALSE);
}
/**
* Return an array of all term ids of a given vocabulary.
*
* @param $vid
* The vocabulary id from where to fetch term ids.
*
* @return
* Array of term ids.
*/
function taxonomy_csv_vocabulary_get_tids($vid) {
// Tids are available in drupal_static('taxonomy_get_tree:terms'), but we
// prefer to use an entity query to avoid issue with cache, if not updated.
$query = new EntityFieldQuery;
$query
->entityCondition('entity_type', 'taxonomy_term')
->propertyCondition('vid', $vid)
;
$result = $query->execute();
return (isset($result['taxonomy_term'])) ?
array_keys($result['taxonomy_term']) :
array();
}
/**
* Return an array of all full terms of a given vocabulary.
*
* @param $vid
* The vocabulary id from where to fetch term ids.
*
* @return
* Array of full term.
*/
function taxonomy_csv_vocabulary_get_terms($vid) {
$result = taxonomy_csv_vocabulary_get_tids($vid);
return taxonomy_term_load_multiple($result);
}
/**
* Calculate number of terms in a vocabulary or in all vocabularies.
*
* @param $vocabulary_id
* (Optional) Id or array of ids of the chosen vocabularies. If not specified,
* count terms in all vocabularies.
*
* @return
* Number of terms in specified vocabularies or in all vocabularies.
*/
function taxonomy_csv_vocabulary_count_terms($vocabulary_id = 0) {
if (!is_array($vocabulary_id)) {
$vocabulary_id = array($vocabulary_id);
}
$sql = "
SELECT COUNT(*)
FROM {taxonomy_term_data}
";
$args = array();
if (($vocabulary_id != array(0)) && ($vocabulary_id != array('0'))) {
$sql .= ' WHERE vid IN (:vid) ';
$args[':vid'] = $vocabulary_id;
}
$result = db_query($sql, $args)->fetchField();
return $result;
}
/**
* Add or create a field to attach to a vocabulary.
*
* @param $vocabulary_machine_name
* Vocabulary machine_name.
* @param $field
* Field array to attach.
*
* @return
* TRUE if success, FALSE else.
*/
function taxonomy_csv_vocabulary_field_attach($vocabulary_machine_name, $field) {
// Check if vocabulary exist.
$vocabulary = taxonomy_vocabulary_machine_name_load($vocabulary_machine_name);
if ($vocabulary) {
// Check if field exists in order to create or to update it.
// No other check is made.
$prior_field = field_info_field($field['field_name']);
// The field doesn't exist, so creates it.
if (empty($prior_field)) {
$field = field_create_field($field);
}
// The field exists: check if an update is needed. Update is needed only
// with taxonomy_term_reference, because this type requires to use specific
// vocabularies.
elseif ($field['type'] == 'taxonomy_term_reference') {
$flag = FALSE;
foreach ($prior_field['settings']['allowed_values'] as $allowed_values) {
// Don't add new allowed values if they exist already.
if ($allowed_values == $field['settings']['allowed_values'][0]) {
$flag = TRUE;
break;
}
}
if (!$flag) {
$prior_field['settings']['allowed_values'][] = $field['settings']['allowed_values'][0];
$result = field_update_field($prior_field);
}
$field = field_info_field($field['field_name']);
}
// The field exists and doesn't need to be updated.
// Field is already created, so use it.
else {
$field = $prior_field;
}
// Check if field is already instanced to vocabulary so attach it if needed.
$prior_instance = field_info_instance('taxonomy_term', $field['field_name'], $vocabulary->machine_name);
if (empty($prior_instance)) {
$result = field_create_instance(array(
'field_name' => $field['field_name'],
'entity_type' => 'taxonomy_term',
'bundle' => $vocabulary->machine_name,
'label' => (isset($field['label']) ? $field['label'] : $field['field_name']),
'description' => (isset($field['description']) ? $field['description'] : ''),
));
}
return TRUE;
}
return FALSE;
}

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,172 @@
README - TAXONOMY MANAGER
**************************
SHORT PROJECT DESCRIPTION
--------------------------
This module provides a powerful interface for managing vocabularies of the taxonomy module.
It's especially very useful for long sets of vocabularies.
Features:
* dynamic tree view
* mass deleting
* mass adding of new terms
* moving of terms in hierarchies
* merging of terms (Term merge module)
* fast weight changing with up and down arrows (and AJAX saving)
* AJAX powered term editing form
* simple search interface
REQUIREMENTS
------------
- Taxonomy module enabled
- JavaScript enabled in your browser
- a user with 'administer taxonomy' permission
INSTALLATION
------------
1. Place the entire taxonomy_manager directory into your Drupal sites/all/modules/ directory.
2. Enable the taxonomy manager module by navigating to:
administer > site building > modules
UPGRAGE to 7.x
---------------
The table 'taxonomy_manager_merge' is deprecated and won't be used by now. This table stores
which terms were merged into which destination term. If you do not need this information, you
can manually remove this table.
USING THE TAXONOMY MANAGER
--------------------------
To use the Taxonomy Manager go to administer > content management > taxonomy manager. This page
contains a list of all available vocabularies. By clicking at one of the vocabularies, you get
redirected to the Taxonomy Manager interface, where you can edit the whole tree structure and
terms.
If you want to edit any general vocabulary settings or if you want to create a new one, go to
the categories (administer > content management > categories) page.
The interface contains a search bar, a toolbar with some operations, a tree view and if a term
gets selected a form for editing the term data.
The following lines describe all operations and some terminology.
- Tree View
The tree view shows all terms of the vocabulary with their hierarchical relations. If your
list of terms gets very long, there is a paging mechanism included with a page size of 50 terms.
If you are having hierarchical vocabularies, all parent terms have a plus symbol, which
means you can expand them to show their child terms. Use the minus symbol to collapse
them again.
In multiple hierarchies, if one term has more parents, the term gets shown under
each of its parents.
- Adding of terms
For adding new term, click on the 'Add' Button. A fieldset containing some textfields expands.
If you want to close this fieldset, click 'Cancel'.
To insert a new term, fill in any textfield. Each textfield can only contain one term.
You don't have to fill in all textfields, they can be left empty.
Depending on your hierarchy settings, it's possible to insert terms and to directly assign
a parent to them. If you want to do this, select a parent term in the tree view by marking
the checkbox. If you have multiple hierarchies enabled, it's even possible to assign the
new inserted terms to more parents at once by selecting more terms in the tree view.
- Weight Editing
Every term has a weight. This weight determines the position the terms get listed. If terms
have the same weight, they are ordered alphabetically.
If you want to change the weight, you have 3 ways to do that.
1st way: select the terms you want to move by one position (can be more terms at once) and press
either the up or the down button in the toolbar. All saving is done automatically through
AJAX.
2nd way: every term in tree view has a mouseover effect. When you move your mouse over a term, two
small up and down arrows will appear. Click them to move this term by one
position.
3rd way: click on the term, where you want to change the weight. A form for editing the
term data appears on the right side of the tree view. At the bottom of this
form, there is a select field, which shows the current weight. By changing the
value, the tree view gets automatically reordered and the values are saved to the
database through AJAX.
- Deleting
If you want to delete terms from the vocabulary, select them by marking the checkbox and click
the 'Delete' button. A fieldset, where you have to confirm the deletion, expands.
For hierarchical vocabularies (single or multi), the fieldset contains an option, which says:
'Delete children of selected, if there are any'. Check this if you want to delete all children
of a selected parent term. Otherwise, if you are deleting the last parent of terms, the terms
get added to root level.
- Moving
This operation is only available in hierarchical (single or multiple) vocabularies. It allows
you to change hierarchies by moving terms from one parent to one other.
Select all terms you want to move by marking the checkbox. Click the 'Move' button. A fieldset with
some options expands.
This fielset contains a autocomplete field, where you have to determine the parent term (under which
the terms should be moved). If you want to move terms to the root level, leave this field empty.
This autocomplete form allows you to either choose a parent term from the list of exisitng terms
or to insert a new terms, which will be used as parent (this parent term will be added to the root
level).
In multiple hierarchical vocabularies, it's possible to move terms to more parents in one step by
inserting more terms into the autocomplete field and separating them by commas. Additional, there
appears an option ('Keep old parents and add new one'), which prevents the replacing of old parents.
- Merging
With the merging action, you can put terms with the same meaning together (e.g. your vocabulary
contains: SoC, Summer of Code, GSoC, Google Summer of Code). All terms, that get merged into
one other, get synonyms of resulting term (here called merged or main term). Additional
all term-node association gets automatically updated (this means nodes, that had a merging term
assigned, now get the resulting merged term instead). All merging terms are deleted afterwards.
In the Taxonomy Manager, you can do that by selecting all terms you want to merge and to click
the 'Merge' button. A fieldset with an autocomplete field an some options expands. In the
autocomplete field you have to specify the resulting merged term (into which the selected get merged).
The merged term can be either chosen from the list of existing terms or can be inserted automatically
and used as merged term.
Additional, there are some options available (they depend on the vocabulary settings). If you want
to add any kind of relations (parents, children, related terms) from the merging terms to the
resulting merged term, select one (or more) of them.
The default taxonomy term page, which shows all assigned nodes, is overriden by the Taxonomy
Manager, so that former merged terms can be considered (if someone calls a term, that was merged,
it redirects to the resulting merged term).
NOTE: At the moment, the Taxonomy Manager only cares about the term-node association inserted
into the term_node table (by the taxonomy module). If you are using any CCK modules, like
CCK Taxonomy or Content Taxonomy, which (can) save the term - node association in cck tables,
don't use the Merging action, because changes are not handled.
If you are using Views filters instead of the default taxonomy term page, merged terms are
either respected.
If you want to customize this by yourself or have some other module, you can use following
function taxonomy_manager_merge_get_main_term($tid) for getting the main term id (if there
is any main term, else return 0). The term merge history gets saved in the
taxonomy_manager_merge table (main_tid, merged_tid) and gets additional cached, so that
checking for a merged terms causes nearly no performance loss.
- Editing term data
If you want to edit or read some term properties, click on the term. A fieldset on the right side
of the tree view gets loaded. This contains all term related information and can be edited. If you
want to change the term name or the description, fill in any changes you want and click the saving
symbol. All saving is done through AJAX, so no reload is necessary.
Additional, this page contains listing of synonyms, related terms and parents (depends on your
vocabulary settings).
Every listed entry has an delete operation. By clicking the delete symbol, the relation gets deleted.
In case of synonyms, the names get deleted from the database. If you are deleting a related term or a
parent, this doesn't delete the term itself, only the relation.
For adding new synonyms, the listing has a textfield below. Insert there any new synonym and click the
plus symbol.
For adding a new related term or a new parent (if multi hierarchy), there is a autocomplete field below
the listing. Use this to insert new terms or to choose existing ones and assign them to the current term.
- Using the search
At the top of the page, there is a collapsed fieldset, called 'Search'. This search allows you to
directly select an existing term for editing. Else, if your input doesn't match an existing term,
the value will be used for filtering root level terms (this doesn't affect any child term).
AUTHOR
------
Matthias Hutterer
User: mh86@drupal.org
Email: m_hutterer@hotmail.com

View File

@@ -0,0 +1,571 @@
/**
* Taxonomy Manager Tree formating
*/
#taxonomy-manager-double-tree-form,
#taxonomy-manager-form {
line-height: 120%;
}
#taxonomy-manager-double-tree-form #edit-jump,
#taxonomy-manager-form #edit-jump {
float: right;
margin-bottom: 2px;
}
#edit-toolbar,
#edit-add {
clear: both;
}
#taxonomy-manager-double-tree-form fieldset,
#taxonomy-manager-form fieldset {
margin: 0.5em 0;
}
#taxonomy-manager-double-tree-form .left,
#taxonomy-manager-form .left {
margin-left: 0;
}
#taxonomy2-manager-tree-outer-div,
#taxonomy-manager-tree-outer-div {
overflow: auto;
width: 46%;
}
#taxonomy2-manager-tree-outer-div fieldset,
#taxonomy-manager-tree-outer-div fieldset {
margin: 0;
padding: 0 5px;
}
.taxonomy-manager-tree-size {
padding: 0px;
margin: 0px;
/*height: 7px;*/
text-align: right;
display: inline;
}
.taxonomy-manager-tree-size img {
cursor: pointer;
}
#taxonomy2-manager-tree-wrapper,
#taxonomy-manager-tree-wrapper {
margin-top: 0em;
}
/* address .treeview additional with #taxonomy-manager to make it compatible with the admin module */
#taxonomy-manager .treeview,
.treeview,
.treeview ul {
padding: 0;
margin: 0;
list-style: none;
}
#taxonomy-manager .treeview .form-item,
.treeview .form-item {
margin: 0;
padding: 0;
display: inline;
}
#taxonomy-manager .treeview li,
.treeview li {
margin: 0;
padding: 0 0 0 15px;
}
#taxonomy-manager .hitArea,
.hitArea {
float: left;
height: 15px;
width: 15px;
margin-left: -15px;
cursor: pointer;
_background: #fff;
_filter: alpha(opacity=0);
_display: inline;
}
#taxonomy-manager .treeview li,
.treeview li {
background: url(../images/tv-item.gif) 0 0 no-repeat;
}
#taxonomy-manager .treeview .collapsable,
.treeview .collapsable {
background-image: url(../images/tv-collapsable.gif);
}
#taxonomy-manager .treeview .expandable,
.treeview .expandable {
background-image: url(../images/tv-expandable.gif);
}
#taxonomy-manager .treeview .last,
.treeview .last {
background-image: url(../images/tv-item-last.gif);
}
#taxonomy-manager .treeview .lastCollapsable,
.treeview .lastCollapsable {
background-image: url(../images/tv-collapsable-last.gif);
}
#taxonomy-manager .treeview .lastExpandable,
.treeview .lastExpandable {
background-image: url(../images/tv-expandable-last.gif);
}
.term-line {
display: inline;
}
.term-item {
display: inline;
padding-right: 5px;
}
.term-has-more-siblings {
height: 38px;
cursor: pointer;
background-image: url(../images/2downarrow.png);
background-repeat: no-repeat;
}
.term-next-count {
display: inline;
color: #4ca108;
line-height: 38px;
padding-left: 38px;
}
.term-operations {
display: inline;
padding-left: 5px;
}
.term-operations span.select-all-children {
text-decoration: none;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-image: url(../images/select-all-children.png);
}
.term-operations span.deselect-all-children {
text-decoration: none;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-image: url(../images/deselect-all-children.png);
}
.term-operations img.term-up,
.term-operations img.term-down,
.term-operations span.select-all-children,
.term-operations span.deselect-all-children,
.term-operations a img {
cursor: pointer;
vertical-align: bottom;
}
.term-operations a:link,
.term-operations a:visited,
.term-operations a:hover,
.term-operations a:active {
text-decoration: none;
}
div.highlightActiveTerm div.form-item {
background-color:#dddddd;
}
.highlightActiveTerm a {
font-weight: bold;
}
span.taxonomy-manager-select-helpers {
padding-left: 5px;
}
span.taxonomy-manager-select-helpers span {
padding-right: 2px;
cursor: pointer;
}
span.taxonomy-manager-select-helpers span.select-all-children {
text-decoration: none;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-image: url(../images/select-all-children-bw.png);
}
span.taxonomy-manager-select-helpers span.select-all-children:hover {
background-image: url(../images/select-all-children.png);
}
span.taxonomy-manager-select-helpers span.deselect-all-children {
text-decoration: none;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-image: url(../images/deselect-all-children-bw.png);
}
span.taxonomy-manager-select-helpers span.deselect-all-children:hover {
background-image: url(../images/deselect-all-children.png);
}
div.form-item-taxonomy-manager-top-language,
div.form-item-taxonomy2-manager-top-language {
padding-right: 0.5em;
text-align: right;
display: inline;
}
div.form-item-taxonomy-manager-top-language label,
div.form-item-taxonomy2-manager-top-language label {
display: inline;
}
.taxonomy-manager-tree-top {
text-align: right;
padding-top: 0.5em;
}
#taxonomy-manager-double-tree-operations {
float: left;
margin-left: 8px;
margin-right: 8px;
margin-top: 120px;
display: inline;
}
.taxonomy-manager-double-tree-operations-buttons input {
padding: 0;
margin: 0;
}
#double-tree-msg {
margin-bottom: 8px;
}
.taxonomy-manager-term-load-helper {
display: none;
}
/**
* Taxonomy Manger Toolbar
*/
#taxonomy-manager-toolbar-throbber {
float: right;
margin: 0px;
padding: 0px;
height: 25px;
}
#taxonomy-manager-toolbar-buttons {
float:left;
}
#taxonomy-manager-double-tree-form input.taxonomy-manager-buttons,
#taxonomy-manager-form input.taxonomy-manager-buttons {
cursor: pointer;
color: #5a5a5a;
border-radius: 15px;
-moz-border-radius: 20px;
-webkit-border-radius: 15px;
text-align: center;
font-weight: normal;
font-size: 1em;
font-family: "Lucida Grande", Verdana, sans-serif;
border: 1px solid #e4e4e4;
border-bottom: 1px solid #b4b4b4;
border-left-color: #D2D2D2;
border-right-color: #D2D2D2;
padding-left: 30px;
padding-bottom: 3px;
padding-right: 3px;
padding-top: 0px;
height: 30px;
margin-bottom:0px;
background-repeat: no-repeat;
background-position:6px;
background-color: #F0EFEF;
background-attachment: scroll;
}
#taxonomy-manager-double-tree-form input.add,
#taxonomy-manager-form input.add {
background-image: url(../images/list-add.png);
}
input#edit-weight-up {
background-image: url(../images/go-up.png);
}
input#edit-weight-down {
background-image: url(../images/go-down.png);
}
#taxonomy-manager-double-tree-form input.delete,
#taxonomy-manager-form input.delete {
background-image: url(../images/list-remove.png);
}
#taxonomy-manager-double-tree-form input.move,
#taxonomy-manager-form input.move {
background-image: url(../images/move.png);
}
#taxonomy-manager-double-tree-form input.merge,
#taxonomy-manager-form input.merge {
background-image: url(../images/merge.png);
}
#taxonomy-manager-double-tree-form input.cancel,
#taxonomy-manager-form input.cancel {
background-image: url(../images/dialog-cancel.png);
}
#taxonomy-manager-double-tree-form input.search,
#taxonomy-manager-form input.search {
background-image: url(../images/edit-find.png);
}
#taxonomy-manager-double-tree-form input.export,
#taxonomy-manager-form input.export {
background-image: url(../images/csv.png);
}
#taxonomy-manager-double-tree-form input.save,
#taxonomy-manager-form input.save {
background-image: url(../images/document-save.png);
margin-bottom: 5px;
}
#taxonomy-manager-double-tree-form input.double-tree,
#taxonomy-manager-form input.double-tree {
background-position:8px;
padding-left: 32px;
background-image: url(../images/double-tree.png);
}
#taxonomy-manager-double-tree-form input.double-tree-disable,
#taxonomy-manager-form input.double-tree-disable {
background-image: url(../images/double-tree-disable.png);
}
/**
* fixing position of autocomplete in seven theme
*/
fieldset#edit-search,
fieldset#edit-move,
fieldset#edit-term-merge {
position: static;
}
/**
* Taxonomy Manager Search
*/
#edit-search .fieldset-wrapper {
overflow: visible; /*fixes hidden autocomplete result in FF*/
}
#edit-search-field .form-item {
padding: 0;
margin: 0;
}
/**
* Term Data Form
*/
.term-data-overlay {
position: absolute;
left: 280px;
top: 100px;
width: 500px;
z-index: 100;
background: #FFFFFF;
}
.term-data-overlay fieldset {
border-width: 5px;
}
.term-data-overlay legend span{
cursor: move;
background-image: url(../images/move-small.png);
background-repeat:no-repeat;
background-position: 8px 3px;
padding-left: 24px;
line-height: 150%;
}
#term-data-close {
padding-top: 8px;
margin: 0;
text-align: right;
line-height: 100%;
}
#term-data-close span {
text-decoration: none;
width: 16px;
height: 16px;
background-repeat: no-repeat;
background-image: url(../images/dialog-cancel-small.png);
cursor: pointer;
}
#term-data-close span:hover {
background-image: url(../images/dialog-cancel-small-hover.png);
}
td.term-data-autocomplete-add,
td.taxonomy-term-data-operations {
text-align: center;
}
/*.term-data-autocomplete-add span {
text-decoration: none;
width: 22px;
height: 22px;
display:block;
background-image: url(../images/list-add.png);
}
.term-data-autocomplete-add span:hover {
text-decoration: none;
background-image: url(../images/list-add-hover.png);
}
.taxonomy-term-data-operations span {
text-decoration: none;
width: 22px;
height: 22px;
display:block;
background-image: url(../images/list-remove.png);
}
.taxonomy-term-data-operations span:hover {
text-decoration: none;
background-image: url(../images/list-remove-hover.png);
}*/
#taxonomy-term-data #edit-term-data-name-wrapper {
margin-top: 0;
}
#taxonomy-term-data .resizable-textarea {
width: 100%;
}
#taxonomy-term-data fieldset {
margin-top: 0;
padding-top: 0;
}
#taxonomy-manager .messages {
margin-top: 5px;
margin-bottom: 5px;
}
#taxonomy-term-data input.form-submit {
margin-bottom: 0px;
}
#taxonomy-term-data input.save {
margin: 10px 0;
padding: 0 50px;
padding-left: 40px;
padding-bottom: 3px;
padding-right: 15px;
padding-top: 0px;
height: 30px;
}
#taxonomy-term-data .filter-wrapper .fieldset-wrapper {
padding-bottom: 0px;
}
#taxonomy-term-data .filter-guidelines {
display:none; //hide descriptions, they use too much space
}
.term-data-form-file-field-info {
font-weight: bold;
}
.taxonomy-term-data-operations {
cursor: pointer;
}
.term-data-autocomplete {
display: inline;
}
.term-data-autocomplete-add {
/*display: inline;*/
cursor: pointer;
}
.term-data-autocomplete-add img {
vertical-align: bottom;
}
#taxonomy-term-data .form-item-term-data-name input {
width: 100%;
padding-right: 0px;
}
#term-data-description {
height: 130px;
}
#taxonomy-term-data .form-item {
margin-bottom: 0.3em;
margin-top: 0.3em;
}
#taxonomy-term-data table .form-item {
margin-bottom: 0.2em;
}
#taxonomy-term-data table {
margin-top: 0.5em;
margin-bottom: 0em;
}
#taxonomy-term-data td, th {
padding-top: 0.1em;
padding-bottom: 0.1em;
padding-left: 0.5em;
padding-right: 0.5em;
}
#edit-term-data-save {
margin: 0.5em 0;
}
.clear {
clear: both;
}
span.taxonomy-manager-message-close {
float:right;
}
/**
overwrites some css settings from the admin module, which break the layout of the taxonomy manager
*/
#taxonomy-manager div.form-item:after,
#taxonomy-manager ul.links:after,
#taxonomy-manager div.admin-panel .body:after,
#taxonomy-manager .clear-block:after {
display: inline;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 834 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 792 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 787 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

View File

@@ -0,0 +1,63 @@
/**
* @file shows / hides form elements
*/
(function ($) {
Drupal.behaviors.TaxonomyManagerHideForm = {
attach: function(context, settings) {
$('#edit-toolbar', context).once('hideForm', function() {
for (var key in settings.hideForm) {
Drupal.attachHideForm(settings.hideForm[key].div, settings.hideForm[key].show_button, settings.hideForm[key].hide_button);
}
});
}
}
/**
* adds click events to show / hide button
*/
Drupal.attachHideForm = function(div, show_button, hide_button) {
var hide = true;
var div = $("#"+ div);
var show_button = $("#"+ show_button);
var hide_button = $("#"+ hide_button);
// don't hide if there is an error in the form
$(div).find("input").each(function() {
if ($(this).hasClass("error")) {
hide = false;
}
});
if (!hide) {
$(div).show();
}
$(show_button).click(function() {
Drupal.hideOtherForms(div);
$(div).toggle();
return false;
});
$(hide_button).click(function() {
$(div).hide();
return false;
});
}
/**
* Helper function that hides all forms, except the current one.
*/
Drupal.hideOtherForms = function(currentFormDiv) {
var currentFormDivId = $(currentFormDiv).attr('id');
var settings = Drupal.settings.hideForm || [];
for (var key in settings) {
var div = settings[key].div;
if (div != currentFormDivId) {
$('#' + div).hide();
}
}
}
})(jQuery);

View File

@@ -0,0 +1,145 @@
/**
* @file js support for term editing form for ajax saving and tree updating
*/
(function ($) {
//global var that holds the current term link object
var active_term = new Object();
/**
* attaches term data form, used after 'Saves changes' submit
*/
Drupal.behaviors.TaxonomyManagerTermData = {
attach: function(context) {
if (!$('#taxonomy-term-data-replace').hasClass('processed')) {
$('#taxonomy-term-data-replace').addClass('processed');
Drupal.attachTermDataForm();
}
}
}
/**
* attaches Term Data functionality, called by tree.js
*/
Drupal.attachTermData = function(ul) {
Drupal.attachTermDataLinks(ul);
}
/**
* adds click events to the term links in the tree structure
*/
Drupal.attachTermDataLinks = function(ul) {
$(ul).find('a.term-data-link').click(function() {
Drupal.activeTermSwapHighlight(this);
var li = $(this).parents("li:first");
Drupal.loadTermDataForm(Drupal.getTermId(li), false);
return false;
});
}
/**
* attaches click events to next siblings
*/
Drupal.attachTermDataToSiblings = function(all, currentIndex) {
var nextSiblings = $(all).slice(currentIndex);
$(nextSiblings).find('a.term-data-link').click(function() {
var li = $(this).parents("li:first");
Drupal.loadTermDataForm(Drupal.getTermId(li), false);
return false;
});
}
/**
* adds click events to term data form, which is already open, when page gets loaded
*/
Drupal.attachTermDataForm = function() {
active_term = $('div.highlightActiveTerm').find('a');
var tid = $('#taxonomy-term-data').find('input:hidden[name="tid"]').val();
if (tid) {
new Drupal.TermData(tid).form();
}
}
/**
* loads term data form
*/
Drupal.loadTermDataForm = function(tid, refreshTree) {
// Triggers an AJAX button
$('#edit-load-tid').val(tid);
if (refreshTree) {
$('#edit-load-tid-refresh-tree').attr("checked", "checked");
}
else {
$('#edit-load-tid-refresh-tree').attr("checked", "");
}
$('#edit-load-tid-submit').click();
}
/**
* TermData Object
*/
Drupal.TermData = function(tid) {
this.tid = tid;
this.div = $('#taxonomy-term-data');
}
/**
* adds events to possible operations
*/
Drupal.TermData.prototype.form = function() {
var termdata = this;
$(this.div).find('#term-data-close span').click(function() {
termdata.div.children().hide();
});
$(this.div).find('a.taxonomy-term-data-name-link').click(function() {
var tid = this.href.split("/").pop();
Drupal.loadTermDataForm(tid, true);
return false;
});
$(this.div).find("legend").each(function() {
var staticOffsetX, staticOffsetY = null;
var left, top = 0;
var div = termdata.div;
var pos = $(div).position();
$(this).mousedown(startDrag);
function startDrag(e) {
if (staticOffsetX == null && staticOffsetY == null) {
staticOffsetX = e.pageX;
staticOffsetY = e.pageY;
}
$(document).mousemove(performDrag).mouseup(endDrag);
return false;
}
function performDrag(e) {
left = e.pageX - staticOffsetX;
top = e.pageY - staticOffsetY;
$(div).css({position: "absolute", "left": pos.left + left +"px", "top": pos.top + top +"px"});
return false;
}
function endDrag(e) {
$(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
}
});
}
/**
* hightlights current term
*/
Drupal.activeTermSwapHighlight = function(link) {
try {
$(active_term).parents('div.term-line').removeClass('highlightActiveTerm');
} catch(e) {}
active_term = link;
$(active_term).parents('div.term-line:first').addClass('highlightActiveTerm');
}
})(jQuery);

View File

@@ -0,0 +1,477 @@
/**
* @files js for collapsible tree view with some helper functions for updating tree structure
*/
(function ($) {
Drupal.behaviors.TaxonomyManagerTree = {
attach: function(context, settings) {
var treeSettings = settings.taxonomytree || [];
if (treeSettings instanceof Array) {
for (var i=0; i<treeSettings.length; i++) {
if (!$('#'+ treeSettings[i].id +'.tm-processed').length) {
new Drupal.TaxonomyManagerTree(treeSettings[i].id, treeSettings[i].vid, treeSettings[i].parents);
}
}
}
//only add throbber for TM sites
var throbberSettings = settings.TMAjaxThrobber || [];
if (throbberSettings['add']) {
if (!$('#taxonomy-manager-toolbar-throbber.tm-processed').length) {
$('#taxonomy-manager-toolbar-throbber').addClass('tm-processed');
Drupal.attachThrobber();
Drupal.attachResizeableTreeDiv();
Drupal.attachGlobalSelectAll();
}
}
Drupal.attachMsgCloseLink(context);
}
}
Drupal.TaxonomyManagerTree = function(id, vid, parents) {
this.div = $("#"+ id);
this.ul = $(this.div).children();
this.form = $(this.div).parents('form');
this.form_build_id = $(this.form).children().children(':input[name="form_build_id"]').val();
this.form_id = $(this.form).children().children(' :input[name="form_id"]').val();
this.form_token = $(this.form).children().children(' :input[name="form_token"]').val();
this.language = this.getLanguage();
this.treeId = id;
this.vocId = vid;
this.formParents = parents;
this.childFormUrl = Drupal.settings.childForm['url'];
this.siblingsFormUrl = Drupal.settings.siblingsForm['url'];
this.attachTreeview(this.ul);
this.attachSiblingsForm(this.ul);
this.attachSelectAllChildren(this.ul);
this.attachLanguageSelector();
//attach term data js, if enabled
var term_data_settings = Drupal.settings.termData || [];
if (term_data_settings['url']) {
Drupal.attachTermData(this.ul);
}
$(this.div).addClass("tm-processed");
}
/**
* adds collapsible treeview to a given list
*/
Drupal.TaxonomyManagerTree.prototype.attachTreeview = function(ul, currentIndex) {
var tree = this;
if (currentIndex) {
ul = $(ul).slice(currentIndex);
}
var expandableParent = $(ul).find("div.hitArea");
$(expandableParent).click(function() {
var li = $(this).parent();
tree.loadChildForm(li);
tree.toggleTree(li);
});
$(expandableParent).parent("li.expandable, li.lastExpandable").children("ul").hide();
}
/**
* toggles a collapsible/expandable tree element by swaping classes
*/
Drupal.TaxonomyManagerTree.prototype.toggleTree = function(node) {
$(node).children("ul").toggle();
this.swapClasses(node, "expandable", "collapsable");
this.swapClasses(node, "lastExpandable", "lastCollapsable");
}
/**
* helper function for swapping two classes
*/
Drupal.TaxonomyManagerTree.prototype.swapClasses = function(node, c1, c2) {
if ($(node).hasClass(c1)) {
$(node).removeClass(c1).addClass(c2);
}
else if ($(node).hasClass(c2)) {
$(node).removeClass(c2).addClass(c1);
}
}
/**
* loads child terms and appends html to list
* adds treeview, weighting etc. js to inserted child list
*/
Drupal.TaxonomyManagerTree.prototype.loadChildForm = function(li, update, callback) {
var tree = this;
if ($(li).is(".has-children") || update == true) {
$(li).removeClass("has-children");
if (update) {
$(li).children("ul").remove();
}
var parentId = Drupal.getTermId(li);
var url = tree.childFormUrl +'/'+ this.treeId +'/'+ this.vocId +'/'+ parentId;
var param = new Object();
param['form_build_id'] = this.form_build_id;
param['form_id'] = this.form_id;
param['tree_id'] = this.treeId;
param['form_parents'] = this.formParents;
param['language'] = this.language;
$.ajax({
data: param,
type: "GET",
url: url,
dataType: 'json',
success: function(response, status) {
$(li).append(response.data);
var ul = $(li).children("ul");
tree.attachTreeview(ul);
tree.attachSiblingsForm(ul);
tree.attachSelectAllChildren(ul);
//only attach other features if enabled!
var weight_settings = Drupal.settings.updateWeight || [];
if (weight_settings['up']) {
Drupal.attachUpdateWeightTerms(li);
}
var term_data_settings = Drupal.settings.termData || [];
if (term_data_settings['url']) {
Drupal.attachTermDataLinks(ul);
}
if (typeof(callback) == "function") {
callback(li, tree);
}
}
});
}
}
/**
* function for reloading root tree elements
*/
Drupal.TaxonomyManagerTree.prototype.loadRootForm = function(tids) {
var tree = this;
var url = this.childFormUrl +'/'+ this.treeId +'/'+ this.vocId +'/0/';
var param = new Object();
param['form_build_id'] = this.form_build_id;
param['form_id'] = this.form_id;
param['tree_id'] = this.treeId;
param['form_parents'] = this.formParents;
param['language'] = this.language;
param['terms_to_expand'] = tids; // can either be a single term id or concatinated ids
$.ajax({
data: param,
type: "GET",
url: url,
dataType: 'json',
success: function(response, status) {
$('#'+ tree.treeId).html(response.data);
var ul = $('#'+ tree.treeId).children("ul");
tree.attachTreeview(ul);
tree.attachSiblingsForm(ul);
tree.attachSelectAllChildren(ul);
Drupal.attachUpdateWeightTerms(ul);
Drupal.attachTermDataLinks(ul);
var lang = $('#edit-'+ tree.treeId +'-language').val();
if (lang != "" && lang != tree.langauge) {
$(tree.div).parent().siblings("div.taxonomy-manager-tree-top").find("select.language-selector option[value="+ lang +"]").attr("selected", "selected");
}
}
});
}
/**
* adds link for loading next siblings terms, when click terms get loaded through ahah
* adds all needed js like treeview, weightning, etc.. to new added terms
*/
Drupal.TaxonomyManagerTree.prototype.attachSiblingsForm = function(ul) {
var tree = this;
var url = this.siblingsFormUrl;
var list = "li.has-more-siblings div.term-has-more-siblings";
if (ul) {
list = $(ul).find(list);
}
$(list).bind('click', function() {
$(this).unbind("click");
var li = this.parentNode;
var all = $('li', li.parentNode);
var currentIndex = all.index(li);
var page = Drupal.getPage(li);
var prev_id = Drupal.getTermId(li);
var parentId = Drupal.getParentId(li);
url += '/'+ tree.treeId +'/'+ page +'/'+ prev_id +'/'+ parentId;
var param = new Object();
param['form_build_id'] = tree.form_build_id;
param['form_id'] = tree.form_id;
param['tree_id'] = tree.treeId;
param['form_parents'] = tree.formParents;
param['language'] = tree.language;
$.ajax({
data: param,
type: "GET",
url: url,
dataType: 'json',
success: function(response, status) {
$(list).remove();
$(li).after(response.data);
tree.attachTreeview($('li', li.parentNode), currentIndex);
tree.attachSelectAllChildren($('li', li.parentNode), currentIndex);
//only attach other features if enabled!
var weight_settings = Drupal.settings.updateWeight || [];
if (weight_settings['up']) {
Drupal.attachUpdateWeightTerms($('li', li.parentNode), currentIndex);
}
var term_data_settings = Drupal.settings.termData || [];
if (term_data_settings['url']) {
Drupal.attachTermDataToSiblings($('li', li.parentNode), currentIndex);
}
$(li).removeClass("last").removeClass("has-more-siblings");
$(li).children().children('.term-operations').hide();
tree.swapClasses(li, "lastExpandable", "expandable");
tree.attachSiblingsForm($(li).parent());
}
});
});
}
/**
* helper function for getting out the current page
*/
Drupal.getPage = function(li) {
return $(li).find("input:hidden[class=page]").attr("value");
}
/**
* returns terms id of a given list element
*/
Drupal.getTermId = function(li) {
return $(li).children().children("input:hidden[class=term-id]").attr("value");
}
/**
* return term id of a prent of a given list element
* if no parent exists (root level), returns 0
*/
Drupal.getParentId = function(li) {
var parentId;
try {
var parentLi = $(li).parent("ul").parent("li");
parentId = Drupal.getTermId(parentLi);
} catch(e) {
return 0;
}
return parentId;
}
/**
* update classes for tree view, if list elements get swaped
*/
Drupal.updateTree = function(upTerm, downTerm) {
if ($(upTerm).is(".last")) {
$(upTerm).removeClass("last");
Drupal.updateTreeDownTerm(downTerm);
}
else if ($(upTerm).is(".lastExpandable")) {
$(upTerm).removeClass("lastExpandable").addClass("expandable");
Drupal.updateTreeDownTerm(downTerm);
}
else if ($(upTerm).is(".lastCollapsable")) {
$(upTerm).removeClass("lastCollapsable").addClass("collapsable");
Drupal.updateTreeDownTerm(downTerm);
}
}
/**
* update classes for tree view for a list element moved downwards
*/
Drupal.updateTreeDownTerm = function(downTerm) {
if ($(downTerm).is(".expandable")) {
$(downTerm).removeClass("expandable").addClass("lastExpandable");
}
else if ($(downTerm).is(".collapsable")) {
$(downTerm).removeClass("collapsable").addClass("lastCollapsable");
}
else {
$(downTerm).addClass("last");
}
}
/**
* Adds button next to parent term to select all available child checkboxes
*/
Drupal.TaxonomyManagerTree.prototype.attachSelectAllChildren = function(parent, currentIndex) {
var tree = this;
if (currentIndex) {
parent = $(parent).slice(currentIndex);
}
$(parent).find('span.select-all-children').click(function() {
tree.SelectAllChildrenToggle(this);
});
}
/**
* (un-)selects nested checkboxes
*/
Drupal.TaxonomyManagerTree.prototype.SelectAllChildrenToggle = function(span) {
var tree = this;
if ($(span).hasClass("select-all-children")) {
var li = $(span).parents("li:first");
if ($(li).hasClass("has-children")) {
this.loadChildForm(li, true, function(li, tree) {
tree.swapClasses(li, "expandable", "collapsable");
tree.swapClasses(li, "lastExpandable", "lastCollapsable");
var this_span = $(li).find('span.select-all-children:first');
tree.SelectAllChildrenToggle(this_span);
return;
});
}
else {
$(span).removeClass("select-all-children").addClass("deselect-all-children");
$(span).attr("title", Drupal.t("Deselect all children"));
$(span).parents("li:first").find('ul:first').each(function() {
var first_element = $(this).find('.term-line:first');
$(first_element).parent().siblings("li").find('div.term-line:first :checkbox').attr('checked', true);
$(first_element).find(' :checkbox').attr('checked', true);
});
}
}
else {
$(span).removeClass("deselect-all-children").addClass("select-all-children");
$(span).parents(".term-line").siblings("ul").find(':checkbox').attr("checked", false);
$(span).attr("title", Drupal.t("Select all children"));
}
}
/**
* language selector
*/
Drupal.TaxonomyManagerTree.prototype.attachLanguageSelector = function() {
var tree = this;
var selector = $(tree.div).parent().siblings("div.taxonomy-manager-tree-top").find("select.language-selector");
$(selector).not(".selector-processed").change(function() {
tree.language = $(this).val();
tree.loadRootForm();
});
$(selector).addClass("selector-processed");
}
Drupal.TaxonomyManagerTree.prototype.getLanguage = function() {
var lang = $('#edit-taxonomy-manager-top-language').val();
if (typeof(lang) == "undefined") {
return "";
}
return lang;
}
/**
* return array of selected terms
*/
Drupal.TaxonomyManagerTree.prototype.getSelectedTerms = function() {
var terms = new Array();
$(this.div).find("input[type=checkbox][checked]").each(function() {
var term = $(this).parents("li").eq(0);
terms.push(term);
});
return terms;
}
/**
* returns li node for a given term id, if it exists in the tree
*/
Drupal.TaxonomyManagerTree.prototype.getLi = function(termId) {
return $(this.div).find("input:hidden[class=term-id][value="+ termId +"]").parent().parent();
}
Drupal.attachMsgCloseLink = function(context) {
$(context).find('div.messages').once(function() {
$('<span class="taxonomy-manager-message-close"><a href="" title="'+ Drupal.t('Close') +'">x</a></span>').appendTo(this).click(function() {
$(this).parent().fadeOut('fast', function() {
$(this).remove();
});
return false;
});
});
}
/**
* attaches a throbber element to the taxonomy manager
*/
Drupal.attachThrobber = function() {
var div = $('#taxonomy-manager');
var throbber = $('<img src="'+ Drupal.settings.taxonomy_manager['modulePath'] +'images/ajax-loader.gif" alt="" height="25">');
throbber.appendTo("#taxonomy-manager-toolbar-throbber").hide();
throbber.ajaxStart(function() {
$(this).show();
});
throbber.ajaxStop(function() {
$(this).hide();
});
throbber.ajaxError(function() {
alert("An AJAX error occurred. Reload the page and check your logs.");
$(this).hide();
});
}
/**
* makes the div resizeable
*/
Drupal.attachResizeableTreeDiv = function() {
$('img.div-grippie').each(function() {
var staticOffset = null;
var div = $(this).parents("fieldset").parent();
$(this).mousedown(startDrag);
function startDrag(e) {
staticOffset = div.width() - e.pageX;
div.css('opacity', 0.5);
$(document).mousemove(performDrag).mouseup(endDrag);
return false;
}
function performDrag(e) {
div.width(Math.max(200, staticOffset + e.pageX) + 'px');
return false;
}
function endDrag(e) {
$(document).unbind("mousemove", performDrag).unbind("mouseup", endDrag);
div.css('opacity', 1);
}
});
}
/**
* Adds select all / remove selection functionality.
*/
Drupal.attachGlobalSelectAll = function() {
$('span.taxonomy-manager-select-helpers').once(function() {
var form = $(this).parents('.form-wrapper:first');
$(this).find('span.select-all-children').click(function() {
// Only select those that are visible to the end user.
$(form).parent().find(' :checkbox:visible').attr('checked', true);
});
$(this).find('span.deselect-all-children').click(function() {
$(form).parent().find(':checkbox').attr("checked", false);
});
});
}
})(jQuery);

View File

@@ -0,0 +1,239 @@
/**
* @file js for changing weights of terms with Up and Down arrows
*/
(function ($) {
//object to store weights (tid => weight)
var termWeightsData = new Object();
Drupal.behaviors.TaxonomyManagerWeights = {
attach: function(context, settings) {
var weightSettings = settings.updateWeight || [];
if (!$('#edit-toolbar.tm-weights-processed').length) {
$('#edit-toolbar').addClass('tm-weights-processed');
termWeightsData['form_token'] = $('input[name=form_token]').val();
termWeightsData['form_id'] = $('input[name=form_id]').val();
termWeightsData['weights'] = new Object();
Drupal.attachUpdateWeightToolbar(weightSettings['up'], weightSettings['down']);
Drupal.attachUpdateWeightTerms();
}
}
}
/**
* adds click events for Up and Down buttons in the toolbar, which
* allow the moving of selected (can be more) terms
*/
Drupal.attachUpdateWeightToolbar = function(upButton, downButton) {
var selected;
var url = Drupal.settings.updateWeight['url'];
$('#'+ upButton).click(function() {
selected = Drupal.getSelectedTerms();
for (var i=0; i < selected.length; i++) {
var upTerm = selected[i];
var downTerm = $(upTerm).prev();
Drupal.orderTerms(upTerm, downTerm);
}
if (selected.length > 0) {
$.post(url, termWeightsData);
}
});
$('#'+ downButton).click(function() {
selected = Drupal.getSelectedTerms();
for (var i=selected.length-1; i >= 0; i--) {
var downTerm = selected[i];
var upTerm = $(downTerm).next();
Drupal.orderTerms(upTerm, downTerm);
}
if (selected.length > 0) {
$.post(url, termWeightsData);
}
});
}
/**
* adds small up and down arrows to each term
* arrows get displayed on mouseover
*/
Drupal.attachUpdateWeightTerms = function(parent, currentIndex) {
var settings = Drupal.settings.updateWeight || [];
var disable = settings['disable_mouseover'];
if (!disable) {
var url = Drupal.settings.updateWeight['url'];
var termLineClass = 'div.term-line';
var termUpClass = 'img.term-up';
var termDownClass = 'img.term-down';
if (parent && currentIndex) {
parent = $(parent).slice(currentIndex);
}
if (parent) {
termLineClass = $(parent).find(termLineClass);
termUpClass = $(parent).find(termUpClass);
termDownClass = $(parent).find(termDownClass);
}
$(termLineClass).mouseover(function() {
$(this).find('div.term-operations').show();
});
$(termLineClass).mouseout(function() {
$(this).find('div.term-operations').hide();
});
$(termUpClass).click(function() {
var upTerm = $(this).parents("li").eq(0);
var downTerm = $(upTerm).prev();
Drupal.orderTerms(upTerm, downTerm);
$.post(url, termWeightsData);
$(downTerm).find(termLineClass).unbind('mouseover');
setTimeout(function() {
$(upTerm).find('div.term-operations').hide();
$(downTerm).find(termLineClass).mouseover(function() {
$(this).find('div.term-operations').show();
});
}, 1500);
});
$(termDownClass).click(function() {
var downTerm = $(this).parents("li").eq(0);
var upTerm = $(downTerm).next();
Drupal.orderTerms(upTerm, downTerm);
$.post(url, termWeightsData);
$(upTerm).find(termLineClass).unbind('mouseover');
setTimeout(function() {
$(downTerm).find('div.term-operations').hide();
$(upTerm).find(termLineClass).mouseover(function() {
$(this).find('div.term-operations').show();
});
}, 1500);
});
}
}
/**
* return array of selected terms
*/
Drupal.getSelectedTerms = function() {
var terms = new Array();
$('.treeview').find("input:checked").each(function() {
var term = $(this).parents("li").eq(0);
terms.push(term);
});
return terms;
}
/**
* reorders terms
* - swap list elements in DOM
* - post updated weights to callback in php
* - update classes of tree view
*/
Drupal.orderTerms = function(upTerm, downTerm) {
try {
Drupal.getTermId(upTerm);
Drupal.swapTerms(upTerm, downTerm);
Drupal.swapWeights(upTerm, downTerm);
Drupal.updateTree(upTerm, downTerm);
} catch(e) {
//no next item, because term to update is last child, continue
}
}
/**
* simple swap of two elements
*/
Drupal.swapTerms = function(upTerm, downTerm) {
$(upTerm).after(downTerm);
$(downTerm).before(upTerm);
}
/**
* updating weights of swaped terms
* if two terms have different weights, then weights are being swapped
* else, if both have same weights, upTerm gets decreased
*
* if prev/next siblings of up/down terms have same weights as current
* swapped, they have to be updated by de/increasing weight (by 1) to ensure
* unique position of swapped terms
*/
Drupal.swapWeights = function(upTerm, downTerm) {
var upWeight = Drupal.getWeight(upTerm);
var downWeight = Drupal.getWeight(downTerm);
var downTid = Drupal.getTermId(downTerm);
var upTid = Drupal.getTermId(upTerm);
//same weight, decrease upTerm
if (upWeight == downWeight) {
termWeightsData['weights'][upTid] = --upWeight;
}
//different weights, swap
else {
termWeightsData['weights'][upTid] = downWeight;
termWeightsData['weights'][downTid] = upWeight;
}
//update prev siblings if necessary
try {
if (Drupal.getWeight($(upTerm).prev()) >= upWeight) {
$(upTerm).prevAll().each(function() {
var id = Drupal.getTermId(this);
var weight = Drupal.getWeight(this);
termWeightsData['weights'][id] = --weight;
});
}
} catch(e) {
//no prev
}
//update next siblings if necessary
try {
if (Drupal.getWeight($(downTerm).next()) <= downWeight) {
$(downTerm).nextAll().each(function() {
var id = Drupal.getTermId(this);
var weight = Drupal.getWeight(this);
termWeightsData['weights'][id] = ++weight;
});
}
} catch(e) {
//no next
}
}
/**
* helper to return weight of a term
*/
Drupal.getWeight = function(li) {
var id = Drupal.getTermId(li);
var weight;
if (termWeightsData['weights'][id] != null) {
weight = termWeightsData['weights'][id];
}
else {
weight = $(li).find("input:hidden[class=weight-form]").attr("value");
}
return weight;
}
})(jQuery);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,14 @@
name = Taxonomy Manager
description = Tool for administrating taxonomy terms.
core = "7.x"
dependencies[] = taxonomy
files[] = taxonomy_manager.admin.inc
configure = admin/config/user-interface/taxonomy-manager-settings
package = Taxonomy
; Information added by drupal.org packaging script on 2013-05-20
version = "7.x-1.0"
core = "7.x"
project = "taxonomy_manager"
datestamp = "1369041918"

View File

@@ -0,0 +1,30 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Taxonomy Manager
*/
/**
* Implements hook_uninstall().
*/
function taxonomy_manager_uninstall() {
variable_del('taxonomy_manager_pager_tree_page_size');
}
/**
* Implements hook_update_N().
* Resets 'taxonomy_override_selector' variable.
*/
function taxonomy_manager_update_7001() {
// remove parent selector in core taxonomy term edit pages
variable_set('taxonomy_override_selector', FALSE);
return t('Successfully updated Taxonomy Manager settings.');
}
/**
* Remove merge_redirect variable.
*/
function taxonomy_manager_update_7002() {
variable_del('taxonomy_manager_disable_merge_redirect');
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,494 @@
diff --git a/images/bullet_move.png b/images/bullet_move.png
new file mode 100644
index 0000000000000000000000000000000000000000..45f869428fd24f50acd6a16be70bf04a9f014e08
Binary files /dev/null and b/images/bullet_move.png differ
diff --git a/term_reference_tree.css b/term_reference_tree.css
index 40ba2cfbe530fa873c2fd7809636b9201eeb2623..5b72ebbfe96c05ff9ea680bf4105ec880c3d7370 100644
--- a/term_reference_tree.css
+++ b/term_reference_tree.css
@@ -94,6 +94,37 @@
background-position: middle left;
}
+.term-reference-tree-track-list.order-list li.track-item {
+ padding-left:0;
+}
+
+.term-reference-tree-track-list.order-list li.track-item:hover {
+ color:#000;
+ background-image: none;
+}
+
+.term-reference-tree-track-list.order-list .term-reference-tree-button-move, .term-reference-tree-track-list.order-list .term-reference-tree-button-delete{
+ display:inline-block; vertical-align:middle; zoom:1;
+ width:16px; height:16px;
+ background-repeat: no-repeat;
+ background-position: bottom center;
+ margin:0 5px;
+}
+
+
+.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-move{
+ background-image: url("images/bullet_move.png");
+}
+.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-move:hover{
+ cursor:move;
+}
+.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-delete{
+ background-image: url("images/bullet_delete.png"); float:right;
+}
+.term-reference-tree-track-list.order-list li.track-item .term-reference-tree-button-:hover{
+ cursor:pointer;
+}
+
.term-reference-tree-track-list li.term_ref_tree_nothing_message {
list-style-type: none;
list-style-image: none;
diff --git a/term_reference_tree.js b/term_reference_tree.js
index 319312f2dd0626ef4545bc66e3310f0467a8fb9e..23e742b3ed68653a749ebbabd0b7e7bbb71af887 100644
--- a/term_reference_tree.js
+++ b/term_reference_tree.js
@@ -28,7 +28,26 @@
//currently selected items to it.
if($(this).hasClass('term-reference-tree-track-list-shown')) {
var track_list_container = $(this).find('.term-reference-tree-track-list');
-
+ var tracklist_is_orderable = track_list_container.is('.order-list');
+ if(tracklist_is_orderable){
+ track_list_container.sortable({
+ update: function(event, ui) {
+ console.log('sort update : event', event);
+ console.log('sort update : ui', ui);
+
+ $.each(event.target.children, function(index, val) {
+ var $item = $(val),
+ // event.target = ul.list
+ // ui.item = li.track-item
+ control_id = $item.data('control_id'),
+ $hiddenInput = $('#'+control_id).parent('.form-item').next('input[type=hidden]');
+ // $hiddenInput.attr('value', $item.index());
+ $hiddenInput.val($item.index());
+ });
+ },
+ });
+ }
+
//Var to track whether using checkboxes or radio buttons.
var input_type =
( $(this).has('input[type=checkbox]').size() > 0 ) ? 'checkbox' : 'radio';
@@ -40,14 +59,22 @@
var labels = checked_controls.next();
var label_element;
+ //get delta
+ if(tracklist_is_orderable){
+ var weights = checked_controls.parent('.form-item').next('input[type=hidden]');
+ }
+
//For each label of the checked boxes, add item to the track list.
labels.each(function(index) {
label_element = $(labels[index]);
+ delta = tracklist_is_orderable ? $(weights[index]).val() : -1;
+
addItemToTrackList(
track_list_container, //Where to add new item.
label_element.html(), //Text of new item.
$(label_element).attr('for'), //Id of control new item is for.
- input_type //checkbox or radio
+ input_type, //checkbox or radio
+ delta //delta
);
}); //End labels.each
@@ -62,10 +89,14 @@
//Remove the "nothing selected" message if showing - add it later if needed.
//removeNothingSelectedMessage(track_list_container);
var event_target = $(event.target);
- var control_id = event_target.data('control_id');
-
- if(control_id) {
- event_target.remove();
+ var event_parent_list = event_target.parent('li');
+ var control_id = event_parent_list.data('control_id');
+ // console.log('event', event);
+ // console.log('event_target.parent("li")', event_target.parent('li'));
+ // console.log('control_id', control_id);
+ // console.log('event_target.is(term-reference-tree-delete)', event_target.is('term-reference-tree-delete'));
+ if(event_target.is('.term-reference-tree-button-delete') && control_id) {
+ event_parent_list.remove();
var checkbox = $('#' + control_id);
checkbox.removeAttr('checked');
@@ -89,7 +120,8 @@
track_list_container, //Where to add new item.
label_element.html(), //Text of new item.
$(label_element).attr('for'), //Id of control new item is for.
- input_type //checkbox or radio
+ input_type, //checkbox or radio
+ -1 // delta
);
}
else {
@@ -144,8 +176,9 @@
*
* @param control_type Control type - 'checkbox' or 'radio'.
*/
- function addItemToTrackList(track_list_container, item_text, control_id, control_type) {
- var new_item = $('<li class="track-item">' + item_text + '</li>');
+ function addItemToTrackList(track_list_container, item_text, control_id, control_type, delta) {
+ console.log('addItemToTrackList');
+ var new_item = $('<li class="track-item" delta="'+ delta +'"><div class="term-reference-tree-button-move"></div>' + item_text + '<div class="term-reference-tree-button-delete"></div></li>');
new_item.data('control_id', control_id);
//Add an id for easy finding of the item.
@@ -173,35 +206,66 @@
}
return;
}
-
- //Using checkboxes, so there can be more than one selected item.
- //Find the right place to put the new item, to match the order of the
- //checkboxes.
+
+ //Using checkboxes, so there can be more than one selected item.
+ //Find the right place to put the new item,
+ // to match the order of the checkboxes.
+ // OR order of delta
var list_items = track_list_container.find('li');
var item_comparing_to;
-
+
//Flag to tell whether the item was inserted.
var inserted_flag = false;
- list_items.each(function(index){
- item_comparing_to = $(list_items[index]);
+
+ if(!track_list_container.is('.order-list')){
+
+ list_items.each(function(index){
+ item_comparing_to = $(list_items[index]);
- //If item is already on the track list, do nothing.
- if ( control_id == item_comparing_to.data('control_id') ) {
- inserted_flag = true;
- return false; //Returning false stops the loop.
- }
- else if ( control_id < item_comparing_to.data('control_id') ) {
- //Add it here.
- item_comparing_to.before(new_item);
- inserted_flag = true;
- return false; //Returning false stops the loop.
- }
- });
+ //If item is already on the track list, do nothing.
+ if ( control_id == item_comparing_to.data('control_id') ) {
+ inserted_flag = true;
+ return false; //Returning false stops the loop.
+ }
+ else if ( control_id < item_comparing_to.data('control_id') ) {
+ //Add it here.
+ item_comparing_to.before(new_item);
+ inserted_flag = true;
+ return false; //Returning false stops the loop.
+ }
+ });
- //If not inserted yet, add new item at the end of the track list.
- if ( ! inserted_flag ) {
- track_list_container.append(new_item);
- }
+ //If not inserted yet, add new item at the end of the track list.
+ if ( ! inserted_flag ) {
+ track_list_container.append(new_item);
+ }
+
+ }else{
+ if( ! track_list_container.find('#'+new_item.attr('id')).size() ){
+
+ if(delta == -1){
+ track_list_container.append(new_item);
+ inserted_flag = true;
+ }else{
+ list_items.each(function(index){
+ item_comparing_to = $(this);
+ if ( delta < item_comparing_to.attr('delta') ) {
+ //Add it here.
+ item_comparing_to.before(new_item);
+ inserted_flag = true;
+ return false; //Returning false stops the loop.
+ }
+ });
+ //If not inserted yet, add new item at the end of the track list.
+ if ( ! inserted_flag )
+ track_list_container.append(new_item);
+
+ }
+
+ track_list_container.sortable('refresh');
+ }
+ }
+
}
/**
@@ -246,7 +310,7 @@
// This helper function checks if the maximum number of choices is already selected.
// If so, it disables all the other options. If not, it enables them.
- function checkMaxChoices(item, checkbox) {
+ function checkMaxChoices(item, checkbox, order_list) {
var maxChoices = -1;
try {
maxChoices = parseInt(Drupal.settings.term_reference_tree.trees[item.attr('id')]['max_choices']);
diff --git a/term_reference_tree.module b/term_reference_tree.module
index 1aea8b531d81c88ea58f3a1bcd05443483ab19a2..4346ed47a589bd32c68d2982bcd3ff80f82160c6 100644
--- a/term_reference_tree.module
+++ b/term_reference_tree.module
@@ -33,7 +33,7 @@ function term_reference_tree_element_info() {
'#input' => false,
'#theme' => array('checkbox_tree_track_list'),
'#pre_render' => array('form_pre_render_conditional_form_element'),
- ),
+ )
);
return $types;
@@ -183,6 +183,7 @@ function _term_reference_tree_flatten($element, &$form_state) {
$children = element_children($element);
foreach($children as $c) {
$child = $element[$c];
+ // dsm($child, '$child');
if (array_key_exists('#type', $child) && ($child['#type'] == 'radio' || $child['#type'] == 'checkbox')) {
$output[] = $child;
}
diff --git a/term_reference_tree.widget.inc b/term_reference_tree.widget.inc
index 8bae7a1fb46632d924d2625b9897235506b7da0f..1d4f6b758387564850b55ed9f9d56a185fdacf1c 100644
--- a/term_reference_tree.widget.inc
+++ b/term_reference_tree.widget.inc
@@ -19,6 +19,7 @@ function term_reference_tree_field_widget_info() {
'select_parents' => 0,
'cascading_selection' => 0,
'track_list' => 0,
+ 'track_list_order' => 0,
'token_display' => '',
'parent_term_id' => '',
'max_depth' => '',
@@ -171,7 +172,18 @@ function term_reference_tree_field_widget_settings_form($field, $instance) {
'#default_value' => $settings['track_list'],
'#return_value' => 1,
);
+
+ $form['track_list_order'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Track list drag and drop order'),
+ '#description' => t(
+ 'Allow drag and drop selected terms ordering on tracklist.'),
+ '#default_value' => $settings['track_list_order'],
+ '#return_value' => 1,
+ '#element_validate' => array('_term_reference_tree_track_list_order_validate'),
+ );
+
$form['max_depth'] = array(
'#type' => 'textfield',
'#title' => t('Maximum Depth'),
@@ -251,6 +263,18 @@ function _term_reference_tree_cascading_selection_validate($element, &$form_stat
}
}
+function _term_reference_tree_track_list_order_validate($element, &$form_state){
+ if ($form_state['values']['instance']['widget']['settings']['track_list'] == 0 && $form_state['values']['instance']['widget']['settings']['track_list_order'] == 1) {
+ // This is pretty wonky syntax for the field name in form_set_error, but it's
+ // correct.
+ form_set_error('field][track_list_order', t('You must enable Track List if Track List Order is enabled.'));
+ }
+ /*
+ TODO check if number of values is diffrent from 1
+ */
+}
+
+
/**
* Process the checkbox_tree widget.
*
@@ -310,6 +334,7 @@ function term_reference_tree_process_checkbox_tree($element, $form_state) {
$element[] = array(
'#type' => 'checkbox_tree_track_list',
'#max_choices' => $max_choices,
+ '#track_list_order' => $element['#track_list_order'],
);
}
}
@@ -443,7 +468,8 @@ function theme_checkbox_tree_label($variables) {
function theme_checkbox_tree_track_list($variables) {
//Should the label be singular or plural? Depends on cardinality of term field.
static $nothingselected;
-
+ // dsm($variables, 'theme_checkbox_tree_track_list : $variables');
+
if(!$nothingselected) {
$nothingselected = t('[Nothing selected]');
//Add the "Nothing selected" text. To style it, replace it with whatever you want.
@@ -459,17 +485,17 @@ function theme_checkbox_tree_track_list($variables) {
'Selected item (click the item to uncheck it)',
'Selected items (click an item to uncheck it)'
);
+ $order = $variables['element']['#track_list_order'] ? 'order-list' : '';
$output =
'<div class="term-reference-track-list-container">
<div class="term-reference-track-list-label">' . $label . '</div>
- <ul class="term-reference-tree-track-list"><li class="term_ref_tree_nothing_message">'.$nothingselected.'</li></ul>
+ <ul class="term-reference-tree-track-list '.$order.'"><li class="term_ref_tree_nothing_message">'.$nothingselected.'</li></ul>
</div>';
return $output;
}
-
/**
* Implements hook_widget_field_form().
*/
@@ -512,6 +538,7 @@ function term_reference_tree_field_widget_form(&$form, &$form_state, $field, $in
$element['#select_parents'] = $settings['select_parents'];
$element['#cascading_selection'] = $settings['cascading_selection'];
$element['#track_list'] = $settings['track_list'];
+ $element['#track_list_order'] = $settings['track_list_order'];
$element['#parent_tid'] = $settings['parent_term_id'] || $field['settings']['allowed_values'][0]['parent'];
$element['#vocabulary'] = $voc->vid;
$element['#token_display'] = module_exists('token') ? $settings['token_display'] : '';
@@ -523,7 +550,11 @@ function term_reference_tree_field_widget_form(&$form, &$form_state, $field, $in
'#element_validate' => array('_term_reference_tree_widget_validate'),
'#properties' => $properties,
);
-
+
+ if ($settings['track_list_order']) {
+ drupal_add_library('system', 'ui.sortable');
+ }
+
return $element;
}
@@ -544,25 +575,58 @@ function term_reference_tree_field_widget_form(&$form, &$form_state, $field, $in
* The validated element.
*/
function _term_reference_tree_widget_validate(&$element, &$form_state) {
+ dsm($element, '_term_reference_tree_widget_validate | $element');
$items = _term_reference_tree_flatten($element, $form_state);
+ dsm($items, '$items');
$value = array();
if ($element['#max_choices'] != 1) {
- foreach($items as $child) {
- if (array_key_exists('#value', $child) && $child['#value'] !== 0) {
- array_push($value, array($element['#value_key'] => $child['#value']));
-
- // If the element is leaves only and select parents is on, then automatically
- // add all the parents of each selected value.
- if ($element['#select_parents'] && $element['#leaves_only']) {
- foreach($child['#parent_values'] as $parent_tid) {
- if (!in_array(array($element['#value_key'] => $parent_tid), $value)) {
- array_push($value, array($element['#value_key'] => $parent_tid));
- }
- }
- }
- }
- }
+ if(!$element['#track_list_order']){
+ foreach($items as $child) {
+ if (array_key_exists('#value', $child) && $child['#value'] !== 0) {
+ array_push($value, array( $element['#value_key'] => $child['#value']));
+
+ // If the element is leaves only and select parents is on, then automatically
+ // add all the parents of each selected value.
+ if ($element['#select_parents'] && $element['#leaves_only']) {
+ foreach($child['#parent_values'] as $parent_tid) {
+ if (!in_array(array($element['#value_key'] => $parent_tid), $value)) {
+ array_push($value, array($element['#value_key'] => $parent_tid));
+ }
+ }
+ }
+
+ }
+ }
+
+ }else{
+ $selected_terms = array();
+ foreach($items as $child) {
+ if (array_key_exists('#value', $child) && $child['#value'] !== 0) {
+ $delta = $form_state['input'][$child['#value'].'-weight'];
+ $selected_terms[$delta] = array($element['#value_key'] => $child['#value']);
+
+ // If the element is leaves only and select parents is on, then automatically
+ // add all the parents of each selected value.
+ if ($element['#select_parents'] && $element['#leaves_only']) {
+ foreach($child['#parent_values'] as $parent_tid) {
+ if (!in_array(array($element['#value_key'] => $parent_tid), $selected_terms)) {
+ $delta = $form_state['input'][$parent_tid.'-weight'];
+ $selected_terms[$delta] = array($element['#value_key'] => $parent_tid);
+ }
+ }
+ }
+
+ }
+ }
+ // reorder items
+ ksort($selected_terms);
+ // record in value
+ foreach ($selected_terms as $delta => $term) {
+ $value[] = $term;
+ }
+
+ }
}
else {
// If it's a tree of radio buttons, they all have the same value, so we can just
@@ -575,6 +639,8 @@ function _term_reference_tree_widget_validate(&$element, &$form_state) {
}
}
+ dsm($value, '$value');
+
if ($element['#required'] && empty($value)) {
// The title is already check_plained so it's appropriate to use !.
form_error($element, t('!name field is required.', array('!name' => $element['#title'])));
@@ -701,7 +767,23 @@ function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$valu
$parents_for_id = array_merge($element['#parents'], array($term->tid));
$e['#id'] = drupal_html_id('edit-' . implode('-', $parents_for_id));
$e['#parents'] = $element['#parents'];
- }
+ }else if($element['#track_list_order']){
+ $delta = 0;
+ $i = -1;
+ if(isset($value[$term->tid])){
+ foreach ($value as $tid) {
+ $i++;
+ if($term->tid == $tid)
+ break;
+ }
+ }
+
+ $e_weight = array(
+ '#type' => 'hidden',
+ '#value' => $i,
+ '#name' => $term->tid.'-weight',
+ );
+ }
}
else {
$e = array(
@@ -710,9 +792,12 @@ function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$valu
);
}
-
$container[$term->tid] = $e;
+ if(isset($e_weight)){
+ $container[$term->tid.'-weight'] = $e_weight;
+ }
+
if (($depth + 1 <= $element['#max_depth'] || !$element['#max_depth']) && property_exists($term, 'children') && count($term->children) > 0) {
$parents = $parent_tids;
$parents[] = $term->tid;

View File

@@ -0,0 +1,93 @@
diff --git a/term_reference_tree.widget.inc b/term_reference_tree.widget.inc
index 8bae7a1fb46632d924d2625b9897235506b7da0f..3159b4a0ee0067512d08e36efe12c494c47676bb 100644
--- a/term_reference_tree.widget.inc
+++ b/term_reference_tree.widget.inc
@@ -22,6 +22,7 @@ function term_reference_tree_field_widget_info() {
'token_display' => '',
'parent_term_id' => '',
'max_depth' => '',
+ 'starting_depth' => 1,
),
),
);
@@ -181,6 +182,15 @@ function term_reference_tree_field_widget_settings_form($field, $instance) {
'#return_value' => 1,
);
+ $form['starting_depth'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Starting Depth'),
+ '#description' => t("Only items equal and down this level will be selectable. First level is 1"),
+ '#default_value' => $settings['starting_depth'],
+ '#size' => 2,
+ '#return_value' => 1,
+ );
+
$form['parent_term_id'] = array(
'#type' => 'textfield',
'#title' => t('Parent Term ID'),
@@ -300,9 +310,10 @@ function term_reference_tree_process_checkbox_tree($element, $form_state) {
if ($max_choices != 1)
$element['#tree'] = TRUE;
+ $starting_depth = !empty($element['#starting_depth']) ? $element['#starting_depth'] : 0;
$tree = new stdClass;
$tree->children = $terms;
- $element[] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $max_choices, array(), 1);
+ $element[] = _term_reference_tree_build_level($element, $tree, $form_state, $value, $max_choices, $starting_depth, array(), 1);
//Add a track list element?
$track_list = !empty($element['#track_list']) && $element['#track_list'];
@@ -506,6 +517,7 @@ function term_reference_tree_field_widget_form(&$form, &$form_state, $field, $in
$element['#default_value'] = $multiple ? $default_value : array(reset($default_value) => reset($default_value));
$element['#max_choices'] = $field['cardinality'];
$element['#max_depth'] = $settings['max_depth'];
+ $element['#starting_depth'] = $settings['starting_depth'];
$element['#start_minimized'] = $settings['start_minimized'];
$element['#leaves_only'] = $settings['leaves_only'];
$element['#filter_view'] = module_exists('views') ? $settings['filter_view'] : '';
@@ -649,7 +661,7 @@ function _term_reference_tree_get_allowed_values($filter) {
* A completed checkbox_tree_item element, which contains a checkbox and
* possibly a checkbox_tree_level element as well.
*/
-function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$value, $max_choices, $parent_tids, $parent, $depth) {
+function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$value, $max_choices, $starting_depth, $parent_tids, $parent, $depth) {
$start_minimized = FALSE;
if (array_key_exists('#start_minimized', $element)) {
$start_minimized = $element['#start_minimized'];
@@ -676,7 +688,7 @@ function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$valu
'#depth' => $depth,
);
- if (!$element['#leaves_only'] || count($term->children) == 0) {
+ if ((!$element['#leaves_only'] || count($term->children) == 0) && $depth >= $element['#starting_depth']) {
$name = "edit-" . str_replace('_', '-', $element['#field_name']);
$e = array(
'#type' => ($max_choices == 1) ? 'radio' : 'checkbox',
@@ -716,7 +728,7 @@ function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$valu
if (($depth + 1 <= $element['#max_depth'] || !$element['#max_depth']) && property_exists($term, 'children') && count($term->children) > 0) {
$parents = $parent_tids;
$parents[] = $term->tid;
- $container[$term->tid . '-children'] = _term_reference_tree_build_level($element, $term, $form_state, $value, $max_choices, $parents, $depth+1);
+ $container[$term->tid . '-children'] = _term_reference_tree_build_level($element, $term, $form_state, $value, $max_choices, $starting_depth, $parents, $depth+1);
$container['#level_start_minimized'] = $container[$term->tid . '-children']['#level_start_minimized'];
}
@@ -744,7 +756,7 @@ function _term_reference_tree_build_item(&$element, &$term, &$form_state, &$valu
* @return
* A completed checkbox_tree_level element.
*/
-function _term_reference_tree_build_level(&$element, &$term, &$form_state, &$value, $max_choices, $parent_tids, $depth) {
+function _term_reference_tree_build_level(&$element, &$term, &$form_state, &$value, $max_choices, $starting_depth, $parent_tids, $depth) {
$start_minimized = FALSE;
if (array_key_exists('#start_minimized', $element)) {
$start_minimized = $element['#start_minimized'];
@@ -766,7 +778,7 @@ function _term_reference_tree_build_level(&$element, &$term, &$form_state, &$val
$container['#level_start_minimized'] = $depth > 1 && $element['#start_minimized'] && !($term->children_selected);
foreach($term->children as $t) {
- $container[$t->tid] = _term_reference_tree_build_item($element, $t, $form_state, $value, $max_choices, $parent_tids, $container, $depth);
+ $container[$t->tid] = _term_reference_tree_build_item($element, $t, $form_state, $value, $max_choices, $starting_depth, $parent_tids, $container, $depth);
}
return $container;