FINAL suepr merge step : added all modules to this super repos
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
|
||||
Entity Translation 7.x-1.x, xxxx-xx-xx
|
||||
--------------------------------------
|
||||
|
||||
|
||||
Entity Translation 7.x-1.0-beta3, 2013-07-23
|
||||
--------------------------------------------
|
||||
#2037789 by Jelle_S: Fixed Default to user_access() access callback like drupal
|
||||
menu does.
|
||||
#1982140 by bforchhammer | carligraph: Fixed Menu item is deleted when adding a
|
||||
third translation.
|
||||
#2000926 by GiorgosK, bforchhammer: Fixed entity_translation_i18n_menu should
|
||||
check if entity is node before deleting menu links.
|
||||
#1991452 by twistor: Fixed Strict warnings on testing page.
|
||||
#1992588 by bforchhammer: Fixed PHP notices on node page (menu-related).
|
||||
#1979406 by milesw: Field values for node translations not filled correctly when
|
||||
making a field translatable.
|
||||
#1969366 by plach | EdNet: Fixed unsupported operand types in
|
||||
entity_translation().module line 951 - Unable to clear cache, run cron,
|
||||
etc from drush or aegir.
|
||||
#1970246 by jweowu: Fixed entity_translation_upgrade() doesn't flush the entity
|
||||
load cache.
|
||||
#1959614 by plach | rutcreate: Fixed Image is deleted by saving node 5 times.
|
||||
#1941080 by plopesc, bforchhammer: Fixed Menu translation on node form is
|
||||
broken.
|
||||
#1937242 by eliosh: Use custom theme function for node translation overview.
|
||||
#1916738 by alberto56, plach: Added Make it more obvious to those new to the
|
||||
concept of field translation that all instances of the field are translatable.
|
||||
#1942732 by OnkelTem: Fixed WSOD when LEFT JOIN'ing entities' translations.
|
||||
#1942712 by das-peter: Fixed Non wrapped entity passed to Rules.
|
||||
#1907456 by leon.nk: Added Allow bulk field updates to copy data for all
|
||||
translations.
|
||||
#1947764 by plach: Improve bundle translatability checks.
|
||||
#1370900 by mojzis, plach | mgladding: Fixed Fatal Error: Cannot access empty
|
||||
property in translation.handler.inc.
|
||||
Issue #1924088 by plach | drzraf: Fixed Wrong form 'language' value in case of
|
||||
content-translation enabled bundles.
|
||||
#1933742 by das-peter: Fixed Delete translation doesn't flush the entity cache.
|
||||
#1903024 by fabsor | dimitrileonidas: Fixed SQL error in Views when adding an
|
||||
entity translation relationship that is not using the base entity of the view.
|
||||
#1890346 by plach: Fixed Entity language change not handled on non-translatable
|
||||
entities with translatable fields.
|
||||
#1883584 by grndlvl: Added Allow for link 'destination' on overview page.
|
||||
#1888324 by plach: Fixed Wrong form language set when only one language is
|
||||
enabled and the entity has LANGUAGE_NONE language.
|
||||
#1888192 by plach: Fixed Field widgets hidden when no #access key is defined.
|
||||
#1885370 by grndlvl: Fixed 'View' title blank on translation overview when using
|
||||
optional title replacement field.
|
||||
#1881872 by plach | Demoshane: Added an explict dependecy on Drupal core 7.15.
|
||||
#1876750 by David_Rothstein: Fixed Fatal error when creating an entity as an
|
||||
anonymous user.
|
||||
#1760270 by plach | Berdir: Fixed Comment translation broken?.
|
||||
#1870236 by plach: Fixed Field translatability migration fails when translation
|
||||
for the entity type is disabled.
|
||||
#1865176 by bforchhammer: Implement hook_entity_translation_delete() on behalf
|
||||
of path module.
|
||||
#1862882 by Cottser: Fixed Typo: accross = across.
|
||||
#1851118 by peximo | impleri: Fixed Incorrect ET Handler set in node/add form
|
||||
when inserting new taxonomy term.
|
||||
#1842540 by peximo, bforchhammer | plach: Added Pathauto core entity types
|
||||
integration.
|
||||
|
||||
|
||||
Entity Translation 7.x-1.0-beta2, 2012-11-21
|
||||
--------------------------------------------
|
||||
#1846104 by David_Rothstein, plach: Fixed Entity Translation shouldn't assume
|
||||
$form['#entity_type()'] is a string.
|
||||
#1845896 by plach, peximo: Fixed Validate translation authoring info on submit.
|
||||
#1835578 by farse, plach: Fixed Entity Translation metadata should be stored
|
||||
only if the entity bundle is translatable.
|
||||
#1437976 by plach | Wolfgang Reszel: Fixed Invalid node translation menu
|
||||
callback set when entity translation for nodes is disabled.
|
||||
#1844844 by plach, bforchhammer: Fixed Content type settings always showed.
|
||||
#1833496 by bforchhammer, plach: Fixed Upgrade to Beta1 results in 'Cannot
|
||||
initialize entity translation path variables'.
|
||||
#1397560 by timofey, fricca, bforchhammer: Fixed Delete translation button not
|
||||
working if destination parameter is set.
|
||||
#1829740 by plach: Comment filtering should be available if node translation is
|
||||
enabled (follow-up).
|
||||
#1829980 by plach | Stan Shevchuk: Fixed Upgrade to Beta1 results in 'Integrity
|
||||
constraint violation: 1048 Column 'module' cannot be null'.
|
||||
|
||||
|
||||
Entity Translation 7.x-1.0-beta1, 2012-11-02
|
||||
--------------------------------------------
|
||||
#1828794 by plach: Make translation workflow permissions optional.
|
||||
#1829666 by bforchhammer: Fixed Fatal error caused by menu translation when node
|
||||
translation is disabled.
|
||||
#1829740 by plach: Fixed Node and comments ET settings are available also when
|
||||
translation is disabled.
|
||||
#1818440 by bforchhammer, plach, miro_dietiker: Fixed Fatal error with unclean
|
||||
edit path.
|
||||
#1672710 by plach, valencianok, miro_dietiker | haclong99: Fixed published
|
||||
source node appears as not published in the translate tab.
|
||||
#1819844 by plach | bforchhammer, renat: Fixed Incompatibility with fields with
|
||||
unlimited number of values introduced in ET -dev.
|
||||
#1818620 by bforchhammer, plach | renat: Fixed fatal error when creating a node
|
||||
which has a field_collection field.
|
||||
#1820742 by mkalkbrenner, plach: Fixed parameter $langcode of core function
|
||||
node_view() ignored.
|
||||
#1820910 by plach | astutonet: Fixed Notices when trying to filter comments by
|
||||
language.
|
||||
#1770748 by bforchhammer, plach: Option to display shared fields only when
|
||||
editing the original values.
|
||||
#1811672 by joelrosen: Fixed Entity Translation stuff showing on content types
|
||||
with multilingual support disabled.
|
||||
#1810322 by Dave Reid: Fixed Fatal error when using the translate link field
|
||||
handler in Views.
|
||||
#1807698 by peximo: Added Hide node translation links.
|
||||
#1798456 by plach, bforchhammer, Berdir: Hide shared form elements when the user
|
||||
does not have the related permission (follow up).
|
||||
#1803362 by Dave Reid: Fixed PHP notice: Undefined index: in
|
||||
EntityTranslationDefaultHandler->localTasksAlter().
|
||||
#1800822 by plach, peximo | facine: Fixed Call to a member function
|
||||
getLanguageKey() on a non-object.
|
||||
#1799770 by bforchhammer: Updated id and bundle when setting a wrapped entity.
|
||||
#1798456 by plach: Hide shared form elements when the user has not the related
|
||||
permission.
|
||||
#1679736 by plach | renat: Provided an option to lock the original language.
|
||||
#1798460 by plach: Made the translate operation contextual.
|
||||
#1280546 by plach: Introduced a language selection widget for every entity.
|
||||
#1676716 by plach | quiptime: Do not list entity types that do not integrate
|
||||
with ET in the admin page.
|
||||
#1792620 by bforchhammer: Cleanup unused variables.
|
||||
#1418076 by bforchhammer, plach: Added support for multiple path schemes.
|
||||
#1792606 by plach: Fixed Notices when deleting the translation of an entity
|
||||
having a file field.
|
||||
#1770202 by plach: Fixed Language tabs not appearing when creating a new
|
||||
translation (follow-up).
|
||||
#1678614 by bforchhammer, plach | Berdir: Make access control for the entity
|
||||
form in the original language more flexible.
|
||||
#1701212 by jonhattan: Fixed fatal error for entities with ET disabled (UUID
|
||||
follow-up).
|
||||
#1701460 by bforchhammer: Fixed title of original language dropdown.
|
||||
#1155134 by bforchhammer, plach: Integrated pathauto bulk generation.
|
||||
#1694478 by bforchhammer: Delete menu items when translation is deleted.
|
||||
#1770250 by bforchhammer, plach: Fixed Translatable fields disappear when
|
||||
editing default language (entities other than node).
|
||||
#1377542 by bforchhammer | Daemon_Byte: Fixed Do not always use the admin theme.
|
||||
#1772874 by bforchhammer | webdrips: Fixed Wrong query alteration for comment
|
||||
language filtering.
|
||||
#1778662 by jherencia: Fixed Add compability with field widgets that uses
|
||||
limit_validation_errors().
|
||||
#1770202 by bforchhammer: Fixed Local tasks (language tabs) visible on node view
|
||||
page.
|
||||
#1701212 by guillaumev: Added Export entity translations using UUID.
|
||||
#1728648 by plach, Dave Reid: Fixed Ensure that empty translation data is
|
||||
handled properly.
|
||||
#1728674 by Dave Reid, plach: Fixed PHP notice when formLanguage is FALSE in
|
||||
EntityTranslationDefaultHandler.
|
||||
#1114410 by plach: Replace hook_translation_info() with hook_entity_info().
|
||||
#1694480 by plach | bforchhammer: Implement hook_module_implements() instead of
|
||||
relying on the module weight.
|
||||
#1434574 by das-peter, plach | cjoy: Added events for rules.
|
||||
#1224590 by plach: Made entity translation CRUD hooks fire only after storing
|
||||
the translation data (follow-up).
|
||||
#1643578 by plach: Added Support menu translation upgrade.
|
||||
#1713196 by plach: Fixed Entity type labels may not be defined in entity info.
|
||||
#1444866 by plach: Removed bogus hook menu link alteration (follow-up).
|
||||
#1699060 by plach: Fixed Bogus form elements on the taxonomy delete confirm
|
||||
form.
|
||||
#1699052 by plach: Fixed Global entity form submit handlers are not invoked on
|
||||
delete except for node.
|
||||
#1661348 by plach | liquidcms: Fixed minimal i18n Taxonomy integration.
|
||||
#1444866 by plach: Fixed the invocation order on presave and menu alteration
|
||||
(follow-up).
|
||||
#1691026 by plach | FrancescoQ: Fixed Fatal error when enabling ET with just one
|
||||
language installed.
|
||||
#1698622 by plach: Fixed Field data is not properly massaged before being stored
|
||||
during an upgrade.
|
||||
#1224590 by bforchhammer: Added entity translation CRUD hooks.
|
||||
#1694472 by bforchhammer: Change module package names to 'Multilingual - Entity
|
||||
Translation'.
|
||||
#1444866 by bforchhammer, plach, peximo: Added UI for translation of menu items
|
||||
for entity-translated nodes.
|
||||
#1133038 by plach: Added support for field column synchronization.
|
||||
#1528624 by plach | phm: Fixed error on user translations overview.
|
||||
#1667582 by peximo: Fixed PDOException (ambiguous tnid field) on upgrade.
|
||||
#1658424 by torpy: Fixed Non-multilingual taxonomy terms attached to
|
||||
multilingual nodes do not display.
|
||||
#1643394 by plach: Allow repeating the upgrade process.
|
||||
#1643414 by plach: Added Allow to select which content types to upgrade.
|
||||
#1643390 by plach: Implement hook_entity_translation_upgrade_translation() on
|
||||
behalf of core modules.
|
||||
#1155134 by das-peter, plach, GiorgosK, zambrey: Added Integrate pathauto.
|
||||
#1282018 by plach, Gábor Hojtsy, webchick, bforchhammer: Drop the translation
|
||||
form in favor of a language-aware edit form.
|
||||
|
||||
|
||||
Entity Translation 7.x-1.0-alpha2, 2012-06-16
|
||||
---------------------------------------------
|
||||
#1587108 by plach | divined: Error: The entities of type Taxonomy term do not
|
||||
define a valid base path: it will not be possible to translate them.
|
||||
#1524210 by fabsor, plach: Expose entity translation table to views.
|
||||
#1095390 by plach | klonos: Capitalize first letters in project's name.
|
||||
#1599568 by torpy: Fixed No content is displayed if fallback is disabled and
|
||||
entity is set to 'Language Neutral'.
|
||||
#1341314 by plach, donquixote | derhasi: Fixed Nested call of entity_get_info()
|
||||
due to menu_get_router() in entity_translation_entity_info_alter().
|
||||
#1519912 by plach: Fixed Empty stored values may mess up the translatable switch
|
||||
update.
|
||||
#1519906 by plach: Migrate aliases when upgrading nodes.
|
||||
#1519898 by plach: Fixed The upgrade process may freeze.
|
||||
#1519894 by plach: Fixed Entity Translation upgrade migration incomplete.
|
||||
#1432206 by bforchhammer: Fixed Loss of field content during translatability
|
||||
switch operation.
|
||||
#1279372 by getgood, loganfsmyth, evolvingweb, plach, Kristen Pol: Enable bulk
|
||||
field language updates when switching field translatability.
|
||||
#1380380 by bojanz: Fixed Prevent notice when translation has been removed from
|
||||
the form.
|
||||
#1367832 by floretan: Fixed Check #parents and #field_parents() for source
|
||||
language in new translation form.
|
||||
#1174242 by djac, good_man, plach | Jerome F: Properly override the
|
||||
node/%node/translate menu router path.
|
||||
#1293638 by das-peter: Fixed Language fallback on Taxonomy Terms (or any entity
|
||||
type without locale support).
|
||||
#1283200 by plach: Enable content language negotiation the right way.
|
||||
#1280660 by mvc, plach: Fixed Gracefully handle module being disabled and
|
||||
re-enabled.
|
||||
#1280544 by mvc, emarchak: Added Clarify difference between multilingual support
|
||||
options.
|
||||
#1280506 by tarmstrong | plach: Fixed Simpletests broken after upgrading to
|
||||
D7.7.
|
||||
#1262512 by claudiu.cristea: Fixed Clear the entity cache after translate.
|
||||
#1230858 by tarmstrong, plach | valderama: Fixed Compatibility with
|
||||
node_clone(); entity_id() of old node is used, resulting in integrity
|
||||
constraint violation.
|
||||
|
||||
|
||||
Entity Translation 7.x-1.0-alpha1, 2011-09-07
|
||||
---------------------------------------------
|
||||
#1045196 by plach: Fixed Image field translation broken.
|
||||
#1003876 by plach: (follow-up) Fixed labels not replaced by Title.
|
||||
#944874 by das-peter, plach: Added basic tests for the translation creation and
|
||||
editing workflow.
|
||||
#1031370 by joostvdl | rfay: Fixed Translation publishing status is hidden under
|
||||
collapsed 'publishing options' fieldset (and is hidden by default).
|
||||
#1003876 by plach, good_man: (follow-up) Fixed original label translation.
|
||||
#1003876 by good_man, plach: Introduced support for translated entity labels.
|
||||
#936646 by good_man, fietserwin, plach, klonos: Fixed Undefined index access
|
||||
callback/arguments in entity_translation_menu().
|
||||
#1109198 by plach, Countzero: Fixed Node translation overview page broken if
|
||||
i18n is enabled.
|
||||
#1098106 by pcambra, jelenex: Fixed Translated fields aren't validated (or
|
||||
processed with presave and submit field_attach_() hooks).
|
||||
#1111686 by plach: Fixed API documentation wrong.
|
||||
#1095390 by plach, klonos, sun: Changed Do not capitalize first letters in
|
||||
project's name.
|
||||
#1032846 by plach, sun, das-peter: Removed read-ony non translatable fields from
|
||||
the translation form.
|
||||
#1082112 by fietserwin | mattwad: Fixed No argument passed to
|
||||
_translation_tab_access()
|
||||
#1081444 by jelenex: Fixed Unable to create non-published node.
|
||||
#1083704 by jelenex: Fixed Unable to edit node when user has only the 'translate
|
||||
node entities' permission.
|
||||
#1096008 by fietserwin, sun: Fixed URL alias not updated.
|
||||
#1060334 by plach: Completed the transition from Translation 2.
|
||||
#902760 by sun: Changed module name from translation to entity_translation.
|
||||
#1032602 by rfay: Fixed bogus 'base path' docs in hook_translation_info().
|
||||
#1032816 by das-peter: Removed superfluous method getHumanReadableId().
|
||||
#1032728 by das-peter, rfay, sun: Fixed entity info cache not cleared after
|
||||
changing module settings.
|
||||
#1021434 by plach | sun, OnkelTem: Fixed `translation` table is not created on
|
||||
upgrading from core translation.
|
||||
#1003320 by good_man | Berdir, pcambra, plach, sun: Fixed The etid insane field.
|
||||
#1009546 by good_man: Fixed form buttons not wrapped in #type actions.
|
||||
#990120 by das-peter: Changed view mode from 'full' to 'default' in edit form.
|
||||
#929444 by das-peter, plach, sun: Fixed coding standards.
|
||||
#920826 by das-peter, sun: Fixed various issues after initial merge.
|
||||
#673300 by plach, sun: Added initial Translatable Fields UI code.
|
||||
by sun: Added initial baseline of module files.
|
@@ -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.
|
@@ -0,0 +1,38 @@
|
||||
|
||||
Entity translation is built and maintained by the Drupal project community.
|
||||
Everyone is encouraged to submit issues and changes (patches) to improve it, and
|
||||
to contribute in other ways -- see http://drupal.org/contribute to find out how.
|
||||
|
||||
Project maintainers
|
||||
-------------------
|
||||
|
||||
The Entity translation maintainers oversee the development of the project as a
|
||||
whole. The project maintainers for Entity translation are:
|
||||
|
||||
- Francesco Placella 'plach' <http://drupal.org/user/183211>
|
||||
- Daniel F. Kudwien 'sun' <http://drupal.org/user/54136>
|
||||
|
||||
|
||||
Component maintainers
|
||||
---------------------
|
||||
|
||||
The Entity translation component maintainers oversee the development of its
|
||||
subsystems. This role maps pretty closely to the one of core maintainer for the
|
||||
Drupal core project (see http://drupal.org/contribute/core-maintainers for more
|
||||
information on their responsibilities), with the exception that here component
|
||||
maintainers are granted commit access for their specific subsystems. See
|
||||
http://drupal.org/node/363367 to find out how to become a component maintainer.
|
||||
Current component maintainers for Entity translation:
|
||||
|
||||
Base system
|
||||
- Francesco Placella 'plach' <http://drupal.org/user/183211>
|
||||
- Benedikt Forchhammer 'bforchhammer' <http://drupal.org/user/216396>
|
||||
|
||||
Menu integration
|
||||
- Benedikt Forchhammer 'bforchhammer' <http://drupal.org/user/216396>
|
||||
|
||||
Views integration
|
||||
- Fabian Sörqvist 'fabsor' <http://drupal.org/user/255704>
|
||||
|
||||
Node translation upgrade
|
||||
- Francesco Placella 'plach' <http://drupal.org/user/183211>
|
@@ -0,0 +1,37 @@
|
||||
|
||||
-- SUMMARY --
|
||||
|
||||
Allows entities to be translated into different languages.
|
||||
|
||||
For a full description of the module, visit the project page:
|
||||
http://drupal.org/project/entity_translation
|
||||
To submit bug reports and feature suggestions, or to track changes:
|
||||
http://drupal.org/project/issues/entity_translation
|
||||
|
||||
|
||||
-- REQUIREMENTS --
|
||||
|
||||
None.
|
||||
|
||||
|
||||
-- INSTALLATION --
|
||||
|
||||
* Install as usual, see http://drupal.org/node/70151 for further information.
|
||||
|
||||
|
||||
-- CONFIGURATION --
|
||||
|
||||
* @todo
|
||||
|
||||
|
||||
-- USAGE --
|
||||
|
||||
* @todo
|
||||
|
||||
|
||||
-- CONTACT --
|
||||
|
||||
Current maintainers:
|
||||
* Francesco Placella (plach) - http://drupal.org/user/183211
|
||||
* Daniel F. Kudwien (sun) - http://drupal.org/user/54136
|
||||
|
@@ -0,0 +1,4 @@
|
||||
.entity-translation-language-tabs {
|
||||
clear: both;
|
||||
padding-top: 0.75em;
|
||||
}
|
@@ -0,0 +1,669 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* The entity translation user interface.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Builder function for the entity translation settings form.
|
||||
*/
|
||||
function entity_translation_admin_form($form, $form_state) {
|
||||
$options = array();
|
||||
|
||||
$form['locale_field_language_fallback'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable language fallback'),
|
||||
'#description' => t('When language fallback is enabled, if a translation is not available for the requested language an existing one will be displayed.'),
|
||||
'#default_value' => variable_get('locale_field_language_fallback', TRUE),
|
||||
);
|
||||
|
||||
$form['entity_translation_shared_labels'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Display shared labels'),
|
||||
'#description' => t('Append an "all languages" hint to entity form widgets shared across translations.'),
|
||||
'#default_value' => variable_get('entity_translation_shared_labels', TRUE),
|
||||
);
|
||||
|
||||
$form['entity_translation_workflow_enabled'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Enable translation workflow permissions'),
|
||||
'#description' => t('By enabling the translation workflow permissions it will be possible to limit the access to the entity form elements. Once this is active every role previously allowed to access the entity form will need to be granted the <em>Edit original values</em> permission to edit the entity in the original language. Moreover, form elements dealing with values shared across the translations will be visible only to roles having been granted the <em>Edit shared values</em> permission.'),
|
||||
'#default_value' => entity_translation_workflow_enabled(),
|
||||
);
|
||||
|
||||
$_null = NULL;
|
||||
list($items, ) = menu_router_build();
|
||||
_entity_translation_validate_path_schemes($_null, FALSE, $items);
|
||||
|
||||
foreach (entity_get_info() as $entity_type => $info) {
|
||||
if ($info['fieldable']) {
|
||||
$et_info = &$info['translation']['entity_translation'];
|
||||
_entity_translation_process_path_schemes($entity_type, $et_info);
|
||||
_entity_translation_validate_path_schemes($et_info['path schemes'], $info['label']);
|
||||
|
||||
// Translation can be enabled for the current entity only if it defines at
|
||||
// least one valid base path.
|
||||
foreach ($et_info['path schemes'] as $delta => $scheme) {
|
||||
if (!empty($scheme['base path'])) {
|
||||
$options[$entity_type] = !empty($info['label']) ? t($info['label']) : $entity_type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Avoid bloating memory with unused data.
|
||||
drupal_static_reset('_entity_translation_validate_path_schemes');
|
||||
$enabled_types = array_filter(variable_get('entity_translation_entity_types', array()));
|
||||
|
||||
$form['enabled'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Translatable entity types'),
|
||||
'#description' => t('Select which entities can be translated.'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => !empty($enabled_types),
|
||||
);
|
||||
|
||||
$form['enabled']['entity_translation_entity_types'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => $options,
|
||||
'#default_value' => $enabled_types,
|
||||
);
|
||||
|
||||
$form['tabs'] = array(
|
||||
'#type' => 'vertical_tabs',
|
||||
);
|
||||
|
||||
$languages = array(
|
||||
ENTITY_TRANSLATION_LANGUAGE_DEFAULT => t('Default language'),
|
||||
ENTITY_TRANSLATION_LANGUAGE_CURRENT => t('Current language'),
|
||||
ENTITY_TRANSLATION_LANGUAGE_AUTHOR => t('Author language'),
|
||||
LANGUAGE_NONE => t('Language neutral'),
|
||||
);
|
||||
foreach (entity_translation_languages($entity_type) as $langcode => $language) {
|
||||
$languages[$langcode] = t($language->name);
|
||||
}
|
||||
|
||||
foreach ($enabled_types as $entity_type) {
|
||||
$label = $options[$entity_type];
|
||||
|
||||
$entity_info = entity_get_info($entity_type);
|
||||
$bundles = !empty($entity_info['bundles']) ? $entity_info['bundles'] : array($entity_type => array('label' => $label));
|
||||
$enabled_bundles = 0;
|
||||
|
||||
foreach ($bundles as $bundle => $info) {
|
||||
if (entity_translation_enabled_bundle($entity_type, $bundle)) {
|
||||
$enabled_bundles++;
|
||||
$settings = entity_translation_settings($entity_type, $bundle);
|
||||
$settings_key = 'entity_translation_settings_' . $entity_type . '__' . $bundle;
|
||||
$form['settings'][$entity_type][$settings_key] = array('#tree' => TRUE);
|
||||
|
||||
// If the entity defines no bundle we do not need the fieldset.
|
||||
if (count($bundles) > 1 || $bundle != $entity_type) {
|
||||
$form['settings'][$entity_type][$settings_key] += array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => $info['label'],
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
$form['settings'][$entity_type][$settings_key]['default_language'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Default language'),
|
||||
'#options' => $languages,
|
||||
'#default_value' => $settings['default_language'],
|
||||
);
|
||||
|
||||
$form['settings'][$entity_type][$settings_key]['hide_language_selector'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Hide language selector'),
|
||||
'#default_value' => $settings['hide_language_selector'],
|
||||
);
|
||||
|
||||
$form['settings'][$entity_type][$settings_key]['exclude_language_none'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Exclude <em>Language neutral</em> from the available languages'),
|
||||
'#default_value' => $settings['exclude_language_none'],
|
||||
);
|
||||
|
||||
$form['settings'][$entity_type][$settings_key]['lock_language'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Prevent language from being changed once the entity has been created'),
|
||||
'#default_value' => $settings['lock_language'],
|
||||
);
|
||||
|
||||
$form['settings'][$entity_type][$settings_key]['shared_fields_original_only'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Hide shared elements on translation forms'),
|
||||
'#description' => t('Display form elements shared across translations only on entity forms for the original language.'),
|
||||
'#default_value' => $settings['shared_fields_original_only'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($enabled_bundles > 0) {
|
||||
$form['settings'][$entity_type][$settings_key]['#collapsed'] = $enabled_bundles > 1;
|
||||
$form['settings'][$entity_type] += array(
|
||||
'#type' => 'fieldset',
|
||||
'#group' => 'tabs',
|
||||
'#title' => $label,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$form = system_settings_form($form);
|
||||
|
||||
// Menu rebuilding needs to be performed after the system settings are saved.
|
||||
$form['#submit'][] = 'entity_translation_admin_form_submit';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the entity translation settings form.
|
||||
*/
|
||||
function entity_translation_admin_form_submit($form, $form_state) {
|
||||
// Clear the entity info cache for the new entity translation settings.
|
||||
entity_info_cache_clear();
|
||||
menu_rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given settings to every defined bundle.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type the settings refer to.
|
||||
* @param $settings
|
||||
* (optional) The settings to be applied. Defaults to the entity default
|
||||
* settings.
|
||||
*/
|
||||
function entity_translation_settings_init($entity_type, $settings = array()) {
|
||||
if (entity_translation_enabled($entity_type)) {
|
||||
$info = entity_get_info($entity_type);
|
||||
$bundles = !empty($info['bundles']) ? array_keys($info['bundles']) : array($entity_type);
|
||||
|
||||
foreach ($bundles as $bundle) {
|
||||
if (entity_translation_enabled_bundle($entity_type, $bundle)) {
|
||||
$settings += entity_translation_settings($entity_type, $bundle);
|
||||
}
|
||||
}
|
||||
|
||||
variable_set('entity_translation_settings_' . $entity_type . '__' . $bundle, $settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translations overview page callback.
|
||||
*/
|
||||
function entity_translation_overview($entity_type, $entity, $callback = NULL) {
|
||||
// Entity translation and node translation share the same system path.
|
||||
if ($callback && entity_translation_node($entity_type, $entity)) {
|
||||
return entity_translation_overview_callback($callback, $entity);
|
||||
}
|
||||
|
||||
$handler = entity_translation_get_handler($entity_type, $entity);
|
||||
$handler->initPathScheme();
|
||||
|
||||
// Initialize translations if they are empty.
|
||||
$translations = $handler->getTranslations();
|
||||
if (empty($translations->original)) {
|
||||
$handler->initTranslations();
|
||||
$handler->saveTranslations();
|
||||
}
|
||||
|
||||
// Ensure that we have a coherent status between entity language and field
|
||||
// languages.
|
||||
if ($handler->initOriginalTranslation()) {
|
||||
// FIXME!
|
||||
field_attach_presave($entity_type, $entity);
|
||||
field_attach_update($entity_type, $entity);
|
||||
}
|
||||
|
||||
$header = array(t('Language'), t('Source language'), t('Translation'), t('Status'), t('Operations'));
|
||||
$languages = entity_translation_languages();
|
||||
$source = $translations->original;
|
||||
$path = $handler->getViewPath();
|
||||
$rows = array();
|
||||
|
||||
if (drupal_multilingual()) {
|
||||
// If we have a view path defined for the current entity get the switch
|
||||
// links based on it.
|
||||
if ($path) {
|
||||
$links = EntityTranslationDefaultHandler::languageSwitchLinks($path);
|
||||
}
|
||||
|
||||
foreach ($languages as $language) {
|
||||
$classes = array();
|
||||
$options = array();
|
||||
$language_name = $language->name;
|
||||
$langcode = $language->language;
|
||||
$edit_path = $handler->getEditPath($langcode);
|
||||
$add_path = "{$handler->getEditPath()}/add/$source/$langcode";
|
||||
|
||||
if ($edit_path) {
|
||||
$add_links = EntityTranslationDefaultHandler::languageSwitchLinks($add_path);
|
||||
$edit_links = EntityTranslationDefaultHandler::languageSwitchLinks($edit_path);
|
||||
}
|
||||
|
||||
if (isset($translations->data[$langcode])) {
|
||||
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
|
||||
|
||||
// Existing translation in the translation set: display status.
|
||||
$is_original = $langcode == $translations->original;
|
||||
$translation = $translations->data[$langcode];
|
||||
$label = _entity_translation_label($entity_type, $entity, $langcode);
|
||||
$link = isset($links->links[$langcode]['href']) ? $links->links[$langcode] : array('href' => $path, 'language' => $language);
|
||||
$row_title = l($label, $link['href'], $link);
|
||||
|
||||
if (empty($link['href'])) {
|
||||
$row_title = $is_original ? $label : t('n/a');
|
||||
}
|
||||
|
||||
if ($edit_path && $handler->getAccess('update') && $handler->getTranslationAccess($langcode)) {
|
||||
$link = isset($edit_links->links[$langcode]['href']) ? $edit_links->links[$langcode] : array('href' => $edit_path, 'language' => $language);
|
||||
$link['query'] = isset($_GET['destination']) ? drupal_get_destination() : FALSE;
|
||||
$options[] = l(t('edit'), $link['href'], $link);
|
||||
}
|
||||
|
||||
$status = $translation['status'] ? t('Published') : t('Not published');
|
||||
$classes[] = $translation['status'] ? 'published' : 'not-published';
|
||||
$status .= isset($translation['translate']) && $translation['translate'] ? theme('entity_translation_overview_outdated', array('message' => t('outdated'))) : '';
|
||||
$classes[] = isset($translation['translate']) && $translation['translate'] ? 'outdated' : '';
|
||||
|
||||
if ($is_original) {
|
||||
$language_name = t('<strong>@language_name</strong>', array('@language_name' => $language_name));
|
||||
$source_name = t('(original content)');
|
||||
}
|
||||
else {
|
||||
$source_name = $languages[$translation['source']]->name;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No such translation in the set yet: help user to create it.
|
||||
$row_title = $source_name = t('n/a');
|
||||
|
||||
if ($source != $langcode && $handler->getAccess('update')) {
|
||||
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
|
||||
$translatable = FALSE;
|
||||
|
||||
foreach (field_info_instances($entity_type, $bundle) as $instance) {
|
||||
$field_name = $instance['field_name'];
|
||||
$field = field_info_field($field_name);
|
||||
if ($field['translatable']) {
|
||||
$translatable = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$link = isset($add_links->links[$langcode]['href']) ? $add_links->links[$langcode] : array('href' => $add_path, 'language' => $language);
|
||||
$link['query'] = isset($_GET['destination']) ? drupal_get_destination() : FALSE;
|
||||
$options[] = $translatable ? l(t('add'), $link['href'], $link) : t('No translatable fields');
|
||||
$classes[] = $translatable ? '' : 'non-traslatable';
|
||||
}
|
||||
$status = t('Not translated');
|
||||
}
|
||||
$rows[] = array(
|
||||
'data' => array($language_name, $source_name, $row_title, $status, implode(" | ", $options)),
|
||||
'class' => $classes,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
drupal_set_title(t('Translations of %label', array('%label' => $handler->getLabel())), PASS_THROUGH);
|
||||
|
||||
// Add metadata to the build render allow to let other modules know about
|
||||
// which entity this is.
|
||||
$build['#entity_type'] = $entity_type;
|
||||
$build['#entity'] = $entity;
|
||||
$build['entity_translation_overview'] = array(
|
||||
'#theme' => 'entity_translation_overview',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
);
|
||||
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the appropriate translation overview callback.
|
||||
*/
|
||||
function entity_translation_overview_callback($callback, $entity) {
|
||||
if (module_exists($callback['module'])) {
|
||||
if (isset($callback['file'])) {
|
||||
$path = isset($callback['file path']) ? $callback['file path'] : drupal_get_path('module', $callback['module']);
|
||||
require_once DRUPAL_ROOT . '/' . $path . '/' . $callback['file'];
|
||||
}
|
||||
return $callback['page callback']($entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate entity label for the given language.
|
||||
*/
|
||||
function _entity_translation_label($entity_type, $entity, $langcode = NULL) {
|
||||
if (function_exists('title_entity_label')) {
|
||||
list (, , $bundle) = entity_extract_ids($entity_type, $entity);
|
||||
$entity_info = entity_get_info($entity_type);
|
||||
if (!empty($entity_info['entity keys']['label'])) {
|
||||
$legacy_field = $entity_info['entity keys']['label'];
|
||||
if (title_field_replacement_enabled($entity_type, $bundle, $legacy_field)) {
|
||||
$title = title_entity_label($entity, $entity_type, $langcode);
|
||||
if (!empty($title)) {
|
||||
return $title;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return t('view');
|
||||
}
|
||||
/**
|
||||
* Theme wrapper for the entity translation language page.
|
||||
*/
|
||||
function theme_entity_translation_overview($variables){
|
||||
$rows = $variables['rows'];
|
||||
return theme('table', array(
|
||||
'rows' => $rows,
|
||||
'header' => $variables['header'],
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme wrapper for the entity translation language outdated translation.
|
||||
*/
|
||||
function theme_entity_translation_overview_outdated($variables){
|
||||
$message = $variables['message'];
|
||||
return ' - <span class="marker">' . $message . '</span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation deletion confirmation form.
|
||||
*/
|
||||
function entity_translation_delete_confirm($form, $form_state, $entity_type, $entity, $langcode) {
|
||||
$handler = entity_translation_get_handler($entity_type, $entity);
|
||||
$languages = language_list();
|
||||
|
||||
$form = array(
|
||||
'#handler' => $handler,
|
||||
'#entity_type' => $entity_type,
|
||||
'#entity' => $entity,
|
||||
'#language' => $langcode,
|
||||
);
|
||||
|
||||
return confirm_form(
|
||||
$form,
|
||||
t('Are you sure you want to delete the @language translation of %label?', array('@language' => $languages[$langcode]->name, '%label' => $handler->getLabel())),
|
||||
$handler->getEditPath($langcode),
|
||||
t('This action cannot be undone.'),
|
||||
t('Delete'),
|
||||
t('Cancel')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the translation deletion confirmation.
|
||||
*/
|
||||
function entity_translation_delete_confirm_submit($form, &$form_state) {
|
||||
$handler = $form['#handler'];
|
||||
$entity_type = $form['#entity_type'];
|
||||
$entity = $form['#entity'];
|
||||
$langcode = $form['#language'];
|
||||
|
||||
// Some modules expect the original values to be present when updating the
|
||||
// field values. Since we are deleting the translation no value has changed.
|
||||
$entity->original = $entity;
|
||||
|
||||
// Remove the translation entry and the related fields.
|
||||
$handler->removeTranslation($langcode);
|
||||
field_attach_presave($entity_type, $entity);
|
||||
field_attach_update($entity_type, $entity);
|
||||
|
||||
$form_state['redirect'] = $handler->getTranslatePath();
|
||||
}
|
||||
|
||||
/*
|
||||
* Confirm form for changing field translatability.
|
||||
*/
|
||||
function entity_translation_translatable_form($form, &$form_state, $field_name) {
|
||||
$field = field_info_field($field_name);
|
||||
|
||||
if ($field['translatable']) {
|
||||
$title = t('Are you sure you want to disable translation for this field?');
|
||||
$text = t('All occurrences of this field will become <em>untranslatable</em>:');
|
||||
if (empty($form_state['input'])) {
|
||||
drupal_set_message(t('All the existing field translations will be deleted. This operation cannot be undone.'), 'warning');
|
||||
}
|
||||
}
|
||||
else {
|
||||
$title = t('Are you sure you want to enable translation for this field?');
|
||||
$text = t('All occurrences of this field will become <em>translatable</em>:');
|
||||
|
||||
$form['options'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Migration settings'),
|
||||
'#weight' => -10,
|
||||
);
|
||||
$form['options']['copy_all_languages'] = array(
|
||||
'#title' => t('Copy translations'),
|
||||
'#description' => t('Copy field data into <em>all</em> existing translations, otherwise data will only be available in the original language.'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
$text .= _entity_translation_field_desc($field);
|
||||
$text .= t('This operation may take a long time to complete.');
|
||||
|
||||
// We need to keep some information for later processing.
|
||||
$form_state['field'] = $field;
|
||||
|
||||
// Store the 'translatable' status on the client side to prevent outdated form
|
||||
// submits from toggling translatability.
|
||||
$form['translatable'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => $field['translatable'],
|
||||
);
|
||||
|
||||
return confirm_form($form, $title, '', $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the field settings form.
|
||||
*
|
||||
* This submit handler maintains consistency between the translatability of an
|
||||
* entity and the language under which the field data is stored. When a field is
|
||||
* marked as translatable, all the data in $entity->{field_name}[LANGUAGE_NONE]
|
||||
* is moved to $entity->{field_name}[$entity_language]. When a field is marked
|
||||
* as untranslatable the opposite process occurs. Note that marking a field as
|
||||
* untranslatable will cause all of its translations to be permanently removed,
|
||||
* with the exception of the one corresponding to the entity language.
|
||||
*/
|
||||
function entity_translation_translatable_form_submit($form, $form_state) {
|
||||
// This is the current state that we want to reverse.
|
||||
$translatable = $form_state['values']['translatable'];
|
||||
$field_name = $form_state['field']['field_name'];
|
||||
$copy_all_languages = !empty($form_state['values']['copy_all_languages']);
|
||||
$field = field_info_field($field_name);
|
||||
|
||||
if ($field['translatable'] !== $translatable) {
|
||||
// Field translatability has changed since form creation, abort.
|
||||
$t_args = array('%field_name' => $field_name, '!translatable' => $translatable ? t('untranslatable') : t('translatable'));
|
||||
drupal_set_message(t('The field %field_name is already !translatable. No change was performed.', $t_args), 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// If a field is untranslatable, it can have no data except under
|
||||
// LANGUAGE_NONE. Thus we need a field to be translatable before we convert
|
||||
// data to the entity language. Conversely we need to switch data back to
|
||||
// LANGUAGE_NONE before making a field untranslatable lest we lose
|
||||
// information.
|
||||
$operations = array(
|
||||
array('entity_translation_translatable_batch', array(!$translatable, $field_name, $copy_all_languages)),
|
||||
array('entity_translation_translatable_switch', array(!$translatable, $field_name)),
|
||||
);
|
||||
$operations = $translatable ? $operations : array_reverse($operations);
|
||||
|
||||
$t_args = array('%field' => $field_name);
|
||||
$title = !$translatable ? t('Enabling translation for the %field field', $t_args) : t('Disabling translation for the %field field', $t_args);
|
||||
|
||||
$batch = array(
|
||||
'title' => $title,
|
||||
'operations' => $operations,
|
||||
'finished' => 'entity_translation_translatable_batch_done',
|
||||
'file' => drupal_get_path('module', 'entity_translation') . '/entity_translation.admin.inc',
|
||||
);
|
||||
|
||||
batch_set($batch);
|
||||
}
|
||||
|
||||
/*
|
||||
* Toggle translatability of the given field.
|
||||
*
|
||||
* This is called from a batch operation, but should only run once per field.
|
||||
*/
|
||||
function entity_translation_translatable_switch($translatable, $field_name) {
|
||||
$field = field_info_field($field_name);
|
||||
|
||||
if ($field['translatable'] === $translatable) {
|
||||
return;
|
||||
}
|
||||
|
||||
$field['translatable'] = $translatable;
|
||||
field_update_field($field);
|
||||
|
||||
// This is needed for versions of Drupal core 7.10 and lower.
|
||||
// See http://drupal.org/node/1380660 for details.
|
||||
drupal_static_reset('field_available_languages');
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch operation. Convert field data to or from LANGUAGE_NONE.
|
||||
*/
|
||||
function entity_translation_translatable_batch($translatable, $field_name, $copy_all_languages, &$context) {
|
||||
if (empty($context['sandbox'])) {
|
||||
$context['sandbox']['progress'] = 0;
|
||||
|
||||
// How many entities will need processing?
|
||||
$query = new EntityFieldQuery();
|
||||
$count = $query
|
||||
->fieldCondition($field_name)
|
||||
->count()
|
||||
->execute();
|
||||
|
||||
if (intval($count) === 0) {
|
||||
// Nothing to do.
|
||||
$context['finished'] = 1;
|
||||
return;
|
||||
}
|
||||
$context['sandbox']['max'] = $count;
|
||||
}
|
||||
|
||||
// Number of entities to be processed for each step.
|
||||
$limit = variable_get('entity_translation_translatable_batch_limit', 10);
|
||||
|
||||
$offset = $context['sandbox']['progress'];
|
||||
$query = new EntityFieldQuery();
|
||||
$result = $query
|
||||
->fieldCondition($field_name)
|
||||
->entityOrderBy('entity_id')
|
||||
->range($offset, $limit)
|
||||
->execute();
|
||||
|
||||
foreach ($result as $entity_type => $entities) {
|
||||
foreach (entity_load($entity_type, array_keys($entities)) as $entity) {
|
||||
$context['sandbox']['progress']++;
|
||||
$handler = entity_translation_get_handler($entity_type, $entity);
|
||||
$langcode = $handler->getLanguage();
|
||||
|
||||
// Skip process for language neutral entities.
|
||||
if ($langcode == LANGUAGE_NONE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need a two-steps approach while updating field translations: given
|
||||
// that field-specific update functions might rely on the stored values to
|
||||
// perform their processing, see for instance file_field_update(), first
|
||||
// we need to store the new translations and only after we can remove the
|
||||
// old ones. Otherwise we might have data loss, since the removal of the
|
||||
// old translations might occur before the new ones are stored.
|
||||
if ($translatable && isset($entity->{$field_name}[LANGUAGE_NONE])) {
|
||||
// If the field is being switched to translatable and has data for
|
||||
// LANGUAGE_NONE then we need to move the data to the right language.
|
||||
|
||||
$translations = $handler->getTranslations();
|
||||
|
||||
if ($copy_all_languages && !empty($translations->data)) {
|
||||
foreach ($translations->data as $translation) {
|
||||
$entity->{$field_name}[$translation['language']] = $entity->{$field_name}[LANGUAGE_NONE];
|
||||
}
|
||||
}
|
||||
else {
|
||||
$entity->{$field_name}[$langcode] = $entity->{$field_name}[LANGUAGE_NONE];
|
||||
}
|
||||
|
||||
// Store the original value.
|
||||
_entity_translation_update_field($entity_type, $entity, $field_name);
|
||||
$entity->{$field_name}[LANGUAGE_NONE] = array();
|
||||
// Remove the language neutral value.
|
||||
_entity_translation_update_field($entity_type, $entity, $field_name);
|
||||
}
|
||||
elseif (!$translatable && isset($entity->{$field_name}[$langcode])) {
|
||||
// The field has been marked untranslatable and has data in the entity
|
||||
// language: we need to move it to LANGUAGE_NONE and drop the other
|
||||
// translations.
|
||||
$entity->{$field_name}[LANGUAGE_NONE] = $entity->{$field_name}[$langcode];
|
||||
// Store the original value.
|
||||
_entity_translation_update_field($entity_type, $entity, $field_name);
|
||||
// Remove translations.
|
||||
foreach ($entity->{$field_name} as $langcode => $items) {
|
||||
if ($langcode != LANGUAGE_NONE) {
|
||||
$entity->{$field_name}[$langcode] = array();
|
||||
}
|
||||
}
|
||||
_entity_translation_update_field($entity_type, $entity, $field_name);
|
||||
}
|
||||
else {
|
||||
// No need to save unchanged entities.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given field translations.
|
||||
*/
|
||||
function _entity_translation_update_field($entity_type, $entity, $field_name) {
|
||||
$empty = 0;
|
||||
$field = field_info_field($field_name);
|
||||
|
||||
// Ensure that we are trying to store only valid data.
|
||||
foreach ($entity->{$field_name} as $langcode => $items) {
|
||||
$entity->{$field_name}[$langcode] = _field_filter_items($field, $entity->{$field_name}[$langcode]);
|
||||
$empty += empty($entity->{$field_name}[$langcode]);
|
||||
}
|
||||
|
||||
// Save the field value only if there is at least one item available,
|
||||
// otherwise any stored empty field value would be deleted. If this happens
|
||||
// the range queries would be messed up.
|
||||
if ($empty < count($entity->{$field_name})) {
|
||||
field_attach_presave($entity_type, $entity);
|
||||
field_attach_update($entity_type, $entity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the exit status of the batch operation.
|
||||
*/
|
||||
function entity_translation_translatable_batch_done($success, $results, $operations) {
|
||||
if ($success) {
|
||||
drupal_set_message(t("Data successfully processed."));
|
||||
}
|
||||
else {
|
||||
// @todo: Do something about this case.
|
||||
drupal_set_message(t("Something went wrong while processing data. Some nodes may appear to have lost fields."));
|
||||
}
|
||||
}
|
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API documentation for the Entity translation module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Allows modules to define their own translation info.
|
||||
*
|
||||
* Entity Translation relies on the core entity information to provide its
|
||||
* translation features. See the documentation of hook_entity_info() in the core
|
||||
* API documentation (system.api.php) for more details on all the entity info
|
||||
* keys that may be defined.
|
||||
*
|
||||
* To make Entity Translation automatically support an entity type some keys
|
||||
* may need to be defined, but none of them is required except the 'base path'
|
||||
* key if the entity path is different from ENTITY_TYPE/%ENTITY_TYPE (e.g.
|
||||
* taxonomy/term/1). The 'base path' key is used to determine the view, edit and
|
||||
* translate path if they follow the default path patterns and to reliably
|
||||
* alter menu information to provide the translation UI. If the entity path
|
||||
* matches the default pattern above, and there is no need for a dedicated
|
||||
* translation handler class, Entity Translation will provide built-in support
|
||||
* for the entity.
|
||||
*
|
||||
* The entity translation info is an associative array that has to match the
|
||||
* following structure. Three nested sub-arrays keyed respectively by entity
|
||||
* type, the 'translation' key and the 'entity_translation' key: the second one
|
||||
* is the key defined by the core entity system while the third one registers
|
||||
* Entity translation as a field translation handler. Elements:
|
||||
* - class: The name of the translation handler class, which is used to handle
|
||||
* the translation process. Defaults to 'EntityTranslationDefaultHandler'.
|
||||
* - base path: The base menu router path to which attach the administration
|
||||
* user interface. Defaults to ENTITY_TYPE/%ENTITY_TYPE.
|
||||
* - access callback: The access callback for the translation pages. Defaults to
|
||||
* 'entity_translation_tab_access'.
|
||||
* - access arguments: The access arguments for the translation pages. Defaults
|
||||
* to "array($entity_type, $entity_position)".
|
||||
* - view path: The menu router path to be used to view the entity. Defaults to
|
||||
* the base path.
|
||||
* - edit path: The menu router path to be used to edit the entity. Defaults to
|
||||
* "$base_path/edit".
|
||||
* - translate path: The menu router path to be used for attaching the
|
||||
* translation UI. Defaults to "$base_path/translate".
|
||||
* - path wildcard: The menu router path wildcard identifying the entity.
|
||||
* Defaults to %ENTITY_TYPE.
|
||||
* - admin theme: Whether the translation UI should use the administration
|
||||
* theme. Defaults to TRUE.
|
||||
* - path schemes: An array of menu router path schemes used for attaching the
|
||||
* entity translation UI. This element can be used to declare additional path
|
||||
* schemes, if an entity type uses multiple schemes for managing entities
|
||||
* (e.g. different schemes for different bundles). Each path scheme can define
|
||||
* the following elements (descriptions see above): 'base path', 'view path',
|
||||
* 'edit path', 'translate path', 'path wildcard' and 'admin theme'. All path
|
||||
* elements that are defined directly on the entity translation info array are
|
||||
* automatically added as a 'default' path scheme.
|
||||
* - theme callback: The callback to be used to determine the translation
|
||||
* theme. Defaults to 'variable_get'.
|
||||
* - theme arguments: The arguments to be used to determine the translation
|
||||
* theme. Defaults to "array('admin_theme')".
|
||||
* - edit form: The key to be used to retrieve the entity object from the form
|
||||
* state array. An empty value prevents Entity translation from performing
|
||||
* alterations to the entity form. Defaults to ENTITY_TYPE.
|
||||
* - skip original values access: A flag specifying whether skipping access
|
||||
* control when editing original values for this entity. Defaults to FALSE.
|
||||
* - bundle callback: A callback to check whether the passed bundle has entity
|
||||
* translation enabled. If empty all bundles are supposed to be enabled.
|
||||
* - default settings: The defaults to be applied to settings when an explicit
|
||||
* choice is missing.
|
||||
*/
|
||||
function hook_entity_info() {
|
||||
$info['custom_entity'] = array(
|
||||
'translation' => array(
|
||||
'entity_translation' => array(
|
||||
'class' => 'EntityTranslationCustomEntityHandler',
|
||||
'base path' => 'custom_entity/%custom_entity',
|
||||
'access callback' => 'custom_entity_tab_access',
|
||||
'access arguments' => array(1),
|
||||
'edit form' => 'custom_entity_form_state_key',
|
||||
'bundle callback' => 'custom_entity_translation_enabled_bundle',
|
||||
'default settings' => array(
|
||||
'default_language' => LANGUAGE_NONE,
|
||||
'hide_language_selector' => FALSE,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Entity type which has multiple (e.g. bundle-specific) paths.
|
||||
$info['custom_entity_2'] = array(
|
||||
'translation' => array(
|
||||
'entity_translation' => array(
|
||||
'class' => 'EntityTranslationCustomEntityHandler',
|
||||
'path sets' => array(
|
||||
'default' => array(
|
||||
'base path' => 'custom_entity_2/%custom_entity',
|
||||
'path wildcard' => '%custom_entity',
|
||||
),
|
||||
'fancy' => array(
|
||||
// Base path is not required.
|
||||
'edit path' => 'fancy/%entity/edit',
|
||||
'path wildcard' => '%entity',
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows modules to act when a new translation is added.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type.
|
||||
* @param $entity
|
||||
* The entity.
|
||||
* @param $translation
|
||||
* The inserted translation array.
|
||||
* @param $values
|
||||
* The translated set of values, if any.
|
||||
*/
|
||||
function hook_entity_translation_insert($entity_type, $entity, $translation, $values = array()) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows modules to act when a translation is updated.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type.
|
||||
* @param $entity
|
||||
* The entity.
|
||||
* @param $translation
|
||||
* The updated translation array.
|
||||
* @param $values
|
||||
* The translated set of values, if any.
|
||||
*/
|
||||
function hook_entity_translation_update($entity_type, $entity, $translation, $values = array()) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows modules to act when a translation is deleted.
|
||||
*
|
||||
* @param $entity_type
|
||||
* The entity type.
|
||||
* @param $entity
|
||||
* The entity.
|
||||
* @param $langcode
|
||||
* The langcode of the translation which was deleted.
|
||||
*/
|
||||
function hook_entity_translation_delete($entity_type, $entity, $langcode) {
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
name = Entity Translation
|
||||
description = Allows entities to be translated into different languages.
|
||||
package = Multilingual - Entity Translation
|
||||
core = 7.x
|
||||
configure = admin/config/regional/entity_translation
|
||||
dependencies[] = locale (>7.14)
|
||||
|
||||
files[] = includes/translation.handler.inc
|
||||
files[] = includes/translation.handler.comment.inc
|
||||
files[] = includes/translation.handler.node.inc
|
||||
files[] = includes/translation.handler.taxonomy_term.inc
|
||||
files[] = includes/translation.handler.user.inc
|
||||
|
||||
files[] = tests/entity_translation.test
|
||||
|
||||
files[] = views/entity_translation_handler_relationship.inc
|
||||
files[] = views/entity_translation_handler_field_translate_link.inc
|
||||
files[] = views/entity_translation_handler_field_label.inc
|
||||
files[] = views/entity_translation_handler_filter_entity_type.inc
|
||||
files[] = views/entity_translation_handler_filter_language.inc
|
||||
files[] = views/entity_translation_handler_filter_translation_exists.inc
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-07-23
|
||||
version = "7.x-1.0-beta3"
|
||||
core = "7.x"
|
||||
project = "entity_translation"
|
||||
datestamp = "1374601567"
|
||||
|
@@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation functions for Entity Translation module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function entity_translation_schema() {
|
||||
$schema['entity_translation'] = array(
|
||||
'description' => 'Table to track entity translations',
|
||||
'fields' => array(
|
||||
'entity_type' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The entity type this translation relates to',
|
||||
),
|
||||
'entity_id' => array(
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'description' => 'The entity id this translation relates to',
|
||||
),
|
||||
// @todo: Consider an integer field for 'language'.
|
||||
'language' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The target language for this translation.',
|
||||
),
|
||||
'source' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => 'The source language from which this translation was created.',
|
||||
),
|
||||
'uid' => array(
|
||||
'description' => 'The author of this translation.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'status' => array(
|
||||
'description' => 'Boolean indicating whether the translation is published (visible to non-administrators).',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
),
|
||||
'translate' => array(
|
||||
'description' => 'A boolean indicating whether this translation needs to be updated.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'created' => array(
|
||||
'description' => 'The Unix timestamp when the translation was created.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
'changed' => array(
|
||||
'description' => 'The Unix timestamp when the translation was most recently saved.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
),
|
||||
'primary key' => array('entity_type', 'entity_id', 'language'),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function entity_translation_install() {
|
||||
// entity_translation_form_alter() needs to run after locale_form_alter() and
|
||||
// translation_menu(); entity_translation_menu_alter() needs to run after
|
||||
// i18n_node_menu_alter().
|
||||
db_update('system')
|
||||
->fields(array('weight' => 11))
|
||||
->condition('name', 'entity_translation')
|
||||
->execute();
|
||||
|
||||
// Enable translation for nodes.
|
||||
variable_set('entity_translation_entity_types', array('node' => 'node'));
|
||||
|
||||
// Make translation use the content language type.
|
||||
variable_set('translation_language_type', LANGUAGE_TYPE_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant 'edit $type original values' permission to existing roles.
|
||||
*/
|
||||
function _entity_translation_grant_edit_permissions() {
|
||||
variable_set('entity_translation_workflow_enabled', TRUE);
|
||||
$permissions = array();
|
||||
|
||||
// Nodes.
|
||||
$permissions['node'][] = 'bypass node access';
|
||||
foreach (node_permissions_get_configured_types() as $type) {
|
||||
$permissions['node'][] = "edit own $type content";
|
||||
$permissions['node'][] = "edit any $type content";
|
||||
}
|
||||
|
||||
// Comments.
|
||||
if (module_exists('comment')) {
|
||||
$permissions['comment'][] = 'administer comments';
|
||||
$permissions['comment'][] = 'edit own comments';
|
||||
}
|
||||
|
||||
// Taxonomy terms.
|
||||
if (module_exists('taxonomy')) {
|
||||
$permissions['taxonomy_term'][] = 'administer taxonomy';
|
||||
foreach (taxonomy_get_vocabularies() as $vocabulary) {
|
||||
$permissions['taxonomy_term'][] = "edit terms in {$vocabulary->vid}";
|
||||
}
|
||||
}
|
||||
|
||||
$assignments = array();
|
||||
foreach ($permissions as $entity_type => $permissions_filter) {
|
||||
if (entity_translation_enabled($entity_type)) {
|
||||
$permission = "edit $entity_type original values";
|
||||
$assignments[] = _entity_translation_grant_permission($permission, $permissions_filter);
|
||||
$permission = "edit $entity_type translation shared fields";
|
||||
$assignments[] = _entity_translation_grant_permission($permission, $permissions_filter);
|
||||
}
|
||||
}
|
||||
$assignments = '<ul><li>' . implode('</li><li>', $assignments) . '</li></ul>';
|
||||
|
||||
$t = get_t();
|
||||
return $t('The following permissions have been assigned to existing roles: !assignments', array('!assignments' => $assignments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant the given permission to all roles which already have any of the
|
||||
* permissions specified in the $permissions_filter parameter.
|
||||
*
|
||||
* @param $permission
|
||||
* The new permission which to grant.
|
||||
* @param $permissions_filter
|
||||
* List of permissions used for loading roles.
|
||||
*
|
||||
* @return
|
||||
* A message describing permission changes.
|
||||
*/
|
||||
function _entity_translation_grant_permission($permission, $permissions_filter = NULL) {
|
||||
$roles = user_roles(FALSE, $permissions_filter);
|
||||
foreach ($roles as $rid => $role) {
|
||||
user_role_grant_permissions($rid, array($permission));
|
||||
}
|
||||
$t = get_t();
|
||||
return $t('%permission was assigned to %roles', array(
|
||||
'%permission' => $permission,
|
||||
'%roles' => implode(', ', $roles)
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_enable().
|
||||
*/
|
||||
function entity_translation_enable() {
|
||||
// Re-activate entity translation for content types which had used it when
|
||||
// the module was last disabled (if any), unless these have since been altered
|
||||
// by the user to use a different translation option.
|
||||
$entity_translation_types = variable_get('entity_translation_disabled_content_types', array());
|
||||
foreach ($entity_translation_types as $index => $type) {
|
||||
if (variable_get("language_content_type_$type", 0) == 0) {
|
||||
variable_set("language_content_type_$type", ENTITY_TRANSLATION_ENABLED);
|
||||
}
|
||||
// We should show the warning only if we actually restored at least one
|
||||
// content type.
|
||||
else {
|
||||
unset($entity_translation_types[$index]);
|
||||
}
|
||||
}
|
||||
if ($entity_translation_types) {
|
||||
drupal_set_message(t('All content types previously configured to use field translation are now using it again.'), 'warning');
|
||||
}
|
||||
variable_del('entity_translation_disabled_content_types');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_disable().
|
||||
*/
|
||||
function entity_translation_disable() {
|
||||
// Store record of which types are using entity translation, and set those
|
||||
// types to not be translated. These content types will be reset to use entity
|
||||
// translation again if the module is later re-enabled, unless they have been
|
||||
// changed by the user in the meantime.
|
||||
$entity_translation_types = array();
|
||||
foreach (node_type_get_types() as $type => $object) {
|
||||
if (variable_get("language_content_type_$type", 0) == ENTITY_TRANSLATION_ENABLED) {
|
||||
$entity_translation_types[] = $type;
|
||||
variable_set("language_content_type_$type", 0);
|
||||
}
|
||||
}
|
||||
if ($entity_translation_types) {
|
||||
variable_set('entity_translation_disabled_content_types', $entity_translation_types);
|
||||
drupal_set_message(t('All content types configured to use field translation now have multilingual support disabled. This change will be reverted if the entity translation module is enabled again.'), 'warning');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function entity_translation_uninstall() {
|
||||
db_delete('variable')
|
||||
->condition('name', db_like('entity_translation_') . '%', 'LIKE')
|
||||
->execute();
|
||||
|
||||
variable_del('translation_language_type');
|
||||
variable_del('locale_field_language_fallback');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_update_N().
|
||||
*/
|
||||
function entity_translation_update_7001() {
|
||||
db_update('system')
|
||||
->fields(array('weight' => 11))
|
||||
->condition('name', 'entity_translation')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant 'edit original values' and 'edit shared field' permissions to roles which have entity editing permissions.
|
||||
*/
|
||||
function entity_translation_update_7002() {
|
||||
// Grant the 'edit original values' permission, so we don't break editing on
|
||||
// existing sites.
|
||||
return _entity_translation_grant_edit_permissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure node and comment language settings to the prior default behavior.
|
||||
*/
|
||||
function entity_translation_update_7003() {
|
||||
module_load_include('inc', 'entity_translation', 'entity_translation.admin');
|
||||
foreach (array_keys(entity_get_info()) as $entity_type) {
|
||||
entity_translation_settings_init($entity_type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild entity information to update the path scheme settings.
|
||||
*/
|
||||
function entity_translation_update_7004() {
|
||||
entity_info_cache_clear();
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
|
||||
(function ($) {
|
||||
|
||||
Drupal.behaviors.translationNodeFieldsetSummaries = {
|
||||
attach: function (context) {
|
||||
$('fieldset#edit-translation', context).drupalSetSummary(function (context) {
|
||||
var status = $('#edit-translation-status', context).is(':checked') ? Drupal.t('Translation published') : Drupal.t('Translation not published');
|
||||
var translate;
|
||||
if ($('#edit-translation-retranslate', context).size()) {
|
||||
translate = $('#edit-translation-retranslate', context).is(':checked') ? Drupal.t('Flag translations as outdated') : Drupal.t('Do not flag translations as outdated');
|
||||
}
|
||||
else {
|
||||
translate = $('#edit-translation-translate', context).is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
|
||||
}
|
||||
return status + ', ' + translate;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* The node specific translation functions and hook implementations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Identifies a content type which has translation support enabled.
|
||||
*/
|
||||
define('ENTITY_TRANSLATION_ENABLED', 4);
|
||||
|
||||
/**
|
||||
* Hides translation metadata.
|
||||
*/
|
||||
define('ENTITY_TRANSLATION_METADATA_HIDE', 0);
|
||||
|
||||
/**
|
||||
* Adds translation metadata to the original authoring information.
|
||||
*/
|
||||
define('ENTITY_TRANSLATION_METADATA_SHOW', 1);
|
||||
|
||||
/**
|
||||
* Replaces the original authoring information with translation metadata.
|
||||
*/
|
||||
define('ENTITY_TRANSLATION_METADATA_REPLACE', 2);
|
||||
|
||||
/**
|
||||
* Checks if the given entity has node translation enabled.
|
||||
*/
|
||||
function entity_translation_node($entity_type, $node) {
|
||||
return $entity_type == 'node' && function_exists('translation_supported_type') && translation_supported_type($node->type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Node-specific menu alterations.
|
||||
*/
|
||||
function entity_translation_node_menu_alter(&$items, $backup) {
|
||||
if (isset($backup['node'])) {
|
||||
$item = $backup['node'];
|
||||
// Preserve the menu router item defined by other modules.
|
||||
$callback['page callback'] = $item['page callback'];
|
||||
$callback['file'] = $item['file'];
|
||||
$callback['module'] = $item['module'];
|
||||
$access_arguments = array_merge(array(1, $item['access callback']), $item['access arguments']);
|
||||
}
|
||||
else {
|
||||
$callback = FALSE;
|
||||
$access_arguments = array(1);
|
||||
}
|
||||
|
||||
$items['node/%node/translate']['page callback'] = 'entity_translation_overview';
|
||||
$items['node/%node/translate']['page arguments'] = array('node', 1, $callback);
|
||||
$items['node/%node/translate']['access arguments'] = $access_arguments;
|
||||
$items['node/%node/translate']['access callback'] = 'entity_translation_node_tab_access';
|
||||
$items['node/%node/translate']['file'] = 'entity_translation.admin.inc';
|
||||
$items['node/%node/translate']['module'] = 'entity_translation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Node specific access callback.
|
||||
*/
|
||||
function entity_translation_node_tab_access() {
|
||||
$args = func_get_args();
|
||||
$node = array_shift($args);
|
||||
if (entity_translation_node('node', $node)) {
|
||||
$function = array_shift($args);
|
||||
return call_user_func_array($function, $args);
|
||||
}
|
||||
else {
|
||||
return entity_translation_tab_access('node', $node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given node type has support for translations.
|
||||
*
|
||||
* @return
|
||||
* Boolean value.
|
||||
*/
|
||||
function entity_translation_node_supported_type($type) {
|
||||
return variable_get('language_content_type_' . $type, 0) == ENTITY_TRANSLATION_ENABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_view().
|
||||
*
|
||||
* Provides content language switcher links to navigate among node translations.
|
||||
*/
|
||||
function entity_translation_node_view($node, $build_mode, $langcode) {
|
||||
if (!empty($node->translations) && drupal_multilingual() && entity_translation_node_supported_type($node->type) && !variable_get("entity_translation_hide_translation_links_{$node->type}", FALSE)) {
|
||||
$path = 'node/' . $node->nid;
|
||||
$links = EntityTranslationDefaultHandler::languageSwitchLinks($path);
|
||||
|
||||
if (is_object($links) && !empty($links->links)) {
|
||||
$handler = entity_translation_get_handler('node', $node);
|
||||
$translations = $handler->getTranslations()->data;
|
||||
|
||||
// Remove the link for the current language.
|
||||
unset($links->links[$langcode]);
|
||||
|
||||
// Remove links to unavailable translations.
|
||||
foreach ($links->links as $langcode => $link) {
|
||||
if (!isset($translations[$langcode]) || !entity_translation_access('node', $translations[$langcode])) {
|
||||
unset($links->links[$langcode]);
|
||||
}
|
||||
}
|
||||
|
||||
$node->content['links']['translation'] = array(
|
||||
'#theme' => 'links',
|
||||
'#links' => $links->links,
|
||||
'#attributes' => array('class' => 'links inline'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Provides settings into the node content type form to choose for entity
|
||||
* translation metadata and comment filtering.
|
||||
*/
|
||||
function entity_translation_form_node_type_form_alter(&$form, &$form_state) {
|
||||
if (entity_translation_enabled('node')) {
|
||||
$type = $form['#node_type']->type;
|
||||
|
||||
$t_args = array('!url' => url('admin/config/regional/entity_translation'));
|
||||
$form['workflow']['language_content_type']['#options'][ENTITY_TRANSLATION_ENABLED] = t('Enabled, with field translation');
|
||||
$form['workflow']['language_content_type']['#description'] .= ' <p>' . t('If field translation is selected you can have per-field translation for each available language. You can find more options in the <a href="!url">entity translation settings</a>.', $t_args) . '</p>';
|
||||
|
||||
// Hide settings when entity translation is disabled for this content type.
|
||||
$states = array(
|
||||
'visible' => array(
|
||||
':input[name="language_content_type"]' => array('value' => ENTITY_TRANSLATION_ENABLED),
|
||||
),
|
||||
);
|
||||
|
||||
$form['workflow']['entity_translation_hide_translation_links'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => variable_get("entity_translation_hide_translation_links_$type", FALSE),
|
||||
'#title' => t('Hide content translation links'),
|
||||
'#description' => t('Hide the links to translations in content body and teasers. If you choose this option, switching language will only be available from the language switcher block.'),
|
||||
'#states' => $states,
|
||||
);
|
||||
|
||||
$form['display']['entity_translation_node_metadata'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Translation post information'),
|
||||
'#description' => t('Whether the translation authoring information should be hidden, shown, or replace the node\'s authoring information.'),
|
||||
'#default_value' => variable_get("entity_translation_node_metadata_$type", ENTITY_TRANSLATION_METADATA_HIDE),
|
||||
'#options' => array(t('Hidden'), t('Shown'), t('Replacing post information')),
|
||||
'#states' => $states,
|
||||
);
|
||||
|
||||
if (isset($form['comment'])) {
|
||||
$form['comment']['entity_translation_comment_filter'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Filter comments per language'),
|
||||
'#default_value' => variable_get("entity_translation_comment_filter_$type", FALSE),
|
||||
'#description' => t('Show only comments whose language matches content language.'),
|
||||
'#states' => $states,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_node().
|
||||
*
|
||||
* Alters node template variables to show/replace entity translation metadata.
|
||||
*/
|
||||
function entity_translation_preprocess_node(&$variables) {
|
||||
$node = $variables['node'];
|
||||
$submitted = variable_get("node_submitted_{$node->type}", TRUE);
|
||||
$mode = variable_get("entity_translation_node_metadata_{$node->type}", ENTITY_TRANSLATION_METADATA_HIDE);
|
||||
|
||||
if ($submitted && $mode != ENTITY_TRANSLATION_METADATA_HIDE) {
|
||||
global $language_content, $user;
|
||||
|
||||
$handler = entity_translation_get_handler('node', $node);
|
||||
$translations = $handler->getTranslations();
|
||||
$langcode = $language_content->language;
|
||||
|
||||
if (isset($translations->data[$langcode]) && $langcode != $translations->original) {
|
||||
$translation = $translations->data[$langcode];
|
||||
$date = format_date($translation['created']);
|
||||
$name = FALSE;
|
||||
|
||||
if ($node->uid != $translation['uid']) {
|
||||
$account = $user->uid != $translation['uid'] ? user_load($translation['uid']) : $user;
|
||||
$name = theme('username', array('account' => $account));
|
||||
}
|
||||
|
||||
switch ($mode) {
|
||||
case ENTITY_TRANSLATION_METADATA_SHOW:
|
||||
$variables['date'] .= ' (' . t('translated on <em>!date</em>', array('!date' => $date)) . ')';
|
||||
if ($name) {
|
||||
$variables['name'] .= ' (' . t('translated by !name', array('!name' => $name)) . ')';
|
||||
}
|
||||
break;
|
||||
|
||||
case ENTITY_TRANSLATION_METADATA_REPLACE:
|
||||
$variables['date'] = $date;
|
||||
if ($name) {
|
||||
$variables['name'] = $name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given comment type has support for translations.
|
||||
*
|
||||
* @return
|
||||
* Boolean value.
|
||||
*/
|
||||
function entity_translation_comment_supported_type($comment_type) {
|
||||
$type = str_replace('comment_node_', '', $comment_type);
|
||||
return entity_translation_node_supported_type($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter().
|
||||
*
|
||||
* Filters out node comments by content language.
|
||||
*
|
||||
* @todo Find a way to track node comment statistics per language.
|
||||
*/
|
||||
function entity_translation_query_comment_filter_alter(QueryAlterableInterface $query) {
|
||||
$node = $query->getMetaData('node');
|
||||
if (!empty($node->type) && variable_get("entity_translation_comment_filter_{$node->type}", FALSE)) {
|
||||
// Determine alias for "comment" table.
|
||||
$comment_alias = FALSE;
|
||||
foreach ($query->getTables() as $table) {
|
||||
if (is_string($table['table']) && $table['table'] == 'comment') {
|
||||
$comment_alias = $table['alias'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Only show comments without language or matching the current content language.
|
||||
if ($comment_alias) {
|
||||
$query->condition(db_or()
|
||||
->condition($comment_alias . '.language', $GLOBALS['language_content']->language)
|
||||
->condition($comment_alias . '.language', LANGUAGE_NONE)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file provides the rules integration for this module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements of hook_rules_event_info().
|
||||
*/
|
||||
function entity_translation_rules_event_info() {
|
||||
$events = array(
|
||||
'entity_translation_insert' => array(
|
||||
'label' => t('After adding an entity translation'),
|
||||
'group' => t('Entity'),
|
||||
'variables' => array(
|
||||
'entity_type' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Entity type'),
|
||||
),
|
||||
'entity' => array(
|
||||
'type' => 'entity',
|
||||
'label' => t('Entity'),
|
||||
),
|
||||
'language_code' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Language code'),
|
||||
),
|
||||
'values' => array(
|
||||
'type' => 'list',
|
||||
'label' => t('Raw values'),
|
||||
'optional' => TRUE,
|
||||
),
|
||||
),
|
||||
),
|
||||
'entity_translation_update' => array(
|
||||
'label' => t('After updating an entity translation'),
|
||||
'group' => t('Entity'),
|
||||
'variables' => array(
|
||||
'entity_type' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Entity type'),
|
||||
),
|
||||
'entity' => array(
|
||||
'type' => 'entity',
|
||||
'label' => t('Entity'),
|
||||
),
|
||||
'language_code' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Language code'),
|
||||
),
|
||||
'values' => array(
|
||||
'type' => 'list',
|
||||
'label' => t('Raw values'),
|
||||
'optional' => TRUE,
|
||||
),
|
||||
),
|
||||
),
|
||||
'entity_translation_delete' => array(
|
||||
'label' => t('After deleting an entity translation'),
|
||||
'group' => t('Entity'),
|
||||
'variables' => array(
|
||||
'entity_type' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Entity type'),
|
||||
),
|
||||
'entity' => array(
|
||||
'type' => 'entity',
|
||||
'label' => t('Entity'),
|
||||
),
|
||||
'language_code' => array(
|
||||
'type' => 'text',
|
||||
'label' => t('Language code'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return $events;
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
name = Entity Translation Menu
|
||||
description = Allows menu items to be translated on the entity form.
|
||||
package = Multilingual - Entity Translation
|
||||
core = 7.x
|
||||
dependencies[] = entity_translation
|
||||
dependencies[] = i18n
|
||||
dependencies[] = i18n_menu
|
||||
files[] = entity_translation_i18n_menu.test
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-07-23
|
||||
version = "7.x-1.0-beta3"
|
||||
core = "7.x"
|
||||
project = "entity_translation"
|
||||
datestamp = "1374601567"
|
||||
|
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* The menu specific translation functions and hook implementations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_node_prepare().
|
||||
*
|
||||
* Translates the menu item shown on node edit forms if the node language does
|
||||
* not equal the language of the menu item. This means either loading the
|
||||
* respective menu item from the translation set or localizing the item.
|
||||
*/
|
||||
function entity_translation_i18n_menu_node_prepare($node) {
|
||||
$langcode = entity_language('node', $node);
|
||||
if (!empty($langcode) && !empty($node->menu['language']) && $node->menu['language'] != $langcode && entity_translation_i18n_menu_item($node->menu)) {
|
||||
$handler = entity_translation_get_handler('node', $node);
|
||||
$source_langcode = $handler->getSourceLanguage();
|
||||
// If we are creating a translation we need to use the source language.
|
||||
entity_translation_i18n_menu_node_menu_item_translate($node, $source_langcode ? $source_langcode : $langcode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_module_implements_alter().
|
||||
*/
|
||||
function entity_translation_i18n_menu_module_implements_alter(&$implementations, $hook) {
|
||||
switch ($hook) {
|
||||
case 'node_prepare':
|
||||
case 'node_presave':
|
||||
// Move some of our hook implementations to end of list. Required so that
|
||||
// the 'menu' key is populated when our implementation gets called. This
|
||||
// also prevents our changes from being overridden.
|
||||
$group = $implementations['entity_translation_i18n_menu'];
|
||||
unset($implementations['entity_translation_i18n_menu']);
|
||||
$implementations['entity_translation_i18n_menu'] = $group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_presave().
|
||||
*/
|
||||
function entity_translation_i18n_menu_node_presave($node) {
|
||||
if (!entity_translation_enabled('node')) {
|
||||
return;
|
||||
}
|
||||
$handler = entity_translation_get_handler('node', $node);
|
||||
$translations = $handler->getTranslations();
|
||||
$source_langcode = $handler->getSourceLanguage();
|
||||
$tset = !empty($node->menu['tset']);
|
||||
|
||||
// If no translation is available the menu data is always supposed to be
|
||||
// entered in the source string language. This way we avoid having unneeded
|
||||
// string translations hanging around.
|
||||
if (empty($source_langcode) && count($translations->data) < 2 && !$tset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When creating a new translation, leave the source menu item intact and
|
||||
// create a new one.
|
||||
$langcode = entity_language('node', $node);
|
||||
if (!empty($node->menu) && $tset && !empty($source_langcode)) {
|
||||
$node->source_menu = menu_link_load($node->menu['mlid']);
|
||||
unset($node->menu['mlid']);
|
||||
}
|
||||
|
||||
// Store the entity language for later reference when saving a translation.
|
||||
// If we are editing a translation in the string source language, we can skip
|
||||
// item processing since the proper values are already in place. Instead when
|
||||
// creating the translation we need to process the link item before saving it.
|
||||
if (!empty($node->menu) && !empty($langcode) && ($source_langcode || $langcode != i18n_string_source_language())) {
|
||||
$node->menu['entity_language'] = $langcode;
|
||||
$node->menu['entity_translation_handler'] = $handler;
|
||||
}
|
||||
|
||||
// If we have a translation set here we should prepare it for storage,
|
||||
// otherwise we need to ensure the menu item has no language so it can be
|
||||
// localized.
|
||||
if ($tset) {
|
||||
entity_translation_i18n_menu_item_tset_prepare($node, $langcode);
|
||||
}
|
||||
else {
|
||||
$node->menu['language'] = LANGUAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function entity_translation_i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
|
||||
$form['#validate'][] = 'entity_translation_i18n_menu_form_menu_edit_item_validate';
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_link_alter().
|
||||
*/
|
||||
function entity_translation_i18n_menu_menu_link_alter(&$link) {
|
||||
if (!empty($link['mlid']) && !empty($link['entity_language']) && $link['language'] == LANGUAGE_NONE && entity_translation_i18n_menu_item($link)) {
|
||||
$sources = array();
|
||||
foreach (array('title' => 'link_title', 'description' => 'description') as $key => $link_field) {
|
||||
$name = array('menu', 'item', $link['mlid'], $key);
|
||||
$source = i18n_string_get_source($name);
|
||||
// The source might not exist yet.
|
||||
$sources[$key] = is_object($source) ? $source->get_string() : $link[$link_field];
|
||||
}
|
||||
|
||||
// If the link values to be saved are translated, we need to revert the
|
||||
// localized menu link back to the original. This way they can be saved
|
||||
// without accidentially storing a translation string as a source string.
|
||||
// The translated values are put in a separate key for later reference.
|
||||
if ($link['entity_language'] != i18n_string_source_language()) {
|
||||
$link['entity_translation_strings'] = array(
|
||||
'title' => $link['link_title'],
|
||||
'description' => $link['description'],
|
||||
);
|
||||
$link['link_title'] = $sources['title'];
|
||||
$link['options']['attributes']['title'] = $sources['description'];
|
||||
}
|
||||
// If the link values are in the string source language, we need to save
|
||||
// the previous source values as translations. As a matter of fact this can
|
||||
// happen only when initially creating a menu item in a language different
|
||||
// from the source string one.
|
||||
else {
|
||||
$link['entity_translation_strings'] = array(
|
||||
'title' => $sources['title'],
|
||||
'description' => $sources['description'],
|
||||
);
|
||||
$link['entity_language'] = $link['entity_translation_handler']->getLanguage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_link_update().
|
||||
*/
|
||||
function entity_translation_i18n_menu_menu_link_update($link) {
|
||||
// Make sure localizations are saved properly.
|
||||
if (entity_translation_i18n_menu_item($link) && !empty($link['entity_translation_strings'])) {
|
||||
$string_langcode = isset($link['entity_language']) ? $link['entity_language'] : i18n_string_source_language();
|
||||
$name = implode(':', array('menu', 'item', $link['mlid']));
|
||||
foreach ($link['entity_translation_strings'] as $key => $translation) {
|
||||
i18n_string_translation_update($name . ':' . $key, $translation, $string_langcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu specific alterations for the entity form.
|
||||
*
|
||||
* Adds to the regular menu item widget a checkbox to choose whether the current
|
||||
* menu item should be localized or part of a translation set.
|
||||
*/
|
||||
function entity_translation_i18n_menu_form(&$form, &$form_state) {
|
||||
$info = entity_translation_edit_form_info($form, $form_state);
|
||||
|
||||
if ($info && $info['entity type'] == 'node') {
|
||||
$node = $info['entity'];
|
||||
$source_menu = isset($node->source_menu) ? $node->source_menu : $node->menu;
|
||||
|
||||
// Check that the menu item of the source node is translatable.
|
||||
if (isset($form['menu']) && !empty($source_menu) && i18n_menu_mode($source_menu['menu_name'], I18N_MODE_MULTIPLE)) {
|
||||
$default = isset($source_menu['language']) && $source_menu['language'] != LANGUAGE_NONE;
|
||||
$languages = language_list();
|
||||
$handler = entity_translation_entity_form_get_handler($form, $form_state);
|
||||
$langcode = $handler->getFormLanguage();
|
||||
$language_name = isset($languages[$langcode]) ? t($languages[$langcode]->name) : t('current');
|
||||
|
||||
$form['menu']['#multilingual'] = TRUE;
|
||||
|
||||
$form['menu']['link']['tset'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Menu link enabled only for the %language language', array('%language' => $language_name)),
|
||||
'#prefix' => '<label>' . t('Menu translation') . '</label>',
|
||||
'#default_value' => $default,
|
||||
'#description' => t('Create a different menu link for each translation. Every link will have its own parent and weight, otherwise only title and description will be translated.'),
|
||||
'#weight' => 10,
|
||||
);
|
||||
|
||||
if (!empty($default)) {
|
||||
$translation_set = i18n_menu_translation_load($source_menu['i18n_tsid']);
|
||||
$translations = $translation_set ? $translation_set->get_translations() : FALSE;
|
||||
if (!empty($translations) && (count($translations) > 1 || !isset($translations[$langcode]))) {
|
||||
$form['menu']['link']['tset']['#disabled'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation handler for the menu item edit form.
|
||||
*/
|
||||
function entity_translation_i18n_menu_form_menu_edit_item_validate($form, &$form_state) {
|
||||
$item = $form_state['values'];
|
||||
|
||||
// Localizable menu items should not be created when a translation set for the
|
||||
// same path already exists.
|
||||
if ($item['language'] == LANGUAGE_NONE) {
|
||||
$count = db_select('menu_links', 'ml')
|
||||
->condition('ml.link_path', $item['link_path'])
|
||||
->condition('ml.i18n_tsid', 0, '<>')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
|
||||
if (!empty($count)) {
|
||||
form_set_error('language', t('There are already one or more items with a language assigned for the given path. Remove them or assign a language to this item too.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given menu item is translatable through entity translation.
|
||||
*
|
||||
* @param array $item
|
||||
* A menu item.
|
||||
*
|
||||
* @todo
|
||||
* Find more generic way of determining whether ET is enabled for a link; add
|
||||
* support for other entities, e.g. taxonomy_term (?).
|
||||
*/
|
||||
function entity_translation_i18n_menu_item($item) {
|
||||
$cache = &drupal_static(__FUNCTION__, array());
|
||||
|
||||
if (!isset($cache[$item['link_path']])) {
|
||||
// First check that the item belongs to a menu which has translation
|
||||
// enabled.
|
||||
if (!i18n_menu_mode($item['menu_name'], I18N_MODE_MULTIPLE)) {
|
||||
$cache[$item['link_path']] = FALSE;
|
||||
}
|
||||
|
||||
// Check if the respective node type has entity translation enabled.
|
||||
if (preg_match('!^node/(\d+)(/.+|)$!', $item['link_path'], $matches)) {
|
||||
if (!entity_translation_enabled('node')) {
|
||||
$cache[$item['link_path']] = FALSE;
|
||||
}
|
||||
else {
|
||||
$type = db_select('node', 'n')
|
||||
->condition('nid', $matches[1])
|
||||
->fields('n', array('type'))
|
||||
->execute()->fetchField();
|
||||
$cache[$item['link_path']] = entity_translation_node_supported_type($type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$cache[$item['link_path']] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return $cache[$item['link_path']];
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the menu item on the given node with a localized version.
|
||||
*
|
||||
* If the menu item is replaced by a different menu item from the translation
|
||||
* set, the original item is stored in $node->source_menu.
|
||||
*
|
||||
* @param $node
|
||||
* A node object, with a menu item ($node->menu).
|
||||
* @param $langcode
|
||||
* The language into which the menu item should be translated.
|
||||
*/
|
||||
function entity_translation_i18n_menu_node_menu_item_translate($node, $langcode) {
|
||||
// Localization.
|
||||
if ($node->menu['language'] == LANGUAGE_NONE) {
|
||||
_i18n_menu_link_localize($node->menu, $langcode);
|
||||
// Update properties 'link_title' and 'options.attributes.title' which are
|
||||
// used for the node menu form; i18n_menu_link_localize only localizes
|
||||
// rendered properties 'title' and 'localized_options.attributes.title'.
|
||||
$node->menu['link_title'] = $node->menu['title'];
|
||||
$node->menu['options']['attributes']['title'] = isset($node->menu['localized_options']['attributes']['title']) ? $node->menu['localized_options']['attributes']['title'] : '';
|
||||
}
|
||||
// Translation sets.
|
||||
else {
|
||||
$menu = NULL;
|
||||
if (!empty($node->menu['i18n_tsid']) && $translation_set = i18n_menu_translation_load($node->menu['i18n_tsid'])) {
|
||||
// Load menu item from translation set.
|
||||
$menu = $translation_set->get_item($langcode);
|
||||
|
||||
// Set parent_depth_limit (required on node forms).
|
||||
if (!empty($menu) && !isset($menu['parent_depth_limit'])) {
|
||||
$menu['parent_depth_limit'] = _menu_parent_depth_limit($menu);
|
||||
}
|
||||
|
||||
// Make sure the menu item is not set to hidden; i18n_menu automatically
|
||||
// hides any menu items not matching the current interface language.
|
||||
if (!empty($menu)) {
|
||||
$menu['hidden'] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the menu item with the translated version, or null if there is
|
||||
// no translated item. Store the original one in $node->source_menu.
|
||||
$node->source_menu = $node->menu;
|
||||
$node->menu = $menu;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the menu item attached to given entity for saving.
|
||||
*
|
||||
* - Ensures that different menu items attached to the entity and its
|
||||
* translations are stored within the same translation set.
|
||||
* - Sets missing default values, and cleans out null values.
|
||||
* - Sets the language of the menu item to given target language.
|
||||
*
|
||||
* @param $entity
|
||||
* Node object.
|
||||
* @param $langcode
|
||||
* Target language.
|
||||
*/
|
||||
function entity_translation_i18n_menu_item_tset_prepare($entity, $langcode) {
|
||||
// Load or create a translation set.
|
||||
if (!empty($entity->source_menu)) {
|
||||
if (!empty($entity->source_menu['i18n_tsid'])) {
|
||||
$translation_set = i18n_translation_set_load($entity->source_menu['i18n_tsid']);
|
||||
}
|
||||
else {
|
||||
// Make sure that the source menu item does have a language assigned.
|
||||
if ($entity->source_menu['language'] == LANGUAGE_NONE) {
|
||||
$entity->source_menu['language'] = $entity->menu['entity_translation_handler']->getSourceLanguage();
|
||||
menu_link_save($entity->source_menu);
|
||||
}
|
||||
|
||||
// Create new translation set.
|
||||
$translation_set = i18n_translation_set_build('menu_link')
|
||||
->add_item($entity->source_menu);
|
||||
}
|
||||
$entity->menu['translation_set'] = $translation_set;
|
||||
}
|
||||
|
||||
// Extract menu_name and pid from parent property.
|
||||
if (!empty($entity->menu['parent'])) {
|
||||
list($entity->menu['menu_name'], $entity->menu['plid']) = explode(':', $entity->menu['parent']);
|
||||
}
|
||||
|
||||
// Remove null values.
|
||||
$entity->menu = array_filter($entity->menu);
|
||||
|
||||
$entity->menu['language'] = $langcode;
|
||||
|
||||
$entity->menu += array(
|
||||
'description' => '',
|
||||
'customized' => 1,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_upgrade().
|
||||
*/
|
||||
function entity_translation_i18n_menu_entity_translation_upgrade($node, $translation) {
|
||||
menu_node_prepare($node);
|
||||
menu_node_prepare($translation);
|
||||
|
||||
if (!empty($node->menu['mlid']) && !empty($translation->menu['mlid'])) {
|
||||
$link = $node->menu;
|
||||
$link['link_title'] = $translation->menu['link_title'];
|
||||
$link['description'] = $translation->menu['description'];
|
||||
$link['entity_language'] = $translation->language;
|
||||
$link['language'] = LANGUAGE_NONE;
|
||||
menu_link_save($link, $node->menu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_delete().
|
||||
*/
|
||||
function entity_translation_i18n_menu_entity_translation_delete($entity_type, $entity, $langcode) {
|
||||
// Make sure that we are working with an entity of type node.
|
||||
if ($entity_type != 'node') {
|
||||
return;
|
||||
}
|
||||
|
||||
list($entity_id, , ) = entity_extract_ids($entity_type, $entity);
|
||||
|
||||
// Clean-up all menu module links that point to this node.
|
||||
$result = db_select('menu_links', 'ml')
|
||||
->fields('ml', array('mlid', 'language'))
|
||||
->condition('link_path', 'node/' . $entity_id)
|
||||
->condition('module', 'menu')
|
||||
->execute()
|
||||
->fetchAllAssoc('mlid');
|
||||
|
||||
foreach ($result as $link) {
|
||||
// Delete all menu links matching the deleted language.
|
||||
if ($link->language == $langcode) {
|
||||
menu_link_delete($link->mlid);
|
||||
}
|
||||
|
||||
// Delete string translations for all language-neutral menu items.
|
||||
if ($link->language == LANGUAGE_NONE) {
|
||||
$name = array('menu', 'item', $link->mlid);
|
||||
foreach (array('title', 'description') as $key) {
|
||||
$name[] = $key;
|
||||
$source = i18n_string_get_source($name);
|
||||
if(!empty($source->lid)) {
|
||||
db_delete('locales_target')
|
||||
->condition('lid', $source->lid)
|
||||
->condition('language', $langcode)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Tests for Entity translation module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for the translation of menu items on entity forms.
|
||||
*/
|
||||
class EntityTranslationMenuTranslationTestCase extends EntityTranslationTestCase {
|
||||
|
||||
/**
|
||||
* Return the test information.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Menu translation',
|
||||
'description' => 'Tests for the translation of menu items on entity forms.',
|
||||
'group' => 'Entity translation',
|
||||
'dependencies' => array('i18n_menu'),
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('locale', 'entity_translation', 'i18n_menu', 'entity_translation_i18n_menu');
|
||||
$this->login($this->getAdminUser(array('administer menu')));
|
||||
$this->addLanguage('en');
|
||||
$this->addLanguage('es');
|
||||
$this->addLanguage('it');
|
||||
$this->configureContentType();
|
||||
$this->configureMenu();
|
||||
$this->enableUrlLanguageDetection();
|
||||
$this->login($this->getTranslatorUser(array('administer menu')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the "Main Menu" for multilingual menu items ("Translate & Localize").
|
||||
*/
|
||||
function configureMenu() {
|
||||
$edit = array();
|
||||
$edit['i18n_mode'] = I18N_MODE_MULTIPLE; // Translate & Localize.
|
||||
$this->drupalPost('admin/structure/menu/manage/main-menu/edit', $edit, t('Save'));
|
||||
$this->assertRaw(t('Your configuration has been saved.'), t('Menu settings have been saved.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create page with menu item.
|
||||
*/
|
||||
function createPage($link_title, $description, $langcode) {
|
||||
$edit = array();
|
||||
$edit['title'] = $this->randomName();
|
||||
$edit['language'] = $langcode;
|
||||
$edit['menu[enabled]'] = TRUE;
|
||||
$edit['menu[link_title]'] = $link_title;
|
||||
$edit['menu[description]'] = $description ? $description : 'link language = ' . $langcode;
|
||||
$this->drupalPost('node/add/page', $edit, t('Save'));
|
||||
$this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit['title'])), t('Basic page created.'));
|
||||
|
||||
// Check to make sure the node was created.
|
||||
$node = $this->drupalGetNodeByTitle($edit['title']);
|
||||
$this->assertTrue($node, t('Node found in database.'));
|
||||
|
||||
// Check to make sure menu link was created.
|
||||
$this->get($langcode, '<front>');
|
||||
$this->assertText($link_title, 'New menu link found.');
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translation with menu item.
|
||||
*/
|
||||
function createTranslation($node, $link_title, $description, $langcode, $use_tsets = FALSE) {
|
||||
$this->drupalGet('node/' . $node->nid . '/edit/add/' . $node->language . '/' . $langcode);
|
||||
|
||||
$edit = array();
|
||||
$edit['menu[enabled]'] = TRUE;
|
||||
$edit['menu[link_title]'] = $link_title;
|
||||
$edit['menu[description]'] = $description ? $description : 'link language = ' . $langcode;
|
||||
if ($use_tsets != NULL) {
|
||||
$edit['menu[tset]'] = $use_tsets;
|
||||
}
|
||||
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
$this->drupalGet('node/' . $node->nid . '/translate');
|
||||
$this->assertLinkByHref('node/' . $node->nid . '/edit/' . $langcode, 0, t('Translation edit link found. Translation created.'));
|
||||
|
||||
// Check to make sure menu link was created.
|
||||
$this->get($langcode, '<front>');
|
||||
$this->assertText($link_title, 'Translation menu link found.');
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove translation in given langcode from node.
|
||||
*/
|
||||
function removeTranslation($node, $langcode) {
|
||||
$this->drupalGet('node/' . $node->nid . '/translate/delete/' . $langcode);
|
||||
$this->drupalPost(NULL, array(), t('Delete'));
|
||||
|
||||
// Check to make sure translation was deleted.
|
||||
$this->drupalGet('node/' . $node->nid . '/edit/add/' . $node->language . '/' . $langcode);
|
||||
$this->assertResponse(200, 'Translation add page found. Old translation deleted.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a page menu item.
|
||||
*
|
||||
* Check that node form contains old menu link title, then replace with given
|
||||
* new title.
|
||||
*/
|
||||
function editPage($node, $old_link_title, $link_title, $langcode) {
|
||||
$this->drupalGet('node/' . $node->nid . '/edit/' . $langcode);
|
||||
|
||||
$this->assertFieldByXPath("//input[@name='menu[link_title]']", $old_link_title, 'Old link title value correctly populated: ' . $old_link_title);
|
||||
|
||||
$edit = array();
|
||||
$edit['menu[link_title]'] = $link_title;
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
|
||||
// Check to make sure menu link was updated.
|
||||
$this->get($langcode, '<front>');
|
||||
$this->assertNoText($old_link_title, 'Old menu link title not found: ' . $old_link_title);
|
||||
$this->assertText($link_title, 'New menu link title found: ' . $link_title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if menu localization works.
|
||||
*/
|
||||
function testMenuLocalization() {
|
||||
// Create Basic page in English.
|
||||
$link_title_en = $this->randomName();
|
||||
$node = $this->createPage($link_title_en, NULL, 'en');
|
||||
|
||||
// Submit translation in Spanish.
|
||||
$link_title_es = $this->randomName();
|
||||
$node_translation = $this->createTranslation($node, $link_title_es, NULL, 'es');
|
||||
|
||||
// Check menu links in both languages.
|
||||
$this->get('en', '<front>');
|
||||
$this->assertText($link_title_en);
|
||||
$this->get('es', '<front>');
|
||||
$this->assertText($link_title_es);
|
||||
|
||||
// Edit English menu link.
|
||||
$link_title_en2 = $this->randomName();
|
||||
$this->editPage($node, $link_title_en, $link_title_en2, 'en');
|
||||
|
||||
// Check that Spanish menu link has not changed.
|
||||
$this->get('es', '<front>');
|
||||
$this->assertText($link_title_es);
|
||||
|
||||
// Edit Spanish menu link.
|
||||
$link_title_es2 = $this->randomName();
|
||||
$this->editPage($node, $link_title_es, $link_title_es2, 'es');
|
||||
|
||||
// Check that English menu link has not changed.
|
||||
$this->get('en', '<front>');
|
||||
$this->assertText($link_title_en2);
|
||||
|
||||
// Delete Spanish translation and check that the respective menu item has
|
||||
// been deleted as well.
|
||||
$this->removeTranslation($node, 'es');
|
||||
$this->get('es', '<front>');
|
||||
$this->assertNoText($link_title_es2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if menu localization works (source language != default language).
|
||||
*/
|
||||
function testMenuLocalizationCustomSourceLanguage() {
|
||||
// Create Basic page in Spanish.
|
||||
$link_title_es = $this->randomName();
|
||||
$node = $this->createPage($link_title_es, NULL, 'es');
|
||||
|
||||
// Submit translation in English.
|
||||
$link_title_en = $this->randomName();
|
||||
$node_translation = $this->createTranslation($node, $link_title_en, NULL, 'en');
|
||||
|
||||
// Check menu links in both languages.
|
||||
$this->get('es', '<front>');
|
||||
$this->assertText($link_title_es);
|
||||
$this->get('en', '<front>');
|
||||
$this->assertText($link_title_en);
|
||||
|
||||
// Edit Spanish menu link.
|
||||
$link_title_es2 = $this->randomName();
|
||||
$this->editPage($node, $link_title_es, $link_title_es2, 'es');
|
||||
|
||||
// Check that English menu link has not changed.
|
||||
$this->get('en', '<front>');
|
||||
$this->assertText($link_title_en);
|
||||
|
||||
// Edit English menu link.
|
||||
$link_title_en2 = $this->randomName();
|
||||
$this->editPage($node, $link_title_en, $link_title_en2, 'en');
|
||||
|
||||
// Check that Spanish menu link has not changed.
|
||||
$this->get('es', '<front>');
|
||||
$this->assertText($link_title_es2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if menu translation works with separate menu items.
|
||||
*/
|
||||
function testMenuTranslation() {
|
||||
// Create Basic page in English.
|
||||
$link_title_en = $this->randomName();
|
||||
$node = $this->createPage($link_title_en, NULL, 'en');
|
||||
|
||||
// Submit translation in Spanish.
|
||||
$link_title_es = $this->randomName();
|
||||
$node_translation = $this->createTranslation($node, $link_title_es, NULL, 'es', TRUE);
|
||||
|
||||
// Check menu links in both languages.
|
||||
$this->get('en', '<front>');
|
||||
$this->assertText($link_title_en);
|
||||
$this->get('es', '<front>');
|
||||
$this->assertText($link_title_es);
|
||||
|
||||
// Edit English menu link.
|
||||
$link_title_en2 = $this->randomName();
|
||||
$this->editPage($node, $link_title_en, $link_title_en2, 'en');
|
||||
|
||||
// Check that Spanish menu link has not changed.
|
||||
$this->get('es', '<front>');
|
||||
$this->assertText($link_title_es);
|
||||
|
||||
// Edit Spanish menu link.
|
||||
$link_title_es2 = $this->randomName();
|
||||
$this->editPage($node, $link_title_es, $link_title_es2, 'es');
|
||||
|
||||
// Check that English menu link has not changed.
|
||||
$this->get('en', '<front>');
|
||||
$this->assertText($link_title_en2);
|
||||
|
||||
// Add another translation, and check that other menu items are not
|
||||
// affected. See https://drupal.org/node/1982140
|
||||
$link_title_it = $this->randomName();
|
||||
$node_translation = $this->createTranslation($node, $link_title_it, NULL, 'it', NULL);
|
||||
|
||||
// Check that Spanish and English menu links have not changed.
|
||||
$this->get('es', '<front>');
|
||||
$this->assertText($link_title_es2);
|
||||
$this->get('en', '<front>');
|
||||
$this->assertText($link_title_en2);
|
||||
|
||||
// Delete Spanish translation and check that the respective menu item has
|
||||
// been deleted as well.
|
||||
$this->removeTranslation($node, 'es');
|
||||
$this->get('es', '<front>');
|
||||
$this->assertNoText($link_title_es2);
|
||||
}
|
||||
}
|
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Converts node translations into field-based translations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The number of node translations that should be processed for each batch step.
|
||||
*/
|
||||
define('ENTITY_TRANSLATION_UPGRADE_BATCH_SIZE', 10);
|
||||
|
||||
/**
|
||||
* Starts the batch process to perform the upgrade.
|
||||
*/
|
||||
function entity_translation_upgrade_start($types) {
|
||||
$batch = array(
|
||||
'operations' => array(
|
||||
array('entity_translation_upgrade_do', array($types)),
|
||||
array('entity_translation_upgrade_complete', array()),
|
||||
),
|
||||
'finished' => 'entity_translation_upgrade_end',
|
||||
'title' => t('Entity Translation Upgrade'),
|
||||
'init_message' => t('Entity Translation Upgrade is starting.'),
|
||||
'error_message' => t('Entity Translation Upgrade has encountered an error.'),
|
||||
'file' => drupal_get_path('module', 'entity_translation_upgrade') . '/entity_translation_upgrade.admin.inc',
|
||||
);
|
||||
batch_set($batch);
|
||||
batch_process('admin/config/regional/entity_translation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finshed batch callback.
|
||||
*/
|
||||
function entity_translation_upgrade_end($success, $results, $operations, $elapsed) {
|
||||
if (!empty($results)) {
|
||||
$message = format_plural($results, '1 node translation successfully upgraded.', '@count node translations successfully upgraded.');
|
||||
watchdog('entity translation upgrade', '@count node translations successfully upgraded.', array('@count' => $results), WATCHDOG_INFO);
|
||||
}
|
||||
else {
|
||||
$message = t('No node translation available for the upgrade.');
|
||||
}
|
||||
|
||||
drupal_set_message($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch process to convert node translations to field-based translations.
|
||||
*/
|
||||
function entity_translation_upgrade_do($types, &$context) {
|
||||
$query = db_select('node', 'n');
|
||||
$query->addJoin('LEFT', 'entity_translation_upgrade_history', 'etuh', 'n.nid = etuh.nid');
|
||||
$query
|
||||
->fields('n', array('nid', 'tnid'))
|
||||
->where('etuh.complete IS NULL OR etuh.complete = 0')
|
||||
->condition('n.tnid', 0, '<>')
|
||||
->condition('n.tnid <> n.nid', array(), '')
|
||||
->condition('n.type', $types)
|
||||
->orderBy('n.nid');
|
||||
|
||||
// Initialize the batch process.
|
||||
if (empty($context['sandbox'])) {
|
||||
$total = $query
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
|
||||
$context['sandbox']['count'] = 0;
|
||||
$context['sandbox']['total'] = $total;
|
||||
$context['finished'] = $total == 0;
|
||||
}
|
||||
else {
|
||||
$batch_size = variable_get('entity_translation_upgrade_batch_size', ENTITY_TRANSLATION_UPGRADE_BATCH_SIZE);
|
||||
$result = $query
|
||||
->range($context['sandbox']['count'], $batch_size)
|
||||
->execute()
|
||||
->fetchAllKeyed();
|
||||
|
||||
// Here we load original nodes and translations all together, but the batch
|
||||
// size is determined only by node translations.
|
||||
$nids = array_keys($result);
|
||||
$nodes = node_load_multiple($nids + array_unique($result));
|
||||
|
||||
$updated_nodes = array();
|
||||
$node_translations = array();
|
||||
$node_translation_sets = array();
|
||||
$instances = array();
|
||||
$field_info = array();
|
||||
|
||||
foreach ($nids as $nid) {
|
||||
$node = $nodes[$nid];
|
||||
$original = $nodes[$node->tnid];
|
||||
$handler = entity_translation_get_handler('node', $original);
|
||||
|
||||
if (!isset($instances[$node->type])) {
|
||||
$instances[$node->type] = field_info_instances('node', $node->type);
|
||||
}
|
||||
|
||||
reset($instances[$node->type]);
|
||||
|
||||
foreach ($instances[$node->type] as $instance) {
|
||||
$field_name = $instance['field_name'];
|
||||
$field = isset($field_info[$field_name]) ? $field_info[$field_name] : $field_info[$field_name] = field_info_field($field_name);
|
||||
|
||||
// Copy field data.
|
||||
if ($field['translatable']) {
|
||||
$langcode = isset($node->{$field_name}[$node->language]) ? $node->language : LANGUAGE_NONE;
|
||||
if (isset($node->{$field_name}[$langcode])) {
|
||||
$original->{$field_name}[$node->language] = $node->{$field_name}[$langcode];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the new translation.
|
||||
$handler->setTranslation(array(
|
||||
'translate' => $node->translate,
|
||||
'status' => $node->status,
|
||||
'language' => $node->language,
|
||||
'source' => $original->language,
|
||||
'uid' => $node->uid,
|
||||
'created' => $node->created,
|
||||
'changed' => $node->changed,
|
||||
));
|
||||
|
||||
// Build a list of updated nodes. They will be saved after all the node
|
||||
// translation conversions.
|
||||
$updated_nodes[$original->nid] = $original;
|
||||
|
||||
// Build a list of obsolete node translations to be unpublished.
|
||||
$node_translations[$node->nid] = $node;
|
||||
|
||||
// Build a list of obsolete translations sets to be passed to module hook
|
||||
// implementations.
|
||||
$node_translation_sets[$original->nid][$node->nid] = $node;
|
||||
|
||||
$context['sandbox']['count']++;
|
||||
}
|
||||
|
||||
// Ensure that the multilingual support configuration is set to the right
|
||||
// value for the current node type.
|
||||
foreach ($instances as $type_name => $data) {
|
||||
variable_set("language_content_type_$type_name", ENTITY_TRANSLATION_ENABLED);
|
||||
}
|
||||
|
||||
// Save field data and translations for the updated nodes.
|
||||
foreach ($updated_nodes as $nid => $node) {
|
||||
field_attach_presave('node', $node);
|
||||
field_attach_update('node', $node);
|
||||
entity_translation_get_handler('node', $node)->saveTranslations();
|
||||
|
||||
foreach ($node_translation_sets[$nid] as $translation) {
|
||||
// Allow modules to upgrade their node additions, if possible.
|
||||
module_invoke_all('entity_translation_upgrade', $node, $translation);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($node_translations)) {
|
||||
$nids = array_keys($node_translations);
|
||||
|
||||
// Unpublish the obsolete node translations.
|
||||
db_update('node')
|
||||
->fields(array('status' => 0))
|
||||
->condition('nid', $nids)
|
||||
->execute();
|
||||
|
||||
db_update('node_revision')
|
||||
->fields(array('status' => 0))
|
||||
->condition('nid', $nids)
|
||||
->execute();
|
||||
|
||||
// Populate history table.
|
||||
$columns = array('nid', 'tnid', 'language');
|
||||
$query = db_insert('entity_translation_upgrade_history')->fields($columns);
|
||||
|
||||
foreach ($node_translations as $node) {
|
||||
$query->values((array) $node);
|
||||
}
|
||||
|
||||
$query->execute();
|
||||
|
||||
// Flush the modified nodes from the entity load cache.
|
||||
entity_get_controller('node')->resetCache($nids);
|
||||
}
|
||||
|
||||
$context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
|
||||
|
||||
if ($context['finished'] >= 1) {
|
||||
$context['results'] = $context['sandbox']['total'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the translation sets for all the upgraded nodes.
|
||||
*/
|
||||
function entity_translation_upgrade_complete(&$context) {
|
||||
if ($nids = db_query('SELECT DISTINCT etuh.tnid FROM {entity_translation_upgrade_history} etuh WHERE etuh.complete = 0')->fetchCol()) {
|
||||
// Remove translation sets for migrated nodes.
|
||||
db_query('UPDATE {node} SET tnid = 0 WHERE nid IN (:nids)', array(':nids' => $nids));
|
||||
entity_get_controller('node')->resetCache($nids);
|
||||
// Mark nodes as migrated.
|
||||
db_query('UPDATE {entity_translation_upgrade_history} SET complete = 1 WHERE complete = 0');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementations of hook_entity_translation_upgrade() on behalf of core
|
||||
* modules.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_upgrade().
|
||||
*/
|
||||
function path_entity_translation_upgrade($node, $translation) {
|
||||
// Update URL aliases.
|
||||
db_update('url_alias')
|
||||
->fields(array('source' => 'node/' . $node->nid))
|
||||
->condition('source', 'node/' . $translation->nid)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_upgrade().
|
||||
*/
|
||||
function comment_entity_translation_upgrade($node, $translation) {
|
||||
// Attach comments to the original node.
|
||||
db_update('comment')
|
||||
->fields(array('nid' => $node->nid, 'language' => $translation->language))
|
||||
->condition('nid', $translation->nid)
|
||||
->execute();
|
||||
|
||||
// Update node-comment statistics.
|
||||
$ncs = db_select('node_comment_statistics', 'ncs')
|
||||
->fields('ncs')
|
||||
->condition('nid', array($node->nid, $translation->nid))
|
||||
->execute()
|
||||
->fetchAllAssoc('nid');
|
||||
|
||||
$last = $ncs[$node->nid]->last_comment_timestamp > $ncs[$translation->nid]->last_comment_timestamp;
|
||||
$ncs_updated = $last ? $ncs[$node->nid] : $ncs[$translation->nid];
|
||||
$ncs_updated->nid = $node->nid;
|
||||
$ncs_updated->comment_count = $ncs[$node->nid]->comment_count + $ncs[$translation->nid]->comment_count;
|
||||
|
||||
db_update('node_comment_statistics')
|
||||
->fields((array) $ncs_updated)
|
||||
->condition('nid', $node->nid)
|
||||
->execute();
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
name = Entity Translation Upgrade
|
||||
description = Provides an upgrade path from node-based translation to field-based translation.
|
||||
package = Multilingual - Entity Translation
|
||||
core = 7.x
|
||||
dependencies[] = entity_translation
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-07-23
|
||||
version = "7.x-1.0-beta3"
|
||||
core = "7.x"
|
||||
project = "entity_translation"
|
||||
datestamp = "1374601567"
|
||||
|
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Schema declaration for the entity_translation_upgrade module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function entity_translation_upgrade_schema() {
|
||||
$schema['entity_translation_upgrade_history'] = array(
|
||||
'description' => 'The history table for node translations.',
|
||||
'fields' => array(
|
||||
'nid' => array(
|
||||
'description' => 'The node translation nid.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'tnid' => array(
|
||||
'description' => 'The translation set id for the node translation.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'language' => array(
|
||||
'description' => 'The node translation language.',
|
||||
'type' => 'varchar',
|
||||
'length' => 12,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'complete' => array(
|
||||
'description' => 'Boolean indicating whether the node migration has completed.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
),
|
||||
'indexes' => array('tnid' => array('tnid'), 'complete' => array('complete')),
|
||||
'primary key' => array('nid'),
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_enable().
|
||||
*/
|
||||
function entity_translation_upgrade_enable() {
|
||||
$args = array('!url' => url('admin/config/regional/entity_translation'));
|
||||
drupal_set_message(t('<em>Entity Translation Upgrade</em> enabled: visit the <a href="!url">entity translation settings</a> page to perform the upgrade.', $args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_update_N().
|
||||
*
|
||||
* Adds the 'complete' column to the history table.
|
||||
*/
|
||||
function entity_translation_upgrade_update_7001() {
|
||||
$table = 'entity_translation_upgrade_history';
|
||||
$name = 'complete';
|
||||
|
||||
// Add the 'complete' field.
|
||||
$spec = array(
|
||||
'description' => 'Boolean indicating whether the node migration has completed.',
|
||||
'type' => 'int',
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
);
|
||||
db_add_field($table, $name, $spec);
|
||||
|
||||
// Add the 'complete' index.
|
||||
db_add_index($table, $name, array($name));
|
||||
|
||||
// Existing records are supposed to concern already completed migrations.
|
||||
db_update($table)
|
||||
->fields(array('complete' => 1))
|
||||
->execute();
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides permanent redirects for unavailable node translations.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function entity_translation_upgrade_menu() {
|
||||
return array(
|
||||
'admin/config/regional/entity_translation/upgrade' => array(
|
||||
'title' => 'Entity Translation Upgrade',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('entity_translation_upgrade_form'),
|
||||
'access arguments' => array('administer software updates'),
|
||||
'file' => 'entity_translation_upgrade.admin.inc',
|
||||
'type' => MENU_CALLBACK,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_alter().
|
||||
*/
|
||||
function entity_translation_upgrade_menu_alter(&$items) {
|
||||
// Obsolete node translations might be left unpublished instead of being
|
||||
// deleted.
|
||||
$items['node/%node']['access callback'] = 'entity_translation_upgrade_access';
|
||||
$items['node/%node']['access arguments'] = array(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback.
|
||||
*
|
||||
* Performs a redirect to the corresponding field-based translation if the
|
||||
* current user has not the permission to access the requested node translation.
|
||||
*/
|
||||
function entity_translation_upgrade_access($node) {
|
||||
// If the user has the right to access the node, we need to do nothing.
|
||||
if (node_access('view', $node)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// If we have a node translation, we need to redirect the user to the original
|
||||
// node.
|
||||
if ($node->tnid && $node->nid != $node->tnid) {
|
||||
entity_translation_upgrade_redirect($node->tnid, $node->language);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_init().
|
||||
*/
|
||||
function entity_translation_upgrade_init() {
|
||||
// If have a node/$nid path but we are not able to load a node for the given
|
||||
// nid we might have an upgraded translation, hence we need to look for a
|
||||
// record matching the requested nid in the history table.
|
||||
if ($nid = entity_translation_upgrade_check_path() && $data = entity_translation_upgrade_load($nid)) {
|
||||
entity_translation_upgrade_redirect($data->tnid, $data->language);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function entity_translation_upgrade_form_entity_translation_admin_form_alter(&$form, $form_state) {
|
||||
$form['entity_translation_upgrade'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Entity Translation Upgrade'),
|
||||
'#description' => t('This will create an entity translation for each available node translation, which will be then unpublished.'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
);
|
||||
|
||||
$options = array();
|
||||
foreach (node_type_get_types() as $type) {
|
||||
$options[$type->type] = $type->name;
|
||||
}
|
||||
|
||||
$form['entity_translation_upgrade']['types'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Node types'),
|
||||
'#description' => t('Select which node types will be upgraded.'),
|
||||
'#options' => $options,
|
||||
);
|
||||
|
||||
$form['entity_translation_upgrade']['upgrade'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Upgrade'),
|
||||
'#validate' => array('entity_translation_upgrade_validate'),
|
||||
'#submit' => array('entity_translation_upgrade_submit'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation handler for the entity_translation_admin_form() form.
|
||||
*/
|
||||
function entity_translation_upgrade_validate($form, &$form_state) {
|
||||
if (!count(array_filter($form_state['values']['types']))) {
|
||||
form_set_error('types', t('Please specify at least one node type.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit handler for the entity_translation_admin_form() form.
|
||||
*/
|
||||
function entity_translation_upgrade_submit($form, &$form_state) {
|
||||
module_load_include('inc', 'entity_translation_upgrade', 'entity_translation_upgrade.admin');
|
||||
entity_translation_upgrade_start(array_filter($form_state['values']['types']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the redirect to original node with the given language.
|
||||
*/
|
||||
function entity_translation_upgrade_redirect($nid, $langcode) {
|
||||
$languages = language_list();
|
||||
drupal_goto("node/$nid", array('language' => $languages[$langcode]), 301);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks wether the requested path belongs to an upgraded translation.
|
||||
*/
|
||||
function entity_translation_upgrade_check_path() {
|
||||
$result = arg(0) == 'node' && ($nid = arg(1)) && is_int($nid) && !node_load($nid);
|
||||
return $result ? $nid : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the upgrade history entry for the given nid.
|
||||
*/
|
||||
function entity_translation_upgrade_load($nid) {
|
||||
return db_select('entity_translation_upgrade_history', 'etu')
|
||||
->fields('etu')
|
||||
->condition('etu.nid', $nid)
|
||||
->execute()
|
||||
->fetchObject();
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Comment translation handler for the entity translation module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Comment translation handler.
|
||||
*/
|
||||
class EntityTranslationCommentHandler extends EntityTranslationDefaultHandler {
|
||||
|
||||
public function __construct($entity_type, $entity_info, $entity) {
|
||||
parent::__construct('comment', $entity_info, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityForm()
|
||||
*/
|
||||
public function entityForm(&$form, &$form_state) {
|
||||
parent::entityForm($form, $form_state);
|
||||
// Adjust the translation fieldset weight to move it below the admin one.
|
||||
$form['translation']['#weight'] = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityFormLanguageWidgetSubmit()
|
||||
*/
|
||||
public function entityFormLanguageWidgetSubmit($form, &$form_state) {
|
||||
$this->updateFormLanguage($form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityFormTitle()
|
||||
*/
|
||||
protected function entityFormTitle() {
|
||||
return t('Edit comment @subject', array('@subject' => $this->getLabel()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::getStatus()
|
||||
*/
|
||||
protected function getStatus() {
|
||||
return (boolean) $this->entity->status;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Node translation handler for the entity translation module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Node translation handler.
|
||||
*
|
||||
* Overrides default behaviours for Node properties.
|
||||
*/
|
||||
class EntityTranslationNodeHandler extends EntityTranslationDefaultHandler {
|
||||
|
||||
public function __construct($entity_type, $entity_info, $entity) {
|
||||
parent::__construct('node', $entity_info, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::isRevision()
|
||||
*/
|
||||
public function isRevision() {
|
||||
return !empty($this->entity->revision);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::getAccess()
|
||||
*/
|
||||
public function getAccess($op) {
|
||||
return node_access($op, $this->entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::getTranslationAccess()
|
||||
*/
|
||||
public function getTranslationAccess($langcode) {
|
||||
return user_access('bypass node access') || parent::getTranslationAccess($langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the translation update status fieldset into a vartical tab.
|
||||
*/
|
||||
public function entityForm(&$form, &$form_state) {
|
||||
parent::entityForm($form, $form_state);
|
||||
|
||||
// Move the translation fieldset to a vertical tab.
|
||||
if (isset($form['translation'])) {
|
||||
$form['translation'] += array(
|
||||
'#group' => 'additional_settings',
|
||||
'#weight' => 100,
|
||||
'#attached' => array(
|
||||
'js' => array(drupal_get_path('module', 'entity_translation') . '/entity_translation.node-form.js'),
|
||||
),
|
||||
);
|
||||
|
||||
if (!$this->isTranslationForm()) {
|
||||
$form['translation']['name']['#access'] = FALSE;
|
||||
$form['translation']['created']['#access'] = FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Path aliases natively support multilingual values.
|
||||
if (isset($form['path'])) {
|
||||
$form['path']['#multilingual'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::menuForm()
|
||||
*/
|
||||
protected function menuForm(&$form, &$form_state) {
|
||||
entity_translation_i18n_menu_form($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityFormLanguageWidgetSubmit()
|
||||
*/
|
||||
function entityFormLanguageWidgetSubmit($form, &$form_state) {
|
||||
$this->updateFormLanguage($form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityFormSubmit()
|
||||
*/
|
||||
public function entityFormSubmit($form, &$form_state) {
|
||||
if (!isset($form_state['values']['translation'])) {
|
||||
// Always publish the original values when we have no translations.
|
||||
$form_state['values']['translation'] = array('status' => TRUE);
|
||||
}
|
||||
$values = &$form_state['values']['translation'];
|
||||
|
||||
if (!$this->isTranslationForm()) {
|
||||
// Inherit entity authoring information for the original values.
|
||||
$values['name'] = $form_state['values']['name'];
|
||||
if (!empty($form_state['values']['date'])) {
|
||||
$values['created'] = $form_state['values']['date'];
|
||||
}
|
||||
}
|
||||
|
||||
parent::entityFormSubmit($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityFormTitle()
|
||||
*/
|
||||
protected function entityFormTitle() {
|
||||
$type_name = node_type_get_name($this->entity);
|
||||
return t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $this->getLabel()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::getStatus()
|
||||
*/
|
||||
protected function getStatus() {
|
||||
return (boolean) $this->entity->status;
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Taxonomy term translation handler for the entity translation module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Taxonomy term translation handler.
|
||||
*/
|
||||
class EntityTranslationTaxonomyTermHandler extends EntityTranslationDefaultHandler {
|
||||
|
||||
public function __construct($entity_type, $entity_info, $entity) {
|
||||
parent::__construct('taxonomy_term', $entity_info, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityForm()
|
||||
*/
|
||||
public function entityForm(&$form, &$form_state) {
|
||||
parent::entityForm($form, $form_state);
|
||||
|
||||
// Remove the translation fieldset when the deletion confirm form is being
|
||||
// displayed.
|
||||
if (isset($form_state['confirm_delete'])) {
|
||||
unset(
|
||||
$form[$this->getLanguageKey()],
|
||||
$form['source_language'],
|
||||
$form['translation'],
|
||||
$form['actions']['delete_translation']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User translation handler for the entity translation module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* User translation handler.
|
||||
*/
|
||||
class EntityTranslationUserHandler extends EntityTranslationDefaultHandler {
|
||||
|
||||
public function __construct($entity_type, $entity_info, $entity) {
|
||||
parent::__construct('user', $entity_info, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::entityForm()
|
||||
*/
|
||||
public function entityForm(&$form, &$form_state) {
|
||||
parent::entityForm($form, $form_state);
|
||||
$form['picture']['#multilingual'] = FALSE;
|
||||
$form['locale']['#multilingual'] = FALSE;
|
||||
$form['locale']['#title'] = t('Preferred language settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* @see EntityTranslationDefaultHandler::getLanguageKey()
|
||||
*/
|
||||
public function getLanguageKey() {
|
||||
return 'entity_language';
|
||||
}
|
||||
}
|
@@ -0,0 +1,526 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Tests for Entity translation module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base class for entity translation module tests.
|
||||
*/
|
||||
class EntityTranslationTestCase extends DrupalWebTestCase {
|
||||
|
||||
protected $current_user;
|
||||
protected $admin_user;
|
||||
protected $translator_user;
|
||||
|
||||
function setUp() {
|
||||
$args = func_get_args();
|
||||
call_user_func_array(array('parent', 'setUp'), $args);
|
||||
|
||||
// Reset user fields to make test object reusable.
|
||||
unset($this->current_user);
|
||||
unset($this->admin_user);
|
||||
unset($this->translator_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Drupal path or an absolute path with language.
|
||||
*
|
||||
* @param $language
|
||||
* Language code or language object.
|
||||
*/
|
||||
function get($language, $path = '', array $options = array(), array $headers = array()) {
|
||||
$options['language'] = $this->getLanguage($language);
|
||||
return $this->drupalGet($path, $options, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts to a Drupal path with language.
|
||||
*/
|
||||
function post($language, $path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
|
||||
$options['language'] = $this->getLanguage($language);
|
||||
$this->drupalPost($path, $edit, $submit, $options, $headers, $form_html_id, $extra_post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Login the given user only if she has not changed.
|
||||
*/
|
||||
function login($user) {
|
||||
if (!isset($this->current_user) || $this->current_user->uid != $user->uid) {
|
||||
$this->current_user = $user;
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a user with administration rights.
|
||||
*
|
||||
* @param $permissions
|
||||
* Additional permissions for administrative user.
|
||||
*/
|
||||
function getAdminUser(array $permissions = array()) {
|
||||
if (!isset($this->admin_user)) {
|
||||
$this->admin_user = $this->drupalCreateUser(array_merge(array(
|
||||
'bypass node access',
|
||||
'administer nodes',
|
||||
'administer languages',
|
||||
'administer content types',
|
||||
'administer blocks',
|
||||
'access administration pages',
|
||||
'administer site configuration',
|
||||
), $permissions));
|
||||
}
|
||||
return $this->admin_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a user with minimal translation rights.
|
||||
*
|
||||
* @param $permissions
|
||||
* Additional permissions for administrative user.
|
||||
*/
|
||||
function getTranslatorUser(array $permissions = array()) {
|
||||
if (!isset($this->translator_user)) {
|
||||
$this->translator_user = $this->drupalCreateUser(array_merge(array(
|
||||
'create page content',
|
||||
'edit own page content',
|
||||
'delete own page content',
|
||||
'translate any entity',
|
||||
), $permissions));
|
||||
}
|
||||
return $this->translator_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure the clean urls are enabled.
|
||||
*/
|
||||
function enableCleanUrls() {
|
||||
$this->drupalGet('admin/config/search/clean-urls');
|
||||
$edit = array();
|
||||
$edit['clean_url'] = TRUE;
|
||||
$this->drupalPost(NULL, $edit, t('Save configuration'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable URL language detection.
|
||||
*/
|
||||
function enableUrlLanguageDetection() {
|
||||
// Enable URL language detection and selection.
|
||||
$edit = array(
|
||||
'language[enabled][locale-url]' => TRUE,
|
||||
'language_content[enabled][locale-interface]' => TRUE
|
||||
);
|
||||
$this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
|
||||
$this->assertRaw(t('Language negotiation configuration saved.'), t('URL language detection enabled.'));
|
||||
$this->drupalGet('admin/config/regional/language/configure');
|
||||
// Reset caches.
|
||||
drupal_static_reset('locale_url_outbound_alter');
|
||||
drupal_static_reset('language_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a language object from a language code.
|
||||
*/
|
||||
public function getLanguage($langcode) {
|
||||
if (is_object($langcode)) {
|
||||
return $langcode;
|
||||
}
|
||||
else {
|
||||
$language_list = language_list();
|
||||
return $language_list[$langcode];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a specified language if it has not been already, otherwise make sure that the language is enabled.
|
||||
*
|
||||
* @param $langcode
|
||||
* The language code to check.
|
||||
*/
|
||||
function addLanguage($langcode) {
|
||||
// Check to make sure that language has not already been installed.
|
||||
$this->drupalGet('admin/config/regional/language');
|
||||
|
||||
if (strpos($this->drupalGetContent(), 'enabled[' . $langcode . ']') === FALSE) {
|
||||
// Doesn't have language installed so add it.
|
||||
$edit = array();
|
||||
$edit['langcode'] = $langcode;
|
||||
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
// Make sure we are not using a stale list.
|
||||
drupal_static_reset('language_list');
|
||||
$languages = language_list('language');
|
||||
$this->assertTrue(array_key_exists($langcode, $languages), t('Language was installed successfully.'));
|
||||
|
||||
if (array_key_exists($langcode, $languages)) {
|
||||
$this->assertRaw(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => $languages[$langcode]->name, '@locale-help' => url('admin/help/locale'))), t('Language has been created.'));
|
||||
}
|
||||
}
|
||||
elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $langcode . ']'))) {
|
||||
// It is installed and enabled. No need to do anything.
|
||||
$this->assertTrue(TRUE, 'Language [' . $langcode . '] already installed and enabled.');
|
||||
}
|
||||
else {
|
||||
// It is installed but not enabled. Enable it.
|
||||
$this->assertTrue(TRUE, 'Language [' . $langcode . '] already installed.');
|
||||
$this->drupalPost(NULL, array('enabled[' . $langcode . ']' => TRUE), t('Save configuration'));
|
||||
$this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the "Basic page" content type for entity translation tests.
|
||||
*/
|
||||
function configureContentType() {
|
||||
// Configure the "Basic page" content type to use multilingual support with
|
||||
// translation.
|
||||
$edit = array();
|
||||
$edit['language_content_type'] = ENTITY_TRANSLATION_ENABLED;
|
||||
$this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
|
||||
$this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
|
||||
|
||||
// Set body field's cardinality to unlimited and toggle translatability.
|
||||
$edit = array();
|
||||
$edit['field[cardinality]'] = FIELD_CARDINALITY_UNLIMITED;
|
||||
$edit['field[translatable]'] = 1;
|
||||
$this->drupalPost('admin/structure/types/manage/page/fields/body', $edit, t('Save settings'));
|
||||
$this->assertRaw(t('Saved %field configuration.', array('%field' => 'Body')), t('Body field settings have been updated.'));
|
||||
|
||||
// Check if the setting works.
|
||||
$this->drupalGet('node/add/page');
|
||||
$this->assertFieldById('edit-body-und-add-more', t('Add another item'), t('Add another item button found.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a "Basic page" in the specified language.
|
||||
*
|
||||
* @param $title
|
||||
* Title of the basic page in the specified language.
|
||||
* @param $body
|
||||
* Body of the basic page in the specified language.
|
||||
* @param $langcode
|
||||
* The language code to be assigned to the specified values.
|
||||
*/
|
||||
function createPage($title, $body, $langcode) {
|
||||
$edit = array();
|
||||
$language_none = LANGUAGE_NONE;
|
||||
$edit["title"] = $title;
|
||||
$edit["body[$language_none][0][value]"] = $body;
|
||||
$edit['language'] = $langcode;
|
||||
$this->drupalPost('node/add/page', $edit, t('Save'));
|
||||
$this->assertRaw(t('Basic page %title has been created.', array('%title' => $title)), t('Basic page created.'));
|
||||
|
||||
// Check to make sure the node was created.
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
$this->assertTrue($node, t('Node found in database.'));
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translation.
|
||||
*
|
||||
* @param $node
|
||||
* Node of the basic page to create translation for.
|
||||
* @param $title
|
||||
* Title of the basic page in the specified language.
|
||||
* @param $body
|
||||
* Body of the basic page in the specified language.
|
||||
* @param $langcode
|
||||
* The language code to be assigned to the specified values.
|
||||
*/
|
||||
function createTranslation($node, $title, $body, $langcode, $source_langcode = 'en') {
|
||||
$this->drupalGet('node/' . $node->nid . '/edit/add/' . $source_langcode . '/' . $langcode);
|
||||
|
||||
$body_key = "body[$langcode][0][value]";
|
||||
$this->assertFieldByXPath("//textarea[@name='$body_key']", $node->body[$node->language][0]['value'], 'Original body value correctly populated.');
|
||||
$this->assertFieldById('edit-body-' . $langcode . '-add-more', t('Add another item'), t('Add another item button found.'));
|
||||
|
||||
$edit = array();
|
||||
$edit[$body_key] = $body;
|
||||
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
$this->drupalGet('node/' . $node->nid . '/translate');
|
||||
$this->assertLinkByHref('node/' . $node->nid . '/edit/' . $langcode, 0, t('Translation edit link found. Translation created.'));
|
||||
|
||||
return $node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic tests for the translation creation/editing workflow.
|
||||
*/
|
||||
class EntityTranslationTranslationTestCase extends EntityTranslationTestCase {
|
||||
|
||||
/**
|
||||
* Return the test information.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Entity translation workflow',
|
||||
'description' => 'Basic tests for the translation creation/editing workflow.',
|
||||
'group' => 'Entity translation',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('locale', 'entity_translation');
|
||||
$this->login($this->getAdminUser());
|
||||
$this->addLanguage('en');
|
||||
$this->addLanguage('es');
|
||||
$this->configureContentType();
|
||||
$this->login($this->getTranslatorUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if field based translation works.
|
||||
*
|
||||
* Enable field based translation for basic pages. Add a field with a
|
||||
* cardinality higher than 1, to test if field_default_extract_form_values()
|
||||
* is invoked. Create a basic page and translate it.
|
||||
*/
|
||||
function testFieldTranslation() {
|
||||
// Create Basic page in English.
|
||||
$node_title = $this->randomName();
|
||||
$node_body = $this->randomName();
|
||||
$node = $this->createPage($node_title, $node_body, 'en');
|
||||
|
||||
// Submit translation in Spanish.
|
||||
$node_translation_title = $this->randomName();
|
||||
$node_translation_body = $this->randomName();
|
||||
$node_translation = $this->createTranslation($node, $node_translation_title, $node_translation_body, 'es');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic tests for comment related things.
|
||||
*
|
||||
* @todo Add tests for comment translation workflow.
|
||||
*/
|
||||
class EntityTranslationCommentTestCase extends EntityTranslationTestCase {
|
||||
|
||||
protected $comment_user;
|
||||
|
||||
/**
|
||||
* Return the test information.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Comment translation',
|
||||
'description' => 'Basic tests for comment translation/filtering.',
|
||||
'group' => 'Entity translation',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('locale', 'entity_translation', 'comment');
|
||||
$this->login($this->getAdminUser());
|
||||
$this->addLanguage('en');
|
||||
$this->addLanguage('es');
|
||||
$this->enableUrlLanguageDetection();
|
||||
$this->configureContentType();
|
||||
$this->configureComments(FALSE);
|
||||
$this->login($this->getTranslatorUser());
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
unset($this->comment_user);
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
function getCommentUser() {
|
||||
if (empty($this->comment_user)) {
|
||||
$this->comment_user = $this->drupalCreateUser(array(
|
||||
'access comments',
|
||||
'post comments',
|
||||
'edit own comments',
|
||||
));
|
||||
}
|
||||
return $this->comment_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable comments and comment filtering by language.
|
||||
*/
|
||||
function configureComments($filter_by_language = TRUE) {
|
||||
$edit = array();
|
||||
$edit['comment'] = COMMENT_NODE_OPEN;
|
||||
$edit['entity_translation_comment_filter'] = $filter_by_language;
|
||||
$this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
|
||||
$this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')));
|
||||
|
||||
$this->drupalGet('admin/structure/types/manage/page');
|
||||
if ($filter_by_language) {
|
||||
$this->assertFieldChecked('edit-entity-translation-comment-filter', 'Comment filtering is enabled.');
|
||||
}
|
||||
else {
|
||||
$this->assertNoFieldChecked('edit-entity-translation-comment-filter', 'Comment filtering is disabled.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment for the given node.
|
||||
*
|
||||
* @param $node
|
||||
* The node for which to add the comment.
|
||||
* @param $comment_body
|
||||
* The comment body text.
|
||||
* @param $language
|
||||
* The comment language.
|
||||
*/
|
||||
function postComment($node, $comment_body, $language) {
|
||||
$edit = array();
|
||||
$edit['comment_body[' . LANGUAGE_NONE . '][0][value]'] = $comment_body;
|
||||
$this->post($language, 'comment/reply/' . $node->nid, $edit, t('Save'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test comment filtering by language.
|
||||
*/
|
||||
function testCommentLanguageFiltering() {
|
||||
$node = $this->createPage($this->randomName(), $this->randomName(), 'en');
|
||||
$this->login($this->getCommentUser());
|
||||
|
||||
// Create comments in different languages.
|
||||
$comment_en = $this->randomName();
|
||||
$this->postComment($node, $comment_en, 'en');
|
||||
$comment_es = $this->randomName();
|
||||
$this->postComment($node, $comment_es, 'es');
|
||||
|
||||
// Check that ALL comments are being displayed when comment language filter
|
||||
// is disabled (default behavior).
|
||||
$this->get('en', 'node/' . $node->nid);
|
||||
$this->assertText($comment_en, 'English comment found.');
|
||||
$this->assertText($comment_es, 'Spanish comment found.');
|
||||
|
||||
// Enable comment filtering by language.
|
||||
$this->login($this->getAdminUser());
|
||||
$this->configureComments(TRUE);
|
||||
$this->login($this->getCommentUser());
|
||||
|
||||
// Load page in different languages. Check that only comments matching
|
||||
// current language are being displayed.
|
||||
$this->get('en', 'node/' . $node->nid);
|
||||
$this->assertText($comment_en, 'English comment found.');
|
||||
$this->assertNoText($comment_es, 'Spanish comment not found.');
|
||||
|
||||
$this->get('es', 'node/' . $node->nid);
|
||||
$this->assertNoText($comment_en, 'English comment not found.');
|
||||
$this->assertText($comment_es, 'Spanish comment found.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test CRUD hook invocation.
|
||||
*/
|
||||
class EntityTranslationHookTestCase extends EntityTranslationTestCase {
|
||||
|
||||
/**
|
||||
* Return the test information.
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Entity translation hooks',
|
||||
'description' => 'Test that entity translation hooks are properly fired.',
|
||||
'group' => 'Entity translation',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('locale', 'entity_translation', 'entity_translation_test');
|
||||
$this->login($this->getAdminUser());
|
||||
$this->addLanguage('it');
|
||||
$this->addLanguage('es');
|
||||
$this->configureContentType();
|
||||
$this->login($this->getTranslatorUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether hooks are properly fired in the regular form workflow.
|
||||
*/
|
||||
function testFormWorkflow() {
|
||||
// Create Basic page in English.
|
||||
$node_title = $this->randomName();
|
||||
$node_body = $this->randomName();
|
||||
$node = $this->createPage($node_title, $node_body, 'en');
|
||||
|
||||
// Submit translation in Italian.
|
||||
$node_translation_body = $this->randomName();
|
||||
$this->createTranslation($node, NULL, $node_translation_body, 'it');
|
||||
$info = $this->getHookInfo();
|
||||
$this->assertTrue(!empty($info['insert']), t('Insert hook has been properly fired.'));
|
||||
|
||||
// Edit translation in Italian.
|
||||
$edit = array("body[it][0][value]" => $this->randomName());
|
||||
$this->drupalPost('node/' . $node->nid . '/edit/it', $edit, t('Save'));
|
||||
$info = $this->getHookInfo();
|
||||
$this->assertTrue(!empty($info['update']), t('Update hook has been properly fired.'));
|
||||
|
||||
// Delete the Basic page.
|
||||
$edit = array();
|
||||
$this->drupalPost('node/' . $node->nid . '/delete', $edit, t('Delete'));
|
||||
$info = $this->getHookInfo('delete');
|
||||
$this->assertTrue(count($info) == 2 && !empty($info['en']) && !empty($info['it']), t('Delete hook has been properly fired.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether hooks are properly fired when using the API.
|
||||
*/
|
||||
function testAPI() {
|
||||
// Create Basic page in English.
|
||||
$node = $this->createPage($this->randomName(), $this->randomName(), 'en');
|
||||
$handler = entity_translation_get_handler('node', $node);
|
||||
|
||||
// Create a translation in Italian.
|
||||
$translation = array('source' => 'en', 'language' => 'it');
|
||||
$handler->setTranslation($translation);
|
||||
$handler->saveTranslations();
|
||||
$node = node_load($node->nid, NULL, TRUE);
|
||||
$handler = entity_translation_get_handler('node', $node, TRUE);
|
||||
$translations = $handler->getTranslations();
|
||||
$this->assertTrue(!empty($translations->data['it']), t('An Italian translation has been created'));
|
||||
$info = $this->getHookInfo();
|
||||
$this->assertTrue(!empty($info['insert']), t('Insert hook has been properly fired.'));
|
||||
|
||||
// Check that the update hook is properly fired.
|
||||
$translation['status'] = 1;
|
||||
$handler->setTranslation($translation);
|
||||
$handler->saveTranslations();
|
||||
$info = $this->getHookInfo();
|
||||
$this->assertTrue(!empty($info['update']), t('Update hook has been properly fired.'));
|
||||
|
||||
// Create a Spanish translation and update it before saving it.
|
||||
$translation = array('source' => 'it', 'language' => 'es');
|
||||
$handler->setTranslation($translation);
|
||||
$translation['status'] = 1;
|
||||
$handler->setTranslation($translation);
|
||||
$handler->saveTranslations();
|
||||
$node = node_load($node->nid, NULL, TRUE);
|
||||
$handler = entity_translation_get_handler('node', $node, TRUE);
|
||||
$translations = $handler->getTranslations();
|
||||
$this->assertTrue(!empty($translations->data['es']), t('A Spanish translation has been created'));
|
||||
$info = $this->getHookInfo();
|
||||
$this->assertTrue(!empty($info['insert']), t('Insert hook has been properly fired.'));
|
||||
|
||||
// Delete a translation after updating it without saving.
|
||||
$translation['status'] = 0;
|
||||
$handler->setTranslation($translation);
|
||||
$handler->removeTranslation('es');
|
||||
$handler->saveTranslations();
|
||||
$info = $this->getHookInfo();
|
||||
$this->assertTrue(empty($info['update']), t('Update hook has not been fired.'));
|
||||
$info = $this->getHookInfo('delete');
|
||||
$this->assertTrue(!empty($info['es']), t('Delete hook has been properly fired.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the information stored by hook implementations.
|
||||
*/
|
||||
protected function getHookInfo($op = 'save') {
|
||||
$name = 'entity_translation_test_' . $op;
|
||||
$info = variable_get($name);
|
||||
variable_del($name);
|
||||
return $info;
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Entity Translation testing
|
||||
description = Tests Entity Translation module functionality. Do not enable.
|
||||
core = 7.x
|
||||
package = Testing
|
||||
hidden = TRUE
|
||||
dependencies[] = entity_translation
|
||||
files[] = entity_translation_test.module
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-07-23
|
||||
version = "7.x-1.0-beta3"
|
||||
core = "7.x"
|
||||
project = "entity_translation"
|
||||
datestamp = "1374601567"
|
||||
|
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation functionality for Entity Translation testing module.
|
||||
*/
|
||||
|
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Testing functionality for Entity Translation module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_insert().
|
||||
*/
|
||||
function entity_translation_test_entity_translation_insert($entity_type, $entity, $translation, $values = array()) {
|
||||
entity_translation_test_check_save($entity_type, $entity, $translation, 'insert');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_insert().
|
||||
*/
|
||||
function entity_translation_test_entity_translation_update($entity_type, $entity, $translation, $values = array()) {
|
||||
entity_translation_test_check_save($entity_type, $entity, $translation, 'update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test implementation for save hooks.
|
||||
*/
|
||||
function entity_translation_test_check_save($entity_type, $entity, $translation, $hook) {
|
||||
list($id, ,) = entity_extract_ids($entity_type, $entity);
|
||||
$match = FALSE;
|
||||
|
||||
$row = db_select('entity_translation', 'et')
|
||||
->fields('et')
|
||||
->condition('entity_type', $entity_type)
|
||||
->condition('entity_id', $id)
|
||||
->condition('language', $translation['language'])
|
||||
->orderBy('created')
|
||||
->execute()
|
||||
->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($row) {
|
||||
$match = TRUE;
|
||||
foreach ($translation as $key => $value) {
|
||||
if ($row[$key] != $value) {
|
||||
$match = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable_set('entity_translation_test_save', array($hook => $match));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_translation_delete().
|
||||
*/
|
||||
function entity_translation_test_entity_translation_delete($entity_type, $entity, $langcode) {
|
||||
list($id, ,) = entity_extract_ids($entity_type, $entity);
|
||||
|
||||
$row = db_select('entity_translation', 'et')
|
||||
->fields('et')
|
||||
->condition('entity_type', $entity_type)
|
||||
->condition('entity_id', $id)
|
||||
->condition('language', $langcode)
|
||||
->orderBy('created')
|
||||
->execute()
|
||||
->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$info = variable_get('entity_translation_test_delete', array());
|
||||
$info[$langcode] = empty($row);
|
||||
variable_set('entity_translation_test_delete', $info);
|
||||
}
|
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* Provide views data and handlers for entity_translation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_views_data().
|
||||
*/
|
||||
function entity_translation_views_data() {
|
||||
$data = array();
|
||||
$data['entity_translation']['table']['group'] = t('Entity translation');
|
||||
// Advertise this table as a possible base table.
|
||||
$data['entity_translation']['table']['base'] = array(
|
||||
'field' => 'entity_id',
|
||||
'title' => t('Entity translation'),
|
||||
'help' => t('Information about a translation of an entity.'),
|
||||
);
|
||||
$data['entity_translation']['entity_id'] = array(
|
||||
'title' => t('Entity id'),
|
||||
'help' => t('The entity id.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field_numeric',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_numeric',
|
||||
'numeric' => TRUE,
|
||||
'validate type' => 'entity_id',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'views_handler_filter_numeric',
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['entity_type'] = array(
|
||||
'title' => t('Entity type'),
|
||||
'help' => t('The entity type.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_string',
|
||||
'numeric' => FALSE,
|
||||
'validate type' => 'entity_type',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'entity_translation_handler_filter_entity_type',
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['language'] = array(
|
||||
'title' => t('Language'),
|
||||
'help' => t('The language of this translation.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field_locale_language',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_locale_language',
|
||||
'numeric' => FALSE,
|
||||
'validate type' => 'language',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'entity_translation_handler_filter_language',
|
||||
'allow empty' => TRUE,
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['source'] = array(
|
||||
'title' => t('Source'),
|
||||
'help' => t('The source language.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field_locale_language',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_locale_language',
|
||||
'name field' => 'title',
|
||||
'numeric' => FALSE,
|
||||
'validate type' => 'string',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'entity_translation_handler_filter_language',
|
||||
'allow empty' => TRUE,
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['status'] = array(
|
||||
'title' => t('Translation status'),
|
||||
'help' => t('The status of this translation.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field_boolean',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_numeric',
|
||||
'numeric' => TRUE,
|
||||
'validate type' => 'boolean',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'views_handler_filter_boolean_operator',
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['translate'] = array(
|
||||
'title' => t('Needs update'),
|
||||
'help' => t('Indicates if the translation needs to be updated.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field_boolean',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_numeric',
|
||||
'name field' => 'title',
|
||||
'numeric' => TRUE,
|
||||
'validate type' => 'boolean',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'views_handler_filter_boolean_operator',
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['created'] = array(
|
||||
'title' => t('Created'),
|
||||
'help' => t('Created date.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field_date',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_date',
|
||||
'numeric' => FALSE,
|
||||
'validate type' => 'date',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'views_handler_filter_date',
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort_date',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['changed'] = array(
|
||||
'title' => t('Changed'),
|
||||
'help' => t('Changed date.'),
|
||||
'field' => array(
|
||||
'handler' => 'views_handler_field_date',
|
||||
'click sortable' => TRUE,
|
||||
),
|
||||
'argument' => array(
|
||||
'handler' => 'views_handler_argument_date',
|
||||
'numeric' => FALSE,
|
||||
'validate type' => 'date',
|
||||
),
|
||||
'filter' => array(
|
||||
'handler' => 'views_handler_filter_date',
|
||||
),
|
||||
'sort' => array(
|
||||
'handler' => 'views_handler_sort_date',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['translate_link'] = array(
|
||||
'title' => t('Translate link'),
|
||||
'help' => t('Link to translation overview page.'),
|
||||
'field' => array(
|
||||
'handler' => 'entity_translation_handler_field_translate_link',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['translation_exists'] = array(
|
||||
'title' => t('Translation exists'),
|
||||
'help' => t('Link to translation overview page.'),
|
||||
'filter' => array(
|
||||
'handler' => 'entity_translation_handler_filter_translation_exists',
|
||||
),
|
||||
);
|
||||
$data['entity_translation']['label'] = array(
|
||||
'title' => t('Label'),
|
||||
'help' => t('The label of the entity.'),
|
||||
'field' => array(
|
||||
'handler' => 'entity_translation_handler_field_label',
|
||||
),
|
||||
);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_views_data_alter().
|
||||
*
|
||||
* Add entity translation information to the various entity tables.
|
||||
*/
|
||||
function entity_translation_views_data_alter(&$data) {
|
||||
foreach (entity_get_info() as $type => $info) {
|
||||
if ($info['fieldable'] && isset($data[$info['base table']])) {
|
||||
$table = &$data[$info['base table']];
|
||||
$table['entity_translations'] = array(
|
||||
'title' => t('Entity translation: translations'),
|
||||
'help' => t('Translation information.'),
|
||||
'relationship' => array(
|
||||
'label' => t('Translations'),
|
||||
'base' => 'entity_translation',
|
||||
'base field' => 'entity_id',
|
||||
'relationship field' => $info['entity keys']['id'],
|
||||
'handler' => 'entity_translation_handler_relationship',
|
||||
// We add our information here in the definition, so we can copy it
|
||||
// later.
|
||||
'left_table' => $info['base table'],
|
||||
'left_field' => $info['entity keys']['id'],
|
||||
'entity type' => $type,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* This file contains a label field handler for entity translation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This handler shows the entity label for entities in the entity_translation table.
|
||||
*/
|
||||
class entity_translation_handler_field_label extends views_handler_field {
|
||||
function construct() {
|
||||
parent::construct();
|
||||
$this->additional_fields['entity_id'] = 'entity_id';
|
||||
$this->additional_fields['entity_type'] = 'entity_type';
|
||||
}
|
||||
|
||||
function query() {
|
||||
$this->ensure_my_table();
|
||||
$this->add_additional_fields();
|
||||
}
|
||||
/**
|
||||
* Add a 'link to entity' option definition.
|
||||
* @see views_handler_field::option:definition()
|
||||
*/
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['link_to_entity'] = array('default' => '', 'translatable' => FALSE);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a 'link to entity' option.
|
||||
* @see views_handler_field::options_form()
|
||||
*/
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
$form['link_to_entity'] = array(
|
||||
'#title' => t('Link this field to it\'s entity'),
|
||||
'#type' => 'checkbox',
|
||||
'#default_value' => $this->options['link_to_entity']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all entities, so that we can get the label.
|
||||
*/
|
||||
function post_execute(&$values) {
|
||||
$ids = array();
|
||||
$ids_by_type = array();
|
||||
foreach ($values as $row) {
|
||||
if ($entity_type = $this->get_value($row, 'entity_type')) {
|
||||
$ids_by_type[$entity_type][] = $this->get_value($row, 'entity_id');
|
||||
}
|
||||
}
|
||||
foreach ($ids_by_type as $type => $ids) {
|
||||
$this->entities[$type] = entity_load($type, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
function render($values) {
|
||||
$entity_type = $this->get_value($values, 'entity_type');
|
||||
$entity_id = $this->get_value($values, 'entity_id');
|
||||
// Check if entity is not empty
|
||||
if (!$entity_id || !$entity_type) {
|
||||
return NULL;
|
||||
}
|
||||
$entity = $this->entities[$entity_type][$entity_id];
|
||||
// We could also use entity_label(), but since this we might want to let
|
||||
// the handler decide what's best to show.
|
||||
$handler = entity_translation_get_handler($entity_type, $entity);
|
||||
$label = $handler->getLabel();
|
||||
if ($this->options['link_to_entity']) {
|
||||
$this->options['alter']['make_link'] = TRUE;
|
||||
$this->options['alter']['path'] = $handler->getViewPath();
|
||||
}
|
||||
return $label;
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Translate link plugin.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This handler adds translate link for all translatable entities.
|
||||
*/
|
||||
class entity_translation_handler_field_translate_link extends views_handler_field {
|
||||
|
||||
function construct() {
|
||||
parent::construct();
|
||||
$this->additional_fields['entity_id'] = 'entity_id';
|
||||
$this->additional_fields['entity_type'] = 'entity_type';
|
||||
$this->additional_fields['language'] = 'language';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add required additional fields.
|
||||
*/
|
||||
function query() {
|
||||
$this->ensure_my_table();
|
||||
$this->add_additional_fields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the text option.
|
||||
* @see views_handler_field::option_definition()
|
||||
*/
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['text'] = array('default' => '', 'translatable' => TRUE);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the option to set the title of the translate link.
|
||||
* @see views_handler_field::options_form()
|
||||
*/
|
||||
function options_form(&$form, &$form_state) {
|
||||
$form['text'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Text to display'),
|
||||
'#default_value' => $this->options['text'],
|
||||
);
|
||||
parent::options_form($form, $form_state);
|
||||
|
||||
// The path is set by render_link function so don't allow setting it.
|
||||
$form['alter']['path'] = array('#access' => FALSE);
|
||||
$form['alter']['external'] = array('#access' => FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all entities based on the data we have.
|
||||
*/
|
||||
function post_execute(&$values) {
|
||||
$ids = array();
|
||||
$ids_by_type = array();
|
||||
foreach ($values as $row) {
|
||||
if ($entity_type = $this->get_value($row, 'entity_type')) {
|
||||
$ids_by_type[$entity_type][] = $this->get_value($row, 'entity_id');
|
||||
}
|
||||
}
|
||||
foreach ($ids_by_type as $type => $ids) {
|
||||
$this->entities[$type] = entity_load($type, $ids);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see views_handler_field::render()
|
||||
*/
|
||||
function render($values) {
|
||||
$type = $this->get_value($values, 'entity_type');
|
||||
$entity_id = $this->get_value($values, 'entity_id');
|
||||
// Check if entity is not empty
|
||||
if (!$entity_id || !$type) {
|
||||
return NULL;
|
||||
}
|
||||
$language = $this->get_value($values, 'language');
|
||||
$entity = $this->entities[$type][$entity_id];
|
||||
return $this->render_link($type, $entity_id, $entity, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the link to the translation overview page of the entity.
|
||||
*/
|
||||
function render_link($entity_type, $entity_id, $entity, $language) {
|
||||
if (!entity_translation_tab_access($entity_type, $entity)) {
|
||||
return;
|
||||
}
|
||||
// We use the entity info here to avoid having to call entity_load() for all
|
||||
// the entities.
|
||||
$info = entity_get_info($entity_type);
|
||||
$path = $info['translation']['entity_translation']['path schemes']['default']['translate path'];
|
||||
$path = str_replace($info['translation']['entity_translation']['path schemes']['default']['path wildcard'], $entity_id, $path);
|
||||
$this->options['alter']['make_link'] = TRUE;
|
||||
$this->options['alter']['path'] = $path;
|
||||
$this->options['alter']['query'] = drupal_get_destination();
|
||||
$text = !empty($this->options['text']) ? $this->options['text'] : t('translate');
|
||||
return $text;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains an entity type filter handler.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This handler shows all available entity types that are enabled for entity
|
||||
* translation as options.
|
||||
*/
|
||||
class entity_translation_handler_filter_entity_type extends views_handler_filter_in_operator {
|
||||
|
||||
/**
|
||||
* Show all entity types that are enabled for entity translation as options.
|
||||
*/
|
||||
function get_value_options() {
|
||||
if (!isset($this->value_options)) {
|
||||
$this->value_title = t('Entity type');
|
||||
$allowed_types_options = variable_get('entity_translation_entity_types');
|
||||
$allowed_types = array();
|
||||
$entity_info = entity_get_info();
|
||||
foreach ($allowed_types_options as $key => $allowed) {
|
||||
if ($allowed) {
|
||||
$allowed_types[$key] = $entity_info[$key]['label'];
|
||||
}
|
||||
}
|
||||
$this->value_options = $allowed_types;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains a language filter handler.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extends the locale language filter in order for it to work with the entity
|
||||
* translation table structure.
|
||||
*/
|
||||
class entity_translation_handler_filter_language extends views_handler_filter_locale_language {
|
||||
|
||||
/**
|
||||
* Override the default behaviour, insert an empty string instead of NULL.
|
||||
*/
|
||||
function op_empty() {
|
||||
$this->ensure_my_table();
|
||||
if ($this->operator == 'empty') {
|
||||
$operator = "=";
|
||||
}
|
||||
else {
|
||||
$operator = "<>";
|
||||
}
|
||||
|
||||
$this->query->add_where($this->options['group'], "$this->table_alias.$this->real_field", '', $operator);
|
||||
}
|
||||
}
|
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains an entity type filter handler.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This handler determines if a translation exists for a particular translation.
|
||||
*/
|
||||
class entity_translation_handler_filter_translation_exists extends views_handler_filter_locale_language {
|
||||
|
||||
/**
|
||||
* Add a 'entity_type' option definition.
|
||||
* @see views_handler_field::option:definition()
|
||||
*/
|
||||
function option_definition() {
|
||||
$options = parent::option_definition();
|
||||
$options['entity_type'] = array('default' => '', 'translatable' => FALSE);
|
||||
$options['use_filter'] = array('default' => '', 'translatable' => FALSE);
|
||||
$options['filter'] = array('default' => '', 'translatable' => FALSE);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default title for the operators.
|
||||
*/
|
||||
function operators() {
|
||||
$operators = parent::operators();
|
||||
$operators['in']['title'] = t('Translation exists');
|
||||
$operators['not in']['title'] = t('Translation doesn\'t exist');
|
||||
return $operators;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add option for setting entity type either directly or through a filter.
|
||||
* @see views_handler_field::options_form()
|
||||
*/
|
||||
function options_form(&$form, &$form_state) {
|
||||
parent::options_form($form, $form_state);
|
||||
$filters = $this->get_entity_type_filters();
|
||||
if (!empty($filters)) {
|
||||
$form['use_filter'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Use an entity type filter.'),
|
||||
'#default_value' => $this->options['use_filter'],
|
||||
);
|
||||
$form['filter'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Filter'),
|
||||
'#options' => $filters,
|
||||
'#dependency' => array(
|
||||
'edit-options-use-filter' => array(1)
|
||||
),
|
||||
'#default_value' => $this->options['filter'],
|
||||
);
|
||||
}
|
||||
$form['entity_type'] = array(
|
||||
'#title' => t('Entity type'),
|
||||
'#type' => 'select',
|
||||
'#options' => $this->get_allowed_types(),
|
||||
'#dependency' => array(
|
||||
'edit-options-use-filter' => array(0)
|
||||
),
|
||||
'#default_value' => $this->options['entity_type'],
|
||||
'#description' => t('You have to filter on a particular entity type when you use this filter'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available entity type filters that can be used to build the query.
|
||||
*/
|
||||
function get_entity_type_filters() {
|
||||
// We need to build the query to know about the available fields.
|
||||
$this->view->build();
|
||||
$filters = array();
|
||||
foreach ($this->view->filter as $key => $filter) {
|
||||
// Break if we encounter our filter, the filter must be before this one.
|
||||
if ($filter == $this) {
|
||||
break;
|
||||
}
|
||||
if ($filter instanceof entity_translation_handler_filter_entity_type && count($filter->value) == 1 && empty($filter->options['expose']['multiple'])) {
|
||||
$filters[$key] = $filter->value_title;
|
||||
}
|
||||
}
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity types managed by entity translation.
|
||||
*/
|
||||
function get_allowed_types() {
|
||||
$allowed_types_options = variable_get('entity_translation_entity_types');
|
||||
$allowed_types = array();
|
||||
$entity_info = entity_get_info();
|
||||
foreach ($allowed_types_options as $key => $allowed) {
|
||||
if ($allowed) {
|
||||
$allowed_types[$key] = $entity_info[$key]['label'];
|
||||
}
|
||||
}
|
||||
return $allowed_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default behaviour of the handler.
|
||||
*/
|
||||
function query() {
|
||||
$this->ensure_my_table();
|
||||
// We need a subquery to determine not in.
|
||||
if ($this->operator == 'not in') {
|
||||
$entity_type = 'node';
|
||||
if ($this->options['use_filter'] && isset($this->view->filter[$this->options['filter']])) {
|
||||
$filter = $this->view->filter[$this->options['filter']];
|
||||
$entity_type = current($filter->value);
|
||||
}
|
||||
else {
|
||||
$this->query->add_where($this->options['group'], "$this->table_alias.entity_type", $this->options['entity_type'], '=');
|
||||
$entity_type = $this->options['entity_type'];
|
||||
}
|
||||
$query = db_select('entity_translation', 'es')
|
||||
->condition('entity_type', $entity_type)
|
||||
->condition('language', $this->value);
|
||||
$query->addField('es', 'entity_id');
|
||||
$this->query->add_where($this->options['group'], "$this->table_alias.entity_id", $query, $this->operator);
|
||||
}
|
||||
// We can determine if a translation exists without a subquery.
|
||||
else {
|
||||
$value = array_keys($this->value);
|
||||
$this->query->add_where($this->options['group'], "$this->table_alias.source", '', '<>');
|
||||
$this->query->add_where($this->options['group'], "$this->table_alias.language", array_values($this->value), $this->operator);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Contains the relationship plugin for relating entities to translation metadata.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add a relationship to the entity translation table.
|
||||
*/
|
||||
class entity_translation_handler_relationship extends views_handler_relationship {
|
||||
|
||||
/**
|
||||
* Add a relationship to the entity_translation table.
|
||||
*/
|
||||
function query() {
|
||||
$this->ensure_my_table();
|
||||
$def = $this->definition;
|
||||
$def['table'] = 'entity_translation';
|
||||
$def['field'] = 'entity_id';
|
||||
$def['left_table'] = $this->table_alias;
|
||||
$def['type'] = empty($this->options['required']) ? 'LEFT' : 'INNER';
|
||||
$join = new views_join();
|
||||
$join->definition = $def;
|
||||
$join->construct();
|
||||
$join->adjusted = TRUE;
|
||||
// Use a short alias for the table.
|
||||
$alias = $def['table'] . '_' . $this->table;
|
||||
// We need to add a condition on entity type to the join to avoid getting
|
||||
// relationships to entities with other types.
|
||||
$join->extra = "$alias.entity_type = '{$def['entity type']}'";
|
||||
$this->alias = $this->query->add_relationship($alias, $join, 'entity_translation', $this->relationship);
|
||||
}
|
||||
}
|
40
sites/all/modules/contrib/localisation/i18n/INSTALL.txt
Normal file
40
sites/all/modules/contrib/localisation/i18n/INSTALL.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
********************************************************************
|
||||
D R U P A L M O D U L E
|
||||
********************************************************************
|
||||
Internationalization.
|
||||
Provides multilingual features.
|
||||
|
||||
********************************************************************
|
||||
This is the 7.x version of i18n module, and works with Drupal 7.x
|
||||
********************************************************************
|
||||
|
||||
********************************************************************
|
||||
Updated documentation will be kept on-line at http://drupal.org/node/133977
|
||||
********************************************************************
|
||||
|
||||
INSTALLATION:
|
||||
============
|
||||
1. Create folder 'sites/all/modules/i18n' and copy all the modules files, keeping directory structure, to this folder.
|
||||
2. If updating, run the update.php script following the standard procedure for Drupal updates.
|
||||
|
||||
UPGRADING FROM DRUPAL 6:
|
||||
=======================
|
||||
- Read up to date instructions at http://drupal.org/node/1113358
|
||||
|
||||
POST-INSTALLATION/CONFIGURATION:
|
||||
================================
|
||||
- First of all review Drupal language settings and make sure you have chosen the right default language.
|
||||
- Enable the needed modules grouped under "Internationalization" package
|
||||
- Read the on-line handbook on
|
||||
|
||||
IMPORTANT:
|
||||
==========
|
||||
- This module requires a complex set up, make sure you read the handbook and understand the different options
|
||||
- Before creating a support request, do read the handbook: http://drupal.org/node/133977
|
||||
|
||||
Additional Support
|
||||
==================
|
||||
See README.txt
|
||||
|
||||
====================================================================
|
||||
Jose A. Reyero, drupal at reyero dot net, http://reyero.net
|
339
sites/all/modules/contrib/localisation/i18n/LICENSE.txt
Normal file
339
sites/all/modules/contrib/localisation/i18n/LICENSE.txt
Normal 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.
|
27
sites/all/modules/contrib/localisation/i18n/README.txt
Normal file
27
sites/all/modules/contrib/localisation/i18n/README.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
README.txt
|
||||
==========
|
||||
|
||||
********************************************************************
|
||||
This is i18n package 7.x, and will work with Drupal 7.x
|
||||
********************************************************************
|
||||
WARNING: DO READ THE INSTALL FILE AND the ON-LINE HANDBOOK
|
||||
********************************************************************
|
||||
|
||||
This is a collection of modules providing multilingual features.
|
||||
These modules will build onto Drupal 7 core features enabling a full multilingual site
|
||||
|
||||
Up to date documentation will be kept on-line at http://drupal.org/node/133977
|
||||
|
||||
Additional Support
|
||||
=================
|
||||
For support, please create a support request for this module's project:
|
||||
http://drupal.org/project/i18n
|
||||
|
||||
Support questions by email to the module maintainer will be simply ignored. Use the issue tracker.
|
||||
|
||||
Now if you want professional (paid) support the module maintainer may be available occassionally.
|
||||
Drop me a message to check availability and hourly rates, http://reyero.net/en/contact
|
||||
|
||||
====================================================================
|
||||
Jose A. Reyero, drupal at reyero dot net, http://reyero.net
|
117
sites/all/modules/contrib/localisation/i18n/i18n.api.php
Normal file
117
sites/all/modules/contrib/localisation/i18n/i18n.api.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* API documentation for Internationalization module
|
||||
*
|
||||
* Most i18n hooks can be placed on each module.i18n.inc file but in this case
|
||||
* such file must be listed in the module.info file.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Provide information about object types handled by i18n system.
|
||||
*
|
||||
* @see i18n_object_info()
|
||||
*
|
||||
* Other features like translation sets (i18n_translation) or string translation (i18n_string)
|
||||
* rely on the information provided by this hook for automating string translation
|
||||
*/
|
||||
function hook_i18n_object_info() {
|
||||
// Information for node type object, see i18n_node_i18n_object_info()
|
||||
$info['node_type'] = array(
|
||||
// Generic object properties, title, etc..
|
||||
'title' => t('Node type'),
|
||||
// Field to be used as key to index different node types
|
||||
'key' => 'type',
|
||||
// Mapping object fields and menu place holders
|
||||
'placeholders' => array(
|
||||
'%node_type' => 'type',
|
||||
),
|
||||
// Path for automatically generated translation tabs. Note placeholders above are used here.
|
||||
'edit path' => 'admin/structure/types/manage/%node_type',
|
||||
'translate tab' => 'admin/structure/types/manage/%node_type/translate',
|
||||
// We can easily list all these objects because they should be limited and manageable
|
||||
// Only in this case we provide a 'list callback'.
|
||||
'list callback' => 'node_type_get_types',
|
||||
// Metadata for string translation
|
||||
// In this case we are defining fields and keys for string translation's string names
|
||||
// String ids are of the form: [textgroup]:[type]:[key]:[property]
|
||||
// Thus in this case we'll have string names like
|
||||
// - node:type:story:name
|
||||
// - node:type:story:description
|
||||
'string translation' => array(
|
||||
'textgroup' => 'node',
|
||||
'type' => 'type',
|
||||
'properties' => array(
|
||||
'name' => t('Name'),
|
||||
'title_label' => t('Title label'),
|
||||
'description' => t('Description'),
|
||||
'help' => t('Help text'),
|
||||
),
|
||||
'translate path' => 'admin/structure/types/manage/%node_type/translate/%i18n_language',
|
||||
)
|
||||
);
|
||||
// Example information for taxonomy term object, see i18n_taxonomy_i18n_object_info().
|
||||
$info['taxonomy_term'] = array(
|
||||
'title' => t('Taxonomy term'),
|
||||
'class' => 'i18n_taxonomy_term',
|
||||
'entity' => 'taxonomy_term',
|
||||
'key' => 'tid',
|
||||
'placeholders' => array(
|
||||
'%taxonomy_term' => 'tid',
|
||||
),
|
||||
// Auto generate edit path
|
||||
'edit path' => 'taxonomy/term/%taxonomy_term/edit',
|
||||
// Auto-generate translate tab
|
||||
'translate tab' => 'taxonomy/term/%taxonomy_term/translate',
|
||||
'string translation' => array(
|
||||
'textgroup' => 'taxonomy',
|
||||
'type' => 'term',
|
||||
'properties' => array(
|
||||
'name' => t('Name'),
|
||||
'description' => array(
|
||||
'title' => t('Description'),
|
||||
'format' => 'format',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter i18n object information provided by modules with the previous hook
|
||||
*
|
||||
* @see i18n_object_info()
|
||||
*/
|
||||
function hook_i18n_object_info_alter(&$info) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide information about available translations for specific path.
|
||||
*
|
||||
* @see i18n_get_path_translations($path)
|
||||
*
|
||||
* @param $path
|
||||
* Internal path to translate.
|
||||
* @return array
|
||||
* Translations indexed by language code. Each translation is an array with:
|
||||
* - 'path'
|
||||
* - 'title'
|
||||
* - 'options'
|
||||
*/
|
||||
function hook_i18n_translate_path($path) {
|
||||
if ($path == 'mypath') {
|
||||
$translations['es'] = array(
|
||||
'path' => 'mypath/spanish',
|
||||
'title' => t('My Spanish translation'),
|
||||
);
|
||||
return $translations;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter path translations
|
||||
*/
|
||||
function hook_i18n_translate_path_alter(&$translations, $path) {
|
||||
}
|
16
sites/all/modules/contrib/localisation/i18n/i18n.info
Normal file
16
sites/all/modules/contrib/localisation/i18n/i18n.info
Normal file
@@ -0,0 +1,16 @@
|
||||
name = Internationalization
|
||||
description = Extends Drupal support for multilingual features.
|
||||
dependencies[] = locale
|
||||
dependencies[] = variable
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
files[] = i18n_object.inc
|
||||
files[] = i18n.test
|
||||
configure = admin/config/regional/i18n
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
57
sites/all/modules/contrib/localisation/i18n/i18n.install
Normal file
57
sites/all/modules/contrib/localisation/i18n/i18n.install
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation file for Internationalization (i18n) module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function i18n_install() {
|
||||
// Set module weight for it to run after core modules
|
||||
db_query("UPDATE {system} SET weight = 10 WHERE name = 'i18n' AND type = 'module'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function i18n_uninstall() {
|
||||
variable_del('i18n_drupal6_update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fields to schema if they don't exist
|
||||
*/
|
||||
function i18n_install_create_fields($table, $fields) {
|
||||
static $schema;
|
||||
// Do not force schema refresh more than once per request.
|
||||
$schema = drupal_get_schema($table, !isset($schema));
|
||||
foreach ($fields as $field) {
|
||||
if (!empty($schema['fields'][$field])) {
|
||||
if (!db_field_exists($table, $field)) {
|
||||
db_add_field($table, $field, $schema['fields'][$field]);
|
||||
}
|
||||
else {
|
||||
// The field exists, make sure field definition is up to date.
|
||||
db_change_field($table, $field, $field, $schema['fields'][$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this as updated so all (renamed) modules know they need to update from D6 version when installing
|
||||
*/
|
||||
function i18n_update_7000() {
|
||||
variable_set('i18n_drupal6_update', TRUE);
|
||||
variable_del('i18n_selection_mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh caches and rebuild menus.
|
||||
*/
|
||||
function i18n_update_7001() {
|
||||
drupal_flush_all_caches();
|
||||
}
|
21
sites/all/modules/contrib/localisation/i18n/i18n.js
Normal file
21
sites/all/modules/contrib/localisation/i18n/i18n.js
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Rewrite autocomplete inputs to pass the language of the node currently being
|
||||
* edited in the path.
|
||||
*
|
||||
* This functionality ensures node autocompletes get suggestions for the node's
|
||||
* language rather than the current interface language.
|
||||
*/
|
||||
Drupal.behaviors.i18n = {
|
||||
attach: function (context) {
|
||||
if (Drupal.settings && Drupal.settings.i18n) {
|
||||
$('form[id^=node-form]', context).find('input.autocomplete[value^=' + Drupal.settings.i18n.interface_path + ']').each(function () {
|
||||
$(this).val($(this).val().replace(Drupal.settings.i18n.interface_path, Drupal.settings.i18n.content_path));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
609
sites/all/modules/contrib/localisation/i18n/i18n.module
Normal file
609
sites/all/modules/contrib/localisation/i18n/i18n.module
Normal file
@@ -0,0 +1,609 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) module.
|
||||
*
|
||||
* This module extends multilingual support being the base module for the i18n package.
|
||||
* - Multilingual variables
|
||||
* - Extended languages for nodes
|
||||
* - Extended language API
|
||||
*
|
||||
* @author Jose A. Reyero, 2004
|
||||
*/
|
||||
|
||||
// All multilingual options disabled
|
||||
define('I18N_LANGUAGE_DISABLED', 0);
|
||||
// Language list will include all enabled languages
|
||||
define('I18N_LANGUAGE_ENABLED', 1);
|
||||
// Language list will include also disabled languages
|
||||
define('I18N_LANGUAGE_EXTENDED', 4);
|
||||
// Disabled languages will be hidden when possible
|
||||
define('I18N_LANGUAGE_HIDDEN', 8);
|
||||
// All defined languages will be allowed but hidden when possible
|
||||
define('I18N_LANGUAGE_EXTENDED_NOT_DISPLAYED', I18N_LANGUAGE_EXTENDED | I18N_LANGUAGE_HIDDEN);
|
||||
|
||||
// No multilingual options
|
||||
define('I18N_MODE_NONE', 0);
|
||||
// Localizable object. Run through the localization system
|
||||
define('I18N_MODE_LOCALIZE', 1);
|
||||
// Predefined language for this object and all related ones.
|
||||
define('I18N_MODE_LANGUAGE', 2);
|
||||
// Multilingual objects, translatable but not localizable.
|
||||
define('I18N_MODE_TRANSLATE', 4);
|
||||
// Objects are translatable (if they have language or localizable if not)
|
||||
define('I18N_MODE_MULTIPLE', I18N_MODE_LOCALIZE | I18N_MODE_TRANSLATE);
|
||||
|
||||
/**
|
||||
* Global variable for i18n context language.
|
||||
*/
|
||||
define('I18N_LANGUAGE_TYPE_CONTEXT', 'i18n_language_context');
|
||||
|
||||
/**
|
||||
* Implements hook_boot().
|
||||
*/
|
||||
function i18n_boot() {
|
||||
// Just make sure the module is loaded for boot and the API is available.
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hook_info().
|
||||
*/
|
||||
function i18n_hook_info() {
|
||||
$hooks['i18n_object_info'] = array(
|
||||
'group' => 'i18n',
|
||||
);
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* WARNING: Obsolete function, use other i18n_language_* instead.
|
||||
*
|
||||
* Get global language object, make sure it is initialized
|
||||
*
|
||||
* @param $language
|
||||
* Language code or language object to convert to valid language object
|
||||
*/
|
||||
function i18n_language($language = NULL) {
|
||||
if ($language && ($language_object = i18n_language_object($language))) {
|
||||
return $language_object;
|
||||
}
|
||||
else {
|
||||
return i18n_language_interface();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language object from language code or object.
|
||||
*
|
||||
* @param $language
|
||||
* Language code or language object to convert to valid language object.
|
||||
* @return
|
||||
* Language object if this is an object or a valid language code.
|
||||
*/
|
||||
function i18n_language_object($language) {
|
||||
if (is_object($language)) {
|
||||
return $language;
|
||||
}
|
||||
else {
|
||||
$list = language_list();
|
||||
return isset($list[$language]) ? $list[$language] : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interface language, make sure it is initialized.
|
||||
*/
|
||||
function i18n_language_interface() {
|
||||
if (empty($GLOBALS[LANGUAGE_TYPE_INTERFACE])) {
|
||||
// We don't have language yet, initialize the language system and retry
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
|
||||
}
|
||||
return $GLOBALS[LANGUAGE_TYPE_INTERFACE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content language, make sure it is initialized.
|
||||
*/
|
||||
function i18n_language_content() {
|
||||
if (empty($GLOBALS[LANGUAGE_TYPE_CONTENT])) {
|
||||
// We don't have language yet, initialize the language system and retry
|
||||
drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
|
||||
}
|
||||
return $GLOBALS[LANGUAGE_TYPE_CONTENT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get / set language from current context / content.
|
||||
*
|
||||
* Depending on the page content we may need to use a different language for some operations.
|
||||
*
|
||||
* This should be the language of the specific page content. I.e. node language for node pages
|
||||
* or term language for taxonomy term page.
|
||||
*
|
||||
* @param $language
|
||||
* Optional language object to set for context.
|
||||
* @return
|
||||
* Context language object, which defaults to content language.
|
||||
*
|
||||
* @see hook_i18n_context_language().
|
||||
*/
|
||||
function i18n_language_context($language = NULL) {
|
||||
if ($language) {
|
||||
$GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT] = $language;
|
||||
}
|
||||
elseif (!isset($GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT])) {
|
||||
// It will default to content language.
|
||||
$GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT] = i18n_language_content();
|
||||
// Get language from the first module that provides it.
|
||||
foreach (module_implements('i18n_context_language') as $module) {
|
||||
if ($language = module_invoke($module, 'i18n_context_language')) {
|
||||
$GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT] = $language;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu object loader, language
|
||||
*/
|
||||
function i18n_language_load($langcode) {
|
||||
$list = language_list();
|
||||
return isset($list[$langcode]) ? $list[$langcode] : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language selector form element
|
||||
*/
|
||||
function i18n_element_language_select($default = LANGUAGE_NONE) {
|
||||
if (is_object($default) || is_array($default)) {
|
||||
$default = i18n_object_langcode($default, LANGUAGE_NONE);
|
||||
}
|
||||
return array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Language'),
|
||||
'#default_value' => $default,
|
||||
'#options' => array(LANGUAGE_NONE => t('Language neutral')) + i18n_language_list(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language field for hook_field_extra_fields()
|
||||
*/
|
||||
function i18n_language_field_extra() {
|
||||
return array(
|
||||
'form' => array(
|
||||
'language' => array(
|
||||
'label' => t('Language'),
|
||||
'description' => t('Language selection'),
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
'display' => array(
|
||||
'language' => array(
|
||||
'label' => t('Language'),
|
||||
'description' => t('Language'),
|
||||
'weight' => 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full language list
|
||||
*
|
||||
* @todo See about creating a permission for seeing disabled languages
|
||||
*/
|
||||
function i18n_language_list($field = 'name', $mode = NULL) {
|
||||
$mode = isset($mode) ? $mode : variable_get('i18n_language_list', I18N_LANGUAGE_ENABLED);
|
||||
return locale_language_list($field, I18N_LANGUAGE_EXTENDED & $mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language name for any defined (enabled or not) language
|
||||
*
|
||||
* @see locale_language_list()
|
||||
*/
|
||||
function i18n_language_name($lang) {
|
||||
$list = &drupal_static(__FUNCTION__);
|
||||
if (!isset($list)) {
|
||||
$list = locale_language_list('name', TRUE);
|
||||
}
|
||||
if (!$lang || $lang === LANGUAGE_NONE) {
|
||||
return t('Undefined');
|
||||
}
|
||||
elseif (isset($list[$lang])) {
|
||||
return check_plain($list[$lang]);
|
||||
}
|
||||
else {
|
||||
return t('Unknown');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid language code for current page or check whether the code is a defined language
|
||||
*/
|
||||
function i18n_langcode($langcode = NULL) {
|
||||
return $langcode && $langcode !== LANGUAGE_NONE ? $langcode : i18n_language()->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function i18n_help($path = 'admin/help#i18n', $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#i18n' :
|
||||
$output = '<p>' . t('This module improves support for multilingual content in Drupal sites:') . '</p>';
|
||||
$output .= '<ul>';
|
||||
$output .= '<li>' . t('Shows content depending on page language.') . '</li>';
|
||||
$output .= '<li>' . t('Handles multilingual variables.') . '</li>';
|
||||
$output .= '<li>' . t('Extended language option for chosen content types. For these content types transations will be allowed for all defined languages, not only for enabled ones.') . '</li>';
|
||||
$output .= '<li>' . t('Provides a block for language selection and two theme functions: <em>i18n_flags</em> and <em>i18n_links</em>.') . '</li>';
|
||||
$output .= '</ul>';
|
||||
$output .= '<p>' . t('This is the base module for several others adding different features:') . '</p>';
|
||||
$output .= '<ul>';
|
||||
$output .= '<li>' . t('Multilingual menu items.') . '</li>';
|
||||
$output .= '<li>' . t('Multilingual taxonomy adds a language field for taxonomy vocabularies and terms.') . '</li>';
|
||||
$output .= '</ul>';
|
||||
$output .= '<p>' . t('For more information, see the online handbook entry for <a href="@i18n">Internationalization module</a>.', array('@i18n' => 'http://drupal.org/node/133977')) . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'admin/config/language/i18n':
|
||||
$output = '<ul>';
|
||||
$output .= '<li>' . t('To enable multilingual support for specific content types go to <a href="@configure_content_types">configure content types</a>.', array('@configure_content_types' => url('admin/structure/types'))) . '</li>';
|
||||
$output .= '</ul>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function i18n_menu() {
|
||||
$items['admin/config/regional/i18n'] = array(
|
||||
'title' => 'Multilingual settings',
|
||||
'description' => 'Configure extended options for multilingual content and translations.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('variable_module_form', 'i18n'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'weight' => 10,
|
||||
);
|
||||
$items['admin/config/regional/i18n/configure'] = array(
|
||||
'title' => 'Multilingual system',
|
||||
'description' => 'Configure extended options for multilingual content and translations.',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
);
|
||||
module_load_include('pages.inc', 'i18n');
|
||||
$items += i18n_page_menu_items();
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple i18n API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Switch select Mode on off if enabled
|
||||
*
|
||||
* Usage for disabling content selection for a while then return to previous state
|
||||
*
|
||||
* // Disable selection, but store previous mode
|
||||
* $previous = i18n_select(FALSE);
|
||||
*
|
||||
* // Other code to be run without content selection here
|
||||
* ..........................
|
||||
*
|
||||
* // Return to previous mode
|
||||
* i18n_select($previous);
|
||||
*
|
||||
* @param $value
|
||||
* Optional, enable/disable selection: TRUE/FALSE
|
||||
* @return boolean
|
||||
* Previous selection mode (TRUE/FALSE)
|
||||
*/
|
||||
function i18n_select($value = NULL) {
|
||||
static $mode = FALSE;
|
||||
|
||||
if (isset($value)) {
|
||||
$previous = $mode;
|
||||
$mode = $value;
|
||||
return $previous;
|
||||
}
|
||||
else {
|
||||
return $mode;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language properties.
|
||||
*
|
||||
* @param $code
|
||||
* Language code.
|
||||
* @param $property
|
||||
* It may be 'name', 'native', 'ltr'...
|
||||
*/
|
||||
function i18n_language_property($code, $property) {
|
||||
$languages = language_list();
|
||||
return isset($languages[$code]->$property) ? $languages[$code]->$property : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_preprocess_html().
|
||||
*/
|
||||
function i18n_preprocess_html(&$variables) {
|
||||
global $language;
|
||||
$variables['classes_array'][] = 'i18n-' . $language->language;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate or update user defined string. Entry point for i18n_string API if enabled.
|
||||
*
|
||||
* This function is from i18n_string sub module and is subject to be moved back.
|
||||
*
|
||||
* @param $name
|
||||
* Textgroup and context glued with ':'.
|
||||
* @param $default
|
||||
* String in default language. Default language may or may not be English.
|
||||
* @param $options
|
||||
* An associative array of additional options, with the following keys:
|
||||
* - 'langcode' (defaults to the current language) The language code to translate to a language other than what is used to display the page.
|
||||
* - 'filter' Filtering callback to apply to the translated string only
|
||||
* - 'format' Input format to apply to the translated string only
|
||||
* - 'callback' Callback to apply to the result (both to translated or untranslated string
|
||||
* - 'update' (defaults to FALSE) Whether to update source table
|
||||
* - 'translate' (defaults to TRUE) Whether to return a translation
|
||||
*
|
||||
* @return $string
|
||||
* Translated string, $string if not found
|
||||
*/
|
||||
function i18n_string($name, $string, $options = array()) {
|
||||
$options += array('translate' => TRUE, 'update' => FALSE);
|
||||
if ($options['update']) {
|
||||
$result = function_exists('i18n_string_update') ? i18n_string_update($name, $string, $options) : FALSE;
|
||||
}
|
||||
if ($options['translate']) {
|
||||
$result = function_exists('i18n_string_translate') ? i18n_string_translate($name, $string, $options) : $string;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object wrapper.
|
||||
*
|
||||
* Create an object wrapper or retrieve it from the static cache if
|
||||
* a wrapper for the same object was created before.
|
||||
*
|
||||
* @see i18n_object_info()
|
||||
*
|
||||
* @param $type
|
||||
* The object type.
|
||||
*/
|
||||
function i18n_object($type, $object) {
|
||||
$key = i18n_object_key($type, $object);
|
||||
return i18n_get_object($type, $key, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object wrapper by object key.
|
||||
*
|
||||
* @param $type
|
||||
* The object type to load e.g. node_type, menu, taxonomy_term.
|
||||
* @param $key
|
||||
* The object key, can be an scalar or an array.
|
||||
* @param $object
|
||||
* Optional Drupal object or array. It will be autoloaded using the key if not present.
|
||||
*
|
||||
* @return
|
||||
* A fully-populated object wrapper.
|
||||
*/
|
||||
function i18n_get_object($type, $key, $object = NULL) {
|
||||
$cache = &drupal_static(__FUNCTION__);
|
||||
$index = is_array($key) ? implode(':', $key) : $key;
|
||||
if (!isset($cache[$type][$index])) {
|
||||
$class = i18n_object_info($type, 'class', 'i18n_object_wrapper');
|
||||
$cache[$type][$index] = new $class($type, $key, $object);
|
||||
}
|
||||
return $cache[$type][$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object language code
|
||||
*
|
||||
* @param $object
|
||||
* Object or array having language field or plain language field
|
||||
* @param $default
|
||||
* What value to return if the object doesn't have a valid language
|
||||
*/
|
||||
function i18n_object_langcode($object, $default = FALSE, $field = 'language') {
|
||||
$value = i18n_object_field($object, $field, $default);
|
||||
return $value && $value != LANGUAGE_NONE ? $value : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation information for objects
|
||||
*/
|
||||
function i18n_object_info($type = NULL, $property = NULL, $default = NULL) {
|
||||
$info = &drupal_static(__FUNCTION__);
|
||||
if (!$info) {
|
||||
$info = module_invoke_all('i18n_object_info');
|
||||
drupal_alter('i18n_object_info', $info);
|
||||
}
|
||||
if ($property) {
|
||||
return isset($info[$type][$property]) ? $info[$type][$property] : $default;
|
||||
}
|
||||
elseif ($type) {
|
||||
return isset($info[$type]) ? $info[$type] : array();
|
||||
}
|
||||
else {
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field value from object/array
|
||||
*/
|
||||
function i18n_object_field($object, $field, $default = NULL) {
|
||||
if (is_array($field)) {
|
||||
// We can handle a list of fields too. This is useful for multiple keys (like blocks)
|
||||
foreach ($field as $key => $name) {
|
||||
$values[$key] = i18n_object_field($object, $name);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
elseif (strpos($field, '.')) {
|
||||
// Access nested properties with the form 'name1.name2..', will map to $object->name1->name2...
|
||||
$names = explode('.', $field);
|
||||
$current = array_shift($names);
|
||||
if ($nested = i18n_object_field($object, $current)) {
|
||||
return i18n_object_field($nested, implode('.', $names), $default);
|
||||
}
|
||||
else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
elseif (is_object($object)) {
|
||||
return isset($object->$field) ? $object->$field : $default;
|
||||
}
|
||||
elseif (is_array($object)) {
|
||||
return isset($object[$field]) ? $object[$field] : $default;
|
||||
}
|
||||
else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key value from object/array
|
||||
*/
|
||||
function i18n_object_key($type, $object, $default = NULL) {
|
||||
if ($field = i18n_object_info($type, 'key')) {
|
||||
return i18n_object_field($object, $field, $default);
|
||||
}
|
||||
else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu access callback for mixed translation tab
|
||||
*/
|
||||
function i18n_object_translate_access($type, $object) {
|
||||
return i18n_object($type, $object)->get_translate_access();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translations for path.
|
||||
*
|
||||
* @param $path
|
||||
* Path to get translations for or '<front>' for front page.
|
||||
*/
|
||||
function i18n_get_path_translations($path) {
|
||||
$translations = &drupal_static(__FUNCTION__);
|
||||
if (!isset($translations[$path])) {
|
||||
$translations[$path] = array();
|
||||
foreach (module_implements('i18n_translate_path') as $module) {
|
||||
$translated = call_user_func($module . '_i18n_translate_path', $path);
|
||||
// Add into the array, if two modules returning a translation first takes precedence.
|
||||
if ($translated) {
|
||||
$translations[$path] += $translated;
|
||||
}
|
||||
}
|
||||
// Chance for altering the results.
|
||||
drupal_alter('i18n_translate_path', $translations[$path], $path);
|
||||
}
|
||||
return $translations[$path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_language_switch_links_alter().
|
||||
*
|
||||
* Replaces links with pointers to translated versions of the content.
|
||||
*/
|
||||
function i18n_language_switch_links_alter(array &$links, $type, $path) {
|
||||
// For the front page we have nothing to add to Drupal core links.
|
||||
if ($path != '<front>' && ($translations = i18n_get_path_translations($path))) {
|
||||
foreach ($translations as $langcode => $translation) {
|
||||
if (isset($links[$langcode])) {
|
||||
$links[$langcode]['href'] = $translation['href'];
|
||||
if (!empty($translation['title'])) {
|
||||
$links[$langcode]['attributes']['title'] = $translation['title'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build translation link
|
||||
*/
|
||||
function i18n_translation_link($path, $langcode, $link = array()) {
|
||||
$language = i18n_language_object($langcode);
|
||||
$link += array(
|
||||
'href' => $path,
|
||||
'title' => $language->native,
|
||||
'language' => $language,
|
||||
'i18n_translation' => TRUE,
|
||||
);
|
||||
$link['attributes']['class'] = array('language-link');
|
||||
// @todo Fix languageicons weight, but until that
|
||||
if (function_exists('languageicons_link_add')) {
|
||||
languageicons_link_add($link);
|
||||
}
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_form_block_admin_display_form_alter(&$form, &$form_state) {
|
||||
$form['#submit'][] = 'i18n_form_block_admin_display_form_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a help message when enabling the language switcher block.
|
||||
*/
|
||||
function i18n_form_block_admin_display_form_submit($form, &$form_state) {
|
||||
foreach ($form_state['values']['blocks'] as $key => $block) {
|
||||
$previous = $form['blocks'][$key]['region']['#default_value'];
|
||||
if (empty($previous) && $block['region'] != -1 && $block['module'] == 'locale') {
|
||||
$message = t('The language switcher will appear only after configuring <a href="!url">language detection</a>. You need to enable at least one method that alters URLs like <em>URL</em> or <em>Session</em>.', array('!url' => url('admin/config/regional/language/configure')));
|
||||
drupal_set_message($message, 'warning', FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal path should be checked with menu item's language to avoid
|
||||
* troubles when a node and it's translation has the same url alias.
|
||||
*/
|
||||
function i18n_prepare_normal_path($link_path, $language) {
|
||||
$normal_path = drupal_get_normal_path($link_path, $language);
|
||||
if ($link_path != $normal_path) {
|
||||
drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $link_path, '%normal_path' => $normal_path)));
|
||||
}
|
||||
return $normal_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an entity translation is enabled for the given entity type.
|
||||
* @param $entity_type
|
||||
*/
|
||||
function i18n_entity_translation_enabled($entity_type) {
|
||||
$cache = &drupal_static(__FUNCTION__);
|
||||
if (!isset($cache[$entity_type])) {
|
||||
// Check if the entity_translation module exists and if so if the given
|
||||
// entity type is handled.
|
||||
$cache[$entity_type] = module_exists('entity_translation') && entity_translation_enabled($entity_type);
|
||||
}
|
||||
return $cache[$entity_type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_modules_enabled().
|
||||
*/
|
||||
function i18n_modules_enabled($modules) {
|
||||
drupal_static_reset('i18n_object_info');
|
||||
}
|
95
sites/all/modules/contrib/localisation/i18n/i18n.pages.inc
Normal file
95
sites/all/modules/contrib/localisation/i18n/i18n.pages.inc
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Generic translation pages
|
||||
*
|
||||
* It handles generic 'translation' tabs, redirecting to the right module depending on
|
||||
* object type and properties.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create menu items for translatable objecs
|
||||
*/
|
||||
function i18n_page_menu_items() {
|
||||
$items = array();
|
||||
// Menus are rebuilt right after other modules are disabled, if no reset we get tabs for disabled modules.
|
||||
drupal_static_reset('i18n_object_info');
|
||||
foreach (i18n_object_info() as $type => $info) {
|
||||
// These objects should have a 'translate tab' property
|
||||
if (!empty($info['translate tab'])) {
|
||||
$path = $info['translate tab'];
|
||||
$localize = module_exists('i18n_string') && !empty($info['string translation']);
|
||||
$translate = module_exists('i18n_translation') && i18n_translation_set_info($type);
|
||||
if ($translate && $localize) {
|
||||
$page_callback = 'i18n_page_translate_tab';
|
||||
}
|
||||
elseif ($translate) {
|
||||
$page_callback = 'i18n_page_translate_translation';
|
||||
}
|
||||
elseif ($localize) {
|
||||
$page_callback = 'i18n_page_translate_localize';
|
||||
}
|
||||
// Find the position for the object placeholder. We assume the first one.
|
||||
$placeholder = key($info['placeholders']);
|
||||
$parts = explode('/', $path);
|
||||
$position = array_search($placeholder, $parts);
|
||||
// Now create items with the right callbacks
|
||||
if ($translate || $localize) {
|
||||
$items[$path] = array(
|
||||
'title' => 'Translate',
|
||||
'page callback' => $page_callback,
|
||||
'page arguments' => array($type, $position),
|
||||
'access callback' => 'i18n_object_translate_access',
|
||||
'access arguments' => array($type, $position),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'file' => 'i18n.pages.inc',
|
||||
'weight' => 10,
|
||||
);
|
||||
}
|
||||
if ($localize) {
|
||||
$items[$path . '/%i18n_language'] = array(
|
||||
'title' => 'Translate',
|
||||
'page callback' => $page_callback,
|
||||
'page arguments' => array($type, $position, count($parts)),
|
||||
'access callback' => 'i18n_object_translate_access',
|
||||
'access arguments' => array($type, $position),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'i18n.pages.inc',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate or localize page for object
|
||||
*/
|
||||
function i18n_page_translate_tab($type, $object, $language = NULL) {
|
||||
// Check whether object should be part of a translation set
|
||||
switch (i18n_object($type, $object)->get_translate_mode()) {
|
||||
case I18N_MODE_TRANSLATE:
|
||||
return i18n_page_translate_translation($type, $object);
|
||||
case I18N_MODE_LOCALIZE:
|
||||
return i18n_page_translate_localize($type, $object, $language);
|
||||
default:
|
||||
drupal_access_denied();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate object, create translation set
|
||||
*/
|
||||
function i18n_page_translate_translation($type, $object) {
|
||||
module_load_include('pages.inc', 'i18n_translation');
|
||||
return i18n_translation_object_translate_page($type, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate object, string translation
|
||||
*/
|
||||
function i18n_page_translate_localize($type, $object, $language = NULL) {
|
||||
module_load_include('pages.inc', 'i18n_string');
|
||||
return i18n_string_object_translate_page($type, $object, $language);
|
||||
}
|
464
sites/all/modules/contrib/localisation/i18n/i18n.test
Normal file
464
sites/all/modules/contrib/localisation/i18n/i18n.test
Normal file
@@ -0,0 +1,464 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Base class for Internationalization tests
|
||||
*/
|
||||
class Drupali18nTestCase extends DrupalWebTestCase {
|
||||
protected $current_user;
|
||||
protected $default_language;
|
||||
protected $secondary_language;
|
||||
|
||||
function setUpLanguages($admin_permissions = array()) {
|
||||
// Setup admin user.
|
||||
$this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate interface'), $admin_permissions));
|
||||
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
// Add languages.
|
||||
$this->default_language = 'en';
|
||||
$this->secondary_language = 'es';
|
||||
$this->addLanguage($this->default_language);
|
||||
$this->addLanguage($this->secondary_language);
|
||||
|
||||
// Enable URL language detection and selection to make the language switcher
|
||||
// block appear.
|
||||
$edit = array('language[enabled][locale-url]' => TRUE);
|
||||
$this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
|
||||
$this->assertRaw(t('Language negotiation configuration saved.'), t('URL language detection enabled.'));
|
||||
$this->drupalGet('admin/config/regional/language/configure');
|
||||
$this->resetCaches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up content-type (with translation).
|
||||
*/
|
||||
function setUpContentType($settings = array()) {
|
||||
$settings += array(
|
||||
'type' => 'page',
|
||||
'mode' => TRANSLATION_ENABLED,
|
||||
'status' => 1,
|
||||
'promote' => 0,
|
||||
);
|
||||
|
||||
$type = node_type_get_type($settings['type']);
|
||||
// Create content editor with translation permissions.
|
||||
$this->content_editor = $this->drupalCreateUser(array(
|
||||
'create ' . $type->type . ' content',
|
||||
'edit own ' . $type->type . ' content',
|
||||
'translate content',
|
||||
'translate interface',
|
||||
));
|
||||
|
||||
$this->drupalLogin($this->admin_user);
|
||||
// Set content type to use multilingual support with translation.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $type->type);
|
||||
$edit = array();
|
||||
$edit['language_content_type'] = $settings['mode'];
|
||||
// Mark status and promoted
|
||||
$edit['node_options[status]'] = $settings['status'];
|
||||
$edit['node_options[promote]'] = $settings['promote'];
|
||||
$this->drupalPost('admin/structure/types/manage/' . $type->type, $edit, t('Save content type'));
|
||||
$this->assertRaw(t('The content type %type has been updated.', array('%type' => $type->name)), t('%type content type has been updated.', array('%type' => $type->name)));
|
||||
$this->drupalGet('admin/structure/types/manage/' . $type->type);
|
||||
$this->enableLanguageBlock();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the language switcher block.
|
||||
*/
|
||||
function enableLanguageBlock() {
|
||||
// Enable the language switcher block.
|
||||
$language_type = LANGUAGE_TYPE_INTERFACE;
|
||||
$edit = array("blocks[locale_$language_type][region]" => 'sidebar_first');
|
||||
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up translation for content type (default: page).
|
||||
*/
|
||||
function setUpContentTranslation($settings = array()) {
|
||||
$settings += array(
|
||||
'mode' => TRANSLATION_ENABLED,
|
||||
);
|
||||
$this->setUpContentType($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a the specified language if it has not been already. Otherwise make sure that
|
||||
* the language is enabled.
|
||||
*
|
||||
* @param $language_code
|
||||
* The language code the check.
|
||||
*/
|
||||
function addLanguage($language_code) {
|
||||
// Check to make sure that language has not already been installed.
|
||||
$this->drupalGet('admin/config/regional/language');
|
||||
|
||||
if (strpos($this->drupalGetContent(), 'enabled[' . $language_code . ']') === FALSE) {
|
||||
// Doesn't have language installed so add it.
|
||||
$edit = array();
|
||||
$edit['langcode'] = $language_code;
|
||||
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
|
||||
|
||||
// Make sure we are not using a stale list.
|
||||
drupal_static_reset('language_list');
|
||||
$languages = language_list('language');
|
||||
$this->assertTrue(array_key_exists($language_code, $languages), t('Language was installed successfully.'));
|
||||
|
||||
if (array_key_exists($language_code, $languages)) {
|
||||
$this->assertRaw(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => $languages[$language_code]->name, '@locale-help' => url('admin/help/locale'))), t('Language has been created.'));
|
||||
}
|
||||
}
|
||||
elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $language_code . ']'))) {
|
||||
// It's installed and enabled. No need to do anything.
|
||||
$this->assertTrue(true, 'Language [' . $language_code . '] already installed and enabled.');
|
||||
}
|
||||
else {
|
||||
// It's installed but not enabled. Enable it.
|
||||
$this->assertTrue(true, 'Language [' . $language_code . '] already installed.');
|
||||
$this->drupalPost(NULL, array('enabled[' . $language_code . ']' => TRUE), t('Save configuration'));
|
||||
$this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create translation set from a node
|
||||
*
|
||||
* @param $source
|
||||
* Source node
|
||||
* @param $languages
|
||||
* Optional list of language codes
|
||||
*/
|
||||
function createNodeTranslationSet(&$source, $languages = NULL) {
|
||||
if (empty($source->tnid)) {
|
||||
$source->tnid = $source->nid;
|
||||
}
|
||||
$translations[$source->language] = $source;
|
||||
foreach (language_list() as $language) {
|
||||
if ($language->language != $source->language) {
|
||||
$translations[$language->language] = $this->createNodeTranslation($source, $language);
|
||||
}
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a node of the specified type in the specified language.
|
||||
|
||||
* @param $type
|
||||
* The node type.
|
||||
* @param $title
|
||||
* Title of node in specified language.
|
||||
* @param $body
|
||||
* Body of node in specified language.
|
||||
* @param $langcode
|
||||
* Language code.
|
||||
*/
|
||||
function createNode($type, $title, $body, $langcode, $edit = array()) {
|
||||
$lang = LANGUAGE_NONE;
|
||||
$edit["title"] = $title;
|
||||
$edit["body[$lang][0][value]"] = $body;
|
||||
$edit['language'] = $langcode;
|
||||
$this->drupalPost('node/add/' . $type, $edit, t('Save'));
|
||||
$info = node_type_load($type);
|
||||
$message = t('@name %title has been created.', array('@name' => $info->name, '%title' => $title));
|
||||
$this->assertRaw($message);
|
||||
|
||||
// Check to make sure the node was created.
|
||||
$node = $this->drupalGetNodeByTitle($title);
|
||||
$this->assertTrue($node, t('Node found in database.'));
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a translation for the specified node in the specified language.
|
||||
*
|
||||
* @param $node
|
||||
* The basic page to create translation for.
|
||||
* @param $title
|
||||
* Title of node in specified language.
|
||||
* @param $body
|
||||
* Body of node in specified language.
|
||||
* @param $language
|
||||
* Language code.
|
||||
*/
|
||||
function createNodeTranslation($node, $language, $title = NULL, $body = NULL) {
|
||||
$body = $body ? $body : $this->randomName();
|
||||
$title = $title ? $title : $this->randomName();
|
||||
$this->drupalGet('node/add/' . $node->type, array('query' => array('translation' => $node->nid, 'target' => $language->language)));
|
||||
|
||||
|
||||
$this->assertFieldByXPath('//input[@id="edit-title"]', $node->title, "Original title value correctly populated.");
|
||||
$field_lang = field_language('node', $node, 'body');
|
||||
$body_key = "body[und][0][value]";
|
||||
$this->assertFieldByXPath("//textarea[@name='$body_key']", $node->body[$field_lang][0]['value'], "Original body value correctly populated.");
|
||||
|
||||
$edit = array();
|
||||
$edit["title"] = $title;
|
||||
$edit[$body_key] = $body;
|
||||
$this->drupalPost(NULL, $edit, t('Save'));
|
||||
$info = node_type_load($node->type);
|
||||
$message = t('@name %title has been created.', array('@name' => $info->name, '%title' => $title));
|
||||
$this->assertRaw($message);
|
||||
// Check to make sure that translation was successful.
|
||||
$translation = $this->drupalGetNodeByTitle($title);
|
||||
$this->assertTrue($translation, t('Node found in database.'));
|
||||
$this->assertTrue($translation->tnid == $node->nid, t('Translation set id correctly stored.'));
|
||||
|
||||
return $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a Drupal path or an absolute path with language
|
||||
*
|
||||
* @param $language
|
||||
* Language code or language object
|
||||
*/
|
||||
protected function i18nGet($language, $path = '', array $options = array(), array $headers = array()) {
|
||||
$options['language'] = $this->getLanguage($language);
|
||||
return $this->drupalGet($path, $options, $headers);
|
||||
}
|
||||
/**
|
||||
* Check strings for different languages
|
||||
*/
|
||||
function i18nAssertTranslations($translations, $path = '', $message = 'Translation found for language.') {
|
||||
foreach ($translations as $langcode => $text) {
|
||||
$language = $this->getLanguage($langcode);
|
||||
if ($language->enabled) {
|
||||
$this->i18nGet($language, $path);
|
||||
$this->assertRaw($text, $message . ' ' . $language->name . ': ' . check_plain($text));
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create node with language
|
||||
*/
|
||||
protected function i18nCreateNode($language, $settings = array()) {
|
||||
$language = $this->getLanguage($language);
|
||||
$settings += array('language' => $language->language, 'body' => array());
|
||||
$settings['body'] += array($language->language => array(array()));
|
||||
return $this->drupalCreateNode($settings);
|
||||
}
|
||||
/**
|
||||
* Move block to region, from block.test
|
||||
*/
|
||||
function moveBlockToRegion($block, $region = 'sidebar_first') {
|
||||
$this->drupalLogin($this->admin_user);
|
||||
// Set the created block to a specific region.
|
||||
$edit = array();
|
||||
$edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region;
|
||||
$this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
|
||||
|
||||
// Confirm that the block was moved to the proper region.
|
||||
$this->assertText(t('The block settings have been updated.'), t('Block successfully moved to %region_name region.', array( '%region_name' => $region)));
|
||||
|
||||
// Confirm that the block is being displayed.
|
||||
$this->drupalGet('node');
|
||||
$this->assertText(check_plain($block['title']), t('Block successfully being displayed on the page.'));
|
||||
|
||||
// Confirm that the custom block was found at the proper region.
|
||||
$xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array(
|
||||
':region-class' => 'region region-' . str_replace('_', '-', $region),
|
||||
':block-id' => 'block-' . $block['module'] . '-' . $block['delta'],
|
||||
));
|
||||
$this->assertFieldByXPath($xpath, NULL, t('Custom block found in %region_name region.', array('%region_name' => $region)));
|
||||
}
|
||||
/**
|
||||
* Get language object for langcode
|
||||
*/
|
||||
public function getLanguage($langcode) {
|
||||
if (is_object($langcode)) {
|
||||
return $langcode;
|
||||
}
|
||||
else {
|
||||
$language_list = language_list();
|
||||
return $language_list[$langcode];
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Switch global language
|
||||
*/
|
||||
public function switchLanguage($newlang = NULL) {
|
||||
$newlang = $newlang ? $newlang : $this->install_locale;
|
||||
$GLOBALS[LANGUAGE_TYPE_INTERFACE] = $this->getLanguage($newlang);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all languages that are not default
|
||||
*/
|
||||
public function getOtherLanguages() {
|
||||
$languages = language_list();
|
||||
unset($languages[language_default('language')]);
|
||||
return $languages;
|
||||
}
|
||||
/**
|
||||
* Get enabled languages
|
||||
*/
|
||||
public function getEnabledLanguages() {
|
||||
$list = array();
|
||||
foreach (language_list() as $langcode => $language) {
|
||||
if (!empty($language->enabled)) {
|
||||
$list[$langcode] = $language;
|
||||
}
|
||||
}
|
||||
return $list;
|
||||
}
|
||||
/**
|
||||
* Create translation for string in textgroup
|
||||
*
|
||||
* @param $translations
|
||||
* Optional array of langcode => translation. If not present, it will be generated.
|
||||
*/
|
||||
function createStringTranslation($textgroup, $name, $translations = NULL) {
|
||||
// Generate translations if not found, they will be the same length as source string
|
||||
if (!$translations) {
|
||||
$length = strlen($name);
|
||||
foreach ($this->getOtherLanguages() as $language) {
|
||||
$translations[$language->language] = $this->randomName($length);
|
||||
}
|
||||
}
|
||||
$this->drupalLogin($this->translator);
|
||||
// This is the language indicator on the translation search screen for
|
||||
// untranslated strings. Copied straight from locale.inc.
|
||||
$language_indicator = "<em class=\"locale-untranslated\">";
|
||||
// Search for the name and translate it.
|
||||
$search = array(
|
||||
'string' => $name,
|
||||
'language' => 'all',
|
||||
'translation' => 'all',
|
||||
'group' => $textgroup,
|
||||
);
|
||||
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
|
||||
// assertText() seems to remove the input field where $name always could be
|
||||
// found, so this is not a false assert. See how assertNoText succeeds
|
||||
// later.
|
||||
$this->assertText(check_plain($name), t('Search found the name.'));
|
||||
$this->assertRaw($language_indicator, t('Name is untranslated.'));
|
||||
// Assume this is the only result, given the random name.
|
||||
$this->clickLink(t('edit'));
|
||||
// We save the lid from the path.
|
||||
$matches = array();
|
||||
preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches);
|
||||
$lid = $matches[1];
|
||||
// No t() here, it's surely not translated yet.
|
||||
$this->assertText(check_plain($name), t('name found on edit screen.'));
|
||||
foreach ($translations as $langcode => $translation) {
|
||||
$edit["translations[$langcode]"] = $translation;
|
||||
}
|
||||
$this->drupalPost(NULL, $edit, t('Save translations'));
|
||||
$this->assertText(t('The string has been saved.'), t('The string has been saved.'));
|
||||
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
|
||||
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
|
||||
// The indicator should not be here.
|
||||
$this->assertNoRaw($language_indicator, t('String is translated.'));
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset static caches to make the test code match the client site behavior.
|
||||
*/
|
||||
function resetCaches() {
|
||||
drupal_static_reset('locale_url_outbound_alter');
|
||||
drupal_static_reset('language_list');
|
||||
}
|
||||
|
||||
/**
|
||||
* Print out a variable for debugging
|
||||
*/
|
||||
function printDebug($data, $title = 'Debug') {
|
||||
$output = '<h2>' . $title . '<h2 />';
|
||||
$output .= '<pre>';
|
||||
$output .= is_array($data) || is_object($data) ? print_r($data, TRUE) : $data;
|
||||
$output .= '<pre>';
|
||||
//$this->assertTrue(TRUE, $output);
|
||||
$this->verbose($output);
|
||||
}
|
||||
/**
|
||||
* Debug dump object with some formatting
|
||||
*/
|
||||
function printObject($object, $title = 'Object') {
|
||||
$output = $this->formatTable($object);
|
||||
$this->printDebug($output, $title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Print out current HTML page
|
||||
*/
|
||||
function printPage() {
|
||||
$this->printDebug($this->drupalGetContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump table contents
|
||||
*
|
||||
* @params $table1, $table2..
|
||||
* One or more table names
|
||||
*/
|
||||
function dumpTable() {
|
||||
$output = '';
|
||||
foreach (func_get_args() as $table) {
|
||||
$header = $rows = array();
|
||||
$result = db_query('SELECT * FROM {' . $table . '}');
|
||||
$output .= '<h2>Table dump <i>' . $table . '</i>:</h2>';
|
||||
while ($row = $result->fetchAssoc()) {
|
||||
$rows[] = $row;
|
||||
if (empty($header)) {
|
||||
$header = array_keys($row);
|
||||
}
|
||||
}
|
||||
if (!empty($rows)) {
|
||||
$output .= theme('table', array('header' => $header, 'rows' => $rows));
|
||||
}
|
||||
else {
|
||||
$output .= ' No rows';
|
||||
}
|
||||
$output .= '<br />';
|
||||
}
|
||||
$this->verbose($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format object as table, recursive
|
||||
*/
|
||||
function formatTable($object) {
|
||||
foreach ($object as $key => $value) {
|
||||
$rows[] = array(
|
||||
$key,
|
||||
is_array($value) || is_object($value) ? $this->formatTable($value) : $value,
|
||||
);
|
||||
}
|
||||
if (!empty($rows)) {
|
||||
return theme('table', array('rows' => $rows));
|
||||
}
|
||||
else {
|
||||
return 'No properties';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Drupali18nConfigTestCase extends Drupali18nTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Multilingual configuration',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Basic configuration for the i18n module',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('translation', 'i18n_node');
|
||||
parent::setUpLanguages();
|
||||
}
|
||||
|
||||
function testEnableLanguages() {
|
||||
// A language with two letter code may help too
|
||||
$this->addLanguage('pt-br');
|
||||
|
||||
// Disable Italian to test the translation behavior with disabled languages.
|
||||
$this->addLanguage('it');
|
||||
$edit = array('enabled[it]' => FALSE);
|
||||
$this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
|
||||
}
|
||||
}
|
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Variable information
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_variable_group_info().
|
||||
*/
|
||||
function i18n_variable_group_info() {
|
||||
$groups['i18n'] = array(
|
||||
'title' => t('Multilingual settings'),
|
||||
'description' => t('Mixed options for multilingual sites.'),
|
||||
'access' => 'administer site configuration',
|
||||
'path' => 'admin/config/regional/i18n',
|
||||
);
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_variable_info().
|
||||
*/
|
||||
function i18n_variable_info($options = array()) {
|
||||
$variables['i18n_language_list'] = array(
|
||||
'title' => t('Languages for content', array(), $options),
|
||||
'description' => t('Determines which languages will be allowed for content creation.', array(), $options),
|
||||
'type' => 'select',
|
||||
'options callback' => 'i18n_variable_option_list',
|
||||
'default' => I18N_LANGUAGE_ENABLED,
|
||||
'group' => 'i18n',
|
||||
);
|
||||
return $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_variable_type_info().
|
||||
*/
|
||||
function i18n_variable_type_info() {
|
||||
// Multiple variable per language options
|
||||
$types['multiple_language'] = array(
|
||||
'title' => t('Multiple language'),
|
||||
'element' => array('#type' => 'fieldset'),
|
||||
'build callback' => 'variable_build_multiple',
|
||||
'format callback' => 'variable_format_multiple',
|
||||
'element callback' => 'variable_form_element_multiple',
|
||||
'value callback' => 'variable_multiple_get_value',
|
||||
'default callback' => 'variable_multiple_get_default',
|
||||
'multiple callback' => 'i18n_variable_multiple_language_options',
|
||||
'language list' => I18N_LANGUAGE_EXTENDED,
|
||||
);
|
||||
return $types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for content languages
|
||||
*/
|
||||
function i18n_variable_option_list($variable, $options = array()) {
|
||||
return array(
|
||||
I18N_LANGUAGE_ENABLED => t('Enabled languages only.', array(), $options),
|
||||
I18N_LANGUAGE_EXTENDED => t('All defined languages will be allowed.', array(), $options),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for multiple per-language variables
|
||||
*/
|
||||
function i18n_variable_multiple_language_options($variable, $options = array()) {
|
||||
return i18n_language_list('name', $variable['language list']);
|
||||
}
|
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_object_info().
|
||||
*/
|
||||
function i18n_block_i18n_object_info() {
|
||||
$info['block'] = array(
|
||||
'title' => t('Block'),
|
||||
'class' => 'i18n_block_object',
|
||||
'load callback' => 'block_load',
|
||||
'key' => array('module', 'delta'),
|
||||
'placeholders' => array(
|
||||
'%module' => 'module',
|
||||
'%delta' => 'delta',
|
||||
),
|
||||
'edit path' => 'admin/structure/block/manage/%module/%delta/configure',
|
||||
'string translation' => array(
|
||||
'textgroup' => 'blocks',
|
||||
'properties' => array(
|
||||
'title' => array(
|
||||
'title' => t('Title'),
|
||||
'empty' => '<none>',
|
||||
),
|
||||
'body' => array(
|
||||
'title' => t('Body'),
|
||||
'format' => 'format',
|
||||
),
|
||||
),
|
||||
'translate path' => 'admin/structure/block/manage/%module/%delta/translate/%i18n_language',
|
||||
)
|
||||
);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_string_info().
|
||||
*/
|
||||
function i18n_block_i18n_string_info() {
|
||||
$groups['blocks'] = array(
|
||||
'title' => t('Blocks'),
|
||||
'description' => t('Configurable blocks titles and content.'),
|
||||
'format' => TRUE, // This group has strings with format (block body)
|
||||
'list' => TRUE, // This group can list all strings
|
||||
);
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_string_objects().
|
||||
*/
|
||||
function i18n_block_i18n_string_objects($type) {
|
||||
if ($type == 'block') {
|
||||
$query = db_select('block', 'b')
|
||||
->distinct()
|
||||
->fields('b', array('module', 'delta', 'title', 'i18n_mode'))
|
||||
->fields('bc', array('body', 'format'))
|
||||
->condition('i18n_mode', I18N_MODE_LOCALIZE);
|
||||
$query->leftJoin('block_custom', 'bc', 'b.bid = bc.bid');
|
||||
return $query->execute()->fetchAll(PDO::FETCH_OBJ);
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Blocks textgroup handler
|
||||
*/
|
||||
|
||||
/**
|
||||
* Block object
|
||||
*/
|
||||
class i18n_block_object extends i18n_string_object_wrapper {
|
||||
|
||||
/**
|
||||
* Load a block object.
|
||||
*
|
||||
* @param $object
|
||||
* An array with module and delta.
|
||||
*/
|
||||
function load_object($object) {
|
||||
$this->object = call_user_func_array($this->get_info('load callback'), $object);
|
||||
return $this->get_object();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base keys for translating this object
|
||||
*/
|
||||
public function get_string_context() {
|
||||
return array($this->object->module, $this->object->delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object strings for translation
|
||||
*/
|
||||
protected function build_properties() {
|
||||
if ($this->object->module == 'block' && !isset($this->object->body)) {
|
||||
$block = (object) block_custom_block_get($this->object->delta);
|
||||
$this->object->body = $block->body;
|
||||
$this->object->format = $block->format;
|
||||
}
|
||||
$properties = parent::build_properties();
|
||||
// Body is available only for custom blocks.
|
||||
if ($this->object->module != 'block') {
|
||||
unset($properties[$this->get_textgroup()][$this->object->module][$this->object->delta]['body']);
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation mode for object
|
||||
*/
|
||||
public function get_translate_mode() {
|
||||
return !empty($this->object->i18n_mode) ? I18N_MODE_LOCALIZE : I18N_MODE_NONE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
name = Block languages
|
||||
description = Enables language selector for blocks and optional block translation.
|
||||
dependencies[] = block
|
||||
dependencies[] = i18n_string
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
files[] = i18n_block.inc
|
||||
files[] = i18n_block.test
|
||||
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation file for i18nblocks module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function i18n_block_install() {
|
||||
module_load_install('i18n');
|
||||
i18n_install_create_fields('block', array('i18n_mode'));
|
||||
// Set module weight for it to run after all block visibility modules have run
|
||||
db_query("UPDATE {system} SET weight = 100 WHERE name = 'i18n_block' AND type = 'module'");
|
||||
// If updating from D6, module changed name
|
||||
if (variable_get('i18n_drupal6_update')) {
|
||||
i18n_block_update_7000();
|
||||
i18n_block_update_7001();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function i18n_block_uninstall() {
|
||||
db_drop_field('block', 'i18n_mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function i18n_block_schema() {
|
||||
$schema['i18n_block_language'] = array(
|
||||
'description' => 'Sets block visibility based on language',
|
||||
'fields' => array(
|
||||
'module' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 64,
|
||||
'not null' => TRUE,
|
||||
'description' => "The block's origin module, from {block}.module.",
|
||||
),
|
||||
'delta' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 32,
|
||||
'not null' => TRUE,
|
||||
'description' => "The block's unique delta within module, from {block}.delta.",
|
||||
),
|
||||
'language' => array(
|
||||
'type' => 'varchar',
|
||||
'length' => 12,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
'description' => "Language code, e.g. 'de' or 'en-US'.",
|
||||
),
|
||||
),
|
||||
'primary key' => array('module', 'delta', 'language'),
|
||||
'indexes' => array(
|
||||
'language' => array('language'),
|
||||
),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema_alter().
|
||||
*
|
||||
* Add block table i18n_mode field
|
||||
*/
|
||||
function i18n_block_schema_alter(&$schema) {
|
||||
$schema['block']['fields']['i18n_mode'] = array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'description' => 'Block multilingual mode.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Drupal 6 update from old i18nblocks module.
|
||||
*/
|
||||
function i18n_block_update_7000() {
|
||||
// D6-D7 updates, to be written
|
||||
// move block language from i18n_blocks into i18n_block_language
|
||||
// Move block type from i18n_blocks into block table (i18n_mode)
|
||||
if (db_table_exists('i18n_blocks')) {
|
||||
foreach (db_query("SELECT * FROM {i18n_blocks}")->fetchAll() as $block) {
|
||||
if ($block->language) {
|
||||
// Set language for block
|
||||
db_merge('i18n_block_language')
|
||||
->key(array('module' => $block->module, 'delta' => $block->delta))
|
||||
->fields(array('language' => $block->language))
|
||||
->execute();
|
||||
}
|
||||
else {
|
||||
// Mark block as translatable
|
||||
db_update('block')
|
||||
->fields(array('i18n_mode' => 1))
|
||||
->condition('module', $block->module)
|
||||
->condition('delta', $block->delta)
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop Drupal 6 {i18n_blocks} table after migration.
|
||||
*/
|
||||
function i18n_block_update_7001() {
|
||||
if (db_table_exists('i18n_blocks')) {
|
||||
db_drop_table('i18n_blocks');
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Provide the summary information for the block settings vertical tab.
|
||||
*/
|
||||
Drupal.behaviors.i18nSettingsSummary = {
|
||||
attach: function (context) {
|
||||
|
||||
$('fieldset#edit-languages', context).drupalSetSummary(function (context) {
|
||||
var summary = '';
|
||||
if ($('.form-item-i18n-mode input[type=checkbox]:checked', context).val()) {
|
||||
summary += Drupal.t('Translatable');
|
||||
}
|
||||
else {
|
||||
summary += Drupal.t('Not translatable');
|
||||
}
|
||||
summary += ', ';
|
||||
if ($('.form-item-languages input[type=checkbox]:checked', context).val()) {
|
||||
summary += Drupal.t('Restricted to certain languages');
|
||||
}
|
||||
else {
|
||||
summary += Drupal.t('Not restricted');
|
||||
}
|
||||
return summary;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) submodule: Multilingual meta-blocks
|
||||
*
|
||||
* @author Jose A. Reyero, 2005
|
||||
*
|
||||
* @ TODO Add strings on block update.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*
|
||||
* Add translate tab to blocks.
|
||||
*/
|
||||
function i18n_block_menu() {
|
||||
$items['admin/structure/block/manage/%/%/translate'] = array(
|
||||
'title' => 'Translate',
|
||||
'access callback' => 'i18n_block_translate_tab_access',
|
||||
'access arguments' => array(4, 5),
|
||||
'page callback' => 'i18n_block_translate_tab_page',
|
||||
'page arguments' => array(4, 5),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
|
||||
'weight' => 10,
|
||||
);
|
||||
$items['admin/structure/block/manage/%/%/translate/%i18n_language'] = array(
|
||||
'title' => 'Translate',
|
||||
'access callback' => 'i18n_block_translate_tab_access',
|
||||
'access arguments' => array(4, 5),
|
||||
'page callback' => 'i18n_block_translate_tab_page',
|
||||
'page arguments' => array(4, 5, 7),
|
||||
'type' => MENU_CALLBACK,
|
||||
'weight' => 10,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement hook_menu_alter().
|
||||
*
|
||||
* Reorganize block tabs so that they make sense.
|
||||
*/
|
||||
function i18n_block_menu_alter(&$items) {
|
||||
// Give the configure tab a short name and make it display.
|
||||
$items['admin/structure/block/manage/%/%/configure']['weight'] = -100;
|
||||
$items['admin/structure/block/manage/%/%/configure']['title'] = 'Configure';
|
||||
$items['admin/structure/block/manage/%/%/configure']['context'] = MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE;
|
||||
// Hide the delete tab. Not sure why this was even set a local task then
|
||||
// set to not show in any context...
|
||||
$items['admin/structure/block/manage/%/%/delete']['type'] = MENU_CALLBACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu access callback function.
|
||||
*
|
||||
* Only let blocks translated which are configured to be translatable.
|
||||
*/
|
||||
function i18n_block_translate_tab_access($module, $delta) {
|
||||
$block = block_load($module, $delta);
|
||||
return user_access('translate interface') && isset($block) && ($block->i18n_mode == I18N_MODE_LOCALIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a translation page for the given block.
|
||||
*/
|
||||
function i18n_block_translate_tab_page($module, $delta, $language = NULL) {
|
||||
$block = block_load($module, $delta);
|
||||
return i18n_string_object_translate_page('block', $block, $language);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_list_alter().
|
||||
*
|
||||
* Translate localizable blocks.
|
||||
*
|
||||
* To be run after all block visibility modules have run, just translate the blocks to be displayed
|
||||
*/
|
||||
function i18n_block_block_list_alter(&$blocks) {
|
||||
global $theme_key, $language;
|
||||
|
||||
// Build an array of node types for each block.
|
||||
$block_languages = array();
|
||||
$result = db_query('SELECT module, delta, language FROM {i18n_block_language}');
|
||||
foreach ($result as $record) {
|
||||
$block_languages[$record->module][$record->delta][$record->language] = TRUE;
|
||||
}
|
||||
|
||||
foreach ($blocks as $key => $block) {
|
||||
if (!isset($block->theme) || !isset($block->status) || $block->theme != $theme_key || $block->status != 1) {
|
||||
// This block was added by a contrib module, leave it in the list.
|
||||
continue;
|
||||
}
|
||||
if (isset($block_languages[$block->module][$block->delta]) && !isset($block_languages[$block->module][$block->delta][$language->language])) {
|
||||
// Block not visible for this language
|
||||
unset($blocks[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_view().
|
||||
*/
|
||||
function i18n_block_block_view_alter(&$data, $block) {
|
||||
if (!empty($block->i18n_mode)) {
|
||||
if (!empty($block->title) && $block->title != '<none>') {
|
||||
// Unfiltered, as $block->subject will be created later from the title.
|
||||
$data['title'] = i18n_string(array('blocks', $block->module, $block->delta, 'title'), $block->title, array('sanitize' => FALSE));
|
||||
}
|
||||
if ($block->module == 'block' && isset($data['content'])) {
|
||||
$data['content'] = i18n_string(array('blocks', $block->module, $block->delta, 'body'), $data['content']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_context_block_info_alter().
|
||||
*/
|
||||
function i18n_block_context_block_info_alter(&$block_info) {
|
||||
$theme_key = variable_get('theme_default', 'garland');
|
||||
$result = db_select('block')
|
||||
->fields('block', array('module', 'delta', 'i18n_mode'))
|
||||
->condition('theme', $theme_key)
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
if (isset($block_info["{$row->module}-{$row->delta}"])) {
|
||||
$block_info["{$row->module}-{$row->delta}"]->i18n_mode = $row->i18n_mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function i18n_block_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#i18n_block':
|
||||
$output = '<p>' . t('This module provides support for multilingual blocks.') . '</p>';
|
||||
$output .= '<p>' . t('You can set up a language for a block or define it as translatable:') . '</p>';
|
||||
$output .= '<ul>';
|
||||
$output .= '<li>' . t('Blocks with a language will be displayed only in pages with that language.') . '</li>';
|
||||
$output .= '<li>' . t('Translatable blocks can be translated using the localization interface.') . '</li>';
|
||||
$output .= '</ul>';
|
||||
$output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</p>';
|
||||
return $output;
|
||||
case 'admin/config/regional/i18n':
|
||||
$output = '<p>'. t('To set up multilingual options for blocks go to the <a href="@configure_blocks">Blocks administration page</a>.', array('@configure_blocks' => url('admin/structure/block'))) .'</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove strings for deleted custom blocks.
|
||||
*/
|
||||
function i18n_block_block_delete_submit(&$form, $form_state) {
|
||||
$delta = $form_state['values']['delta'];
|
||||
// Delete stored strings for the title and content fields.
|
||||
i18n_string_remove("blocks:block:$delta:title");
|
||||
i18n_string_remove("blocks:block:$delta:body");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements block hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Remove block title for multilingual blocks.
|
||||
*/
|
||||
function i18n_block_form_block_add_block_form_alter(&$form, &$form_state, $form_id) {
|
||||
//i18n_block_alter_forms($form, $form_state, $form_id);
|
||||
i18n_block_form_block_admin_configure_alter($form, $form_state, $form_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements block hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Remove block title for multilingual blocks.
|
||||
*/
|
||||
function i18n_block_form_block_admin_configure_alter(&$form, &$form_state, $form_id) {
|
||||
$form['i18n_block']['languages'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Languages'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#group' => 'visibility',
|
||||
'#weight' => 5,
|
||||
'#attached' => array(
|
||||
'js' => array(drupal_get_path('module', 'i18n_block') . '/i18n_block.js'),
|
||||
),
|
||||
);
|
||||
|
||||
// Add translatable option, just title for module blocks, title and content
|
||||
// for custom blocks.
|
||||
$description = '';
|
||||
$module = $form['module']['#value'];
|
||||
$delta = $form['delta']['#value'];
|
||||
|
||||
// User created menus are exposed by the menu module, others by system.module.
|
||||
if ($module == 'menu' || ($module == 'system' && !in_array($delta, array('mail', 'help', 'powered-by')))) {
|
||||
$description = t('To translate the block content itself, <a href="@menu_translate_url">translate the menu</a> that is being shown.', array('@menu_translate_url' => url('admin/structure/menu/manage/' . $form['delta']['#value'])));
|
||||
}
|
||||
elseif ($module == 'views' && module_exists('i18nviews')) {
|
||||
$name = substr($delta, 0, strpos($delta, '-'));
|
||||
$description = t('To translate the block content itself, <a href="@views_translate_url">translate the view</a> that is being shown.', array('@views_translate_url' => url('admin/structure/views/view/' . $name . '/translate')));
|
||||
}
|
||||
elseif ($module != 'block') {
|
||||
$description = t('This block has generated content, only the title can be translated here.');
|
||||
}
|
||||
|
||||
$block = block_load($form['module']['#value'], $form['delta']['#value']);
|
||||
$form['i18n_block']['languages']['i18n_mode'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Make this block translatable'),
|
||||
'#default_value' => isset($block->i18n_mode) ? $block->i18n_mode : I18N_MODE_NONE,
|
||||
'#description' => $description,
|
||||
);
|
||||
|
||||
// Add option to select which language pages to show on.
|
||||
$default_options = db_query("SELECT language FROM {i18n_block_language} WHERE module = :module AND delta = :delta", array(
|
||||
':module' => $form['module']['#value'],
|
||||
':delta' => $form['delta']['#value'],
|
||||
))->fetchCol();
|
||||
$form['i18n_block']['languages']['languages'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#title' => t('Show this block for these languages'),
|
||||
'#default_value' => $default_options,
|
||||
'#options' => i18n_language_list(),
|
||||
'#description' => t('If no language is selected, block will show regardless of language.'),
|
||||
);
|
||||
if (user_access('translate interface')) {
|
||||
$form['actions']['translate'] = array(
|
||||
'#type' => 'submit',
|
||||
'#name' => 'save_translate',
|
||||
'#value' => t('Save and translate'),
|
||||
'#states' => array(
|
||||
'visible' => array(
|
||||
// The value must be a string so that the javascript comparison works.
|
||||
":input[name=i18n_mode]" => array('checked' => TRUE),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
$form['#submit'][] = 'i18n_block_form_block_admin_configure_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submit handler for block configuration form.
|
||||
*
|
||||
* @see i18n_block_form_block_admin_configure_alter()
|
||||
*/
|
||||
function i18n_block_form_block_admin_configure_submit(&$form, &$form_state) {
|
||||
$module = $form_state['values']['module'];
|
||||
$delta = $form_state['values']['delta'];
|
||||
|
||||
// Update block languages
|
||||
db_delete('i18n_block_language')
|
||||
->condition('module', $module)
|
||||
->condition('delta', $delta)
|
||||
->execute();
|
||||
$query = db_insert('i18n_block_language')->fields(array('language', 'module', 'delta'));
|
||||
foreach (array_filter($form_state['values']['languages']) as $language) {
|
||||
$query->values(array(
|
||||
'language' => $language,
|
||||
'module' => $module,
|
||||
'delta' => $delta,
|
||||
));
|
||||
}
|
||||
$query->execute();
|
||||
// Update block translation options and strings
|
||||
if (isset($form_state['values']['i18n_mode'])) {
|
||||
db_update('block')
|
||||
->fields(array('i18n_mode' => $form_state['values']['i18n_mode']))
|
||||
->condition('module', $module)
|
||||
->condition('delta', $delta)
|
||||
->execute();
|
||||
i18n_block_update_strings($form_state['values'], $form_state['values']['i18n_mode']);
|
||||
|
||||
// If the save and translate button was clicked, redirect to the translate
|
||||
// tab instead of the block overview.
|
||||
if ($form_state['triggering_element']['#name'] == 'save_translate') {
|
||||
$form_state['redirect'] = 'admin/structure/block/manage/' . $module . '/' . $delta . '/translate';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update block strings
|
||||
*/
|
||||
function i18n_block_update_strings($block, $i18n_mode = TRUE) {
|
||||
$title = $i18n_mode && $block['title'] !== '<none>' ? $block['title'] : '';
|
||||
i18n_string_update(array('blocks', $block['module'], $block['delta'], 'title'), $title);
|
||||
if (isset($block['body'])) {
|
||||
if ($i18n_mode) {
|
||||
i18n_string_update(array('blocks', $block['module'], $block['delta'], 'body'), $block['body']['value'], array('format' => $block['body']['format']));
|
||||
}
|
||||
else {
|
||||
i18n_string_remove(array('blocks', $block['module'], $block['delta'], 'body'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORMID_alter().
|
||||
*
|
||||
* Adds node specific submit handler to delete custom block form.
|
||||
*
|
||||
* @see block_custom_block_delete()
|
||||
*/
|
||||
function i18n_block_form_block_custom_block_delete_alter(&$form, &$form_state) {
|
||||
$form['#submit'][] = 'i18n_block_form_block_custom_block_delete_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submit handler for custom block delete form.
|
||||
*
|
||||
* @see node_form_block_custom_block_delete_alter()
|
||||
*/
|
||||
function i18n_block_form_block_custom_block_delete_submit($form, &$form_state) {
|
||||
db_delete('i18n_block_language')
|
||||
->condition('module', 'block')
|
||||
->condition('delta', $form_state['values']['bid'])
|
||||
->execute();
|
||||
// Remove related strings
|
||||
module_invoke('i18n_strings', 'remove',
|
||||
array('blocks', 'block', $form_state['values']['bid']),
|
||||
array('title', 'body')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate block.
|
||||
*
|
||||
* @param $block
|
||||
* Core block object
|
||||
*/
|
||||
function i18n_block_translate_block($block) {
|
||||
if (!empty($block->content) && $localizable) {
|
||||
$block->content = i18n_string_text("blocks:$block->module:$block->delta:body", $block->content);
|
||||
}
|
||||
// If it has a custom title, localize it
|
||||
if (!empty($block->title) && $block->title != '<none>') {
|
||||
// Check plain here to allow module generated titles to keep any markup.
|
||||
$block->subject = i18n_string_plain("blocks:$block->module:$block->delta:title", $block->subject);
|
||||
}
|
||||
return $block;
|
||||
}
|
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Test case for multilingual blocks
|
||||
*/
|
||||
|
||||
class i18nBlocksTestCase extends Drupali18nTestCase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Block translation',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Block translation functions'
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('i18n_block');
|
||||
parent::setUpLanguages();
|
||||
$this->translator = $this->drupalCreateUser(array('translate interface', 'translate user-defined strings'));
|
||||
|
||||
$format = filter_default_format();
|
||||
variable_set('i18n_string_allowed_formats', array($format => $format));
|
||||
$this->drupalLogin($this->admin_user);
|
||||
}
|
||||
|
||||
function testBlockTranslation() {
|
||||
|
||||
$block_translater = $this->drupalCreateUser(array('administer blocks', 'translate interface', 'translate user-defined strings'));
|
||||
|
||||
// Display Language switcher block
|
||||
$switcher = array('module' => 'locale', 'delta' => 'language', 'title' => t('Languages'));
|
||||
$this->moveBlockToRegion($switcher);
|
||||
// Add a custom title to language switcher block and check it displays translated
|
||||
$title = $this->randomName(10);
|
||||
$this->updateBlock($switcher, array('title' => $title, 'i18n_mode' => I18N_MODE_LOCALIZE));
|
||||
$this->assertText($title, "The new custom title is displayed on the home page.");
|
||||
$translations = $this->createStringTranslation('blocks', $title);
|
||||
$this->i18nAssertTranslations($translations);
|
||||
|
||||
// Create a translatable block and test block visibility per language.
|
||||
$block = $this->i18nCreateBlock();
|
||||
// Now set a language for the block and confirm it shows just for that one (without translation)
|
||||
$languages = $this->getEnabledLanguages();
|
||||
$setlanguage = array_shift($languages);
|
||||
$otherlanguage = array_shift($languages);
|
||||
$this->setBlockLanguages($block, array($setlanguage->language));
|
||||
// Show in block's language but not translated
|
||||
$this->i18nGet($setlanguage);
|
||||
$this->assertText($block['title']);
|
||||
// Do not show in the other language
|
||||
$this->i18nGet($otherlanguage);
|
||||
$this->assertNoText($block['title']);
|
||||
|
||||
// Create a new block, translate it and check the right translations are displayed for title and body
|
||||
$box2 = $this->i18nCreateBlock();
|
||||
// Create translations for title and body, source strings should be already there
|
||||
$translations = $this->i18nTranslateBlock($box2);
|
||||
$this->i18nAssertTranslations($translations['title'], '', 'Custom block title translation displayed.');
|
||||
$this->i18nAssertTranslations($translations['body'], '', 'Custom block body translation displayed.');
|
||||
|
||||
// Test the translate tab.
|
||||
$this->drupalLogin($this->admin_user);
|
||||
$this->drupalGet('admin/structure/block/manage/' . $box2['module'] . '/' . $box2['delta'] . '/configure');
|
||||
$this->assertNoFieldByName('save_and_translate');
|
||||
|
||||
$this->drupalLogin($block_translater);
|
||||
$this->drupalPost('admin/structure/block/manage/' . $box2['module'] . '/' . $box2['delta'] . '/configure', array(), t('Save and translate'));
|
||||
// @todo Improve these checks.
|
||||
$this->assertText(t('Spanish'));
|
||||
$this->assertText(t('translated'));
|
||||
|
||||
$this->clickLink(t('translate'));
|
||||
|
||||
$this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':title]', $translations['title']['es']);
|
||||
$this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':body]', $translations['body']['es']);
|
||||
|
||||
// Update the translation.
|
||||
$translations['title']['es'] = $this->randomName(10);
|
||||
$translations['body']['es'] = $this->randomName(20);
|
||||
$edit = array(
|
||||
'strings[blocks:block:' . $box2['delta'] . ':title]' => $translations['title']['es'],
|
||||
'strings[blocks:block:' . $box2['delta'] . ':body]' => $translations['body']['es'],
|
||||
);
|
||||
$this->drupalPost(NULL, $edit, t('Save translation'));
|
||||
$this->i18nAssertTranslations($translations['title'], '', 'Updated block title translation displayed.');
|
||||
$this->i18nAssertTranslations($translations['body'], '', 'Updated block body translation displayed.');
|
||||
|
||||
// Test a block translation with filtering and text formats
|
||||
$box3 = $this->i18nCreateBlock(array(
|
||||
'title' => '<div><script>alert(0)</script>Title</script>',
|
||||
'body' => "Dangerous text\nOne line\nTwo lines<script>alert(1)</script>",
|
||||
));
|
||||
// This should be the actual HTML displayed
|
||||
$title = check_plain($box3['title']);
|
||||
$body = check_markup($box3['body'], $box3['format']);
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw($title, "Title being displayed for default language: " . $title);
|
||||
$this->assertRaw($body, "Body being displayed for default language: " . $body);
|
||||
|
||||
// We add language name to the body just to make sure we get the right translation later
|
||||
// This won't work for block titles as they don't have input format thus scripts will be blocked by locale
|
||||
$translations = array();
|
||||
foreach ($this->getOtherLanguages() as $langcode => $language) {
|
||||
$translations[$langcode] = $box3['body'] . "\n" . $language->name;
|
||||
$filtered[$langcode] = check_markup($translations[$langcode], $box3['format']);
|
||||
}
|
||||
// We need to find the string by this part alone, the rest will be filtered
|
||||
$this->createStringTranslation('blocks', 'Dangerous text', $translations);
|
||||
// Check the right filtered strings are displayed
|
||||
$this->i18nAssertTranslations($filtered);
|
||||
|
||||
// Assert translatable descriptions.
|
||||
$this->drupalLogin($this->admin_user);
|
||||
$this->drupalGet('admin/structure/block/manage/system/powered-by/configure');
|
||||
$this->assertText(t('This block has generated content, only the title can be translated here.'));
|
||||
|
||||
$this->drupalGet('admin/structure/block/manage/system/navigation/configure');
|
||||
$this->assertText(t('To translate the block content itself, translate the menu that is being shown.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate block fields to all languages
|
||||
*/
|
||||
function i18nTranslateBlock($block) {
|
||||
$translations['title'] = $this->createStringTranslation('blocks', $block['title']);
|
||||
$translations['body'] = $this->createStringTranslation('blocks', $block['body']);
|
||||
return $translations;
|
||||
}
|
||||
/**
|
||||
* Test creating custom block (i.e. box), moving it to a specific region and then deleting it.
|
||||
*/
|
||||
function i18nCreateBlock($block = array(), $region = 'sidebar_first', $check_display = TRUE) {
|
||||
$this->drupalLogin($this->admin_user);
|
||||
// Add a new custom block by filling out the input form on the admin/structure/block/add page.
|
||||
$block += array(
|
||||
'info' => $this->randomName(8),
|
||||
'title' => $this->randomName(8),
|
||||
'i18n_mode' => I18N_MODE_LOCALIZE,
|
||||
'body' => $this->randomName(16),
|
||||
);
|
||||
$custom_block = array(
|
||||
'info' => $block['info'],
|
||||
'title' => $block['title'],
|
||||
'i18n_mode' => $block['i18n_mode'],
|
||||
'body[value]' => $block['body'],
|
||||
);
|
||||
$this->drupalPost('admin/structure/block/add', $custom_block, t('Save block'));
|
||||
|
||||
// Confirm that the custom block has been created, and then query the created bid.
|
||||
$this->assertText(t('The block has been created.'), t('Custom block successfully created.'));
|
||||
$bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $block['info']))->fetchField();
|
||||
|
||||
// Check to see if the custom block was created by checking that it's in the database.
|
||||
$this->assertNotNull($bid, t('Custom block found in database'));
|
||||
|
||||
// Check that block_block_view() returns the correct title and content.
|
||||
$data = block_block_view($bid);
|
||||
$format = db_query("SELECT format FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchField();
|
||||
$this->assertTrue(array_key_exists('subject', $data) && empty($data['subject']), t('block_block_view() provides an empty block subject, since custom blocks do not have default titles.'));
|
||||
$this->assertEqual(check_markup($block['body'], $format), $data['content'], t('block_block_view() provides correct block content.'));
|
||||
|
||||
// Check if the block can be moved to all available regions.
|
||||
$block['module'] = 'block';
|
||||
$block['delta'] = $bid;
|
||||
$block['format'] = $format;
|
||||
$this->moveBlockToRegion($block, $region);
|
||||
|
||||
return $block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update block i18n mode
|
||||
*/
|
||||
function setBlockMode($block, $mode = I18N_MODE_LOCALIZE) {
|
||||
$edit['i18n_mode'] = $mode;
|
||||
$this->updateBlock($block, $edit);
|
||||
}
|
||||
/**
|
||||
* Update block visibility for languages
|
||||
*/
|
||||
function setBlockLanguages($block, $languages = array()) {
|
||||
$edit = array();
|
||||
foreach ($this->getEnabledLanguages() as $langcode => $language) {
|
||||
$edit["languages[$langcode]"] = in_array($langcode, $languages) ? TRUE : FALSE;
|
||||
}
|
||||
$this->updateBlock($block, $edit);
|
||||
}
|
||||
/**
|
||||
* Update block
|
||||
*/
|
||||
function updateBlock($block, $edit) {
|
||||
$this->drupalLogin($this->admin_user);
|
||||
$this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block'));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_string_info().
|
||||
*/
|
||||
function i18n_contact_i18n_string_info() {
|
||||
$groups['contact'] = array(
|
||||
'title' => t('Contact forms'),
|
||||
'description' => t('Configurable contact form categories.'),
|
||||
'format' => FALSE,
|
||||
'list' => TRUE,
|
||||
);
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_object_info().
|
||||
*/
|
||||
function i18n_contact_i18n_object_info() {
|
||||
$info['contact_category'] = array(
|
||||
// Generic object title.
|
||||
'title' => t('Contact category'),
|
||||
// The object key field.
|
||||
'key' => 'cid',
|
||||
// The object load callback.
|
||||
'load callback' => 'contact_load',
|
||||
// Placeholders for automatic paths.
|
||||
'placeholders' => array(
|
||||
'%contact' => 'cid',
|
||||
),
|
||||
// To produce edit links automatically.
|
||||
'edit path' => 'admin/structure/contact/edit/%contact',
|
||||
// Auto-generate translate tab.
|
||||
'translate tab' => 'admin/structure/contact/edit/%contact/translate',
|
||||
// Properties for string translation.
|
||||
'string translation' => array(
|
||||
// Text group that will handle this object's strings.
|
||||
'textgroup' => 'contact',
|
||||
// Object type property for string translation.
|
||||
'type' => 'category',
|
||||
// Table where the object is stored, to automate string lists
|
||||
'table' => 'contact',
|
||||
// Translatable properties of these objects.
|
||||
'properties' => array(
|
||||
'category' => t('Category'),
|
||||
'reply' => t('Auto-reply'),
|
||||
),
|
||||
// Path to translate strings to every language.
|
||||
'translate path' => 'admin/structure/contact/edit/%contact/translate/%i18n_language',
|
||||
)
|
||||
);
|
||||
return $info;
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
name = Contact translation
|
||||
description = Makes contact categories and replies available for translation.
|
||||
dependencies[] = contact
|
||||
dependencies[] = i18n_string
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) submodule: Multilingual contact forms
|
||||
*
|
||||
* @author Jose A. Reyero, 2005
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*
|
||||
* Add translate tab to contact config.
|
||||
*/
|
||||
function i18n_contact_menu() {
|
||||
$items['admin/structure/contact/edit/%contact/edit'] = array(
|
||||
'title' => 'Edit',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'weight' => -100,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_contact_form_contact_category_delete_form_alter(&$form, &$form_state) {
|
||||
$form['#submit'][] = 'i18n_contact_form_contact_category_delete_form_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove strings for deleted categories.
|
||||
*/
|
||||
function i18n_contact_form_contact_category_delete_form_submit(&$form, $form_state) {
|
||||
i18n_string_object_remove('contact_category', $form['contact']['#value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_contact_form_contact_category_edit_form_alter(&$form, &$form_state) {
|
||||
$form['actions']['translate'] = array(
|
||||
'#type' => 'submit',
|
||||
'#name' => 'save_translate',
|
||||
'#value' => t('Save and translate'),
|
||||
);
|
||||
$form['#submit'][] = 'i18n_contact_form_contact_category_edit_form_submit';
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove strings for edited/added categories.
|
||||
*/
|
||||
function i18n_contact_form_contact_category_edit_form_submit($form, &$form_state) {
|
||||
$contact = $form_state['values'];
|
||||
i18n_string_object_update('contact_category', $contact);
|
||||
// If the save and translate button was clicked, redirect to the translate
|
||||
// tab instead of the block overview.
|
||||
if ($form_state['triggering_element']['#name'] == 'save_translate') {
|
||||
$form_state['redirect'] = 'admin/structure/contact/edit/' . $contact['cid'] . '/translate';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_contact_form_contact_site_form_alter(&$form, &$form_state) {
|
||||
// Example of array translation. The placeholder '*' indicates the name part to be replace by the array keys
|
||||
if (isset($form['cid']['#options'])) {
|
||||
$form['cid']['#options'] = i18n_string_translate("contact:category:*:category", $form['cid']['#options'], array('sanitize' => FALSE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_mail_alter().
|
||||
*/
|
||||
function i18n_contact_mail_alter(&$message) {
|
||||
if (in_array($message['id'], array('contact_page_mail', 'contact_page_copy', 'contact_page_autoreply'))) {
|
||||
// Alter the first part of the subject of emails going out if they need
|
||||
// translation.
|
||||
$contact = i18n_string_object_translate('contact_category', $message['params']['category'], array('langcode' => $message['language']->language));
|
||||
$message['subject'] = t(
|
||||
'[!category] !subject',
|
||||
array('!category' => $contact['category'], '!subject' => $message['params']['subject']),
|
||||
array('langcode' => $message['language']->language)
|
||||
);
|
||||
|
||||
if ($message['id'] == 'contact_page_autoreply') {
|
||||
// Overwrite the whole message body. Maybe this is not entirely responsible
|
||||
// (it might overwrite other existing items altered in by others),
|
||||
// but unfortunately Drupal core cotact module does not make its item
|
||||
// identifiable easily.
|
||||
$message['body'] = array($contact['reply']);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* API documentation file for Field translation module.
|
||||
*
|
||||
* This module takes care of translating common field elements like title and
|
||||
* description for all fields, plus some field specific values (default, options)
|
||||
* for field types defined by Drupal core.
|
||||
*
|
||||
* Before implementing any of these hooks, consider whether you would be better
|
||||
* off implementing Drupal core's hook_field_widget_form_alter().
|
||||
*
|
||||
* @see i18n_field_field_widget_form_alter()
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provide information about callbacks for translating specific field types.
|
||||
*
|
||||
* This information can be retrieved using i18n_field_type_info().
|
||||
* @return
|
||||
* Array of values indexed by field type. Valid keys are:
|
||||
* - 'translate_default', Callback for translating the default value for this field type.
|
||||
* - 'translate_options', Callback for translating options for this field type.
|
||||
*
|
||||
* @see i18n_field_type_info()
|
||||
* @see i18n_field_i18n_field_info()
|
||||
*
|
||||
* For examples of both callback types:
|
||||
*
|
||||
* @see i18n_field_translate_allowed_values()
|
||||
* @see i18n_field_translate_default()
|
||||
*
|
||||
*/
|
||||
function hook_i18n_field_info() {
|
||||
$info['text'] = $info['text_long'] = $info['text_with_summary'] = array(
|
||||
'translate_default' => 'i18n_field_translate_default',
|
||||
);
|
||||
$info['list_text'] = $info['list_boolean'] = $info['list_integer'] = array(
|
||||
'translate_options' => 'i18n_field_translate_allowed_values',
|
||||
);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alter information provided by hook_i18n_field_info().
|
||||
*
|
||||
* @see i18n_field_type_info()
|
||||
*/
|
||||
function hook_i18n_field_info_alter(&$info) {
|
||||
// Unset the default callback for text fields.
|
||||
unset($info['text']['translate_default']);
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_object_info().
|
||||
*/
|
||||
function i18n_field_i18n_object_info() {
|
||||
$info['field'] = array(
|
||||
'title' => t('Field'),
|
||||
'class' => 'i18n_field',
|
||||
'key' => 'field_name',
|
||||
'load callback' => 'field_info_field',
|
||||
'placeholders' => array(
|
||||
'%field_ui_menu' => 'field_name',
|
||||
'%field_type' => 'type',
|
||||
),
|
||||
'edit path' => 'admin/structure/types/manage/%bundle/fields/%field_ui_menu/field-settings',
|
||||
// We can easily list all these objects
|
||||
'list callback' => 'field_read_fields',
|
||||
'string translation' => array(
|
||||
'textgroup' => 'field',
|
||||
'properties' => array(
|
||||
'label' => array(
|
||||
'title' => t('Label'),
|
||||
),
|
||||
),
|
||||
//'translate path' => 'admin/structure/block/manage/%module/%delta/translate/%i18n_language',
|
||||
)
|
||||
);
|
||||
$info['field_instance'] = array(
|
||||
'title' => t('Field instance'),
|
||||
'class' => 'i18n_field_instance',
|
||||
'key' => array('field_name', 'bundle'),
|
||||
'placeholders' => array(
|
||||
'%bundle' => 'bundle',
|
||||
'%field_ui_menu' => 'field_name',
|
||||
),
|
||||
'edit path' => 'admin/structure/types/manage/%bundle/fields/%field_ui_menu',
|
||||
// We can easily list all these objects.
|
||||
'list callback' => 'field_read_instances',
|
||||
// Metadata for string translation.
|
||||
'string translation' => array(
|
||||
'textgroup' => 'field',
|
||||
'properties' => array(
|
||||
'label' => array(
|
||||
'title' => t('Label'),
|
||||
),
|
||||
'description' => array(
|
||||
'title' => t('Description'),
|
||||
'format' => 'format',
|
||||
),
|
||||
'default_value' => array(
|
||||
'title' => t('Default value'),
|
||||
'format' => 'format',
|
||||
),
|
||||
),
|
||||
//'translate path' => 'admin/structure/types/manage/%bundle/fields/%field_ui_menu/translate/%i18n_language',
|
||||
)
|
||||
);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_string_info().
|
||||
*/
|
||||
function i18n_field_i18n_string_info() {
|
||||
$groups['field'] = array(
|
||||
'title' => t('Fields'),
|
||||
'description' => t('Configurable fields descriptions, defaults, options, etc.'),
|
||||
'format' => FALSE, // This group doesn't have formatted strings
|
||||
'list' => TRUE, // This group can list all strings
|
||||
);
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_field_info().
|
||||
*/
|
||||
function i18n_field_i18n_field_info() {
|
||||
$info['text'] = $info['text_long'] = $info['text_with_summary'] = array(
|
||||
'translate_default' => 'i18n_field_translate_default',
|
||||
);
|
||||
$info['list_text'] = $info['list_boolean'] = $info['list_integer'] = array(
|
||||
'translate_options' => 'i18n_field_translate_allowed_values',
|
||||
);
|
||||
return $info;
|
||||
}
|
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Field and field instance object handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base object for field and field instance
|
||||
*/
|
||||
class i18n_field_base extends i18n_string_object_wrapper {
|
||||
/**
|
||||
* Get base path for object
|
||||
*/
|
||||
protected function get_base_path() {
|
||||
$info = entity_get_info($this->object['entity_type']);
|
||||
if (isset($info['bundles'][$this->object['bundle']]['admin'])) {
|
||||
$admin = $info['bundles'][$this->object['bundle']]['admin'];
|
||||
// Extract path information from the bundle.
|
||||
if (isset($admin['real path'])) {
|
||||
return $admin['real path'] . '/fields/' . $this->object['field_name'];
|
||||
}
|
||||
else {
|
||||
// We don't have real path, use path instead, may work or not.
|
||||
return $admin['path'] . '/fields/' . $this->object['field_name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field object
|
||||
*/
|
||||
class i18n_field extends i18n_field_base {
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* For convenience field objects can be built from field info and from field instance.
|
||||
*/
|
||||
public function __construct($type, $key, $object) {
|
||||
parent::__construct($type, $key, $object);
|
||||
|
||||
// If this is a field instance, get field info but add instance data too.
|
||||
// This instance data will be used to get the paths to get the edit/translate path.
|
||||
if (isset($this->object['bundle']) && isset($this->object['entity_type'])) {
|
||||
$this->object = field_info_field($this->object['field_name']) + array('bundle' => $this->object['bundle'], 'entity_type' => $object['entity_type']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get edit path for object
|
||||
*/
|
||||
public function get_edit_path() {
|
||||
return $this->get_base_path() . '/field-settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translate path for object
|
||||
*/
|
||||
public function get_translate_path($langcode = NULL) {
|
||||
return $this->get_base_path() . '/translate/field' . ($langcode ? '/' . $langcode : '');
|
||||
}
|
||||
/**
|
||||
* Get string context
|
||||
*/
|
||||
public function get_string_context() {
|
||||
return array($this->object['field_name'], '#field');
|
||||
}
|
||||
/**
|
||||
* Get translatable properties
|
||||
*/
|
||||
protected function build_properties() {
|
||||
$properties = parent::build_properties();
|
||||
$object = $this->object;
|
||||
// For select fields field:field_name
|
||||
if (!empty($object['settings']['allowed_values']) && i18n_field_type_info($object['type'], 'translate_options')) {
|
||||
//return array('field', $field['field_name'], '#allowed_values');
|
||||
foreach ($object['settings']['allowed_values'] as $key => $value) {
|
||||
$properties[$this->get_textgroup()][$object['field_name']]['#allowed_values'][$key] = array(
|
||||
'title' => t('Option %name', array('%name' => $value)),
|
||||
'string' => $value,
|
||||
);
|
||||
}
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context to be pre-loaded before translation.
|
||||
*/
|
||||
protected function get_translate_context($langcode, $options) {
|
||||
return array(
|
||||
$this->object['field_name'],
|
||||
array('#field', '#allowed_values'),
|
||||
'*'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field translation for object.
|
||||
*
|
||||
* Mot often, this is a direct field set, but sometimes fields may have different formats.
|
||||
*
|
||||
* @param $object
|
||||
* A clone of the object or array. Field instance.
|
||||
*/
|
||||
protected function translate_field(&$object, $i18nstring, $langcode, $options) {
|
||||
if ($i18nstring->objectid == '#allowed_values') {
|
||||
$object['settings']['#allowed_values'][$i18nstring->key] = $i18nstring->format_translation($langcode, $options);
|
||||
}
|
||||
else {
|
||||
parent::translate_field($object, $i18nstring, $langcode, $options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Field instance object
|
||||
*/
|
||||
class i18n_field_instance extends i18n_field_base {
|
||||
/**
|
||||
* Get edit path for object
|
||||
*/
|
||||
public function get_edit_path() {
|
||||
return $this->get_base_path();
|
||||
}
|
||||
/**
|
||||
* Get translate path for object
|
||||
*/
|
||||
public function get_translate_path($langcode = NULL) {
|
||||
return $this->get_base_path() . '/translate' . ($langcode ? '/' . $langcode : '');
|
||||
}
|
||||
/**
|
||||
* Get string context
|
||||
*/
|
||||
public function get_string_context() {
|
||||
return array($this->object['field_name'], $this->object['bundle']);
|
||||
}
|
||||
/**
|
||||
* Get translatable properties
|
||||
*/
|
||||
protected function build_properties() {
|
||||
$properties = parent::build_properties();
|
||||
$object = $this->object;
|
||||
$field = field_info_field($object['field_name']);
|
||||
// Only for text field types
|
||||
if (!empty($object['default_value']) && i18n_field_type_info($field['type'], 'translate_default')) {
|
||||
$format = isset($object['default_value'][0]['format']) ? $object['default_value'][0]['format'] : NULL;
|
||||
$properties[$this->get_textgroup()][$object['field_name']][$object['bundle']]['default_value']['string'] = $object['default_value'][0]['value'];
|
||||
$properties[$this->get_textgroup()][$object['field_name']][$object['bundle']]['default_value']['format'] = $format;
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field translation for object.
|
||||
*
|
||||
* Mot often, this is a direct field set, but sometimes fields may have different formats.
|
||||
*
|
||||
* @param $object
|
||||
* A clone of the object or array. Field instance.
|
||||
*/
|
||||
protected function translate_field(&$object, $i18nstring, $langcode, $options) {
|
||||
if ($i18nstring->property == 'default_value') {
|
||||
// Render string without applying format
|
||||
$object['default_value'][0]['value'] = $i18nstring->format_translation($langcode, array('sanitize' => FALSE) + $options);
|
||||
}
|
||||
else {
|
||||
parent::translate_field($object, $i18nstring, $langcode, $options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Context to be pre-loaded before translation.
|
||||
*/
|
||||
protected function get_translate_context($langcode, $options) {
|
||||
return array(
|
||||
$this->object['field_name'],
|
||||
array('#field', '#allowed_values', $this->object['bundle']),
|
||||
'*'
|
||||
);
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Field translation
|
||||
description = Translate field properties
|
||||
dependencies[] = field
|
||||
dependencies[] = i18n_string
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
files[] = i18n_field.inc
|
||||
files[] = i18n_field.test
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the i18n_field module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function i18n_field_install() {
|
||||
// If updating from D6, module changed name
|
||||
if (variable_get('i18n_drupal6_update')) {
|
||||
i18n_field_update_7000();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_update_dependencies()
|
||||
*/
|
||||
function i18n_field_update_dependencies() {
|
||||
$dependencies['i18n_field'][7000] = array(
|
||||
'i18n_string' => 7001,
|
||||
);
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_drupal6_update().
|
||||
*
|
||||
* Update old string names
|
||||
*/
|
||||
function i18n_field_update_7000() {
|
||||
// @todo
|
||||
module_load_install('i18n_string');
|
||||
// Old CCK label and description
|
||||
$query = db_select('i18n_string', 's')
|
||||
->fields('s')
|
||||
->condition('textgroup', 'cck')
|
||||
->condition('type', 'field');
|
||||
foreach ($query->execute() as $string) {
|
||||
$string->textgroup = 'field';
|
||||
list($bundle, $field) = explode('-', $string->objectid);
|
||||
$string->type = $field;
|
||||
$string->objectid = $bundle;
|
||||
$string->property = str_replace('widget_', '', $string->property);
|
||||
i18n_string_install_update_string($string);
|
||||
}
|
||||
// @todo Field groups ??
|
||||
// Old Profile fields
|
||||
$query = db_select('i18n_string', 's')
|
||||
->fields('s')
|
||||
->condition('textgroup', 'profile')
|
||||
->condition('type', 'field');
|
||||
foreach ($query->execute() as $string) {
|
||||
$string->textgroup = 'field';
|
||||
$string->type = $string->property;
|
||||
if ($string->objectid == 'options') {
|
||||
// @todo Handle field options
|
||||
$string->objectid = '#allowed_values';
|
||||
}
|
||||
else {
|
||||
$string->objectid = 'user'; // Bundle for profile fields
|
||||
i18n_string_install_update_string($string);
|
||||
}
|
||||
}
|
||||
// @todo Profile categories ??
|
||||
}
|
||||
|
||||
/**
|
||||
* Old strings to update. All these will be handled by i18n_field module
|
||||
*
|
||||
* 'cck:field:'. $content_type .'-'. $field_name .':widget_label'
|
||||
* --> 'field:$field_name:$bundle:label' (though not used atm)
|
||||
* 'cck:field:'. $content_type .'-'. $field_name .':widget_description'
|
||||
* --> 'field:$field_name:$bundle:description'
|
||||
* 'cck:fieldgroup:'. $content_type .'-'. $group_name .':display_description'
|
||||
* 'cck:fieldgroup:'. $content_type .'-'. $group_name .':form_description', $group['settings']['form']['description']);
|
||||
*
|
||||
* Profile:
|
||||
* profile:field:$field_name:title|explanation|options
|
||||
* "profile:category", $field->category
|
||||
*
|
||||
*/
|
@@ -0,0 +1,446 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) module - Field handling
|
||||
*
|
||||
* For string keys we use:
|
||||
* - field:[field_name]:[bundle]:property, when it is an instance property (linked to bundle)
|
||||
* - field:[field_name]:#property..., when it is a field property (that may have multiple values)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function i18n_field_menu() {
|
||||
$items = array();
|
||||
|
||||
// Ensure the following is not executed until field_bundles is working and
|
||||
// tables are updated. Needed to avoid errors on initial installation.
|
||||
if (!module_exists('field_ui') || defined('MAINTENANCE_MODE')) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
// Create tabs for all possible bundles. From field_ui_menu().
|
||||
foreach (entity_get_info() as $entity_type => $entity_info) {
|
||||
if ($entity_info['fieldable']) {
|
||||
foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
|
||||
if (isset($bundle_info['admin'])) {
|
||||
// Extract path information from the bundle.
|
||||
$path = $bundle_info['admin']['path'];
|
||||
// Different bundles can appear on the same path (e.g. %node_type and
|
||||
// %comment_node_type). To allow field_ui_menu_load() to extract the
|
||||
// actual bundle object from the translated menu router path
|
||||
// arguments, we need to identify the argument position of the bundle
|
||||
// name string ('bundle argument') and pass that position to the menu
|
||||
// loader. The position needs to be casted into a string; otherwise it
|
||||
// would be replaced with the bundle name string.
|
||||
if (isset($bundle_info['admin']['bundle argument'])) {
|
||||
$bundle_arg = $bundle_info['admin']['bundle argument'];
|
||||
$bundle_pos = (string) $bundle_arg;
|
||||
}
|
||||
else {
|
||||
$bundle_arg = $bundle_name;
|
||||
$bundle_pos = '0';
|
||||
}
|
||||
// This is the position of the %field_ui_menu placeholder in the
|
||||
// items below.
|
||||
$field_position = count(explode('/', $path)) + 1;
|
||||
|
||||
// Extract access information, providing defaults.
|
||||
$access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments')));
|
||||
$access += array(
|
||||
'access callback' => 'user_access',
|
||||
'access arguments' => array('administer site configuration'),
|
||||
);
|
||||
$items["$path/fields/%field_ui_menu/translate"] = array(
|
||||
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
|
||||
'title' => 'Translate',
|
||||
'page callback' => 'i18n_field_page_translate',
|
||||
'page arguments' => array($field_position),
|
||||
'file' => 'i18n_field.pages.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
) + $access;
|
||||
|
||||
$items["$path/fields/%field_ui_menu/translate/%i18n_language"] = array(
|
||||
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
|
||||
'title' => 'Instance',
|
||||
'page callback' => 'i18n_field_page_translate',
|
||||
'page arguments' => array($field_position, $field_position + 2),
|
||||
'file' => 'i18n_field.pages.inc',
|
||||
'type' => MENU_CALLBACK,
|
||||
) + $access;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_hook_info().
|
||||
*/
|
||||
function i18n_field_hook_info() {
|
||||
$hooks['i18n_field_info'] = array(
|
||||
'group' => 'i18n',
|
||||
);
|
||||
return $hooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_attach_form().
|
||||
*
|
||||
* After the form fields are built. Translate title and description for fields with multiple values.
|
||||
*/
|
||||
function i18n_field_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
|
||||
// Determine the list of instances to iterate on.
|
||||
list(, , $bundle) = entity_extract_ids($entity_type, $entity);
|
||||
$instances = field_info_instances($entity_type, $bundle);
|
||||
foreach ($instances as $field_name => $instance) {
|
||||
if (isset($form[$field_name])) {
|
||||
$langcode = $form[$field_name]['#language'];
|
||||
$field = &$form[$field_name];
|
||||
// Note: cardinality for unlimited fields is -1
|
||||
if (isset($field[$langcode]['#cardinality']) && $field[$langcode]['#cardinality'] != 1) {
|
||||
$translated = i18n_string_object_translate('field_instance', $instance);
|
||||
if (!empty($field[$langcode]['#title'])) {
|
||||
$field[$langcode]['#title'] = $translated['label'];
|
||||
}
|
||||
if (!empty($field[$langcode]['#description'])) {
|
||||
$field[$langcode]['#description'] = $translated['description'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_info().
|
||||
*/
|
||||
function i18n_field_field_formatter_info() {
|
||||
$types = array();
|
||||
foreach (i18n_field_type_info() as $type => $info) {
|
||||
if (!empty($info['translate_options'])) {
|
||||
$types[] = $type;
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'i18n_list_default' => array(
|
||||
'label' => t('Default translated'),
|
||||
'field types' => $types,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_view().
|
||||
*/
|
||||
function i18n_field_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
|
||||
$element = array();
|
||||
|
||||
switch ($display['type']) {
|
||||
case 'i18n_list_default':
|
||||
if (($translate = i18n_field_type_info($field['type'], 'translate_options'))) {
|
||||
$allowed_values = $translate($field);
|
||||
}
|
||||
else {
|
||||
// Defaults to list_default behavior
|
||||
$allowed_values = list_allowed_values($field);
|
||||
}
|
||||
foreach ($items as $delta => $item) {
|
||||
if (isset($allowed_values[$item['value']])) {
|
||||
$output = field_filter_xss($allowed_values[$item['value']]);
|
||||
}
|
||||
else {
|
||||
// If no match was found in allowed values, fall back to the key.
|
||||
$output = field_filter_xss($item['value']);
|
||||
}
|
||||
$element[$delta] = array('#markup' => $output);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_form_alter().
|
||||
*
|
||||
* Translate:
|
||||
* - Title (label)
|
||||
* - Description (help)
|
||||
* - Default value
|
||||
* - List options
|
||||
*/
|
||||
function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
|
||||
global $language;
|
||||
|
||||
// Don't translate if the widget is being shown on the field edit form.
|
||||
if ($form_state['build_info']['form_id'] == 'field_ui_field_edit_form') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if we are missing any of the parameters
|
||||
if (empty($context['field']) || empty($context['instance']) || empty($context['langcode'])) {
|
||||
return;
|
||||
}
|
||||
$field = $context['field'];
|
||||
$instance = $context['instance'];
|
||||
$langcode = $context['langcode'];
|
||||
|
||||
// Get the element to alter. Account for inconsistencies in how the element
|
||||
// is built for different field types.
|
||||
if (isset($element[0]) && count($element) == 1) {
|
||||
// Single-value file fields and image fields.
|
||||
$alter_element = &$element[0];
|
||||
}
|
||||
elseif (isset($element['value'])) {
|
||||
// Number fields. Single-value text fields.
|
||||
$alter_element = &$element['value'];
|
||||
}
|
||||
elseif ($field['type'] == 'entityreference' && isset($element['target_id'])) {
|
||||
// Entityreference fields using the entityreference_autocomplete widget.
|
||||
$alter_element = &$element['target_id'];
|
||||
}
|
||||
else {
|
||||
// All other fields.
|
||||
$alter_element = &$element;
|
||||
}
|
||||
|
||||
// If a subelement has the same title as the parent, translate it instead.
|
||||
// Allows fields such as email and commerce_price to be translated.
|
||||
foreach (element_get_visible_children($element) as $key) {
|
||||
$single_value = ($field['cardinality'] == 1);
|
||||
$has_title = (isset($element['#title']) && isset($element[$key]['#title']));
|
||||
if ($single_value && $has_title && $element[$key]['#title'] == $element['#title']) {
|
||||
$alter_element = &$element[$key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The field language may affect some variables (default) but not others (description will be in current page language)
|
||||
$i18n_langcode = empty($alter_element['#language']) || $alter_element['#language'] == LANGUAGE_NONE ? $language->language : $alter_element['#language'];
|
||||
|
||||
// Translate instance to current page language and set to form_state
|
||||
// so it will be used for validation messages later.
|
||||
$instance_current = i18n_string_object_translate('field_instance', $instance);
|
||||
if (isset($form_state['field'][$instance['field_name']][$langcode]['instance'])) {
|
||||
$form_state['field'][$instance['field_name']][$langcode]['instance'] = $instance_current;
|
||||
}
|
||||
|
||||
// Translate field title if set and it is the default one.
|
||||
if (!empty($instance_current['label']) && $instance_current['label'] != $instance['label']) {
|
||||
if (!empty($alter_element['#title']) && $alter_element['#title'] == check_plain($instance['label'])) {
|
||||
$alter_element['#title'] = check_plain($instance_current['label']);
|
||||
}
|
||||
}
|
||||
|
||||
// Translate field description if set and it is the default one.
|
||||
if (!empty($instance_current['description']) && $instance_current['description'] != $instance['description']) {
|
||||
if (!empty($alter_element['#description'])) {
|
||||
// Allow single-value file fields and image fields to have their
|
||||
// descriptions translated. file_field_widget_form() passes the
|
||||
// description through theme('file_upload_help'), so i18n_field
|
||||
// must do the same.
|
||||
$filefield = in_array($field['type'], array('file', 'image'));
|
||||
$single_value = ($field['cardinality'] == 1);
|
||||
$no_default = empty($alter_element['#default_value']['fid']);
|
||||
if ($filefield && $single_value && $no_default) {
|
||||
$help_variables = array(
|
||||
'description' => field_filter_xss($instance['description']),
|
||||
'upload_validators' => $alter_element['#upload_validators'],
|
||||
);
|
||||
$original_description = theme('file_upload_help', $help_variables);
|
||||
if ($alter_element['#description'] == $original_description) {
|
||||
$help_variables = array(
|
||||
'description' => field_filter_xss($instance_current['description']),
|
||||
'upload_validators' => $alter_element['#upload_validators'],
|
||||
);
|
||||
$alter_element['#description'] = theme('file_upload_help', $help_variables);
|
||||
}
|
||||
}
|
||||
elseif ($alter_element['#description'] == field_filter_xss($instance['description'])) {
|
||||
$alter_element['#description'] = field_filter_xss($instance_current['description']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Translate list options.
|
||||
$has_options = (!empty($alter_element['#options']) || $field['type'] == 'list_boolean');
|
||||
$has_allowed_values = !empty($field['settings']['allowed_values']);
|
||||
$translate = i18n_field_type_info($field['type'], 'translate_options');
|
||||
if ($has_options && $has_allowed_values && $translate) {
|
||||
$alter_element['#options'] = $translate($field, $i18n_langcode);
|
||||
if (isset($alter_element['#properties']) && !empty($alter_element['#properties']['empty_option'])) {
|
||||
$label = theme('options_none', array('instance' => $instance, 'option' => $alter_element['#properties']['empty_option']));
|
||||
$alter_element['#options'] = array('_none' => $label) + $alter_element['#options'];
|
||||
}
|
||||
// Translate list_boolean fields using the checkboxes widget.
|
||||
if (!empty($alter_element['#title']) && $field['type'] == 'list_boolean' && !empty($alter_element['#on_value'])) {
|
||||
$on_value = $alter_element['#on_value'];
|
||||
$alter_element['#options'];
|
||||
$alter_element['#title'] = $alter_element['#options'][$on_value];
|
||||
// For using label instead of "On value".
|
||||
if ($instance['widget']['settings']['display_label']) {
|
||||
$alter_element['#title'] = $instance_current['label'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for more parameters, skip this part if missing.
|
||||
if (!isset($context['delta']) || !isset($context['items'])) {
|
||||
return;
|
||||
}
|
||||
$delta = $context['delta'];
|
||||
$items = $context['items'];
|
||||
|
||||
// Translate default value.
|
||||
$has_default_value = (isset($alter_element['#default_value']) && !empty($instance['default_value'][$delta]['value']));
|
||||
$storage_has_value = !empty($items[$delta]['value']);
|
||||
$translate = i18n_field_type_info($field['type'], 'translate_default');
|
||||
if ($has_default_value && $storage_has_value && $translate) {
|
||||
// Compare the default value with the value currently in storage.
|
||||
if ($instance['default_value'][$delta]['value'] === $items[$delta]['value']) {
|
||||
$alter_element['#default_value'] = $translate($instance, $items[$delta]['value'], $i18n_langcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_attach_view_alter().
|
||||
*/
|
||||
function i18n_field_field_attach_view_alter(&$output, $context) {
|
||||
foreach (element_children($output) as $field_name) {
|
||||
$element = &$output[$field_name];
|
||||
if (!empty($element['#entity_type']) && !empty($element['#field_name']) && !empty($element['#bundle'])) {
|
||||
$instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
|
||||
|
||||
// Translate field title if set
|
||||
if (!empty($instance['label'])) {
|
||||
$element['#title'] = i18n_field_translate_property($instance, 'label');
|
||||
}
|
||||
|
||||
// Translate field description if set
|
||||
if (!empty($instance['description'])) {
|
||||
$element['#description'] = i18n_field_translate_property($instance, 'description');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_create_field().
|
||||
*/
|
||||
function i18n_field_field_create_field($field) {
|
||||
i18n_field_field_update_strings($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_create_instance().
|
||||
*/
|
||||
function i18n_field_field_create_instance($instance) {
|
||||
i18n_field_instance_update_strings($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_delete_instance().
|
||||
*/
|
||||
function i18n_field_field_delete_instance($instance) {
|
||||
i18n_string_object_remove('field_instance', $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_update_instance().
|
||||
*/
|
||||
function i18n_field_field_update_instance($instance, $prior_instance) {
|
||||
i18n_field_instance_update_strings($instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_update_field().
|
||||
*/
|
||||
function i18n_field_field_update_field($field) {
|
||||
i18n_field_field_update_strings($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update field strings
|
||||
*/
|
||||
function i18n_field_field_update_strings($field) {
|
||||
i18n_string_object_update('field', $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update field instance strings
|
||||
*/
|
||||
function i18n_field_instance_update_strings($instance) {
|
||||
i18n_string_object_update('field_instance', $instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the array of translated allowed values for a list field.
|
||||
*
|
||||
* The strings are not safe for output. Keys and values of the array should be
|
||||
* sanitized through field_filter_xss() before being displayed.
|
||||
*
|
||||
* @param $field
|
||||
* The field definition.
|
||||
*
|
||||
* @return
|
||||
* The array of allowed values. Keys of the array are the raw stored values
|
||||
* (number or text), values of the array are the display labels.
|
||||
*/
|
||||
function i18n_field_translate_allowed_values($field, $langcode = NULL) {
|
||||
if (!empty($field['settings']['allowed_values'])) {
|
||||
return i18n_string_translate(array('field', $field['field_name'], '#allowed_values'), $field['settings']['allowed_values'], array('langcode' => $langcode, 'sanitize' => FALSE));
|
||||
}
|
||||
else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate field default.
|
||||
*/
|
||||
function i18n_field_translate_default($instance, $value, $langcode = NULL) {
|
||||
return i18n_string_translate(array('field', $instance['field_name'], $instance['bundle'], 'default_value'), $value, array('langcode' => $langcode));
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate field property
|
||||
*/
|
||||
function i18n_field_translate_property($instance, $property, $langcode = NULL) {
|
||||
// For performance reasons, we translate the whole instance once, which is cached.
|
||||
$instance = i18n_string_object_translate('field_instance', $instance, array('langcode' => $langcode));
|
||||
return $instance[$property];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get i18n information for translating fields.
|
||||
*
|
||||
* @param $type
|
||||
* Optional field type.
|
||||
* @param $property
|
||||
* Optional property to get from field type.
|
||||
*
|
||||
* @return
|
||||
* - The property for the field if $type and $property set.
|
||||
* - Array of properties for the field type if only $type is set.
|
||||
* - Array of translation information for all field types.
|
||||
*/
|
||||
function i18n_field_type_info($type = NULL, $property = NULL) {
|
||||
$info = &drupal_static(__FUNCTION__);
|
||||
if (!isset($info)) {
|
||||
$info = module_invoke_all('i18n_field_info');
|
||||
drupal_alter('i18n_field_info', $info);
|
||||
}
|
||||
if ($property) {
|
||||
return isset($info[$type]) && isset($info[$type][$property]) ? $info[$type][$property] : NULL;
|
||||
}
|
||||
elseif ($type) {
|
||||
return isset($info[$type]) ? $info[$type] : array();
|
||||
}
|
||||
else {
|
||||
return $info;
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Translation page for fields.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Field translation page
|
||||
*
|
||||
* We need to translate field and field instance.
|
||||
*/
|
||||
function i18n_field_page_translate($instance, $language = NULL) {
|
||||
module_load_include('inc', 'i18n_string', 'i18n_string.pages');
|
||||
if (!$language) {
|
||||
// Overview page will be the regular one
|
||||
return i18n_string_translate_page_object('field_instance', $instance);
|
||||
}
|
||||
else {
|
||||
// Because of some weird menu mapping for comment fields language object loader is not working.
|
||||
$language = i18n_language_object($language);
|
||||
drupal_set_title(t('Translate to !language', array('!language' => i18n_language_name($language->language))));
|
||||
//return drupal_get_form('i18n_field_page_translate_form', $instance, $language->language);
|
||||
// Create form with two tabs, one for instance, once for field.
|
||||
$groups = array(
|
||||
'instance' => t('Field instance'),
|
||||
'field' => t('Field settings'),
|
||||
);
|
||||
// Field instance
|
||||
$instance_object = i18n_object('field_instance', $instance);
|
||||
$strings['instance'] = $instance_object->get_strings(array('empty' => TRUE));
|
||||
// Field settings
|
||||
$field_object = i18n_object('field', $instance);
|
||||
$strings['field'] = $field_object->get_strings(array('empty' => TRUE));
|
||||
return drupal_get_form('i18n_string_translate_page_form', $strings, $language->language, $groups);
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Test case for multilingual fields.
|
||||
*/
|
||||
|
||||
|
||||
class i18nFieldTestCase extends Drupali18nTestCase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Field translation',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Field translation functions'
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('i18n_field', 'field_test');
|
||||
parent::setUpLanguages(array('access field_test content', 'administer field_test content'));
|
||||
$this->translator = $this->drupalCreateUser(array('translate interface', 'translate user-defined strings'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the translation of list fields, including allowed values.
|
||||
*/
|
||||
function testListFieldTranslation() {
|
||||
$field_name = drupal_strtolower($this->randomName());
|
||||
$label = $this->randomName();
|
||||
$description = $this->randomName();
|
||||
$value = $this->randomName();
|
||||
|
||||
$field = array(
|
||||
'field_name' => $field_name,
|
||||
'type' => 'list_integer',
|
||||
'cardinality' => 1,
|
||||
'settings' => array(
|
||||
'allowed_values' => array(1 => $value),
|
||||
),
|
||||
);
|
||||
$field = field_create_field($field);
|
||||
|
||||
$instance = array(
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'test_entity',
|
||||
'bundle' => 'test_bundle',
|
||||
'label' => $label,
|
||||
'description' => $description,
|
||||
'widget' => array(
|
||||
'type' => 'options_buttons',
|
||||
),
|
||||
);
|
||||
$instance = field_create_instance($instance);
|
||||
|
||||
// Refresh i18n_strings.
|
||||
$edit = array('groups[field]' => TRUE);
|
||||
$this->drupalPost('admin/config/regional/translate/i18n_string', $edit, t('Refresh strings'));
|
||||
|
||||
// Save translations for each attribute.
|
||||
$label_translation = $this->createStringTranslation('field', $label);
|
||||
$description_translation = $this->createStringTranslation('field', $description);
|
||||
$value_translation = $this->createStringTranslation('field', $value);
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
// Test untranslated values in default language.
|
||||
$this->drupalGet('test-entity/add/test-bundle');
|
||||
$this->assertText($label, 'Field label is not translated');
|
||||
$this->assertText($description, 'Field description is not translated');
|
||||
$this->assertText($value, 'Field allowed values are not translated');
|
||||
|
||||
// Test translated values in secondary language.
|
||||
$this->drupalGet($this->secondary_language . '/test-entity/add/test-bundle');
|
||||
$this->assertText($label_translation[$this->secondary_language], 'Field label is translated');
|
||||
$this->assertText($description_translation[$this->secondary_language], 'Field description is translated');
|
||||
$this->assertText($value_translation[$this->secondary_language], 'Field allowed values are translated');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the translation of text fields, including default values.
|
||||
*/
|
||||
function testTextFieldTranslation() {
|
||||
$field_name = drupal_strtolower($this->randomName());
|
||||
$label = $this->randomName();
|
||||
$description = $this->randomName();
|
||||
$default_value = $this->randomName();
|
||||
|
||||
$field = array(
|
||||
'field_name' => $field_name,
|
||||
'type' => 'text',
|
||||
'cardinality' => 1,
|
||||
);
|
||||
$field = field_create_field($field);
|
||||
|
||||
$instance = array(
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'test_entity',
|
||||
'bundle' => 'test_bundle',
|
||||
'label' => $label,
|
||||
'description' => $description,
|
||||
'default_value' => array(0 => array('value' => $default_value)),
|
||||
'widget' => array(
|
||||
'type' => 'text_textfield',
|
||||
),
|
||||
);
|
||||
$instance = field_create_instance($instance);
|
||||
|
||||
// Refresh i18n_strings.
|
||||
$edit = array('groups[field]' => TRUE);
|
||||
$this->drupalPost('admin/config/regional/translate/i18n_string', $edit, t('Refresh strings'));
|
||||
|
||||
// Save translations for each attribute.
|
||||
$label_translation = $this->createStringTranslation('field', $label);
|
||||
$description_translation = $this->createStringTranslation('field', $description);
|
||||
$default_value_translation = $this->createStringTranslation('field', $default_value);
|
||||
$this->drupalLogin($this->admin_user);
|
||||
|
||||
// Test untranslated values in default language.
|
||||
$this->drupalGet('test-entity/add/test-bundle');
|
||||
$this->assertText($label, 'Field label is not translated');
|
||||
$this->assertText($description, 'Field description is not translated');
|
||||
$this->assertRaw($default_value, 'Default value is not translated');
|
||||
|
||||
// Test translated values in secondary language.
|
||||
$this->drupalGet($this->secondary_language . '/test-entity/add/test-bundle');
|
||||
$this->assertText($label_translation[$this->secondary_language], 'Field label is translated');
|
||||
$this->assertText($description_translation[$this->secondary_language], 'Field description is translated');
|
||||
$this->assertRaw($default_value_translation[$this->secondary_language], 'Default value translated');
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
name = Multilingual forum
|
||||
description = Enables multilingual forum, translates names and containers.
|
||||
dependencies[] = forum
|
||||
dependencies[] = i18n_taxonomy
|
||||
dependencies[] = i18n_node
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
files[] = i18n_forum.test
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Multilingual forum install file.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function i18n_forum_install() {
|
||||
// Set module weight for it to run after core modules and i18n_taxonomy.
|
||||
db_query("UPDATE {system} SET weight = 10 WHERE name = 'i18n_forum' AND type = 'module'");
|
||||
// Make forum vocabulary translatable.
|
||||
if (($vid = variable_get('forum_nav_vocabulary', 0)) && !i18n_taxonomy_vocabulary_mode($vid)) {
|
||||
$vocabulary = taxonomy_vocabulary_load($vid);
|
||||
$vocabulary->i18n_mode = I18N_MODE_LOCALIZE;
|
||||
taxonomy_vocabulary_save($vocabulary);
|
||||
}
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* i18n forum module
|
||||
*
|
||||
* Internationalization (i18n) package.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function i18n_forum_help($path, $arg) {
|
||||
if ($path == 'admin/structure/forum' && ($vocabulary = i18n_forum_vocabulary())) {
|
||||
$base_path = 'admin/structure/taxonomy/' . $vocabulary->machine_name;
|
||||
return t('To translate the forum, <a href="@edit">edit and make it translatable</a>, then <a href="@translate">translate the forum</a> and <a href="@list">its containers and sub-forums</a> on the taxonomy administration page.', array(
|
||||
'@edit' => url($base_path . '/edit'),
|
||||
'@translate' => url($base_path . '/translate'),
|
||||
'@list' => url($base_path . '/list'))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_local_tasks_alter().
|
||||
*/
|
||||
function i18n_forum_menu_local_tasks_alter(&$data, $router_item, $root_path) {
|
||||
// Translate link to 'node/add/forum' on 'forum' sub-pages.
|
||||
if ($root_path == 'forum' || $root_path == 'forum/%') {
|
||||
$tid = (isset($router_item['page_arguments'][0]) ? $router_item['page_arguments'][0]->tid : 0);
|
||||
$forum_term = forum_forum_load($tid);
|
||||
if ($forum_term) {
|
||||
// Loop through all bundles for forum taxonomy vocabulary field.
|
||||
$vid = variable_get('forum_nav_vocabulary', 0);
|
||||
if ($vid && ($vocabulary = taxonomy_vocabulary_load($vid)) && ($field = field_info_field('taxonomy_' . $vocabulary->machine_name))) {
|
||||
foreach ($field['bundles']['node'] as $type) {
|
||||
if (isset($data['actions']['output'][$type])) {
|
||||
$data['actions']['output'][$type]['#link']['title'] = t('Add new @node_type', array('@node_type' => i18n_node_type_name($type, node_type_get_name($type))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter()
|
||||
*/
|
||||
function i18n_forum_form_taxonomy_form_vocabulary_alter(&$form, &$form_state) {
|
||||
$vid = variable_get('forum_nav_vocabulary', 0);
|
||||
if ($vid && !isset($form_state['confirm_delete']) && isset($form['vid']) && $form['vid']['#value'] == $vid) {
|
||||
// Only two options for this vocabulary
|
||||
$replacements = array(
|
||||
'@item_name_multiple' => t('forum containers'),
|
||||
'@item_name_multiple_capitalized' => t('Forum containers'),
|
||||
);
|
||||
$form['i18n_translation']['i18n_mode']['#options'] = i18n_translation_options_list($replacements, array(I18N_MODE_LOCALIZE, I18N_MODE_TRANSLATE));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_view().
|
||||
*
|
||||
* Localize breadcrumb for forum nodes.
|
||||
*/
|
||||
function i18n_forum_node_view($node, $view_mode, $langcode) {
|
||||
if (_forum_node_check_node_type($node)) {
|
||||
if ($view_mode == 'full' && node_is_page($node)) {
|
||||
$vid = variable_get('forum_nav_vocabulary', 0);
|
||||
$vocabulary = taxonomy_vocabulary_load($vid);
|
||||
// Breadcrumb navigation
|
||||
$breadcrumb[] = l(t('Home'), NULL);
|
||||
$breadcrumb[] = l(i18n_taxonomy_vocabulary_name($vocabulary), 'forum');
|
||||
if ($parents = taxonomy_get_parents_all($node->forum_tid)) {
|
||||
$parents = array_reverse($parents);
|
||||
foreach ($parents as $parent) {
|
||||
$breadcrumb[] = l(i18n_taxonomy_term_name($parent), 'forum/' . $parent->tid);
|
||||
}
|
||||
}
|
||||
drupal_set_breadcrumb($breadcrumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_translate_path()
|
||||
*/
|
||||
function i18n_forum_i18n_translate_path($path) {
|
||||
if (strpos($path, 'forum/') === 0 && i18n_forum_mode() & I18N_MODE_TRANSLATE) {
|
||||
return i18n_taxonomy_translate_path($path, 'forum/');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate forums list.
|
||||
*/
|
||||
function i18n_forum_preprocess_forum_list(&$variables) {
|
||||
if (i18n_forum_mode() & I18N_MODE_LOCALIZE) {
|
||||
foreach ($variables['forums'] as $id => $forum) {
|
||||
$variables['forums'][$id]->description = i18n_string('taxonomy:term:' . $forum->tid . ':description', $forum->description);
|
||||
$variables['forums'][$id]->name = i18n_string('taxonomy:term:' . $forum->tid . ':name', $forum->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate forum page.
|
||||
*/
|
||||
function i18n_forum_preprocess_forums(&$variables) {
|
||||
if (i18n_forum_mode()) {
|
||||
$vocabulary = i18n_forum_vocabulary();
|
||||
if (isset($variables['links']['forum'])) {
|
||||
$variables['links']['forum']['title'] = i18n_string('nodetype:type:forum:post_button', 'Post new Forum topic');
|
||||
}
|
||||
// This one is from advanced forum, http://drupal.org/project/advanced_forum
|
||||
if (!empty($variables['forum_description'])) {
|
||||
$variables['forum_description'] = i18n_string('taxonomy:term:' . $variables['tid'] . ':description', $variables['forum_description']);
|
||||
}
|
||||
// Translate breadrumb and page title.
|
||||
$title = $vocabulary_name = !empty($vocabulary->name) ? i18n_taxonomy_vocabulary_name($vocabulary) : '';
|
||||
$breadcrumb[] = l(t('Home'), NULL);
|
||||
if ($variables['tid']) {
|
||||
$breadcrumb[] = l($vocabulary_name, 'forum');
|
||||
}
|
||||
if ($variables['parents']) {
|
||||
$variables['parents'] = array_reverse($variables['parents']);
|
||||
foreach ($variables['parents'] as $p) {
|
||||
if ($p->tid == $variables['tid']) {
|
||||
$title = i18n_taxonomy_term_name($p);
|
||||
}
|
||||
else {
|
||||
$breadcrumb[] = l(i18n_taxonomy_term_name($p), 'forum/' . $p->tid);
|
||||
}
|
||||
}
|
||||
}
|
||||
drupal_set_breadcrumb($breadcrumb);
|
||||
drupal_set_title($title);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forum vocabulary.
|
||||
*/
|
||||
function i18n_forum_vocabulary() {
|
||||
if ($vid = variable_get('forum_nav_vocabulary', 0)) {
|
||||
return taxonomy_vocabulary_load($vid);
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get forum vocabulary translation mode.
|
||||
*/
|
||||
function i18n_forum_mode($mode = NULL) {
|
||||
if ($vocabulary = i18n_forum_vocabulary()) {
|
||||
return i18n_taxonomy_vocabulary_mode($vocabulary);
|
||||
}
|
||||
else {
|
||||
return I18N_MODE_NONE;
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test case for multilingual forums.
|
||||
*/
|
||||
class i18nForumTestCase extends Drupali18nTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Multilingual forum',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Tests multilingual forum',
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp('translation', 'i18n_select', 'i18n_forum');
|
||||
parent::setUpLanguages();
|
||||
parent::setUpContentTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests i18n_select integration.
|
||||
*/
|
||||
public function testI18nSelectTest() {
|
||||
// @TODO: improve test. its just a quick test against the PDO exception
|
||||
// @see http://drupal.org/node/1437932
|
||||
$this->i18nGet($this->default_language, 'forum');
|
||||
}
|
||||
}
|
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Helper functions for menu administration.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Produces a menu translation form.
|
||||
*/
|
||||
function i18n_menu_translation_form($form, $form_state, $translation_set = NULL, $item = NULL) {
|
||||
$translation_set = $translation_set ? $translation_set : i18n_translation_set_create('menu_link');
|
||||
$form['translation_set'] = array('#type' => 'value', '#value' => $translation_set);
|
||||
$translations = $translation_set->get_translations();
|
||||
// What to do with title? Let's make it a hidden field for now, some tests relay on it
|
||||
$form['title'] = array('#type' => 'hidden', '#default_value' => $translation_set->title);
|
||||
if ($item && ($lang = i18n_object_langcode($item))) {
|
||||
$translations[$lang] = $item;
|
||||
}
|
||||
$item = $item ? $item : array('mlid' => 0, 'menu_name' => '', 'plid' => 0);
|
||||
$item_lang = i18n_object_langcode($item);
|
||||
$form['translations'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Translations'),
|
||||
'#tree' => TRUE,
|
||||
'#description' => t('Enter items that will be considered as translations of each other.'),
|
||||
);
|
||||
foreach (i18n_language_list() as $langcode => $language_name) {
|
||||
if ($langcode == $item_lang) {
|
||||
// We've got a predefined item for this language
|
||||
$form['translations'][$langcode] = array('#type' => 'value', '#value' => $item['menu_name'] . ':' . $item['mlid']);
|
||||
$form['translations']['display'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => $language_name,
|
||||
'#markup' => check_plain($item['link_title']),
|
||||
);
|
||||
}
|
||||
else {
|
||||
// Generate a list of possible parents (not including this link or descendants).
|
||||
$options = i18n_menu_parent_options(menu_get_menus(), $item, $langcode);
|
||||
$default = isset($translations[$langcode]) ? $translations[$langcode]['menu_name'] . ':' . $translations[$langcode]['mlid'] : 'navigation:0';
|
||||
if (!isset($options[$default])) {
|
||||
$default = 'navigation:0';
|
||||
}
|
||||
$form['translations'][$langcode] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => $language_name,
|
||||
'#default_value' => $default,
|
||||
'#options' => $options,
|
||||
'#description' => t('The maximum depth for a link and all its children is fixed at !maxdepth. Some menu links may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
|
||||
'#attributes' => array('class' => array('menu-title-select')),
|
||||
);
|
||||
}
|
||||
}
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['update'] = array('#type' => 'submit', '#value' => t('Save'));
|
||||
if ($translation_set) {
|
||||
$form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process form validation
|
||||
*/
|
||||
function i18n_menu_translation_form_validate($form, &$form_state) {
|
||||
if ($form_state['values']['op'] == t('Save')) {
|
||||
$selected = 0;
|
||||
// example array('en' => 'navigation:0')
|
||||
$mlids = array_filter($form_state['values']['translations']);
|
||||
foreach ($mlids as $lang => $item_name) {
|
||||
list($menu_name, $mlid) = explode(':', $item_name);
|
||||
if ($mlid && ($item = menu_link_load($mlid)) && i18n_object_langcode($item)) {
|
||||
$selected++;
|
||||
}
|
||||
else {
|
||||
unset($form_state['values']['translations'][$lang]);
|
||||
}
|
||||
}
|
||||
if ($selected < 1) {
|
||||
form_set_error('translations', t('There are no translations to save.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu item translation form submission
|
||||
*/
|
||||
function i18n_menu_translation_form_submit($form, &$form_state) {
|
||||
$translation_set = $form_state['values']['translation_set'];
|
||||
|
||||
switch ($form_state['values']['op']) {
|
||||
case t('Save'):
|
||||
$mlids = array_filter($form_state['values']['translations']);
|
||||
$translation_set->reset_translations();
|
||||
foreach ($mlids as $lang => $item_name) {
|
||||
list($menu_name, $mlid) = explode(':', $item_name);
|
||||
$item = menu_link_load($mlid);
|
||||
$translation_set->add_item($item, $lang);
|
||||
}
|
||||
$translation_set->title = !empty($form_state['values']['title']) ? $form_state['values']['title'] : '';
|
||||
$translation_set->save(TRUE);
|
||||
drupal_set_message(t('The item translation has been saved.'));
|
||||
break;
|
||||
case t('Delete'):
|
||||
$translation_set->delete(TRUE);
|
||||
drupal_set_message(t('The item translation has been deleted.'));
|
||||
break;
|
||||
}
|
||||
|
||||
$form_state['redirect'] = 'admin/structure/menu';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of menu items that are valid possible parents for the given menu item.
|
||||
*
|
||||
* @param $menus
|
||||
* An array of menu names and titles, such as from menu_get_menus().
|
||||
* @param $item
|
||||
* The menu item or the node type for which to generate a list of parents.
|
||||
* If $item['mlid'] == 0 then the complete tree is returned.
|
||||
* @return
|
||||
* An array of menu link titles keyed on the a string containing the menu name
|
||||
* and mlid. The list excludes the given item and its children.
|
||||
*
|
||||
* @todo This has to be turned into a #process form element callback. The
|
||||
* 'menu_override_parent_selector' variable is entirely superfluous.
|
||||
*/
|
||||
function i18n_menu_parent_options($menus, $item, $langcode) {
|
||||
// The menu_links table can be practically any size and we need a way to
|
||||
// allow contrib modules to provide more scalable pattern choosers.
|
||||
// hook_form_alter is too late in itself because all the possible parents are
|
||||
// retrieved here, unless menu_override_parent_selector is set to TRUE.
|
||||
if (variable_get('i18n_menu_override_parent_selector', FALSE)) {
|
||||
return array();
|
||||
}
|
||||
// If no menu item, create a dummy one
|
||||
$item = $item ? $item : array('mlid' => 0);
|
||||
// Get menus that have a language or have language for terms
|
||||
$available_menus = array();
|
||||
foreach (menu_load_all() as $name => $menu) {
|
||||
if ($menu['i18n_mode'] & I18N_MODE_TRANSLATE) {
|
||||
$available_menus[$name] = $menu;
|
||||
}
|
||||
elseif ($menu['i18n_mode'] & I18N_MODE_LANGUAGE && $menu['language'] == $langcode) {
|
||||
$available_menus[$name] = $menu;
|
||||
}
|
||||
}
|
||||
// Disable i18n selection, enable after the query.
|
||||
$previous = i18n_select(FALSE);
|
||||
$options = _i18n_menu_get_options($menus, $available_menus, $item, $langcode);
|
||||
i18n_select($previous);
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get the items of the given menu.
|
||||
*/
|
||||
function _i18n_menu_get_options($menus, $available_menus, $item, $langcode) {
|
||||
// If the item has children, there is an added limit to the depth of valid parents.
|
||||
if (isset($item['parent_depth_limit'])) {
|
||||
$limit = $item['parent_depth_limit'];
|
||||
}
|
||||
else {
|
||||
$limit = _menu_parent_depth_limit($item);
|
||||
}
|
||||
|
||||
$options = array();
|
||||
foreach ($menus as $menu_name => $title) {
|
||||
if (isset($available_menus[$menu_name])) {
|
||||
if ($tree = i18n_menu_tree_all_data($menu_name, $langcode, NULL)) {
|
||||
$options[$menu_name . ':0'] = '<' . $title . '>';
|
||||
_menu_parents_recurse($tree, $menu_name, '--', $options, $item['mlid'], $limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out menu items that have a different language
|
||||
*/
|
||||
function i18n_menu_tree_all_data($menu_name, $langcode, $link = NULL, $max_depth = NULL) {
|
||||
$tree = menu_tree_all_data($menu_name, $link, $max_depth);
|
||||
return _i18n_menu_tree_filter_items($tree, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out menu items that have a different language
|
||||
*/
|
||||
function _i18n_menu_tree_filter_items($tree, $langcode) {
|
||||
$result = array();
|
||||
foreach ($tree as $key => $item) {
|
||||
$lang = i18n_object_langcode($item['link']);
|
||||
if (!empty($item['below'])) {
|
||||
$item['below'] = _i18n_menu_tree_filter_items($item['below'], $langcode);
|
||||
}
|
||||
if (!empty($item['link']['customized']) && $lang == $langcode) {
|
||||
$result[$key] = $item;
|
||||
}
|
||||
elseif (!empty($item['below'])) {
|
||||
// Keep for the tree but mark as unselectable.
|
||||
$item['link']['title'] = '(' . $item['link']['title'] . ')';
|
||||
$result[$key] = $item;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for menu translation tab.
|
||||
*/
|
||||
function i18n_menu_translation_item_overview($item, $translation_set = NULL) {
|
||||
if ($item['i18n_tsid']) {
|
||||
// Already part of a set, grab that set.
|
||||
$translation_set = i18n_translation_set_load($item['i18n_tsid']);
|
||||
$translations = $translation_set->get_translations();
|
||||
}
|
||||
else {
|
||||
// We have no translation source mlid, this could be a new set, emulate that.
|
||||
$translations = array($item['language'] => $item);
|
||||
}
|
||||
|
||||
$type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
|
||||
$header = array(t('Language'), t('Title'), t('Operations'));
|
||||
$rows = array();
|
||||
|
||||
foreach (i18n_language_list() as $langcode => $language_name) {
|
||||
$options = array();
|
||||
if (isset($translations[$langcode])) {
|
||||
// Existing translation in the translation set: display status.
|
||||
$translation_item = menu_link_load($translations[$langcode]['mlid']);
|
||||
$title = l($translation_item['link_title'], $translation_item['link_path']);
|
||||
$path = 'admin/structure/menu/item/' . $translation_item['mlid'];
|
||||
$options[] = l(t('edit'), $path);
|
||||
|
||||
if ($translation_item['mlid'] == $item['mlid']) {
|
||||
$language_name = t('<strong>@language_name</strong> (source)', array('@language_name' => $language_name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No such translation in the set yet: help user to create it.
|
||||
$title = t('n/a');
|
||||
$options[] = l(t('add translation'), 'admin/structure/menu/manage/' . $item['menu_name'] . '/add', array('query' => array('translation' => $item['mlid'], 'target' => $langcode) + drupal_get_destination()));
|
||||
}
|
||||
$rows[] = array($language_name, $title, implode(" | ", $options));
|
||||
}
|
||||
|
||||
drupal_set_title(t('Translations of menu item %title', array('%title' => $item['link_title'])), PASS_THROUGH);
|
||||
|
||||
$build['translation_item_overview'] = array(
|
||||
'#theme' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
);
|
||||
|
||||
return $build;
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) hooks
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_object_info().
|
||||
*/
|
||||
function i18n_menu_i18n_object_info() {
|
||||
$info['menu'] = array(
|
||||
'title' => t('Menu'),
|
||||
'key' => 'menu_name',
|
||||
'load callback' => 'menu_load',
|
||||
'base path' => 'admin/structure/menu/manage',
|
||||
'placeholders' => array(
|
||||
'%menu' => 'menu_name',
|
||||
),
|
||||
'edit path' => 'admin/structure/menu/manage/%menu/edit',
|
||||
// Auto-generate translate tab.
|
||||
'translate tab' => 'admin/structure/menu/manage/%menu/translate',
|
||||
// We can easily list all these objects
|
||||
'list callback' => 'menu_load_all',
|
||||
// Metadata for string translation
|
||||
'string translation' => array(
|
||||
'textgroup' => 'menu',
|
||||
'type' => 'menu',
|
||||
'properties' => array(
|
||||
'title' => t('Title'),
|
||||
'description' => t('Description'),
|
||||
),
|
||||
),
|
||||
'translation container' => array(
|
||||
'name' => t('menu'),
|
||||
'item type' => 'menu_link',
|
||||
'item name' => t('menu items'),
|
||||
'options' => array(I18N_MODE_NONE, I18N_MODE_MULTIPLE, I18N_MODE_LANGUAGE),
|
||||
),
|
||||
);
|
||||
$info['menu_link'] = array(
|
||||
'title' => t('Menu link'),
|
||||
'class' => 'i18n_menu_link',
|
||||
'key' => 'mlid',
|
||||
'load callback' => 'menu_link_load',
|
||||
'base path' => 'admin/structure/menu/item',
|
||||
'edit path' => 'admin/structure/menu/item/%menu_link/edit',
|
||||
// Auto-generate translate tab
|
||||
'translate tab' => 'admin/structure/menu/item/%menu_link/translate',
|
||||
'placeholders' => array(
|
||||
'%menu_link' => 'mlid',
|
||||
'%menu' => 'menu_name',
|
||||
),
|
||||
'string translation' => array(
|
||||
'textgroup' => 'menu',
|
||||
'type' => 'item',
|
||||
'properties' => array(
|
||||
'title' => array(
|
||||
'title' => t('Title'),
|
||||
'field' => 'link_title',
|
||||
),
|
||||
'description' => array(
|
||||
'title' => t('Description'),
|
||||
'field' => 'options.attributes.title',
|
||||
),
|
||||
),
|
||||
),
|
||||
'translation set' => TRUE,
|
||||
);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_translation_set_info()
|
||||
*/
|
||||
function i18n_menu_i18n_translation_set_info() {
|
||||
$info['menu_link'] = array(
|
||||
'title' => t('Menu link'),
|
||||
'class' => 'i18n_menu_link_translation_set',
|
||||
'table' => 'menu_links',
|
||||
'field' => 'i18n_tsid',
|
||||
'parent' => 'menu',
|
||||
'placeholder' => '%i18n_menu_translation',
|
||||
'list path' => 'admin/structure/menu/manage/translation',
|
||||
'edit path' => 'admin/structure/menu/manage/translation/edit/%i18n_menu_translation',
|
||||
'delete path' => 'admin/structure/menu/manage/translation/delete/%i18n_menu_translation',
|
||||
'page callback' => 'i18n_menu_item_translation_page',
|
||||
);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_string_info()
|
||||
*/
|
||||
function i18n_menu_i18n_string_info() {
|
||||
$groups['menu'] = array(
|
||||
'title' => t('Menu'),
|
||||
'description' => t('Translatable menu items: title and description.'),
|
||||
'format' => FALSE, // This group doesn't have strings with format
|
||||
'list' => TRUE, // This group can list all strings
|
||||
);
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_string_objects()
|
||||
*/
|
||||
function i18n_menu_i18n_string_objects($type) {
|
||||
if ($type == 'menu_link') {
|
||||
// All menu items that have no language and are customized.
|
||||
return db_select('menu_links', 'm')
|
||||
->fields('m')
|
||||
->condition('language', LANGUAGE_NONE)
|
||||
->condition('customized', 1)
|
||||
->execute()
|
||||
->fetchAllAssoc('mlid', PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for menu item translation tab.
|
||||
*/
|
||||
function i18n_menu_item_translation_page($type, $item) {
|
||||
module_load_include('admin.inc', 'i18n_menu');
|
||||
// If the item has a language code, we can only support translation sets.
|
||||
$translation_set = !empty($item['i18n_tsid']) ? i18n_translation_set_load($item['i18n_tsid']) : NULL;
|
||||
$build['overview'] = i18n_menu_translation_item_overview($item, $translation_set);
|
||||
$build['translation_form'] = drupal_get_form('i18n_menu_translation_form', $translation_set, $item);
|
||||
return $build;
|
||||
}
|
||||
|
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) module - Translation set
|
||||
*/
|
||||
class i18n_menu_link_translation_set extends i18n_translation_set {
|
||||
/**
|
||||
* Load all path translations
|
||||
*/
|
||||
public function load_translations() {
|
||||
$translations = array();
|
||||
$query = db_select('menu_links', 'ml');
|
||||
$query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
|
||||
$query->fields('ml');
|
||||
// Weight should be taken from {menu_links}, not {menu_router}.
|
||||
$query->addField('ml', 'weight', 'link_weight');
|
||||
$query->fields('m');
|
||||
$query->condition('ml.i18n_tsid', $this->tsid);
|
||||
foreach ($query->execute()->fetchAll(PDO::FETCH_ASSOC) as $item) {
|
||||
$item['weight'] = $item['link_weight'];
|
||||
_menu_link_translate($item);
|
||||
$translations[$item['language']] = $item;
|
||||
}
|
||||
return $translations;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu link object
|
||||
*/
|
||||
class i18n_menu_link extends i18n_string_object_wrapper {
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct($type, $key, $object) {
|
||||
// Unserialize options if not done
|
||||
if (isset($object['options']) && !is_array($object['options'])) {
|
||||
$object['options'] = unserialize($object['options']);
|
||||
}
|
||||
parent::__construct($type, $key, $object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path for item
|
||||
*/
|
||||
public function get_path() {
|
||||
return $this->object['link_path'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title from item
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->object['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Translation mode for object
|
||||
*/
|
||||
public function get_translate_mode() {
|
||||
$mode = i18n_menu_mode($this->object['menu_name']);
|
||||
if ($this->get_langcode()) {
|
||||
return $mode & I18N_MODE_TRANSLATE;
|
||||
}
|
||||
elseif (!empty($this->object['customized'])) {
|
||||
return $mode & I18N_MODE_LOCALIZE;
|
||||
}
|
||||
else {
|
||||
return I18N_MODE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Access to object translation. This should check object properties and permissions
|
||||
*/
|
||||
protected function translate_access() {
|
||||
return user_access('administer menu') && user_access('translate interface');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translatable properties.
|
||||
*
|
||||
* Check whether title or description are to be translated by default menu
|
||||
* system.
|
||||
*/
|
||||
protected function build_properties() {
|
||||
$properties = parent::build_properties();
|
||||
if ($properties) {
|
||||
$strings = &$properties['menu']['item'][$this->get_key()];
|
||||
$localizable = _i18n_menu_link_localizable_properties($this->object);
|
||||
foreach ($strings as $key => $data) {
|
||||
if (!in_array($key, $localizable)) {
|
||||
unset($strings[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $properties;
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
name = Menu translation
|
||||
description = Supports translatable custom menu items.
|
||||
dependencies[] = i18n
|
||||
dependencies[] = menu
|
||||
dependencies[] = i18n_string
|
||||
dependencies[] = i18n_translation
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
|
||||
files[] = i18n_menu.inc
|
||||
files[] = i18n_menu.test
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation file for i18nmenu module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function i18n_menu_install() {
|
||||
// Set module weight for it to run after core modules, but before views.
|
||||
db_query("UPDATE {system} SET weight = 5 WHERE name = 'i18n_menu' AND type = 'module'");
|
||||
module_load_install('i18n');
|
||||
i18n_install_create_fields('menu_links', array('language', 'i18n_tsid'));
|
||||
i18n_install_create_fields('menu_custom', array('language', 'i18n_mode'));
|
||||
// If updating from D6, module changed name
|
||||
if (variable_get('i18n_drupal6_update')) {
|
||||
i18n_menu_update_7000();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function i18n_menu_uninstall() {
|
||||
db_drop_field('menu_links', 'language');
|
||||
db_drop_field('menu_links', 'i18n_tsid');
|
||||
db_drop_field('menu_custom', 'language');
|
||||
db_drop_field('menu_custom', 'i18n_mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema_alter().
|
||||
*/
|
||||
function i18n_menu_schema_alter(&$schema) {
|
||||
$schema['menu_links']['fields']['language'] = array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => LANGUAGE_NONE);
|
||||
$schema['menu_links']['fields']['i18n_tsid'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0);
|
||||
$schema['menu_custom']['fields']['language'] = array('type' => 'varchar', 'length' => 12, 'not null' => TRUE, 'default' => LANGUAGE_NONE);
|
||||
$schema['menu_custom']['fields']['i18n_mode'] = array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update menu items language field from Drupal 6
|
||||
*/
|
||||
function i18n_menu_update_7000() {
|
||||
// @todo
|
||||
}
|
||||
|
||||
/**
|
||||
* Set alter property for menu items with language.
|
||||
*/
|
||||
function i18n_menu_update_7001() {
|
||||
// Compile a list of menus with i18n options.
|
||||
$i18n_menus = array_filter(menu_get_names(), 'i18n_menu_mode');
|
||||
if ($i18n_menus) {
|
||||
$query = db_select('menu_links', 'm')
|
||||
->fields('m')
|
||||
->condition('menu_name', $i18n_menus);
|
||||
foreach ($query->execute()->fetchAllAssoc('mlid', PDO::FETCH_ASSOC) as $mlid => $item) {
|
||||
$options = unserialize($item['options']);
|
||||
if (_i18n_menu_link_check_alter($item) && empty($options['alter'])) {
|
||||
$options['alter'] = TRUE;
|
||||
db_update('menu_links')
|
||||
->condition('mlid', $mlid)
|
||||
->fields(array('options' => serialize($options)))
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,987 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) submodule: Menu translation.
|
||||
*
|
||||
* @author Jose A. Reyero, 2005
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu()
|
||||
*/
|
||||
function i18n_menu_menu() {
|
||||
$items['admin/structure/menu/manage/translation'] = array(
|
||||
'title' => 'Translation sets',
|
||||
'page callback' => 'i18n_translation_set_list_manage',
|
||||
'page arguments' => array('menu_link'),
|
||||
'access arguments' => array('administer menu'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 10,
|
||||
);
|
||||
$items['admin/structure/menu/manage/translation/add'] = array(
|
||||
'title' => 'Add translation',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('i18n_menu_translation_form'),
|
||||
'access arguments' => array('administer menu'),
|
||||
'type' => MENU_LOCAL_ACTION,
|
||||
'file' => 'i18n_menu.admin.inc',
|
||||
);
|
||||
$items['admin/structure/menu/manage/translation/edit/%i18n_menu_translation'] = array(
|
||||
'title' => 'Edit translation',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('i18n_menu_translation_form', 6),
|
||||
'access arguments' => array('administer menu'),
|
||||
'type' => MENU_CALLBACK,
|
||||
'file' => 'i18n_menu.admin.inc',
|
||||
);
|
||||
$items['admin/structure/menu/manage/translation/delete/%i18n_menu_translation'] = array(
|
||||
'title' => 'Delete translation',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('i18n_translation_set_delete_confirm', 6),
|
||||
'access arguments' => array('administer menu'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_alter()
|
||||
*/
|
||||
function i18n_menu_menu_alter(&$items) {
|
||||
$items['admin/structure/menu/item/%menu_link'] = $items['admin/structure/menu/item/%menu_link/edit'];
|
||||
$items['admin/structure/menu/item/%menu_link']['type'] = MENU_CALLBACK;
|
||||
$items['admin/structure/menu/item/%menu_link/edit']['type'] = MENU_DEFAULT_LOCAL_TASK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_view().
|
||||
*/
|
||||
function i18n_menu_block_view_alter(&$data, $block) {
|
||||
if (($block->module == 'menu' || $block->module == 'system') && (i18n_menu_mode($block->delta) & I18N_MODE_MULTIPLE)) {
|
||||
$menus = menu_get_menus();
|
||||
if (isset($menus[$block->delta])) {
|
||||
if (empty($block->title)) {
|
||||
$data['subject'] = i18n_string_plain(
|
||||
array('menu', 'menu', $block->delta, 'title'),
|
||||
$menus[$block->delta]
|
||||
);
|
||||
}
|
||||
// Add contextual links for this block.
|
||||
if (!empty($data['content'])) {
|
||||
$data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($block->delta));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_translate_path()
|
||||
*/
|
||||
function i18n_menu_i18n_translate_path($path) {
|
||||
$item = i18n_menu_link_load($path, i18n_langcode());
|
||||
if ($item && ($set = i18n_translation_object('menu_link', $item))) {
|
||||
$links = array();
|
||||
foreach ($set->get_translations() as $lang => $link) {
|
||||
$links[$lang] = array(
|
||||
'href' => $link['link_path'],
|
||||
'title' => $link['link_title'],
|
||||
'i18n_type' => 'menu_link',
|
||||
'i18n_object' => $link,
|
||||
);
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_insert()
|
||||
*/
|
||||
function i18n_menu_menu_insert($menu) {
|
||||
i18n_menu_menu_update($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_update()
|
||||
*/
|
||||
function i18n_menu_menu_update($menu) {
|
||||
// Stores the fields of menu links which need an update.
|
||||
$update = array();
|
||||
|
||||
if (!isset($menu['i18n_mode'])) {
|
||||
$menu['i18n_mode'] = I18N_MODE_NONE;
|
||||
}
|
||||
if (!($menu['i18n_mode'] & I18N_MODE_LANGUAGE)) {
|
||||
$menu['language'] = LANGUAGE_NONE;
|
||||
}
|
||||
db_update('menu_custom')
|
||||
->fields(array('language' => $menu['language'], 'i18n_mode' => $menu['i18n_mode']))
|
||||
->condition('menu_name', $menu['menu_name'])
|
||||
->execute();
|
||||
if (!$menu['i18n_mode']) {
|
||||
$update['language'] = LANGUAGE_NONE;
|
||||
}
|
||||
elseif ($menu['i18n_mode'] & I18N_MODE_LANGUAGE) {
|
||||
$update['language'] = $menu['language'];
|
||||
}
|
||||
|
||||
// Non translatable menu.
|
||||
if (!($menu['i18n_mode'] & I18N_MODE_TRANSLATE)) {
|
||||
$tsids = db_select('menu_links')
|
||||
->fields('menu_links', array('i18n_tsid'))
|
||||
->groupBy('i18n_tsid')
|
||||
->condition('menu_name', $menu['menu_name'])
|
||||
->condition('customized', 1)
|
||||
->condition('i18n_tsid', 0, '<>')
|
||||
->execute()
|
||||
->fetchCol(0);
|
||||
if (!empty($tsids)) {
|
||||
foreach ($tsids as $tsid) {
|
||||
if ($translation_set = i18n_translation_set_load($tsid)) {
|
||||
$translation_set->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
$update['i18n_tsid'] = 0;
|
||||
}
|
||||
|
||||
if (!empty($update)) {
|
||||
db_update('menu_links')
|
||||
->fields($update)
|
||||
->condition('menu_name', $menu['menu_name'])
|
||||
->condition('customized', 1)
|
||||
->execute();
|
||||
}
|
||||
|
||||
// Update strings, always add translation if no language
|
||||
if (!i18n_object_langcode($menu)) {
|
||||
i18n_string_object_update('menu', $menu);
|
||||
}
|
||||
|
||||
// Clear all menu caches.
|
||||
menu_cache_clear_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_delete()
|
||||
*/
|
||||
function i18n_menu_menu_delete($menu) {
|
||||
i18n_string_object_remove('menu', $menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_link_alter().
|
||||
*
|
||||
* This function is invoked from menu_link_save() before default
|
||||
* menu link options (menu_name, module, etc.. have been set)
|
||||
*/
|
||||
function i18n_menu_menu_link_alter(&$item) {
|
||||
// We just make sure every link has a valid language property.
|
||||
if (!i18n_object_langcode($item)) {
|
||||
$item['language'] = LANGUAGE_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_link_insert()
|
||||
*/
|
||||
function i18n_menu_menu_link_insert($link) {
|
||||
i18n_menu_menu_link_update($link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_link_update().
|
||||
*/
|
||||
function i18n_menu_menu_link_update($link) {
|
||||
// Stores the fields to update.
|
||||
$fields = array();
|
||||
$menu_mode = i18n_menu_mode($link['menu_name']);
|
||||
|
||||
if ($menu_mode & I18N_MODE_TRANSLATE && isset($link['language'])) {
|
||||
// Multilingual menu links, translatable, it may be part of a
|
||||
// translation set.
|
||||
if (i18n_object_langcode($link)) {
|
||||
if (!empty($link['translation_set'])) {
|
||||
// Translation set comes as parameter, we may be creating a translation,
|
||||
// add link to the set.
|
||||
$translation_set = $link['translation_set'];
|
||||
$translation_set
|
||||
->add_item($link)
|
||||
->save(TRUE);
|
||||
}
|
||||
}
|
||||
elseif ($link['language'] === LANGUAGE_NONE && !empty($link['original_item']['i18n_tsid'])) {
|
||||
if ($translation_set = i18n_translation_set_load($link['original_item']['i18n_tsid'])) {
|
||||
$translation_set->remove_language($link['original_item']['language']);
|
||||
// If there are no links left in this translation set, delete the set.
|
||||
// Otherwise update the set.
|
||||
$translation_set->update_delete();
|
||||
}
|
||||
$fields['i18n_tsid'] = 0;
|
||||
}
|
||||
}
|
||||
// For multilingual menu items, always set a language and mark them for
|
||||
// 'alter' so they can be processed later by
|
||||
// hook_translated_link_menu_alter().
|
||||
if ($menu_mode) {
|
||||
if (!isset($link['language'])) {
|
||||
$link['language'] = LANGUAGE_NONE;
|
||||
}
|
||||
if (_i18n_menu_link_check_alter($link) && empty($link['options']['alter'])) {
|
||||
$fields['options'] = $link['options'];
|
||||
$fields['options']['alter'] = TRUE;
|
||||
}
|
||||
// We cannot unmark links for altering because we don't know what other
|
||||
// modules use it for.
|
||||
}
|
||||
// Update language field if the link has a language value.
|
||||
if (isset($link['language'])) {
|
||||
$fields['language'] = $link['language'];
|
||||
}
|
||||
|
||||
if (!empty($fields)) {
|
||||
// If link options are to be updated, they need to be serialized.
|
||||
if (isset($fields['options'])) {
|
||||
$fields['options'] = serialize($fields['options']);
|
||||
}
|
||||
db_update('menu_links')
|
||||
->fields($fields)
|
||||
->condition('mlid', $link['mlid'])
|
||||
->execute();
|
||||
}
|
||||
// Update translatable strings if any for customized links that belong to a
|
||||
// localizable menu.
|
||||
if (_i18n_menu_link_is_localizable($link)) {
|
||||
i18n_string_object_update('menu_link', $link);
|
||||
}
|
||||
else {
|
||||
i18n_string_object_remove('menu_link', $link);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_delete()
|
||||
*/
|
||||
function i18n_menu_menu_link_delete($link) {
|
||||
// If a translation set exists for this link, remove this link from the set.
|
||||
if (!empty($link['i18n_tsid'])) {
|
||||
if ($translation_set = i18n_translation_set_load($link['i18n_tsid'])) {
|
||||
$translation_set->get_translations();
|
||||
|
||||
$translation_set->remove_language($link['language']);
|
||||
|
||||
// If there are no links left in this translation set, delete the set.
|
||||
// Otherwise update the set.
|
||||
$translation_set->update_delete();
|
||||
}
|
||||
}
|
||||
|
||||
i18n_string_object_remove('menu_link', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu mode or compare with given one
|
||||
*/
|
||||
function i18n_menu_mode($name, $mode = NULL) {
|
||||
$menu = menu_load($name);
|
||||
if (!$menu || !isset($menu['i18n_mode'])) {
|
||||
return isset($mode) ? FALSE : I18N_MODE_NONE;
|
||||
}
|
||||
else {
|
||||
return isset($mode) ? $menu['i18n_mode'] & $mode : $menu['i18n_mode'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_translated_menu_link_alter().
|
||||
*
|
||||
* Translate localizable menu links on the fly.
|
||||
* Filter out items that have a different language from current interface.
|
||||
*
|
||||
* @see i18n_menu_menu_link_alter()
|
||||
*/
|
||||
function i18n_menu_translated_menu_link_alter(&$item) {
|
||||
// Only process links to be displayed not processed before by i18n_menu.
|
||||
if (_i18n_menu_link_process($item)) {
|
||||
if (!_i18n_menu_link_is_visible($item)) {
|
||||
$item['hidden'] = TRUE;
|
||||
}
|
||||
elseif (_i18n_menu_link_is_localizable($item)) {
|
||||
// Item has undefined language, it is a candidate for localization.
|
||||
_i18n_menu_link_localize($item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function i18n_menu_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#i18n_menu' :
|
||||
$output = '<p>' . t('This module adds support for multilingual menus. You can setup multilingual options for each menu:') . '</p>';
|
||||
$output .= '<ul>';
|
||||
$output .= '<li>' . t('Menus can be fully multilingual with translatable (or localized) menu items.') . '</li>';
|
||||
$output .= '<li>' . t('Menus can be configured to have a fixed language. All menu items share this language setting and the menu will be visible in that language only.') . '</li>';
|
||||
$output .= '<li>' . t('Menus can also be configured to have no translations.') . '</li>';
|
||||
$output .= '</ul>';
|
||||
$output .= '<p>' . t('The multilingual options of a menu must be configured before individual menu items can be translated. Go to the <a href="@menu-admin">Menus administration page</a> and follow the "edit menu" link to the menu in question.', array('@menu-admin' => url('admin/structure/menu') ) ) . '</p>';
|
||||
$output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</p>';
|
||||
return $output;
|
||||
|
||||
case 'admin/config/regional/i18n':
|
||||
$output = '<p>' . t('Menus and menu items can be translated on the <a href="@configure_menus">Menu administration page</a>.', array('@configure_menus' => url('admin/structure/menu'))) . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_variable_info_alter()
|
||||
*/
|
||||
function i18n_menu_variable_info_alter(&$variables, $options) {
|
||||
// Make menu variables translatable
|
||||
$variables['menu_main_links_source']['localize'] = TRUE;
|
||||
$variables['menu_secondary_links_source']['localize'] = TRUE;
|
||||
$variables['menu_parent_[node_type]']['localize'] = TRUE;
|
||||
$variables['menu_options_[node_type]']['localize'] = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get localized menu tree.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu the translated tree has to be fetched from.
|
||||
* @param string $langcode
|
||||
* Optional language code to get the menu in, defaults to request language.
|
||||
* @param bool $reset
|
||||
* Whether to reset the internal i18n_menu_translated_tree cache.
|
||||
*/
|
||||
function i18n_menu_translated_tree($menu_name, $langcode = NULL, $reset = FALSE) {
|
||||
$menu_output = &drupal_static(__FUNCTION__);
|
||||
$langcode = $langcode ? $langcode : i18n_language_interface()->language;
|
||||
if (!isset($menu_output[$langcode][$menu_name]) || $reset) {
|
||||
$tree = menu_tree_page_data($menu_name);
|
||||
$tree = i18n_menu_localize_tree($tree, $langcode);
|
||||
$menu_output[$langcode][$menu_name] = menu_tree_output($tree);
|
||||
}
|
||||
return $menu_output[$langcode][$menu_name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize menu tree.
|
||||
*/
|
||||
function i18n_menu_localize_tree($tree, $langcode = NULL) {
|
||||
$langcode = $langcode ? $langcode : i18n_language_interface()->language;
|
||||
foreach ($tree as $index => &$item) {
|
||||
$link = $item['link'];
|
||||
// We only process links that are visible and not processed before.
|
||||
if (_i18n_menu_link_process($item['link'])) {
|
||||
if (!_i18n_menu_link_is_visible($item['link'], $langcode)) {
|
||||
// Remove links for other languages than current.
|
||||
// Links with language wont be localized.
|
||||
unset($tree[$index]);
|
||||
// @todo Research whether the above has any advantage over:
|
||||
// $item['hidden'] = TRUE;
|
||||
}
|
||||
else {
|
||||
if (_i18n_menu_link_is_localizable($item['link'])) {
|
||||
// Item has undefined language, it is a candidate for localization.
|
||||
_i18n_menu_link_localize($item['link'], $langcode);
|
||||
}
|
||||
// Localize subtree.
|
||||
if (!empty($item['below'])) {
|
||||
$item['below'] = i18n_menu_localize_tree($item['below'], $langcode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize menu renderable array
|
||||
*/
|
||||
function i18n_menu_localize_elements(&$elements) {
|
||||
foreach (element_children($elements) as $mlid) {
|
||||
$elements[$mlid]['#title'] = i18n_string(array('menu', 'item', $mlid, 'title'), $elements[$mlid]['#title']);
|
||||
if (!empty($tree[$mlid]['#localized_options']['attributes']['title'])) {
|
||||
$elements[$mlid]['#localized_options']['attributes']['title'] = i18n_string(array('menu', 'item', $mlid, 'description'), $tree[$mlid]['#localized_options']['attributes']['title']);
|
||||
}
|
||||
i18n_menu_localize_elements($elements[$mlid]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of localized links for a navigation menu.
|
||||
*
|
||||
* Localized version of menu_navigation_links()
|
||||
*/
|
||||
function i18n_menu_navigation_links($menu_name, $level = 0) {
|
||||
// Don't even bother querying the menu table if no menu is specified.
|
||||
if (empty($menu_name)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
// Get the menu hierarchy for the current page.
|
||||
$tree = menu_tree_page_data($menu_name, $level + 1);
|
||||
$tree = i18n_menu_localize_tree($tree);
|
||||
|
||||
// Go down the active trail until the right level is reached.
|
||||
while ($level-- > 0 && $tree) {
|
||||
// Loop through the current level's items until we find one that is in trail.
|
||||
while ($item = array_shift($tree)) {
|
||||
if ($item['link']['in_active_trail']) {
|
||||
// If the item is in the active trail, we continue in the subtree.
|
||||
$tree = empty($item['below']) ? array() : $item['below'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a single level of links.
|
||||
$router_item = menu_get_item();
|
||||
$links = array();
|
||||
foreach ($tree as $item) {
|
||||
if (!$item['link']['hidden']) {
|
||||
$class = '';
|
||||
$l = $item['link']['localized_options'];
|
||||
$l['href'] = $item['link']['href'];
|
||||
$l['title'] = $item['link']['title'];
|
||||
if ($item['link']['in_active_trail']) {
|
||||
$class = ' active-trail';
|
||||
$l['attributes']['class'][] = 'active-trail';
|
||||
}
|
||||
// Normally, l() compares the href of every link with $_GET['q'] and sets
|
||||
// the active class accordingly. But local tasks do not appear in menu
|
||||
// trees, so if the current path is a local task, and this link is its
|
||||
// tab root, then we have to set the class manually.
|
||||
if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) {
|
||||
$l['attributes']['class'][] = 'active';
|
||||
}
|
||||
// Keyed with the unique mlid to generate classes in theme_links().
|
||||
$links['menu-' . $item['link']['mlid'] . $class] = $l;
|
||||
}
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get localized menu title
|
||||
*/
|
||||
function _i18n_menu_link_title($link, $langcode = NULL) {
|
||||
$key = i18n_object_info('menu_link', 'key');
|
||||
return i18n_string_translate(array('menu', 'item', $link[$key], 'title'), $link['link_title'], array('langcode' => $langcode, 'sanitize' => FALSE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize menu item title and description.
|
||||
*
|
||||
* This will be invoked always after _menu_item_localize()
|
||||
*
|
||||
* Link properties to manage:
|
||||
* - title, menu router title
|
||||
* - link_title, menu link title
|
||||
* - options.attributes.title, menu link description.
|
||||
* - localized_options.attributes.title,
|
||||
*
|
||||
* @see _menu_item_localize()
|
||||
* @see _menu_link_translate()
|
||||
*/
|
||||
function _i18n_menu_link_localize(&$link, $langcode = NULL) {
|
||||
// Only translate title if it has no special callback.
|
||||
if (empty($link['title callback']) || $link['title callback'] === 't') {
|
||||
$link['title'] = _i18n_menu_link_title($link, $langcode);
|
||||
}
|
||||
if ($description = _i18n_menu_link_description($link, $langcode)) {
|
||||
$link['localized_options']['attributes']['title'] = $description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get localized menu description
|
||||
*/
|
||||
function _i18n_menu_link_description($link, $langcode = NULL) {
|
||||
if (!empty($link['options']['attributes']['title'])) {
|
||||
$key = i18n_object_info('menu_link', 'key');
|
||||
return i18n_string_translate(array('menu', 'item', $link[$key], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode));
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this link is to be processed by i18n_menu and start processing.
|
||||
*/
|
||||
function _i18n_menu_link_process(&$link) {
|
||||
// Only visible links that have a language property and haven't been processed
|
||||
// before. We also check that they belong to a menu with language options.
|
||||
if (empty($link['i18n_menu']) && !empty($link['language']) && !empty($link['access']) && empty($link['hidden']) && i18n_menu_mode($link['menu_name'])) {
|
||||
// Mark so it won't be processed twice.
|
||||
$link['i18n_menu'] = TRUE;
|
||||
// Skip if administering this menu or this menu item.
|
||||
if (arg(0) == 'admin' && arg(1) == 'structure' && arg(2) == 'menu') {
|
||||
if (arg(3) == 'manage' && $link['menu_name'] == arg(4)) {
|
||||
return FALSE;
|
||||
}
|
||||
elseif (arg(3) == 'item' && arg(4) == $link['mlid']) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this menu item should be marked for altering.
|
||||
*
|
||||
* Menu items that have a language or that have any localizable strings
|
||||
* will be marked to be run through hook_translated_menu_link_alter().
|
||||
*
|
||||
* @see i18n_menu_translated_menu_link_alter()
|
||||
*/
|
||||
function _i18n_menu_link_check_alter($link) {
|
||||
return i18n_menu_mode($link['menu_name']) && (i18n_object_langcode($link) || _i18n_menu_link_is_localizable($link, TRUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this link should be localized by i18n_menu.
|
||||
*
|
||||
* @param array $link
|
||||
* Menu link array.
|
||||
* @param bool $check_strings
|
||||
* Whether to check if the link has actually localizable strings. Since this
|
||||
* is a more expensive operation, it will be just checked when editing menu
|
||||
* items.
|
||||
*
|
||||
* @return boolean
|
||||
* Returns TRUE if link is localizable.
|
||||
*/
|
||||
function _i18n_menu_link_is_localizable($link, $check_strings = FALSE) {
|
||||
return !empty($link['customized']) && !i18n_object_langcode($link) && i18n_menu_mode($link['menu_name'], I18N_MODE_LOCALIZE) &&
|
||||
(!$check_strings || _i18n_menu_link_localizable_properties($link));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this menu link is visible for current/given language.
|
||||
*/
|
||||
function _i18n_menu_link_is_visible($link, $langcode = NULL) {
|
||||
$langcode = $langcode ? $langcode : i18n_language_interface()->language;
|
||||
return $link['language'] == LANGUAGE_NONE || $link['language'] == $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get localizable properties for menu link checking agains the router item.
|
||||
*/
|
||||
function _i18n_menu_link_localizable_properties($link) {
|
||||
$props = array();
|
||||
$router = !empty($link['router_path']) ? _i18n_menu_get_router($link['router_path']) : NULL;
|
||||
if (!empty($link['link_title'])) {
|
||||
// If the title callback is 't' and the link title matches the router title
|
||||
// it will be localized by core, not by i18n_menu.
|
||||
if (!$router ||
|
||||
(empty($router['title_callback']) || $router['title_callback'] != 't') ||
|
||||
(empty($router['title']) || $router['title'] != $link['link_title'])
|
||||
) {
|
||||
$props[] = 'title';
|
||||
}
|
||||
}
|
||||
if (!empty($link['options']['attributes']['title'])) {
|
||||
// If the description matches the router description, it will be localized
|
||||
// by core.
|
||||
if (!$router || empty($router['description']) || $router['description'] != $link['options']['attributes']['title']) {
|
||||
$props[] = 'description';
|
||||
}
|
||||
}
|
||||
return $props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the menu router for this router path.
|
||||
*
|
||||
* We need the untranslated title to compare, and this will be fast.
|
||||
* There's no api function to do this?
|
||||
*
|
||||
* @param string $path
|
||||
* The path to fetch from the router.
|
||||
*/
|
||||
function _i18n_menu_get_router($path) {
|
||||
$cache = &drupal_static(__FUNCTION__, array());
|
||||
if (!array_key_exists($path, $cache)) {
|
||||
$cache[$path] = db_select('menu_router', 'mr')
|
||||
->fields('mr', array('title', 'title_callback', 'description'))
|
||||
->condition('path', $path)
|
||||
->execute()
|
||||
->fetchAssoc();
|
||||
}
|
||||
return $cache[$path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_menu_form_menu_edit_menu_alter(&$form, &$form_state) {
|
||||
$menu = menu_load($form['old_name']['#value']);
|
||||
$i18n_mode = $menu && isset($menu['i18n_mode']) ? $menu['i18n_mode'] : I18N_MODE_NONE;
|
||||
$langcode = $menu && isset($menu['language']) ? $menu['language'] : LANGUAGE_NONE;
|
||||
|
||||
$form += i18n_translation_mode_element('menu', $i18n_mode, $langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*
|
||||
* Add a language selector to the menu_edit_item form and register a submit
|
||||
* callback to process items.
|
||||
*/
|
||||
function i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
|
||||
$item = &$form['original_item']['#value'];
|
||||
$item['language'] = i18n_menu_item_get_language($item);
|
||||
// Check whether this item belongs to a node object and it is a supported type.
|
||||
$node_item = ($node = i18n_menu_item_get_node($item)) && i18n_menu_node_supported_type($node->type);
|
||||
if (!$node_item && i18n_menu_mode($item['menu_name'], I18N_MODE_TRANSLATE)) {
|
||||
//$form['i18n'] = array('#type' => 'fieldset');
|
||||
$form['i18n']['language'] = array(
|
||||
'#description' => t('This item belongs to a multilingual menu. You can set a language for it.'),
|
||||
) + i18n_element_language_select($item);
|
||||
|
||||
// If the term to be added will be a translation of a source term,
|
||||
// set the default value of the option list to the target language and
|
||||
// create a form element for storing the translation set of the source term.
|
||||
if (isset($_GET['translation']) && isset($_GET['target']) && ($source_item = menu_link_load($_GET['translation']))) {
|
||||
if (!empty($source_item['i18n_tsid'])) {
|
||||
$translation_set = i18n_translation_set_load($source_item['i18n_tsid']);
|
||||
}
|
||||
else {
|
||||
// Create object and stick the source information in the translation set.
|
||||
$translation_set = i18n_translation_set_build('menu_link')
|
||||
->add_item($source_item);
|
||||
}
|
||||
$form['link_path']['#default_value'] = $source_item['link_path'];
|
||||
|
||||
// Maybe we should disable the 'link_path' and 'parent' form elements?
|
||||
// $form['link_path']['#disabled'] = TRUE;
|
||||
// $form['parent']['#disabled'] = TRUE;
|
||||
|
||||
$form['i18n']['language']['#default_value'] = $_GET['target'];
|
||||
$form['i18n']['language']['#disabled'] = TRUE;
|
||||
|
||||
drupal_set_title(t('%language translation of menu item %title', array('%language' => locale_language_name($_GET['target']), '%title' => $source_item['link_title'])), PASS_THROUGH);
|
||||
}
|
||||
elseif (!empty($item['i18n_tsid'])) {
|
||||
$translation_set = i18n_translation_set_load($item['i18n_tsid']);
|
||||
}
|
||||
|
||||
// Add the translation set to the form so we know the new menu item
|
||||
// needs to be added to that set.
|
||||
if (!empty($translation_set)) {
|
||||
$form['translation_set'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $translation_set,
|
||||
);
|
||||
|
||||
// If the current term is part of a translation set,
|
||||
// remove all other languages of the option list.
|
||||
if ($translations = $translation_set->get_translations()) {
|
||||
unset($form['i18n']['language']['#options'][LANGUAGE_NONE]);
|
||||
foreach ($translations as $langcode => $translation) {
|
||||
if ($translation['mlid'] !== $item['mlid']) {
|
||||
unset($form['i18n']['language']['#options'][$langcode]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$form['language'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $item['language'],
|
||||
);
|
||||
}
|
||||
if ($node_item && i18n_langcode($item['language'])) {
|
||||
$form['i18n']['message'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => t('Language'),
|
||||
'#markup' => i18n_language_name($item['language']),
|
||||
'#description' => t('This menu item belongs to a node, so it will have the same language as the node and cannot be localized.'),
|
||||
);
|
||||
}
|
||||
array_unshift($form['#validate'], 'i18n_menu_menu_item_prepare_normal_path');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
* FORM_ID = menu-overview-form.
|
||||
* Add a "translate" link in operations column for each menu item.
|
||||
*/
|
||||
function i18n_menu_form_menu_overview_form_alter(&$form, &$form_state) {
|
||||
foreach (element_children($form) as $element) {
|
||||
if (substr($element, 0, 5) == 'mlid:') {
|
||||
$mlid = $form[$element]['#item']['mlid'];
|
||||
if (i18n_get_object('menu', $mlid)->get_translate_access()) {
|
||||
$form[$element]['operations']['translate'] = array(
|
||||
'#type' => 'link',
|
||||
'#title' => t('translate'),
|
||||
'#href' => "admin/structure/menu/item/{$mlid}/translate",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal path should be checked with menu item's language to avoid
|
||||
* troubles when a node and it's translation has the same url alias.
|
||||
*/
|
||||
function i18n_menu_menu_item_prepare_normal_path($form, &$form_state) {
|
||||
$item = &$form_state['values'];
|
||||
$item['link_path'] = i18n_prepare_normal_path($item['link_path'], $item['language']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language for menu item
|
||||
*/
|
||||
function i18n_menu_item_get_language($item) {
|
||||
if (isset($item['language'])) {
|
||||
return $item['language'];
|
||||
}
|
||||
else {
|
||||
$menu = menu_load($item['menu_name']);
|
||||
if (!isset($menu['i18n_mode'])) {
|
||||
return LANGUAGE_NONE;
|
||||
}
|
||||
switch ($menu['i18n_mode']) {
|
||||
case I18N_MODE_LANGUAGE:
|
||||
return $menu['language'];
|
||||
case I18N_MODE_NONE:
|
||||
case I18N_MODE_LOCALIZE:
|
||||
return LANGUAGE_NONE;
|
||||
default:
|
||||
if (!empty($item['mlid'])) {
|
||||
return db_select('menu_links', 'm')
|
||||
->fields('m', array('language'))
|
||||
->condition('mlid', $item['mlid'])
|
||||
->execute()
|
||||
->fetchField();
|
||||
}
|
||||
else {
|
||||
return LANGUAGE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_node_form_alter().
|
||||
*
|
||||
* Add language to menu settings of the node form, as well as setting defaults
|
||||
* to match the translated item's menu settings.
|
||||
*/
|
||||
function i18n_menu_form_node_form_alter(&$form, &$form_state, $form_id) {
|
||||
if (isset($form['menu'])) {
|
||||
$node = $form['#node'];
|
||||
$link = $node->menu;
|
||||
if (!empty($link['mlid'])) {
|
||||
// Preserve the menu item language whatever it is.
|
||||
$form['menu']['link']['language'] = array('#type' => 'value', '#value' => $link['language']);
|
||||
}
|
||||
elseif (i18n_menu_node_supported_type($node->type)) {
|
||||
// Set menu language to node language but only if it is a supported node type.
|
||||
$form['menu']['link']['language'] = array('#type' => 'value', '#value' => $node->language);
|
||||
}
|
||||
else {
|
||||
$form['menu']['link']['language'] = array('#type' => 'value', '#value' => LANGUAGE_NONE);
|
||||
}
|
||||
// Customized must be set to 1 to save language.
|
||||
$form['menu']['link']['customized'] = array('#type' => 'value', '#value' => 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a node type has multilingual support (but not entity translation).
|
||||
*/
|
||||
function i18n_menu_node_supported_type($type) {
|
||||
$supported = &drupal_static(__FUNCTION__);
|
||||
if (!isset($supported[$type])) {
|
||||
$mode = variable_get('language_content_type_' . $type, 0);
|
||||
$supported[$type] = $mode == 1 || $mode == 2; // 2 == TRANSLATION_ENABLED
|
||||
}
|
||||
return $supported[$type];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the node object for a menu item.
|
||||
*/
|
||||
function i18n_menu_item_get_node($item) {
|
||||
return isset($item['router_path']) && $item['router_path'] == 'node/%' ? node_load(arg(1, $item['link_path'])) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_presave()
|
||||
*
|
||||
* Set menu link language to node language
|
||||
*/
|
||||
function i18n_menu_node_presave($node) {
|
||||
if (!empty($node->menu) && isset($node->language) && i18n_menu_node_supported_type($node->type)) {
|
||||
$node->menu['language'] = i18n_object_langcode($node, LANGUAGE_NONE);
|
||||
// Store node type with menu item so we can quickly access it later.
|
||||
$node->menu['options']['node_type'] = $node->type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_prepare_translation().
|
||||
*/
|
||||
function i18n_menu_node_prepare_translation($node) {
|
||||
if (empty($node->menu['mlid']) && !empty($node->translation_source)) {
|
||||
$tnode = $node->translation_source;
|
||||
// Prepare the tnode so the menu item will be available.
|
||||
node_object_prepare($tnode);
|
||||
$node->menu['link_title'] = $tnode->menu['link_title'];
|
||||
$node->menu['weight'] = $tnode->menu['weight'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process menu and menu item add/edit form submissions.
|
||||
*
|
||||
* @todo See where this fits
|
||||
*/
|
||||
/*
|
||||
function i18n_menu_edit_item_form_submit($form, &$form_state) {
|
||||
$mid = menu_edit_item_save($form_state['values']);
|
||||
db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", array($form_state['values']['language'], $mid));
|
||||
return 'admin/build/menu';
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Load translation set. Menu loading callback.
|
||||
*/
|
||||
function i18n_menu_translation_load($tsid) {
|
||||
return i18n_translation_set_load($tsid, 'menu_link');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load menu item by path, language
|
||||
*/
|
||||
function i18n_menu_link_load($path, $langcode) {
|
||||
$query = db_select('menu_links', 'ml');
|
||||
$query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
|
||||
$query->fields('ml');
|
||||
// Weight should be taken from {menu_links}, not {menu_router}.
|
||||
$query->addField('ml', 'weight', 'link_weight');
|
||||
$query->fields('m');
|
||||
$query->condition('ml.link_path', $path);
|
||||
$query->condition('ml.language', $langcode);
|
||||
if ($item = $query->execute()->fetchAssoc()) {
|
||||
$item['weight'] = $item['link_weight'];
|
||||
_menu_link_translate($item);
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter() for features_menu_links.
|
||||
* Add needed fields to properly serialize localization information.
|
||||
*/
|
||||
function i18n_menu_query_features_menu_link_alter($query) {
|
||||
$query->fields('menu_links', array('language', 'customized'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_init().
|
||||
*/
|
||||
function i18n_menu_init() {
|
||||
|
||||
// The only way to override the default preferred menu link for a path is to
|
||||
// inject it into the static cache of the function menu_link_get_preferred().
|
||||
|
||||
// The problem with the default implementation is that it does not take the
|
||||
// language of a menu link into account. Whe having different menu trees for
|
||||
// different menus, this means that the active trail will not work for all but
|
||||
// one language.
|
||||
|
||||
// The code below is identical to the mentioned function except the added
|
||||
// language condition on the query.
|
||||
|
||||
// TODO: Adding an alter tag to the query would allow to do this with a simple
|
||||
// hook_query_alter() implementation.
|
||||
|
||||
$preferred_links = &drupal_static('menu_link_get_preferred');
|
||||
|
||||
$path = $_GET['q'];
|
||||
|
||||
// Look for the correct menu link by building a list of candidate paths,
|
||||
// which are ordered by priority (translated hrefs are preferred over
|
||||
// untranslated paths). Afterwards, the most relevant path is picked from
|
||||
// the menus, ordered by menu preference.
|
||||
$item = menu_get_item($path);
|
||||
$path_candidates = array();
|
||||
// 1. The current item href.
|
||||
$path_candidates[$item['href']] = $item['href'];
|
||||
// 2. The tab root href of the current item (if any).
|
||||
if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) {
|
||||
$path_candidates[$tab_root['href']] = $tab_root['href'];
|
||||
}
|
||||
// 3. The current item path (with wildcards).
|
||||
$path_candidates[$item['path']] = $item['path'];
|
||||
// 4. The tab root path of the current item (if any).
|
||||
if (!empty($tab_root)) {
|
||||
$path_candidates[$tab_root['path']] = $tab_root['path'];
|
||||
}
|
||||
// Retrieve a list of menu names, ordered by preference.
|
||||
$menu_names = menu_get_active_menu_names();
|
||||
// Use an illegal menu name as the key for the preferred menu link.
|
||||
$selected_menu = MENU_PREFERRED_LINK;
|
||||
// Put the selected menu at the front of the list.
|
||||
array_unshift($menu_names, $selected_menu);
|
||||
|
||||
$query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
|
||||
$query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
|
||||
$query->fields('ml');
|
||||
// Weight must be taken from {menu_links}, not {menu_router}.
|
||||
$query->addField('ml', 'weight', 'link_weight');
|
||||
$query->fields('m');
|
||||
$query->condition('ml.link_path', $path_candidates, 'IN');
|
||||
|
||||
// Only look menu links with none or the current language.
|
||||
$query->condition('ml.language', array(LANGUAGE_NONE, i18n_language_interface()->language), 'IN');
|
||||
|
||||
// Sort candidates by link path and menu name.
|
||||
$candidates = array();
|
||||
foreach ($query->execute() as $candidate) {
|
||||
$candidate['weight'] = $candidate['link_weight'];
|
||||
$candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
|
||||
// Add any menus not already in the menu name search list.
|
||||
if (!in_array($candidate['menu_name'], $menu_names)) {
|
||||
$menu_names[] = $candidate['menu_name'];
|
||||
}
|
||||
}
|
||||
|
||||
// Store the most specific link for each menu. Also save the most specific
|
||||
// link of the most preferred menu in $preferred_link.
|
||||
foreach ($path_candidates as $link_path) {
|
||||
if (isset($candidates[$link_path])) {
|
||||
foreach ($menu_names as $menu_name) {
|
||||
if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
|
||||
$candidate_item = $candidates[$link_path][$menu_name];
|
||||
$map = explode('/', $path);
|
||||
_menu_translate($candidate_item, $map);
|
||||
if ($candidate_item['access']) {
|
||||
$preferred_links[$path][$menu_name] = $candidate_item;
|
||||
if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
|
||||
// Store the most specific link.
|
||||
$preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test case for multilingual menus.
|
||||
*/
|
||||
class i18nMenuTestCase extends Drupali18nTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Menu translation',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Menu translation functions',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp(array('i18n_menu', 'translation'));
|
||||
parent::setUpLanguages(array('administer menu'));
|
||||
$this->translator = $this->drupalCreateUser(array('translate interface', 'translate user-defined strings'));
|
||||
}
|
||||
|
||||
function testMenuTranslateLocalize() {
|
||||
// Test filtering for menu blocks.
|
||||
$menu = $this->createMenu(array('i18n_mode' => I18N_MODE_MULTIPLE));
|
||||
|
||||
$neutral_item = $this->createMenuLink(array('menu_name' => $menu['menu_name']));
|
||||
$default_item = $this->createMenuLink(array('menu_name' => $menu['menu_name'], 'language' => $this->default_language));
|
||||
$secondary_item = $this->createMenuLink(array('menu_name' => $menu['menu_name'], 'language' => $this->secondary_language));
|
||||
|
||||
$block['title'] = $menu['title'];
|
||||
$block['module'] = 'menu';
|
||||
$block['delta'] = $menu['menu_name'];
|
||||
$this->moveBlockToRegion($block, 'sidebar_first');
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertText($neutral_item['link_title']);
|
||||
$this->assertText($default_item['link_title']);
|
||||
$this->assertNoText($secondary_item['link_title']);
|
||||
|
||||
$this->i18nGet($this->secondary_language, '<front>');
|
||||
$this->assertText($neutral_item['link_title']);
|
||||
$this->assertNoText($default_item['link_title']);
|
||||
$this->assertText($secondary_item['link_title']);
|
||||
|
||||
// Test filtering for built-in menus.
|
||||
$edit = array(
|
||||
'i18n_mode' => I18N_MODE_MULTIPLE,
|
||||
);
|
||||
$this->drupalPost('admin/structure/menu/manage/main-menu/edit', $edit, t('Save'));
|
||||
|
||||
$neutral_item = $this->createMenuLink(array('menu_name' => 'main-menu'));
|
||||
$default_item = $this->createMenuLink(array('menu_name' => 'main-menu', 'language' => $this->default_language));
|
||||
$secondary_item = $this->createMenuLink(array('menu_name' => 'main-menu', 'language' => $this->secondary_language));
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertText($neutral_item['link_title']);
|
||||
$this->assertText($default_item['link_title']);
|
||||
$this->assertNoText($secondary_item['link_title']);
|
||||
|
||||
$this->i18nGet($this->secondary_language, '<front>');
|
||||
$this->assertText($neutral_item['link_title']);
|
||||
$this->assertNoText($default_item['link_title']);
|
||||
$this->assertText($secondary_item['link_title']);
|
||||
|
||||
// Test the same thing with a system menu used as a block.
|
||||
$block['title'] = $menu['title'];
|
||||
$block['module'] = 'system';
|
||||
$block['delta'] = 'main-menu';
|
||||
$this->moveBlockToRegion($block, 'sidebar_first');
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertText($neutral_item['link_title']);
|
||||
$this->assertText($default_item['link_title']);
|
||||
$this->assertNoText($secondary_item['link_title']);
|
||||
|
||||
$this->i18nGet($this->secondary_language, '<front>');
|
||||
$this->assertText($neutral_item['link_title']);
|
||||
$this->assertNoText($default_item['link_title']);
|
||||
$this->assertText($secondary_item['link_title']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test menu items for nodes.
|
||||
*/
|
||||
function testNodeMenuItems() {
|
||||
// Create menu and display it in a block.
|
||||
$menu = $this->createMenu(array(
|
||||
'i18n_mode' => I18N_MODE_MULTIPLE,
|
||||
'language' => LANGUAGE_NONE,
|
||||
'menu_name' => 'test',
|
||||
));
|
||||
|
||||
$neutral_item = $this->createMenuLink(array('menu_name' => $menu['menu_name']));
|
||||
$block['title'] = $menu['title'];
|
||||
$block['module'] = 'menu';
|
||||
$block['delta'] = $menu['menu_name'];
|
||||
$this->moveBlockToRegion($block, 'sidebar_first');
|
||||
$menu_parent = $menu['menu_name'] . ':0';
|
||||
// Create content type 'page', translation enabled, login as translator
|
||||
parent::setUpContentTranslation();
|
||||
$settings = array(
|
||||
'menu_options[' . $menu['menu_name'] . ']' => TRUE,
|
||||
'menu_parent' => $menu_parent,
|
||||
'node_options[promote]' => FALSE,
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/page/edit', $settings, t('Save content type'));
|
||||
|
||||
// Create nodes with language and menu item: es, en, und
|
||||
$edit = array(
|
||||
'menu[enabled]' => TRUE,
|
||||
'menu[parent]' => $menu_parent,
|
||||
'promote' => FALSE,
|
||||
);
|
||||
// English Page => English menu item
|
||||
$en_title = $this->randomName(10);
|
||||
$en_body = $this->randomString(50);
|
||||
$nodes[] = $en_node = $this->createNode('page', $en_title, $en_body, 'en', $edit + array('menu[link_title]' => $en_title));
|
||||
// Spanish page => Spanish menu item
|
||||
$es_title = $this->randomName(10);
|
||||
$es_body = $this->randomString(50);
|
||||
$nodes[] = $es_node = $this->createNode('page', $es_title, $es_body, 'es', $edit + array('menu[link_title]' => $es_title));
|
||||
// Language neutral page, localicable menu item
|
||||
$und_title = $this->randomName(10);
|
||||
$und_body = $this->randomString(50);
|
||||
$nodes[] = $und_node = $this->createNode('page', $und_title, $und_body, LANGUAGE_NONE, $edit + array('menu[link_title]' => $und_title));
|
||||
// Check menu items have right language and we cannot edit them.
|
||||
foreach ($nodes as $node) {
|
||||
menu_node_prepare($node);
|
||||
$this->assertEqual($node->menu['language'], $node->language, 'Menu item has the same language that the node it belongs to.');
|
||||
$this->drupalGet('admin/structure/menu/item/' . $node->menu['mlid'] . '/edit');
|
||||
$this->assertText(t('This menu item belongs to a node, so it will have the same language as the node and cannot be localized.'));
|
||||
$this->assertNoField('language', 'We cannot edit language for menu items that belong to nodes.');
|
||||
}
|
||||
// Check menu items show up for the right language.
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertText($en_title);
|
||||
$this->assertNoText($es_title);
|
||||
$this->assertText($und_title);
|
||||
$this->i18nGet('es', '<front>');
|
||||
$this->assertText($es_title);
|
||||
$this->assertNoText($en_title);
|
||||
$this->assertText($und_title);
|
||||
// Create string translation for neutral menu item and check it shows up
|
||||
$translation = $this->randomName(10);
|
||||
$this->createStringTranslation('menu', $und_title, array('es' => $translation));
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertText($und_title);
|
||||
$this->i18nGet('es', '<front>');
|
||||
$this->assertText($translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the translation set management works.
|
||||
*/
|
||||
function testMenuTranslationSets() {
|
||||
$menu = $this->createMenu(array('i18n_mode' => I18N_MODE_MULTIPLE));
|
||||
|
||||
$neutral_item = $this->createMenuLink(array('menu_name' => $menu['menu_name']));
|
||||
$default_item = $this->createMenuLink(array('menu_name' => $menu['menu_name'], 'language' => $this->default_language));
|
||||
$secondary_item = $this->createMenuLink(array('menu_name' => $menu['menu_name'], 'language' => $this->secondary_language));
|
||||
|
||||
$translationset_edit = array(
|
||||
'translations[' . $this->default_language . ']' => $default_item['menu_name'] . ':' . $default_item['mlid'],
|
||||
'translations[' . $this->secondary_language . ']' => $secondary_item['menu_name'] . ':' . $secondary_item['mlid'],
|
||||
);
|
||||
$translation_set = $this->createMenuLinkTranslationSet($translationset_edit);
|
||||
|
||||
// Check if the overview works
|
||||
$this->drupalGet('admin/structure/menu/manage/translation');
|
||||
$link = $this->xpath('//*/a[contains(@href,"admin/structure/menu/manage/translation/edit/' . $translation_set->tsid . '")]');
|
||||
$this->assertTrue(!empty($link), 'Created translation-set found.');
|
||||
|
||||
// Check if the edit mode works
|
||||
$this->drupalGet('admin/structure/menu/manage/translation/edit/' . $translation_set->tsid);
|
||||
$this->assertFieldByXPath(
|
||||
"//*[@id='edit-translations-" . $this->default_language . "']/option[@selected]/@value",
|
||||
$menu['menu_name'] . ':' . $default_item['mlid'],
|
||||
'Expected option selection for language ' . $this->default_language . ' found.'
|
||||
);
|
||||
$this->assertFieldByXPath(
|
||||
"//*[@id='edit-translations-" . $this->secondary_language . "']/option[@selected]/@value",
|
||||
$menu['menu_name'] . ':' . $secondary_item['mlid'],
|
||||
'Expected option selection for language ' . $this->secondary_language . ' found.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if on a switch from translatable to non translatable the translation
|
||||
* sets and links are cleaned up.
|
||||
*/
|
||||
function testMenuTranslateLocalizeSwitchToNonTranslatable() {
|
||||
// Test filtering for menu blocks.
|
||||
$menu = $this->createMenu(array('i18n_mode' => I18N_MODE_MULTIPLE));
|
||||
|
||||
// Check current menu mode
|
||||
$this->drupalGet('admin/structure/menu/manage/' . $menu['menu_name'] . '/edit');
|
||||
$this->assertFieldByName('i18n_mode', I18N_MODE_MULTIPLE, 'Menu i18n mode set to I18N_MODE_MULTIPLE');
|
||||
|
||||
// Setup menu links for testing.
|
||||
$neutral_item = $this->createMenuLink(array('menu_name' => $menu['menu_name']));
|
||||
$default_item = $this->createMenuLink(array('menu_name' => $menu['menu_name'], 'language' => $this->default_language));
|
||||
$secondary_item = $this->createMenuLink(array('menu_name' => $menu['menu_name'], 'language' => $this->secondary_language));
|
||||
|
||||
$translationset_edit = array(
|
||||
'translations[' . $this->default_language . ']' => $default_item['menu_name'] . ':' . $default_item['mlid'],
|
||||
'translations[' . $this->secondary_language . ']' => $secondary_item['menu_name'] . ':' . $secondary_item['mlid'],
|
||||
);
|
||||
$translation_set = $this->createMenuLinkTranslationSet($translationset_edit);
|
||||
$tsid = $translation_set->tsid;
|
||||
|
||||
// Test language mode switch
|
||||
$edit = array(
|
||||
'i18n_mode' => I18N_MODE_LANGUAGE,
|
||||
'language' => $this->secondary_language,
|
||||
);
|
||||
$this->drupalPost('admin/structure/menu/manage/' . $menu['menu_name'] . '/edit', $edit, t('Save'));
|
||||
$this->drupalGet('admin/structure/menu/manage/' . $menu['menu_name'] . '/edit');
|
||||
$this->assertFieldByName('i18n_mode', I18N_MODE_LANGUAGE, 'Menu i18n mode changed to I18N_MODE_LANGUAGE');
|
||||
|
||||
$this->assertTrue(!empty(i18n_translation_set_load($tsid)->tsid), 'Translation set kept.');
|
||||
|
||||
$menu_link_languages = db_select('menu_links')
|
||||
->fields('menu_links', array('language'))
|
||||
->condition('menu_name', $menu['menu_name'])
|
||||
->groupBy('language')
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertTrue((count($menu_link_languages) == 1 && reset($menu_link_languages) === $this->secondary_language), 'Menu link language changed to menu language.');
|
||||
|
||||
// Test cleanup.
|
||||
$edit = array(
|
||||
'i18n_mode' => I18N_MODE_NONE,
|
||||
);
|
||||
$this->drupalPost('admin/structure/menu/manage/' . $menu['menu_name'] . '/edit', $edit, t('Save'));
|
||||
$this->drupalGet('admin/structure/menu/manage/' . $menu['menu_name'] . '/edit');
|
||||
$this->assertFieldByName('i18n_mode', I18N_MODE_NONE, 'Menu i18n mode changed to I18N_MODE_NONE');
|
||||
$translation_sets = entity_load('i18n_translation', FALSE, array('tsid' => $tsid), TRUE);
|
||||
$this->assertTrue(empty($translation_sets), 'Translation set deleted.');
|
||||
|
||||
$menu_link_languages = db_select('menu_links')
|
||||
->fields('menu_links', array('language'))
|
||||
->condition('menu_name', $menu['menu_name'])
|
||||
->groupBy('language')
|
||||
->execute()
|
||||
->fetchCol();
|
||||
$this->assertTrue(((count($menu_link_languages) == 1) && reset($menu_link_languages) === LANGUAGE_NONE), 'Menu link language switched to LANGUAGE_NONE.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a menu.
|
||||
*/
|
||||
function createMenu($edit = array()) {
|
||||
$edit += array(
|
||||
'title' => $this->randomName(),
|
||||
'menu_name' => substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI),
|
||||
'language' => $this->secondary_language,
|
||||
);
|
||||
$this->drupalPost('admin/structure/menu/add', $edit, t('Save'));
|
||||
return menu_load('menu-' . $edit['menu_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a menu link.
|
||||
*/
|
||||
function createMenuLink($item = array()) {
|
||||
$item += array(
|
||||
'link_title' => $this->randomName(),
|
||||
'link_path' => '<front>',
|
||||
'customized' => TRUE,
|
||||
);
|
||||
|
||||
return menu_link_load(menu_link_save($item));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create a translation set.
|
||||
*/
|
||||
function createMenuLinkTranslationSet($edit = array()) {
|
||||
$edit += array(
|
||||
'title' => $this->randomName(16),
|
||||
);
|
||||
$this->drupalPost('admin/structure/menu/manage/translation/add', $edit, t('Save'));
|
||||
|
||||
// Load translation set entity.
|
||||
$entity = entity_load('i18n_translation', FALSE, array('title' => $edit['title']), TRUE);
|
||||
if (empty($entity)) {
|
||||
$this->fail('Could not create a translation set.', 'i18n_translation');
|
||||
return FALSE;
|
||||
}
|
||||
return reset($entity);
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Feature integration
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_features_pipe_node_alter().
|
||||
*/
|
||||
function i18n_node_features_pipe_node_alter(&$pipe, $data, $export) {
|
||||
if (!empty($data) && module_exists('variable')) {
|
||||
variable_include();
|
||||
foreach (variable_list_module('i18n_node') as $variable) {
|
||||
if (isset($variable['multiple']) && $variable['multiple'] === 'node_type') {
|
||||
$children = variable_build($variable['name']);
|
||||
if (!empty($children['children'])) {
|
||||
foreach ($children['children'] as $child_variable) {
|
||||
if (in_array($child_variable['index'], $data)) {
|
||||
$pipe['variable'][] = $child_variable['name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) hooks
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_object_info().
|
||||
*/
|
||||
function i18n_node_i18n_object_info() {
|
||||
$info['node_type'] = array(
|
||||
'title' => t('Node type'),
|
||||
'key' => 'type',
|
||||
'load callback' => 'node_type_get_type',
|
||||
'placeholders' => array(
|
||||
'%node_type' => 'type',
|
||||
),
|
||||
'edit path' => 'admin/structure/types/manage/%node_type',
|
||||
'translate tab' => 'admin/structure/types/manage/%node_type/translate',
|
||||
// We can easily list all these objects
|
||||
'list callback' => 'node_type_get_types',
|
||||
// Metadata for string translation
|
||||
'string translation' => array(
|
||||
'textgroup' => 'node',
|
||||
'type' => 'type',
|
||||
'properties' => array(
|
||||
'name' => t('Name'),
|
||||
'title_label' => t('Title label'),
|
||||
'description' => t('Description'),
|
||||
'help' => t('Help text'),
|
||||
),
|
||||
'translate path' => 'admin/structure/types/manage/%node_type/translate/%i18n_language',
|
||||
)
|
||||
);
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_string_info()
|
||||
*/
|
||||
function i18n_node_i18n_string_info() {
|
||||
$groups['node'] = array(
|
||||
'title' => t('Node types'),
|
||||
'description' => t('Content type names, descriptions, help texts.'),
|
||||
//'format' => TRUE, // This group has strings with format (block body)
|
||||
'list' => TRUE, // This group can list all strings
|
||||
);
|
||||
return $groups;
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
name = Multilingual content
|
||||
description = Extended node options for multilingual content
|
||||
dependencies[] = translation
|
||||
dependencies[] = i18n
|
||||
dependencies[] = i18n_string
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
configure = admin/config/regional/i18n/node
|
||||
files[]=i18n_node.test
|
||||
files[]=i18n_node.variable.inc
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Installation file for Internationalization (i18n) module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function i18n_node_install() {
|
||||
// If updating from D6, module changed name
|
||||
if (variable_get('i18n_drupal6_update')) {
|
||||
i18n_node_update_7000();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_uninstall().
|
||||
*/
|
||||
function i18n_node_uninstall() {
|
||||
variable_del('i18n_hide_translation_links');
|
||||
variable_del('i18n_selection_mode');
|
||||
foreach (array_keys(node_type_get_types()) as $type) {
|
||||
variable_del('i18n_node_' . $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_update_dependencies()
|
||||
*/
|
||||
function i18n_node_update_dependencies() {
|
||||
$dependencies['i18n_node'][7000] = array(
|
||||
'i18n_string' => 7001,
|
||||
);
|
||||
return $dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_update_drupal6().
|
||||
*
|
||||
* Update old string names
|
||||
*/
|
||||
function i18n_node_update_7000() {
|
||||
// @todo Update from D6 i18n
|
||||
// Variables:
|
||||
// i18n_newnode_current, i18n_required_node, i18n_lock_node => i18n_node_options_[node_type]
|
||||
// i18n_node => 'i18n_node_extended_[node_type]'
|
||||
// Update string names
|
||||
// - nodetype:type:[type]:[property] -> node:type:[type]:[property]
|
||||
// - Property names: title -> title_label
|
||||
module_load_install('i18n_string');
|
||||
i18n_string_install_update_context('nodetype:type:*:*', 'node:type:*:*');
|
||||
i18n_string_install_update_context('node:type:*:title', 'node:type:*:title_label');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete obsoleted variable for switch interface for translating.
|
||||
*/
|
||||
/*
|
||||
function i18n_node_update_7001() {
|
||||
variable_del('i18n_node_translation_switch');
|
||||
}
|
||||
*/
|
@@ -0,0 +1,20 @@
|
||||
(function ($) {
|
||||
|
||||
/**
|
||||
* Rewrite autocomplete inputs to pass the language of the node currently being
|
||||
* edited in the path.
|
||||
*
|
||||
* This functionality ensures node autocompletes get suggestions for the node's
|
||||
* language rather than the current interface language.
|
||||
*/
|
||||
Drupal.behaviors.i18n = {
|
||||
attach : function (context) {
|
||||
if (Drupal.settings && Drupal.settings.i18n) {
|
||||
$('form[id^=node-form]', context).find('input.autocomplete[value^=' + Drupal.settings.i18n.interface_path + ']').each(function () {
|
||||
$(this).val($(this).val().replace(Drupal.settings.i18n.interface_path, Drupal.settings.i18n.content_path));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
@@ -0,0 +1,572 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) module - Node type handling
|
||||
*
|
||||
* The i18n strings created by this module are:
|
||||
* - node:type:[type]:[name,title,body,help]
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_field_extra_fields().
|
||||
*/
|
||||
function i18n_node_field_extra_fields() {
|
||||
$return = array();
|
||||
$info = entity_get_info('node');
|
||||
foreach (array_keys($info['bundles']) as $bundle) {
|
||||
if (i18n_node_type_enabled($bundle)) {
|
||||
$return['node'][$bundle] = i18n_language_field_extra();
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function i18n_node_menu() {
|
||||
$items['admin/config/regional/i18n/node'] = array(
|
||||
'title' => 'Node options',
|
||||
'description' => 'Configure extended options for multilingual content and translations.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('variable_group_form', 'i18n_node'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 10,
|
||||
);
|
||||
$items['i18n/node/autocomplete'] = array(
|
||||
'page callback' => 'i18n_node_autocomplete',
|
||||
'file' => 'i18n_node.pages.inc',
|
||||
'access arguments' => array('administer content translations'),
|
||||
'type' => MENU_CALLBACK,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_view_MODULE_DELTA_alter().
|
||||
*
|
||||
* Set translated help text for node/add pages, replace node_help() text.
|
||||
*/
|
||||
function i18n_node_block_view_system_help_alter(&$block) {
|
||||
$arg = drupal_help_arg(arg(NULL));
|
||||
if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
|
||||
if (($type = node_type_get_type(str_replace('-', '_', $arg[2]))) && !empty($type->help)) {
|
||||
$help = i18n_node_translate_type($type, 'help');
|
||||
if ($help !== $type->help) {
|
||||
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ($arg[0] == 'node' && $arg[2] == 'edit') {
|
||||
$node = menu_get_object();
|
||||
if ($node && isset($node->type)) {
|
||||
$type = node_type_get_type($node->type);
|
||||
$help = i18n_node_translate_type($type, 'help');
|
||||
if ($help !== $type->help) {
|
||||
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function i18n_node_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/help#i18n_node':
|
||||
$output = '<p>' . t('Provides some extended multilingual options for nodes.') . '</p>';
|
||||
$output .= '<p>' . t('Additionally, if <em>String translation</em> enabled, this module will localize all content type configuration texts.') . '</p>';
|
||||
$output .= '<ul>';
|
||||
$output .= '<li>' . t('Content type names') . '</li>';
|
||||
$output .= '<li>' . t('Submission guidelines') . '</li>';
|
||||
$output .= '<li>' . t("Content type descriptions were previously localized so they won't be affected.") . '</li>';
|
||||
$output .= '</ul>';
|
||||
$output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</p>';
|
||||
return $output;
|
||||
case 'admin/config/regional/i18n':
|
||||
case 'admin/config/regional/i18n/node':
|
||||
$output = '<p>' . t('You can find some more per content type options on the <a href="@configure_content">Content types administration page</a>.', array('@configure_content' => url('admin/structure/types'))) . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_context_language().
|
||||
*/
|
||||
function i18n_node_i18n_context_language() {
|
||||
// Node language when loading specific nodes or creating translations.
|
||||
if (arg(0) == 'node' ) {
|
||||
if (($node = menu_get_object('node')) && !empty($node->language) && i18n_node_type_enabled($node)) {
|
||||
return i18n_language_object($node->language);
|
||||
}
|
||||
elseif (arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['target']) && ($source = node_load($_GET['translation'])) && i18n_node_type_enabled($source)) {
|
||||
return i18n_language_object($_GET['target']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_translate_path()
|
||||
*/
|
||||
function i18n_node_i18n_translate_path($path) {
|
||||
if (preg_match("!^node/(\d+)(/.+|)!", $path, $matches) && ($node = node_load((int) $matches[1])) && i18n_object_langcode($node) && !empty($node->tnid)) {
|
||||
if ($translations = translation_node_get_translations($node->tnid)) {
|
||||
$result = array();
|
||||
foreach ($translations as $langcode => $node_translation) {
|
||||
$result[$langcode] = array(
|
||||
'href' => 'node/' . $node_translation->nid . $matches[2],
|
||||
'title' => $node_translation->title,
|
||||
'object' => $node_translation,
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu_alter().
|
||||
*
|
||||
* Take over the node translation page.
|
||||
*/
|
||||
function i18n_node_menu_alter(&$items) {
|
||||
if (isset($items['node/%node/translate'])) {
|
||||
$items['node/%node/translate']['page callback'] = 'i18n_node_translation_overview';
|
||||
$items['node/%node/translate']['file'] = 'i18n_node.pages.inc';
|
||||
$items['node/%node/translate']['module'] = 'i18n_node';
|
||||
}
|
||||
// Take over node/add pages for string translation
|
||||
$items['node/add']['page callback'] = 'i18n_node_add_page';
|
||||
$items['node/add']['file'] = 'i18n_node.pages.inc';
|
||||
$items['node/add']['file path'] = drupal_get_path('module', 'i18n_node');
|
||||
// @TODO avoid iterating over every router path.
|
||||
foreach (node_type_get_types() as $type) {
|
||||
$path = 'node/add/' . str_replace('_', '-', $type->type);
|
||||
if (isset($items[$path])) {
|
||||
$items[$path]['title callback'] = 'i18n_node_type_name';
|
||||
$items[$path]['title arguments'] = array($type->type, $type->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get node language.
|
||||
*/
|
||||
function i18n_node_get_lang($nid, $default = '') {
|
||||
$lang = db_query('SELECT language FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
|
||||
return $lang ? $lang : $default ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed languages for node.
|
||||
*
|
||||
* This allows node types to define its own language list implementing hook 'language_list'.
|
||||
*
|
||||
* @param $node
|
||||
* Node to retrieve language list for.
|
||||
* @param $translate
|
||||
* Only languages available for translation.
|
||||
* @param $select
|
||||
* Only languages that can be selected for this node
|
||||
*/
|
||||
function i18n_node_language_list($node, $translate = FALSE, $select = FALSE) {
|
||||
// Check if the node module manages its own language list.
|
||||
$languages = node_invoke($node, 'language_list', $translate);
|
||||
|
||||
if (!$languages) {
|
||||
$languages = i18n_language_list('name', i18n_node_language_mode($node));
|
||||
if ($translate && isset($node->tnid) && $node->tnid && ($translations = translation_node_get_translations($node->tnid))) {
|
||||
unset($translations[$node->language]);
|
||||
foreach (array_keys($translations) as $langcode) {
|
||||
unset($languages[$langcode]);
|
||||
}
|
||||
}
|
||||
// Language may be locked for this node type, restrict options to current one
|
||||
if ($select && i18n_node_language_options($node, 'lock') && !empty($node->language) && !empty($languages[$node->language])) {
|
||||
$languages = array($node->language => $languages[$node->language]);
|
||||
}
|
||||
// Check language required for this type (no language neutral)
|
||||
elseif (!i18n_node_language_options($node, 'required')) {
|
||||
$languages = array(LANGUAGE_NONE => t('Language neutral')) + $languages;
|
||||
}
|
||||
}
|
||||
|
||||
return $languages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check options for node language
|
||||
*/
|
||||
function i18n_node_language_options($node, $option) {
|
||||
$options = variable_get('i18n_node_options_' . $node->type, array());
|
||||
return in_array($option, $options, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language mode for node or node type
|
||||
*/
|
||||
function i18n_node_language_mode($type) {
|
||||
$type = is_object($type) ? $type->type : $type;
|
||||
return variable_get('i18n_node_extended_' . $type, I18N_LANGUAGE_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_prepare().
|
||||
*/
|
||||
function i18n_node_node_prepare($node) {
|
||||
$options = variable_get('i18n_node_options_' . $node->type, array());
|
||||
if (i18n_node_type_enabled($node) && empty($node->nid) && !i18n_object_langcode($node) && in_array('current', $options)) {
|
||||
// Set current language for new nodes if option enabled
|
||||
$node->language = i18n_language_content()->language;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_permission().
|
||||
*
|
||||
* Permissions defined
|
||||
* - administer all languages
|
||||
* Disables language conditions for administration pages, so the user can view objects for all languages at the same time.
|
||||
* This applies for: menu items, taxonomy
|
||||
* - administer translations
|
||||
* Will allow to add/remove existing nodes to/from translation sets.
|
||||
*/
|
||||
function i18n_node_permission() {
|
||||
return array(
|
||||
'administer content translations' => array(
|
||||
'title' => t('Administer content translations'),
|
||||
'description' => t('Add or remove existing content to translation sets.'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_view()
|
||||
*/
|
||||
function i18n_node_node_view($node) {
|
||||
if (i18n_node_type_enabled($node)) {
|
||||
$node->content['language'] = array(
|
||||
'#type' => 'item',
|
||||
'#title' => t('Language'),
|
||||
'#markup' => i18n_language_name($node->language),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_view_alter().
|
||||
*
|
||||
* Handles links for extended languages. Sets current interface language.
|
||||
*/
|
||||
function i18n_node_node_view_alter(&$build) {
|
||||
$node = $build['#node'];
|
||||
// Hide node translation links.
|
||||
if (variable_get('i18n_hide_translation_links', 0)) {
|
||||
if (isset($build['links']['translation'])) {
|
||||
unset($build['links']['translation']);
|
||||
}
|
||||
}
|
||||
elseif (!empty($node->tnid) && !empty($build['links']['translation']) && i18n_node_language_mode($node) == I18N_LANGUAGE_EXTENDED) {
|
||||
// We only get links for translations for enabled languages
|
||||
// Set the right languages if language support is extended but not visible.
|
||||
$links = &$build['links']['translation']['#links'];
|
||||
$translations = translation_node_get_translations($node->tnid);
|
||||
foreach (i18n_node_language_list($node) as $langcode => $langname) {
|
||||
$index = 'translation_' . $langcode;
|
||||
if ($langcode != $node->language && isset($translations[$langcode]) && $translations[$langcode]->status && !isset($links[$index])) {
|
||||
// This a published translation to a disabled language. As the node is language extended, display the linkso
|
||||
// These links shouldn't switch the interface, though they have a language so the right language icon will be added
|
||||
$language = i18n_language_object($langcode);
|
||||
$links[$index] = array(
|
||||
'href' => 'node/' . $translations[$langcode]->nid,
|
||||
'title' => $language->native,
|
||||
'language' => $language,
|
||||
'attributes' => array(
|
||||
'title' => $translations[$langcode]->title,
|
||||
'class' => array('translation-link'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_type_insert()
|
||||
*/
|
||||
function i18n_node_node_type_insert($info) {
|
||||
i18n_node_node_type_update($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_type_update()
|
||||
*/
|
||||
function i18n_node_node_type_update($info) {
|
||||
if (!empty($info->old_type) && $info->old_type != $info->type) {
|
||||
i18n_string_update_context("node:type:$info->old_type:*", "node:type:$info->type:*");
|
||||
}
|
||||
i18n_string_object_update('node_type', $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_node_type_delete()
|
||||
*/
|
||||
function i18n_node_node_type_delete($info) {
|
||||
i18n_string_object_remove('node_type', $info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_elements().
|
||||
*
|
||||
* Add a process callback for textfields.
|
||||
*
|
||||
* * @todo Update D7
|
||||
*/
|
||||
/*
|
||||
function i18n_node_elements() {
|
||||
$type = array();
|
||||
$type['textfield'] = array('#process' => array('i18n_node_textfield_process'));
|
||||
return $type;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Process callback for textfield elements.
|
||||
*
|
||||
* When editing or translating a node, set Javascript to rewrite autocomplete
|
||||
* paths to use the node language prefix rather than the current content one.
|
||||
*
|
||||
* @todo Update D7
|
||||
*/
|
||||
function i18n_node_textfield_process($element) {
|
||||
global $language;
|
||||
static $sent = FALSE;
|
||||
|
||||
// Ensure we send the Javascript only once.
|
||||
if (!$sent && isset($element['#autocomplete_path']) && !empty($element['#autocomplete_path']) && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
|
||||
// Add a JS file for node forms.
|
||||
// Determine if we are either editing or translating an existing node.
|
||||
// We can't act on regular node creation because we don't have a specified
|
||||
// node language.
|
||||
$node_edit = $node = menu_get_object() && arg(2) == 'edit' && isset($node->language) && !empty($node->language);
|
||||
$node_translate = arg(0) == 'node' && arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language']);
|
||||
if ($node_edit || $node_translate) {
|
||||
$node_language = $node_edit ? $node->language : $_GET['language'];
|
||||
// Only needed if the node language is different from the interface one.
|
||||
if ($node_language != $language->language) {
|
||||
$languages = language_list();
|
||||
if (isset($languages[$node_language])) {
|
||||
drupal_add_js(drupal_get_path('module', 'i18n_node') . '/i18n_node.js');
|
||||
// Pass the interface and content language base paths.
|
||||
// Remove any trailing forward slash. Doing so prevents a mismatch
|
||||
// that occurs when a language has no prefix and hence gets a path
|
||||
// with a trailing forward slash.
|
||||
$interface = rtrim(url('', array('absolute' => TRUE)), '/');
|
||||
$content = rtrim(url('', array('absolute' => TRUE, 'language' => $languages[$node_language])), '/');
|
||||
$data = array('interface_path' => $interface, 'content_path' => $content);
|
||||
drupal_add_js(array('i18n' => $data), 'setting');
|
||||
}
|
||||
}
|
||||
}
|
||||
$sent = TRUE;
|
||||
}
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_node_form_search_form_alter(&$form, &$form_state) {
|
||||
// Advanced search form. Add language and localize content type names
|
||||
if ($form['module']['#value'] == 'node' && !empty($form['advanced'])) {
|
||||
// @todo Handle language search conditions
|
||||
//$form['advanced']['language'] = _i18n_language_select();
|
||||
if (!empty($form['advanced']['type'])) {
|
||||
foreach ($form['advanced']['type']['#options'] as $type => $name) {
|
||||
$form['advanced']['type']['#options'][$type] = i18n_node_translate_type($type, 'name', $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_node_form_node_type_form_alter(&$form, &$form_state) {
|
||||
if (isset($form['type'])) {
|
||||
$disabled = !i18n_node_type_enabled($form['#node_type']);
|
||||
$form['i18n'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Multilingual settings'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => TRUE,
|
||||
'#group' => 'additional_settings',
|
||||
'#attributes' => array(
|
||||
'class' => array('i18n-node-type-settings-form'),
|
||||
),
|
||||
'#description' => t('Extended multilingual options provided by Internationalization module.'),
|
||||
'#disabled' => $disabled,
|
||||
);
|
||||
|
||||
// Some settings about node languages. Add variables for node type from variable definition
|
||||
if ($form['#node_type']->type) {
|
||||
variable_type_include('node_type');
|
||||
$form['i18n'] += node_variable_type_subform($form['#node_type']->type, array('i18n_node_options', 'i18n_node_extended'));
|
||||
}
|
||||
// Add disabled message
|
||||
if ($disabled) {
|
||||
$form['i18n']['#description'] .= ' <em>' . t('These will be available only when you enable Multilingual support in Publishing options above.') . '</em>';
|
||||
foreach (element_children($form['i18n']) as $key) {
|
||||
$form['i18n'][$key]['#disabled'] = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_BASE_FORM_ID_alter().
|
||||
*/
|
||||
function i18n_node_form_node_form_alter(&$form, $form_state) {
|
||||
$node = $form['#node'];
|
||||
/**
|
||||
* i18n has to override locale.module
|
||||
* drupal_alter() fails to order modules correctly in some cases
|
||||
* for example specific hooks like hook_form_BASE_FORM_ID_alter
|
||||
*
|
||||
* its not possbile to reorder hook_form_BASE_FORM_ID_alter with
|
||||
* hook_module_implements_alter
|
||||
*
|
||||
* @see http://drupal.org/node/765860
|
||||
*/
|
||||
|
||||
// Replace core's node submit callback with our own,
|
||||
// in order to translate the node type name.
|
||||
$key = array_search('node_form_submit', $form['actions']['submit']['#submit']);
|
||||
if ($key !== FALSE) {
|
||||
$form['actions']['submit']['#submit'][$key] = 'i18n_node_form_submit';
|
||||
}
|
||||
|
||||
// call a 'private' implemenation of i18n_node_form_node_form_alter()
|
||||
$form['#after_build'][] = '_i18n_node_form_node_form_alter';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replacement for core's node_form_submit(), taking care of
|
||||
* translating node type names.
|
||||
*/
|
||||
function i18n_node_form_submit($form, &$form_state) {
|
||||
$node = node_form_submit_build_node($form, $form_state);
|
||||
$insert = empty($node->nid);
|
||||
node_save($node);
|
||||
$node_link = l(t('view'), 'node/' . $node->nid);
|
||||
$type = node_type_get_type($node->type);
|
||||
$type_name = i18n_node_translate_type($type);
|
||||
|
||||
$watchdog_args = array('@type' => $node->type, '%title' => $node->title);
|
||||
$t_args = array('@type' => $type_name, '%title' => $node->title);
|
||||
|
||||
if ($insert) {
|
||||
watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
|
||||
drupal_set_message(t('@type %title has been created.', $t_args));
|
||||
}
|
||||
else {
|
||||
watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
|
||||
drupal_set_message(t('@type %title has been updated.', $t_args));
|
||||
}
|
||||
if ($node->nid) {
|
||||
$form_state['values']['nid'] = $node->nid;
|
||||
$form_state['nid'] = $node->nid;
|
||||
$form_state['redirect'] = 'node/' . $node->nid;
|
||||
}
|
||||
else {
|
||||
// In the unlikely case something went wrong on save, the node will be
|
||||
// rebuilt and node form redisplayed the same way as in preview.
|
||||
drupal_set_message(t('The post could not be saved.'), 'error');
|
||||
$form_state['rebuild'] = TRUE;
|
||||
}
|
||||
// Clear the page and block caches.
|
||||
cache_clear_all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_BASE_FORM_ID_alter().
|
||||
* Called by i18n_node_form_node_form_alter
|
||||
*/
|
||||
function _i18n_node_form_node_form_alter($form, &$form_state) {
|
||||
$node = $form['#node'];
|
||||
if (i18n_node_type_enabled($node)) {
|
||||
if (!empty($form['language']['#options'])) {
|
||||
$form['language']['#options'] = i18n_node_language_list($node, TRUE, TRUE);
|
||||
}
|
||||
}
|
||||
elseif (variable_get('i18n_node_default_language_none', 0) && !isset($form['#node']->nid)) {
|
||||
// Override locale module setting default language to nodes. It is already in form_state.
|
||||
$form['language']['#value'] = $form_state['values']['language'] = LANGUAGE_NONE;
|
||||
}
|
||||
// Translate field names for title and body for the node edit form.
|
||||
if (!empty($form['title']['#title'])) {
|
||||
$form['title']['#title'] = i18n_node_translate_type($node->type, 'title_label', $form['title']['#title']);
|
||||
}
|
||||
if (!empty($form['body_field']['body']['#title'])) {
|
||||
$form['body_field']['body']['#title'] = i18n_node_translate_type($node->type, 'body', $form['body_field']['body']['#title']);
|
||||
}
|
||||
// Translate page title for node/add/% and node/%/edit pages.
|
||||
$types = node_type_get_types();
|
||||
if (empty($node->nid) && strpos($_GET['q'], 'node/add/' . str_replace('_', '-', $node->type)) === 0) {
|
||||
drupal_set_title(t('Create @name', array('@name' => i18n_node_translate_type($types[$node->type], 'name'))), PASS_THROUGH);
|
||||
}
|
||||
elseif (!empty($node->nid) && $_GET['q'] == 'node/' . $node->nid . '/edit') {
|
||||
drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => i18n_node_translate_type($types[$node->type], 'name'), '@title' => $node->title)), PASS_THROUGH);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function i18n_node_theme() {
|
||||
return array(
|
||||
'i18n_node_select_translation' => array(
|
||||
'render element' => 'element',
|
||||
'file' => 'i18n_node.pages.inc',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorthand for translating node type strings
|
||||
*
|
||||
* @param $type
|
||||
* Node type name or full object
|
||||
*/
|
||||
function i18n_node_translate_type($type, $property = 'name', $source = NULL, $options = array()) {
|
||||
if (is_object($type)) {
|
||||
$source = $type->$property;
|
||||
$type = $type->type;
|
||||
}
|
||||
return i18n_string_translate(array('node', 'type', $type, $property), $source, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate node type name (unfiltered)
|
||||
*/
|
||||
function i18n_node_type_name($type, $name) {
|
||||
return i18n_string_translate(array('node', 'type', $type, 'name'), $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this is a language enabled node type
|
||||
*
|
||||
* @param $type
|
||||
* Node, node type object, or node type name
|
||||
*/
|
||||
function i18n_node_type_enabled($type) {
|
||||
$type = is_object($type) ? $type->type : $type;
|
||||
$mode = variable_get('language_content_type_' . $type, 0);
|
||||
return $mode == 1 || $mode == 2; // 2 == TRANSLATION_ENABLED
|
||||
}
|
@@ -0,0 +1,358 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* User page callbacks for the translation module.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Replacement for node_add_page.
|
||||
*/
|
||||
function i18n_node_add_page() {
|
||||
$item = menu_get_item();
|
||||
$content = system_admin_menu_block($item);
|
||||
// Bypass the node/add listing if only one content type is available.
|
||||
if (count($content) == 1) {
|
||||
$item = array_shift($content);
|
||||
drupal_goto($item['href']);
|
||||
}
|
||||
foreach ($content as &$item) {
|
||||
// Type machine name will be the first page argument
|
||||
$page_arguments = unserialize($item['page_arguments']);
|
||||
// Check whether this has a node type, other items may be here too, see #1264662
|
||||
$type = isset($page_arguments[0]) ? $page_arguments[0] : NULL;
|
||||
if ($type) {
|
||||
// We just need to translate the description, the title is translated by the menu system
|
||||
// The string will be filtered (xss_admin) on the theme layer
|
||||
$item['description'] = i18n_node_translate_type($type, 'description', $item['description']);
|
||||
}
|
||||
}
|
||||
return theme('node_add_list', array('content' => $content));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overview page for a node's translations.
|
||||
*
|
||||
* @param $node
|
||||
* Node object.
|
||||
*/
|
||||
function i18n_node_translation_overview($node) {
|
||||
include_once DRUPAL_ROOT . '/includes/language.inc';
|
||||
|
||||
if (!empty($node->tnid)) {
|
||||
// Already part of a set, grab that set.
|
||||
$tnid = $node->tnid;
|
||||
$translations = translation_node_get_translations($node->tnid);
|
||||
}
|
||||
else {
|
||||
// We have no translation source nid, this could be a new set, emulate that.
|
||||
$tnid = $node->nid;
|
||||
$translations = array($node->language => $node);
|
||||
}
|
||||
|
||||
$type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
|
||||
$header = array(t('Language'), t('Title'), t('Status'), t('Operations'));
|
||||
|
||||
// Modes have different allowed languages
|
||||
foreach (i18n_node_language_list($node) as $langcode => $language_name) {
|
||||
if ($langcode == LANGUAGE_NONE) {
|
||||
// Never show language neutral on the overview.
|
||||
continue;
|
||||
}
|
||||
$options = array();
|
||||
if (isset($translations[$langcode])) {
|
||||
// Existing translation in the translation set: display status.
|
||||
// We load the full node to check whether the user can edit it.
|
||||
$translation_node = node_load($translations[$langcode]->nid);
|
||||
$path = 'node/' . $translation_node->nid;
|
||||
$title = i18n_node_translation_link($translation_node->title, $path, $langcode);
|
||||
if (node_access('update', $translation_node)) {
|
||||
$text = t('edit');
|
||||
$path = 'node/' . $translation_node->nid . '/edit';
|
||||
$options[] = i18n_node_translation_link($text, $path, $langcode);
|
||||
}
|
||||
$status = $translation_node->status ? t('Published') : t('Not published');
|
||||
$status .= $translation_node->translate ? ' - <span class="marker">' . t('outdated') . '</span>' : '';
|
||||
if ($translation_node->nid == $tnid) {
|
||||
$language_name = t('<strong>@language_name</strong> (source)', array('@language_name' => $language_name));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No such translation in the set yet: help user to create it.
|
||||
$title = t('n/a');
|
||||
if (node_access('create', $node->type)) {
|
||||
$text = t('add translation');
|
||||
$path = 'node/add/' . str_replace('_', '-', $node->type);
|
||||
$query = array('query' => array('translation' => $node->nid, 'target' => $langcode));
|
||||
$options[] = i18n_node_translation_link($text, $path, $langcode, $query);
|
||||
}
|
||||
$status = t('Not translated');
|
||||
}
|
||||
$rows[] = array($language_name, $title, $status, implode(" | ", $options));
|
||||
}
|
||||
|
||||
drupal_set_title(t('Translations of %title', array('%title' => $node->title)), PASS_THROUGH);
|
||||
|
||||
$build['translation_node_overview'] = array(
|
||||
'#theme' => 'table',
|
||||
'#header' => $header,
|
||||
'#rows' => $rows,
|
||||
);
|
||||
|
||||
if (user_access('administer content translations')) {
|
||||
$build['translation_node_select'] = drupal_get_form('i18n_node_select_translation', $node, $translations);
|
||||
}
|
||||
return $build;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create link for node translation. This may be a 'edit' or a 'add translation' link.
|
||||
*/
|
||||
function i18n_node_translation_link($text, $path, $langcode, $options = array()) {
|
||||
$type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
|
||||
$links = language_negotiation_get_switch_links($type, $path);
|
||||
// When node not published, links don't have href so we use path instead
|
||||
// Note: this is a bug in Core translation module, see http://drupal.org/node/1137074
|
||||
if (!empty($links->links[$langcode]) && !empty($links->links[$langcode]['href'])) {
|
||||
$options += array('attributes' => array(), 'html' => FALSE);
|
||||
$options['attributes'] += $links->links[$langcode]['attributes'];
|
||||
$options += $links->links[$langcode];
|
||||
$path = $links->links[$langcode]['href'];
|
||||
}
|
||||
return l($text, $path, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Form to select existing nodes as translation
|
||||
*
|
||||
* This one uses autocomplete fields for all languages
|
||||
*/
|
||||
function i18n_node_select_translation($form, &$form_state, $node, $translations) {
|
||||
$form['node'] = array('#type' => 'value', '#value' => $node);
|
||||
$form['translations'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Select translations for %title', array('%title' => $node->title)),
|
||||
'#tree' => TRUE,
|
||||
'#theme' => 'i18n_node_select_translation',
|
||||
'#description' => t("Alternatively, you can select existing nodes as translations of this one or remove nodes from this translation set. Only nodes that have the right language and don't belong to other translation set will be available here.")
|
||||
);
|
||||
foreach (i18n_node_language_list($node) as $langcode => $language_name) {
|
||||
if ($langcode != $node->language && $langcode != LANGUAGE_NONE) {
|
||||
$nid = isset($translations[$langcode]) ? $translations[$langcode]->nid : 0;
|
||||
$form['translations']['nid'][$langcode] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $nid,
|
||||
);
|
||||
$form['translations']['language'][$langcode] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $language_name,
|
||||
);
|
||||
$form['translations']['node'][$langcode] = array(
|
||||
'#type' => 'textfield',
|
||||
'#maxlength' => 255,
|
||||
'#autocomplete_path' => 'i18n/node/autocomplete/' . $node->type . '/' . $langcode,
|
||||
'#default_value' => $nid ? i18n_node_nid2autocomplete($nid) : '',
|
||||
);
|
||||
}
|
||||
}
|
||||
$form['actions'] = array('#type' => 'actions');
|
||||
$form['actions']['update'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Update translations'),
|
||||
);
|
||||
//$form['buttons']['clean'] = array('#type' => 'submit', '#value' => t('Delete translation set'));
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form validation
|
||||
*/
|
||||
function i18n_node_select_translation_validate($form, &$form_state) {
|
||||
foreach ($form_state['values']['translations']['node'] as $lang => $title) {
|
||||
if (!$title) {
|
||||
$nid = 0;
|
||||
}
|
||||
else {
|
||||
$nid = i18n_node_autocomplete2nid($title, "translations][node][$lang", array($form_state['values']['node']->type), array($lang));
|
||||
}
|
||||
$form_state['values']['translations']['nid'][$lang] = $nid;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission: update / delete the translation set
|
||||
*/
|
||||
function i18n_node_select_translation_submit($form, &$form_state) {
|
||||
$op = isset($form_state['values']['op']) ? $form_state['values']['op'] : NULL;
|
||||
$node = $form_state['values']['node'];
|
||||
$translations = $node->tnid ? translation_node_get_translations($node->tnid) : array($node->language => $node);
|
||||
foreach ($translations as $trans) {
|
||||
$current[$trans->language] = $trans->nid;
|
||||
}
|
||||
$update = array($node->language => $node->nid) + array_filter($form_state['values']['translations']['nid']);
|
||||
// Compute the difference to see which are the new translations and which ones to remove
|
||||
$new = array_diff_assoc($update, $current);
|
||||
$remove = array_diff_assoc($current, $update);
|
||||
|
||||
// The tricky part: If the existing source is not in the new set, we need to create a new tnid
|
||||
if ($node->tnid && in_array($node->tnid, $update)) {
|
||||
$tnid = $node->tnid;
|
||||
$add = $new;
|
||||
}
|
||||
else {
|
||||
// Create new tnid, which is the source node
|
||||
$tnid = $node->nid;
|
||||
$add = $update;
|
||||
}
|
||||
// Now update values for all nodes
|
||||
if ($add) {
|
||||
db_update('node')
|
||||
->fields(array(
|
||||
'tnid' => $tnid,
|
||||
))
|
||||
->condition('nid', $add)
|
||||
->execute();
|
||||
entity_get_controller('node')->resetCache($add);
|
||||
if (count($new)) {
|
||||
drupal_set_message(format_plural(count($new), 'Added a node to the translation set.', 'Added @count nodes to the translation set.'));
|
||||
}
|
||||
}
|
||||
if ($remove) {
|
||||
db_update('node')
|
||||
->fields(array(
|
||||
'tnid' => 0,
|
||||
))
|
||||
->condition('nid', $remove)
|
||||
->execute();
|
||||
entity_get_controller('node')->resetCache($remove);
|
||||
drupal_set_message(format_plural(count($remove), 'Removed a node from the translation set.', 'Removed @count nodes from the translation set.'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Node title autocomplete callback
|
||||
*/
|
||||
function i18n_node_autocomplete($type, $language, $string = '') {
|
||||
$params = array('type' => $type, 'language' => $language, 'tnid' => 0);
|
||||
$matches = array();
|
||||
foreach (_i18n_node_references($string, 'contains', $params) as $id => $row) {
|
||||
// Add a class wrapper for a few required CSS overrides.
|
||||
$matches[$row['title'] . " [nid:$id]"] = '<div class="reference-autocomplete">' . $row['rendered'] . '</div>';
|
||||
}
|
||||
drupal_json_output($matches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates 'title [nid:$nid]' for the autocomplete field
|
||||
*/
|
||||
function i18n_node_nid2autocomplete($nid) {
|
||||
if ($node = node_load($nid)) {
|
||||
return $node->title . ' [nid:' . $nid . ']';
|
||||
}
|
||||
else {
|
||||
return t('Not found');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse mapping from node title to nid
|
||||
*
|
||||
* We also handle autocomplete values (title [nid:x]) and validate the form
|
||||
*/
|
||||
function i18n_node_autocomplete2nid($name, $field = NULL, $type, $language) {
|
||||
if (!empty($name)) {
|
||||
preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $name, $matches);
|
||||
if (!empty($matches)) {
|
||||
// Explicit [nid:n].
|
||||
list(, $title, $nid) = $matches;
|
||||
if (!empty($title) && ($node = node_load($nid)) && $title != $node->title) {
|
||||
if ($field) {
|
||||
form_set_error($field, t('Node title mismatch. Please check your selection.'));
|
||||
}
|
||||
$nid = NULL;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No explicit nid.
|
||||
$reference = _i18n_node_references($name, 'equals', array('type' => $type, 'language' => $language), 1);
|
||||
if (!empty($reference)) {
|
||||
$nid = key($reference);
|
||||
}
|
||||
elseif ($field) {
|
||||
form_set_error($field, t('Found no valid post with that title: %title', array('%title' => $name)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return !empty($nid) ? $nid : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find node title matches.
|
||||
*
|
||||
* @param $string
|
||||
* String to match against node title
|
||||
* @param $match
|
||||
* Match mode: 'contains', 'equals', 'starts_with'
|
||||
* @param $params
|
||||
* Other query arguments: type, language or numeric ones
|
||||
*/
|
||||
function _i18n_node_references($string, $match = 'contains', $params = array(), $limit = 10) {
|
||||
$query = db_select('node', 'n')
|
||||
->fields('n', array('nid', 'title' , 'type'))
|
||||
->orderBy('n.title')
|
||||
->orderBy('n.type')
|
||||
->range(0, $limit);
|
||||
|
||||
foreach ($params as $key => $value) {
|
||||
$query->condition($key, $value);
|
||||
}
|
||||
|
||||
switch ($match) {
|
||||
case 'equals':
|
||||
$query->condition('n.title', $string);
|
||||
break;
|
||||
|
||||
case 'starts_with':
|
||||
$query->condition('n.title', $string . '%', 'LIKE');
|
||||
break;
|
||||
|
||||
case 'contains':
|
||||
default:
|
||||
$query->condition('n.title', '%' . $string . '%', 'LIKE');
|
||||
break;
|
||||
}
|
||||
|
||||
// Disable and reenable i18n selection mode so no language conditions are inserted
|
||||
i18n_select(false);
|
||||
$references = array();
|
||||
foreach ($query->execute() as $node) {
|
||||
$references[$node->nid] = array(
|
||||
'title' => $node->title,
|
||||
'rendered' => check_plain($node->title),
|
||||
);
|
||||
}
|
||||
i18n_select(true);
|
||||
return $references;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme select translation form
|
||||
* @ingroup themeable
|
||||
*/
|
||||
function theme_i18n_node_select_translation($variables) {
|
||||
$elements = $variables['element'];
|
||||
$output = '';
|
||||
if (isset($elements['nid'])) {
|
||||
$rows = array();
|
||||
foreach (element_children($elements['nid']) as $lang) {
|
||||
$rows[] = array(
|
||||
$elements['language'][$lang]['#value'],
|
||||
drupal_render($elements['node'][$lang]),
|
||||
);
|
||||
}
|
||||
$output .= theme('table', array('rows' => $rows));
|
||||
$output .= drupal_render_children($elements);
|
||||
}
|
||||
return $output;
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains test cases for the i18n_node module.
|
||||
*/
|
||||
|
||||
class I18nNodeTestCase extends Drupali18nTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Content translation',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Content translation functions',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('translation', 'i18n_node');
|
||||
parent::setUpLanguages(array('administer content translations', 'translate content'));
|
||||
parent::setUpContentTranslation();
|
||||
|
||||
$this->addLanguage('pt-br');
|
||||
// Add a disabled language.
|
||||
$this->addLanguage('it');
|
||||
$edit = array('enabled[it]' => FALSE);
|
||||
$this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for adding content to an existing translation set.
|
||||
*/
|
||||
function testAddContentToTranslationSet() {
|
||||
module_load_include('inc', 'i18n_node', 'i18n_node.pages');
|
||||
|
||||
// Create 3 nodes in different languages.
|
||||
$en_title = $this->randomName(10);
|
||||
$en_body = $this->randomString(50);
|
||||
$en_node = $this->createNode('page', $en_title, $en_body, 'en');
|
||||
|
||||
$es_title = $this->randomName(10);
|
||||
$es_body = $this->randomString(50);
|
||||
$es_node = $this->createNode('page', $es_title, $es_body, 'es');
|
||||
|
||||
$ptbr_title = $this->randomName(10);
|
||||
$ptbr_body = $this->randomString(50);
|
||||
$ptbr_node = $this->createNode('page', $ptbr_title, $ptbr_body, 'pt-br');
|
||||
|
||||
// Check the autocomplete suggestions.
|
||||
$this->drupalGet('i18n/node/autocomplete/page/es/' . substr($es_title, 0, 3));
|
||||
$this->assertText($es_title);
|
||||
$this->assertNoText($en_title);
|
||||
$this->assertNoText($ptbr_title);
|
||||
|
||||
$this->drupalGet('i18n/node/autocomplete/page/es/' . substr($en_title, 0, 3));
|
||||
$this->assertNoText($es_title);
|
||||
$this->assertNoText($en_title);
|
||||
$this->assertNoText($ptbr_title);
|
||||
|
||||
$this->drupalGet('i18n/node/autocomplete/page/pt-br/' . substr($ptbr_title, 0, 3));
|
||||
$this->assertNoText($es_title);
|
||||
$this->assertNoText($en_title);
|
||||
$this->assertText($ptbr_title);
|
||||
|
||||
// Go to the translations tab.
|
||||
$this->drupalGet('node/' . $en_node->nid);
|
||||
$this->clickLink(t('Translate'));
|
||||
|
||||
// Make sure that the disabled language doesn't show up.
|
||||
$this->assertNoText(t('Italian'));
|
||||
|
||||
// Test validation.
|
||||
$edit = array(
|
||||
'translations[node][es]' => $ptbr_title,
|
||||
);
|
||||
$this->drupalPost(NULL, $edit, t('Update translations'));
|
||||
$this->assertText(t('Found no valid post with that title: @title', array('@title' => $ptbr_title)));
|
||||
|
||||
// Add two translated nodes.
|
||||
$edit = array(
|
||||
'translations[node][pt-br]' => $ptbr_title,
|
||||
'translations[node][es]' => $es_title,
|
||||
);
|
||||
$this->drupalPost(NULL, $edit, t('Update translations'));
|
||||
$this->assertText(t('Added @count nodes to the translation set.', array('@count' => 2)));
|
||||
|
||||
$this->assertFieldByName('translations[node][es]', i18n_node_nid2autocomplete($es_node->nid));
|
||||
$this->assertFieldByName('translations[node][pt-br]', i18n_node_nid2autocomplete($ptbr_node->nid));
|
||||
|
||||
// Remove a translation node again.
|
||||
$edit = array(
|
||||
'translations[node][pt-br]' => '',
|
||||
);
|
||||
$this->drupalPost(NULL, $edit, t('Update translations'));
|
||||
$this->assertText(t('Removed a node from the translation set.'));
|
||||
|
||||
$this->assertFieldByName('translations[node][es]', i18n_node_nid2autocomplete($es_node->nid));
|
||||
$this->assertFieldByName('translations[node][pt-br]', '');
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Variable information
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_variable_group_info().
|
||||
*/
|
||||
function i18n_node_variable_group_info() {
|
||||
$groups['i18n_node'] = array(
|
||||
'title' => t('Multilingual node options'),
|
||||
'description' => t('Extended node options for multilingual sites.'),
|
||||
'access' => 'administer site configuration',
|
||||
'path' => 'admin/config/regional/i18n/node',
|
||||
);
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_variable_info().
|
||||
*/
|
||||
function i18n_node_variable_info($options = array()) {
|
||||
$variables['i18n_hide_translation_links'] = array(
|
||||
'type' => 'boolean',
|
||||
'title' => t('Hide content translation links', array(), $options),
|
||||
'description' => t('Hide the links to translations in content body and teasers. If you choose this option, switching language will only be available from the language switcher block.', array(), $options),
|
||||
'default' => 0,
|
||||
'group' => 'i18n_node',
|
||||
);
|
||||
$variables['i18n_node_default_language_none'] = array(
|
||||
'title' => t('Default language for content types with Multilingual support disabled.', array(), $options),
|
||||
'description' => t('Determines which language will be set for newly created content of types that don\'t have Multilingual support enabled.', array(), $options),
|
||||
'type' => 'select',
|
||||
'options' => array(
|
||||
0 => t('The site\'s default language (Default behaviour).', array(), $options),
|
||||
1 => t('Language neutral (Recommended).', array(), $options)
|
||||
),
|
||||
'default' => 0,
|
||||
'group' => 'i18n_node',
|
||||
);
|
||||
$variables['i18n_node_options_[node_type]'] = array(
|
||||
'type' => 'multiple',
|
||||
'title' => t('Extended language options', array(), $options),
|
||||
'repeat' => array(
|
||||
'type' => 'options',
|
||||
'options' => array(
|
||||
'current' => t('Set current language as default for new content.', array(), $options),
|
||||
'required' => t('Require language (Do not allow Language Neutral).', array(), $options),
|
||||
'lock' => t('Lock language (Cannot be changed).', array(), $options),
|
||||
),
|
||||
),
|
||||
'group' => 'i18n',
|
||||
);
|
||||
$variables['i18n_node_extended_[node_type]'] = array(
|
||||
'type' => 'multiple',
|
||||
'title' => t('Extended language support'),
|
||||
'repeat' => array(
|
||||
'type' => 'select',
|
||||
'options callback' => 'i18n_node_variable_extended_options',
|
||||
'default' => I18N_LANGUAGE_ENABLED,
|
||||
),
|
||||
'description' => t('If enabled, all defined languages will be allowed for this content type in addition to only enabled ones. This is useful to have more languages for content than for the interface.', array(), $options),
|
||||
'group' => 'i18n',
|
||||
);
|
||||
|
||||
return $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options callback for i18n_node_extended_
|
||||
*/
|
||||
function i18n_node_variable_extended_options($variable, $options) {
|
||||
return array(
|
||||
I18N_LANGUAGE_ENABLED => t('Normal - All enabled languages will be allowed.', array(), $options),
|
||||
I18N_LANGUAGE_EXTENDED => t('Extended - All defined languages will be allowed.', array(), $options),
|
||||
I18N_LANGUAGE_EXTENDED | I18N_LANGUAGE_HIDDEN => t('Extended, but not displayed - All defined languages will be allowed for input, but not displayed in links.', array(), $options),
|
||||
);
|
||||
}
|
248
sites/all/modules/contrib/localisation/i18n/i18n_object.inc
Normal file
248
sites/all/modules/contrib/localisation/i18n/i18n_object.inc
Normal file
@@ -0,0 +1,248 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* i18n Object Class
|
||||
*/
|
||||
|
||||
/**
|
||||
* Object wrapper
|
||||
*/
|
||||
class i18n_object_wrapper {
|
||||
protected $type;
|
||||
protected $key;
|
||||
protected $object;
|
||||
// Object translations, static cache.
|
||||
protected $translations;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public function __construct($type, $key, $object) {
|
||||
$this->type = $type;
|
||||
$this->key = $key;
|
||||
$this->object = $object ? $object : $this->load_object($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get edit path for object
|
||||
*/
|
||||
function get_edit_path() {
|
||||
return $this->path_replace($this->get_info('edit path'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field value from object/array
|
||||
*/
|
||||
function get_field($field, $default = NULL) {
|
||||
return i18n_object_field($this->object, $field, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set field value to object/array
|
||||
*/
|
||||
function set_field($field, $value) {
|
||||
if (is_object($this->object)) {
|
||||
$this->object->$field = $value;
|
||||
}
|
||||
elseif (is_array($this->object)) {
|
||||
$this->object[$field] = $value;
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get string numeric key for indexing.
|
||||
*/
|
||||
function get_index() {
|
||||
$key = $this->get_key();
|
||||
return is_array($key) ? implode(':', $key) : $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key value from object/array
|
||||
*/
|
||||
function get_key($default = NULL) {
|
||||
if ($field = $this->get_info('key')) {
|
||||
return $this->get_field($field, $default);
|
||||
}
|
||||
else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language code
|
||||
*/
|
||||
public function get_langcode() {
|
||||
return i18n_object_langcode($this->object);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get real object or array.
|
||||
*/
|
||||
public function get_object() {
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load real object or array.
|
||||
*
|
||||
* @param $object
|
||||
*/
|
||||
function load_object($object) {
|
||||
if ($callback = $this->get_info('load callback', NULL)) {
|
||||
$this->object = call_user_func($callback, $object);
|
||||
}
|
||||
elseif ($entity_type = $this->get_info('entity', NULL)) {
|
||||
$entity = entity_load($entity_type, array($object));
|
||||
$this->object = $entity ? reset($entity) : FALSE;
|
||||
}
|
||||
|
||||
return $this->get_object();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get menu placehoders for object
|
||||
*/
|
||||
protected function get_placeholders() {
|
||||
$placeholders = $this->get_info('placeholders', array());
|
||||
foreach ($placeholders as $name => $field) {
|
||||
$placeholders[$name] = $this->get_field($field);
|
||||
}
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get link for item
|
||||
*/
|
||||
public function get_path() {
|
||||
if ($uri = entity_uri($this->type, $this->object)) {
|
||||
return $uri['path'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title from item
|
||||
*/
|
||||
public function get_title() {
|
||||
return entity_label($this->type, $this->object);
|
||||
}
|
||||
/**
|
||||
* Get object type
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu access callback for mixed translation tab
|
||||
*/
|
||||
function get_translate_access() {
|
||||
switch ($this->get_translate_mode()) {
|
||||
case I18N_MODE_TRANSLATE:
|
||||
return $this->translate_access();
|
||||
case I18N_MODE_LOCALIZE:
|
||||
return $this->localize_access();
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translate or localize mode for object
|
||||
*/
|
||||
function get_translate_mode() {
|
||||
return I18N_MODE_NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translation set id if any
|
||||
*/
|
||||
function get_tsid() {
|
||||
return $this->get_field($this->get_translation_info('field', 'i18n_tsid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set translation set id
|
||||
*/
|
||||
function set_tsid($tsid) {
|
||||
return $this->set_field($this->get_translation_info('field', 'i18n_tsid'), $tsid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Localize object if localizable.
|
||||
*/
|
||||
function localize($langcode, $options = array()) {
|
||||
if ($this->get_translate_mode() == I18N_MODE_LOCALIZE) {
|
||||
return $this->translate($langcode, $options);
|
||||
}
|
||||
else {
|
||||
return $this->object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate object if translatable.
|
||||
*/
|
||||
function translate($langcode, $options = array()) {
|
||||
if (isset($this->translations[$langcode])) {
|
||||
return $this->translations[$langcode];
|
||||
}
|
||||
else {
|
||||
return $this->object;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate access (translation sets)
|
||||
*/
|
||||
protected function translate_access() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate access (localize strings)
|
||||
*/
|
||||
protected function localize_access() {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace path with placeholders
|
||||
*
|
||||
* @param $path
|
||||
* Path to replace
|
||||
* @param $replacements
|
||||
* Replacement variables to override or add to placeholders
|
||||
*/
|
||||
protected function path_replace($path, $replacements = array()) {
|
||||
if ($path) {
|
||||
$path = strtr($path, $replacements + $this->get_placeholders());
|
||||
// Clean up duplicated and final '/' (empty placeholders)
|
||||
$path = strtr($path, array('//' => '/'));
|
||||
return trim($path, '/');
|
||||
}
|
||||
else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get object info
|
||||
*/
|
||||
public function get_info($property, $default = NULL) {
|
||||
return i18n_object_info($this->type, $property, $default);
|
||||
}
|
||||
/**
|
||||
* Get object translation set info
|
||||
*/
|
||||
public function get_translation_info($property, $default = NULL) {
|
||||
return function_exists('i18n_translation_set_info') ? i18n_translation_set_info($this->type, $property, $default) : $default;
|
||||
}
|
||||
/**
|
||||
* Get object string translation info
|
||||
*/
|
||||
public function get_string_info($property, $default = NULL) {
|
||||
$info = $this->get_info('string translation');
|
||||
return $info && isset($info[$property]) ? $info[$property] : $default;
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
|
||||
README.txt
|
||||
==========
|
||||
Drupal module: Path translation
|
||||
==================================
|
||||
|
||||
This module provides some basic path translation feature for generic paths.
|
||||
|
||||
For paths belonging to objects that have translations, like nodes and taxonomy terms, the system can produce automatic
|
||||
links for the language switcher.
|
||||
|
||||
For the rest of paths, this module allows to define which path is translation of which. Example:
|
||||
|
||||
1. We define a new 'path translation set' like
|
||||
- English: node/1
|
||||
- Spanish: taxonomy/term/3
|
||||
|
||||
2. Every time we are on any of these pages, the language switcher will point to the other path for each language.
|
||||
|
||||
This module is intended for translation of generic paths that don't have other way of being translated.
|
||||
|
||||
Note: path translations must be defined without aliases.
|
||||
|
||||
====================================================================
|
||||
Jose A. Reyero, http://reyero.net
|
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Administration pages for path translation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Path overview page
|
||||
*/
|
||||
function i18n_path_admin_overview($type = NULL) {
|
||||
module_load_include('admin.inc', 'i18n_translation');
|
||||
return i18n_translation_admin_overview('path');
|
||||
}
|
||||
|
||||
/**
|
||||
* Path add/edit form
|
||||
*/
|
||||
function i18n_path_admin_form($form, $form_state, $translation_set = NULL) {
|
||||
$form['translation_set'] = array('#type' => 'value', '#value' => $translation_set);
|
||||
if ($translation_set) {
|
||||
$paths = $translation_set->get_translations();
|
||||
}
|
||||
else {
|
||||
$paths = array();
|
||||
}
|
||||
$form['title'] = array(
|
||||
'#title' => t('Title'),
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => $translation_set ? $translation_set->title : '',
|
||||
'#description' => t('Optional descriptive name for this set.'),
|
||||
);
|
||||
$form['translations'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Translations'),
|
||||
'#tree' => TRUE,
|
||||
'#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
|
||||
);
|
||||
foreach (i18n_language_list() as $langcode => $name) {
|
||||
$form['translations'][$langcode] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => check_plain($name),
|
||||
'#default_value' => !empty($paths[$langcode]) ? $paths[$langcode]->path : '',
|
||||
);
|
||||
}
|
||||
|
||||
$form['controls']['update'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Save'),
|
||||
'#name' => 'save',
|
||||
);
|
||||
|
||||
if ($translation_set) {
|
||||
$form['controls']['delete'] = array(
|
||||
'#type' => 'submit',
|
||||
'#value' => t('Delete'),
|
||||
'#name' => 'delete',
|
||||
);
|
||||
}
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process form validation
|
||||
*/
|
||||
function i18n_path_admin_form_validate($form, &$form_state) {
|
||||
if ($form_state['triggering_element']['#name'] == 'save') {
|
||||
$paths = &$form_state['values']['translations'];
|
||||
if ($paths = array_filter($paths)) {
|
||||
module_load_include('inc', 'menu', 'menu.admin');
|
||||
foreach ($paths as $language => &$link_path) {
|
||||
$link_path = i18n_prepare_normal_path($link_path, $language);
|
||||
$validation_form_state = array(
|
||||
'values' => array(
|
||||
'link_path' => $link_path,
|
||||
),
|
||||
);
|
||||
menu_edit_item_validate(array(), $validation_form_state);
|
||||
}
|
||||
}
|
||||
else {
|
||||
form_set_error('paths', t('There are no path translations to save.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process form submission
|
||||
*/
|
||||
function i18n_path_admin_form_submit($form, &$form_state) {
|
||||
$translation_set = $form_state['values']['translation_set'];
|
||||
switch ($form_state['triggering_element']['#name']) {
|
||||
case 'save':
|
||||
$paths = array_filter($form_state['values']['translations']);
|
||||
$translation_set = $translation_set ? $translation_set : i18n_translation_set_create('path');
|
||||
$translation_set->title = '';
|
||||
$translations = $translation_set->get_translations();
|
||||
foreach ($paths as $lang => $path) {
|
||||
if (isset($translations[$lang])) {
|
||||
$translations[$lang]->path = $path;
|
||||
}
|
||||
else {
|
||||
$translation_set->add_item($path, $lang);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_diff(array_keys($translation_set->get_translations()), array_keys($paths)) as $language) {
|
||||
$translation_set->remove_language($language);
|
||||
unset($translations[$language]);
|
||||
}
|
||||
|
||||
if (!empty($form_state['values']['title'])) {
|
||||
$translation_set->title = $form_state['values']['title'];
|
||||
}
|
||||
|
||||
$translation_set->save(TRUE);
|
||||
drupal_set_message(t('The path translation has been saved.'));
|
||||
break;
|
||||
case 'delete':
|
||||
$destination = array();
|
||||
if (isset($_GET['destination'])) {
|
||||
$destination = drupal_get_destination();
|
||||
unset($_GET['destination']);
|
||||
}
|
||||
$form_state['redirect'] = array($translation_set->get_delete_path(), array('query' => $destination));
|
||||
return;
|
||||
}
|
||||
$form_state['redirect'] = 'admin/config/regional/i18n_translation/path';
|
||||
}
|
||||
|
||||
/**
|
||||
* Save path translation set.
|
||||
*/
|
||||
function i18n_path_save_translations($paths, $tpid = NULL) {
|
||||
$paths = array_filter($paths);
|
||||
if (lock_acquire('i18n_path')) {
|
||||
if ($tpid) {
|
||||
db_delete('i18n_path')->condition('tpid', $tpid)->execute();
|
||||
}
|
||||
else {
|
||||
$tpid = 1 + (int)db_query('SELECT MAX(tpid) FROM {i18n_path}')->fetchField();
|
||||
}
|
||||
foreach ($paths as $langcode => $path) {
|
||||
db_insert('i18n_path')
|
||||
->fields(array('tpid' => $tpid, 'language' => $langcode, 'path' => $path))
|
||||
->execute();
|
||||
}
|
||||
lock_release('i18n_path');
|
||||
return $tpid;
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) module - Translation set
|
||||
*/
|
||||
class i18n_path_translation_set extends i18n_translation_set {
|
||||
/**
|
||||
* Add translation item
|
||||
*/
|
||||
public function add_item($path, $langcode = NULL) {
|
||||
// Path may be object or plain string
|
||||
$item = is_object($path) ? $path : (object)array('path' => $path, 'language' => $langcode);
|
||||
return parent::add_item($item, $langcode);
|
||||
}
|
||||
/**
|
||||
* Clean path translations.
|
||||
*
|
||||
* Unlike other translation sets this actually deletes paths
|
||||
*/
|
||||
public function clean_translations() {
|
||||
$delete = db_delete('i18n_path')
|
||||
->condition('tsid', $this->tsid)
|
||||
->condition('language', array_keys($this->get_translations()), 'NOT IN')
|
||||
->execute();
|
||||
}
|
||||
/**
|
||||
* Delete translation set
|
||||
*/
|
||||
public function delete_translations() {
|
||||
return db_delete('i18n_path')
|
||||
->condition('tsid', $this->tsid)
|
||||
->execute();
|
||||
}
|
||||
/**
|
||||
* Save all path translations
|
||||
*/
|
||||
public function save_translations() {
|
||||
foreach ($this->get_translations() as $lang => $path) {
|
||||
$path = is_object($path) ? $path : (object) array('path' => $path, 'language' => $lang, 'tsid' => $this->tsid);
|
||||
drupal_write_record('i18n_path', $path, !empty($path->tpid) ? 'tpid' : array());
|
||||
$this->add_item($path, $lang);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Path object
|
||||
*/
|
||||
class i18n_path_object extends i18n_object_wrapper {
|
||||
/**
|
||||
* Get title from item
|
||||
*/
|
||||
public function get_title() {
|
||||
return $this->object->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get path for item
|
||||
*/
|
||||
public function get_path() {
|
||||
return check_url($this->object->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get translate mode
|
||||
*/
|
||||
public function get_translate_mode() {
|
||||
return I18N_MODE_TRANSLATE;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Path translation
|
||||
description = Define translations for generic paths
|
||||
dependencies[] = i18n_translation
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
|
||||
files[] = i18n_path.inc
|
||||
files[] = i18n_path.test
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the text module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_install().
|
||||
*/
|
||||
function i18n_path_install() {
|
||||
// Set module weight for it to run after core modules, but before views.
|
||||
db_update('system')
|
||||
->fields(array('weight' => 5))
|
||||
->condition('name', 'i18n_path', '=')
|
||||
->condition('type', 'module', '=')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function i18n_path_schema() {
|
||||
$schema['i18n_path'] = array(
|
||||
'description' => 'Path translation',
|
||||
'fields' => array(
|
||||
'tpid' => array(
|
||||
'description' => 'The primary identifier for a path in the translation set.',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'tsid' => array(
|
||||
'description' => 'The primary identifier for a translation set.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
),
|
||||
'path' => array(
|
||||
'description' => 'The Drupal path this alias is for; e.g. node/12.',
|
||||
'type' => 'varchar',
|
||||
'length' => 255,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'language' => array(
|
||||
'description' => "The language for which this path is a translation.",
|
||||
'type' => 'varchar',
|
||||
'length' => 12,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
),
|
||||
'pid' => array(
|
||||
'description' => 'A unique path alias identifier if the path has an alias.',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
),
|
||||
),
|
||||
'indexes' => array(
|
||||
'path' => array('path'),
|
||||
),
|
||||
'unique keys' => array(
|
||||
'set_language' => array('tsid', 'language'),
|
||||
),
|
||||
'foreign keys' => array(
|
||||
'path_language' => array(
|
||||
'table' => 'languages',
|
||||
'columns' => array('language' => 'language'),
|
||||
),
|
||||
'translation_set' => array(
|
||||
'table' => 'i18n_translation',
|
||||
'columns' => array('tsid' => 'tsid'),
|
||||
),
|
||||
),
|
||||
'primary key' => array('tpid'),
|
||||
);
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set module weight.
|
||||
*/
|
||||
function i18n_path_update_7000(&$sandbox) {
|
||||
// Set module weight for it to run after core modules, but before views.
|
||||
db_update('system')
|
||||
->fields(array('weight' => 5))
|
||||
->condition('name', 'i18n_path', '=')
|
||||
->condition('type', 'module', '=')
|
||||
->execute();
|
||||
}
|
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) module - Path translation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_menu()
|
||||
*/
|
||||
function i18n_path_menu() {
|
||||
$items['admin/config/regional/i18n_translation/path'] = array(
|
||||
'title' => 'Paths',
|
||||
'description' => 'Path translation.',
|
||||
'page callback' => 'i18n_path_admin_overview',
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'i18n_path.admin.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'weight' => 10,
|
||||
);
|
||||
$items['admin/config/regional/i18n_translation/path/list'] = array(
|
||||
'title' => 'Paths',
|
||||
'type' => MENU_DEFAULT_LOCAL_TASK,
|
||||
'weight' => -10,
|
||||
);
|
||||
$items['admin/config/regional/i18n_translation/path/add'] = array(
|
||||
'title' => 'Add path translation',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('i18n_path_admin_form'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'i18n_path.admin.inc',
|
||||
'type' => MENU_LOCAL_ACTION,
|
||||
'parent' => 'admin/config/regional/i18n_translation',
|
||||
);
|
||||
$items['admin/config/regional/i18n_translation/path/edit/%i18n_path_translation_set'] = array(
|
||||
'title' => 'Edit path translation',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('i18n_path_admin_form', 6),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'i18n_path.admin.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'context' => MENU_CONTEXT_INLINE,
|
||||
);
|
||||
$items['admin/config/regional/i18n_translation/path/delete/%i18n_path_translation_set'] = array(
|
||||
'title' => 'Delete path translation',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('i18n_translation_set_delete_confirm', 6),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'i18n_path.admin.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
'context' => MENU_CONTEXT_INLINE,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_url_outbound_alter()
|
||||
*/
|
||||
/*
|
||||
function i18n_path_url_outbound_alter(&$path, &$options, $original_path) {
|
||||
if (!empty($options['language'])) {
|
||||
$langcode = $options['language']->language;
|
||||
$original = $options['alias'] ? drupal_get_normal_path($path, $langcode) : $original_path;
|
||||
if (($translations = i18n_path_get_translations($path)) && !empty($translations[$langcode])) {
|
||||
$path = $options['alias'] ? drupal_get_path_alias($translations[$langcode], $langcode) : $translations[$langcode];
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get translations for path
|
||||
*/
|
||||
function i18n_path_get_translations($path) {
|
||||
static $translations;
|
||||
|
||||
if (!isset($translations)) {
|
||||
$translations = drupal_static(__FUNCTION__, array());
|
||||
}
|
||||
if (!isset($translations[$path])) {
|
||||
$translations[$path] = db_query('SELECT p.language, p.path FROM {i18n_path} p INNER JOIN {i18n_path} ps ON p.tsid = ps.tsid WHERE ps.path = :path',
|
||||
array(':path' => $path)
|
||||
)->fetchAllKeyed();
|
||||
}
|
||||
return $translations[$path];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_object_info().
|
||||
*/
|
||||
function i18n_path_i18n_object_info() {
|
||||
return array(
|
||||
'path' => array(
|
||||
'title' => t('Path'),
|
||||
'class' => 'i18n_path_object',
|
||||
'key' => array('path', 'language'),
|
||||
'translation set' => TRUE,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_translation_set_info()
|
||||
*/
|
||||
function i18n_path_i18n_translation_set_info() {
|
||||
return array(
|
||||
'path' => array(
|
||||
'title' => t('Path'),
|
||||
'class' => 'i18n_path_translation_set',
|
||||
'table' => 'i18n_path',
|
||||
'field' => 'tsid',
|
||||
'placeholder' => '%i18n_path_translation_set',
|
||||
'edit path' => 'admin/config/regional/i18n_translation/path/edit/%i18n_path_translation_set',
|
||||
'delete path' => 'admin/config/regional/i18n_translation/path/delete/%i18n_path_translation_set',
|
||||
'list path' => 'admin/config/regional/i18n_translation/path',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_i18n_translate_path()
|
||||
*/
|
||||
function i18n_path_i18n_translate_path($path) {
|
||||
if ($translations = i18n_path_get_translations($path)) {
|
||||
$result = array();
|
||||
foreach ($translations as $langcode => $translated) {
|
||||
$result[$langcode] = array(
|
||||
'href' => $translated,
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load translation set. Menu loading callback.
|
||||
*/
|
||||
function i18n_path_translation_set_load($tsid) {
|
||||
return i18n_translation_set_load($tsid, 'path');
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Test case for multilingual menus.
|
||||
*/
|
||||
class i18nPathTestCase extends Drupali18nTestCase {
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Path translation',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Path translation functions',
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('translation', 'i18n_path');
|
||||
parent::setUpLanguages(array('administer site configuration'));
|
||||
}
|
||||
|
||||
function checkTranslationLink($path, $language, $method = 'assertRaw') {
|
||||
$this->{$method}($path, t('Found translation link. :language - :path', array(':language' => $language, ':path' => $path)));
|
||||
}
|
||||
|
||||
function testPathTranslation() {
|
||||
$this->setUpContentType(array('type' => 'page', 'mode' => TRANSLATION_ENABLED));
|
||||
|
||||
// Create 2 nodes in different languages.
|
||||
$first_title = $this->randomName(10);
|
||||
$first_body = $this->randomString(50);
|
||||
$first_node = $this->createNode('page', $first_title, $first_body, $this->default_language);
|
||||
|
||||
$secondary_title = $this->randomName(10);
|
||||
$secondary_body = $this->randomString(50);
|
||||
$secondary_node = $this->createNode('page', $secondary_title, $secondary_body, $this->secondary_language);
|
||||
|
||||
$this->drupalGet('node/' . $first_node->nid);
|
||||
$this->checkTranslationLink('node/' . $first_node->nid, $first_node->language);
|
||||
$this->checkTranslationLink($this->secondary_language . '/node/' . $first_node->nid, $this->secondary_language, 'assertNoRaw');
|
||||
|
||||
$this->drupalGet('node/' . $secondary_node->nid);
|
||||
$this->checkTranslationLink('node/' . $secondary_node->nid, $secondary_node->language);
|
||||
$this->checkTranslationLink($this->secondary_language . '/node/' . $secondary_node->nid, $this->secondary_language);
|
||||
|
||||
$this->drupalGet('admin/config/regional/i18n_translation/path');
|
||||
$this->clickLink(t('Add path translation'));
|
||||
|
||||
// create new translation set with two node links
|
||||
$edit = array(
|
||||
'title' => $this->randomName(10),
|
||||
'translations[' . $this->default_language . ']' => 'node/' . $first_node->nid,
|
||||
'translations[' . $this->secondary_language . ']' => 'node/' . $secondary_node->nid,
|
||||
);
|
||||
$this->drupalPost('admin/config/regional/i18n_translation/path/add', $edit, t('Save'));
|
||||
|
||||
$this->drupalGet('node/' . $first_node->nid);
|
||||
$this->checkTranslationLink('node/' . $first_node->nid, $first_node->language);
|
||||
$this->checkTranslationLink($this->secondary_language . '/node/' . $secondary_node->nid, $this->secondary_language);
|
||||
|
||||
$this->drupalGet('node/' . $secondary_node->nid);
|
||||
$this->checkTranslationLink('node/' . $first_node->nid, $first_node->language);
|
||||
$this->checkTranslationLink('node/' . $secondary_node->nid, $this->secondary_language);
|
||||
|
||||
// create new translation set with one node and one menu "token"
|
||||
$edit = array(
|
||||
'translations[' . $this->default_language . ']' => 'node/' . $first_node->nid,
|
||||
'translations[' . $this->secondary_language . ']' => '<front>',
|
||||
);
|
||||
$this->drupalPost('admin/config/regional/i18n_translation/path/edit/1', $edit, t('Save'));
|
||||
|
||||
$this->drupalGet('node/' . $first_node->nid);
|
||||
$this->checkTranslationLink('node/' . $first_node->nid, $first_node->language);
|
||||
$this->checkTranslationLink('node/' . $secondary_node->nid, $this->secondary_language, 'assertNoLinkByHref');
|
||||
$this->checkTranslationLink($this->secondary_language, $this->secondary_language);
|
||||
|
||||
// create new translation set with one node and an external menu link.
|
||||
$url = 'http://' . $this->randomName(10) . '.' . $this->randomName(2);
|
||||
$edit = array(
|
||||
'translations[' . $this->default_language . ']' => 'node/' . $first_node->nid,
|
||||
'translations[' . $this->secondary_language . ']' => $url,
|
||||
);
|
||||
$this->drupalPost('admin/config/regional/i18n_translation/path/edit/1', $edit, t('Save'));
|
||||
|
||||
$this->drupalGet('node/' . $first_node->nid);
|
||||
$this->checkTranslationLink('node/' . $first_node->nid, $first_node->language);
|
||||
$this->checkTranslationLink($url, $this->secondary_language);
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
name = Translation redirect
|
||||
description = Redirect to translated page when available. SEO for multilingual sites.
|
||||
dependencies[] = i18n
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internationalization (i18n) module.
|
||||
*
|
||||
* Redirect to language path when we have a translation for the current language.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function i18n_redirect_help($path, $arg) {
|
||||
switch ($path) {
|
||||
case 'admin/config/regional/i18n':
|
||||
if (!module_exists('i18n_node')) {
|
||||
$output = '<p>' . t('To have <em>Translation redirect</em> working with your content you should <a href="@admin_modules">enable the <em>Multilingual content</em> module</a>.', array('@admin_modules' => url('admin/modules'))) . '</p>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_init()
|
||||
*/
|
||||
function i18n_redirect_init() {
|
||||
$path = $_GET['q'];
|
||||
$language = i18n_language_interface();
|
||||
// Not for logged in users nor for home page
|
||||
if (!$path || drupal_is_front_page() || !empty($GLOBALS['user']->uid)) {
|
||||
return;
|
||||
}
|
||||
elseif ($translations = i18n_get_path_translations($path)) {
|
||||
if (isset($translations[$language->language]) && $translations[$language->language]['href'] != $path) {
|
||||
drupal_goto($translations[$language->language]['href'], array('language' => $language), 301);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Helper functions for select administration.
|
||||
*/
|
||||
|
||||
function i18n_select_admin_settings() {
|
||||
$form['types'] = array(
|
||||
'#type' => 'variable_fieldset',
|
||||
'#title' => t('Content to filter by language'),
|
||||
'#variable_list' => array('i18n_select_nodes', 'i18n_select_taxonomy'),
|
||||
);
|
||||
$form['mode'] = array(
|
||||
'#type' => 'variable_fieldset',
|
||||
'#title' => t('Content selection mode'),
|
||||
'#variable_list' => array('i18n_select_missing_translation', 'i18n_select_skip_tags'),
|
||||
);
|
||||
// Enable for specific pages. This works pretty much like block visibility
|
||||
// Note this page requires 'administer site configuration' so we don't need to check permissions
|
||||
$form['pages'] = array(
|
||||
'#type' => 'variable_fieldset',
|
||||
'#title' => t('Enable for specific pages'),
|
||||
'#variable_list' => array('i18n_select_page_mode', 'i18n_select_page_list', 'i18n_select_page_block'),
|
||||
);
|
||||
return system_settings_form($form);
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
name = Multilingual select
|
||||
description = API module for multilingual content selection
|
||||
dependencies[] = i18n
|
||||
package = Multilingual - Internationalization
|
||||
core = 7.x
|
||||
configure = admin/config/regional/i18n/select
|
||||
files[] = i18n_select.test
|
||||
|
||||
; Information added by drupal.org packaging script on 2013-08-21
|
||||
version = "7.x-1.10"
|
||||
core = "7.x"
|
||||
project = "i18n"
|
||||
datestamp = "1377069696"
|
||||
|
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Multilingual content selection module.
|
||||
*
|
||||
* Alters content queries to add language conditions.
|
||||
*
|
||||
* Queries tagged with 'i18n_select' or that already have a language condition will not be altered.
|
||||
*/
|
||||
|
||||
// No language selection
|
||||
define('I18N_SELECT_NONE', 0);
|
||||
// Content with current language and undefined language
|
||||
define('I18N_SELECT_NORMAL', 1);
|
||||
// Select default language when current language is missing
|
||||
define('I18N_SELECT_MISSING', 2);
|
||||
|
||||
/**
|
||||
* Enable on every page except the listed pages.
|
||||
*/
|
||||
define('I18N_SELECT_PAGE_NOTLISTED', 0);
|
||||
/**
|
||||
* Enable on only the listed pages.
|
||||
*/
|
||||
define('I18N_SELECT_PAGE_LISTED', 1);
|
||||
/**
|
||||
* Enable if the associated PHP code returns TRUE.
|
||||
*/
|
||||
define('I18N_SELECT_PAGE_PHP', 2);
|
||||
|
||||
/**
|
||||
* Implements hook_init().
|
||||
*/
|
||||
function i18n_select_init() {
|
||||
// Determine selection mode for this page
|
||||
i18n_select(i18n_select_page());
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_list_alter().
|
||||
*
|
||||
* Dirty trick to enable selection for blocks though it may be disabled for the current page.
|
||||
*/
|
||||
function i18n_select_block_list_alter(&$blocks) {
|
||||
// Still, skip for form submission. There are pages like the ones produced
|
||||
// by overlay that render the blocks before the page.
|
||||
// See overlay_init(), overlay_overlay_child_initialize()
|
||||
if (empty($_POST) && !isset($_GET['token']) && variable_get('i18n_select_page_block', TRUE)) {
|
||||
i18n_select(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_menu().
|
||||
*/
|
||||
function i18n_select_menu() {
|
||||
$items['admin/config/regional/i18n/select'] = array(
|
||||
'title' => 'Selection',
|
||||
'description' => 'Configure extended options for multilingual content and translations.',
|
||||
'page callback' => 'drupal_get_form',
|
||||
'page arguments' => array('i18n_select_admin_settings'),
|
||||
'access arguments' => array('administer site configuration'),
|
||||
'file' => 'i18n_select.admin.inc',
|
||||
'type' => MENU_LOCAL_TASK,
|
||||
);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current mode for i18n selection
|
||||
*
|
||||
* @param $type
|
||||
* Selection type: 'nodes', 'taxonomy', etc..
|
||||
*/
|
||||
function i18n_select_mode($type = NULL) {
|
||||
if (i18n_select() && (!$type || variable_get('i18n_select_' . $type, TRUE))) {
|
||||
return I18N_SELECT_NORMAL;
|
||||
}
|
||||
else {
|
||||
return I18N_SELECT_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check current path to enable selection
|
||||
*
|
||||
* This works pretty much like block visibility
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if content selection should be enabled for this page.
|
||||
*/
|
||||
function i18n_select_page() {
|
||||
static $mode;
|
||||
|
||||
if (!isset($mode)) {
|
||||
$mode = &drupal_static(__FUNCTION__);
|
||||
$visibility = variable_get('i18n_select_page_mode', I18N_SELECT_PAGE_NOTLISTED);
|
||||
if ($pages = variable_get('i18n_select_page_list', 'admin/*')) {
|
||||
// Convert path to lowercase. This allows comparison of the same path
|
||||
// with different case. Ex: /Page, /page, /PAGE.
|
||||
$pages = drupal_strtolower($pages);
|
||||
if ($visibility < I18N_SELECT_PAGE_PHP) {
|
||||
// Convert the Drupal path to lowercase
|
||||
$path = drupal_strtolower(drupal_get_path_alias($_GET['q']));
|
||||
// Compare the lowercase internal and lowercase path alias (if any).
|
||||
$page_match = drupal_match_path($path, $pages);
|
||||
if ($path != $_GET['q']) {
|
||||
$page_match = $page_match || drupal_match_path($_GET['q'], $pages);
|
||||
}
|
||||
// When $visibility has a value of 0 (I18N_SELECT_PAGE_NOTLISTED),
|
||||
// the block is displayed on all pages except those listed in $pages.
|
||||
// When set to 1 (I18N_SELECT_PAGE_LISTED), it is displayed only on those
|
||||
// pages listed in $pages.
|
||||
$mode = !($visibility xor $page_match);
|
||||
}
|
||||
elseif (module_exists('php')) {
|
||||
$mode = php_eval($pages);
|
||||
}
|
||||
else {
|
||||
$mode = FALSE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No pages defined, still respect the setting (unlike blocks)
|
||||
$mode = $visibility == I18N_SELECT_PAGE_NOTLISTED;
|
||||
}
|
||||
}
|
||||
|
||||
return $mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_query_node_access_alter().
|
||||
*
|
||||
* Rewrite node queries so language selection options are enforced.
|
||||
*/
|
||||
function i18n_select_query_node_access_alter(QueryAlterableInterface $query) {
|
||||
if (i18n_select_mode('nodes') && i18n_select_check_query($query, 'nid') &&
|
||||
($table_alias = i18n_select_check_table($query, 'node', 'nid'))) {
|
||||
$query->condition($table_alias . '.language', i18n_select_langcodes());
|
||||
// Mark query as altered
|
||||
$query->addTag('i18n_select');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of hook_query_term_access_alter().
|
||||
*
|
||||
* Rewrite taxonomy term queries so language selection options are enforced.
|
||||
*/
|
||||
function i18n_select_query_term_access_alter(QueryAlterableInterface $query) {
|
||||
// dsm($query, 'i18n query alter');
|
||||
|
||||
if (module_exists('i18n_taxonomy')
|
||||
&& i18n_select_mode('taxonomy')
|
||||
&& i18n_select_check_query($query, 'tid')
|
||||
&& ($table_alias = i18n_select_check_table($query, 'taxonomy_term_data', 'tid'))
|
||||
) {
|
||||
$query->condition($table_alias . '.language', i18n_select_langcodes());
|
||||
// Mark query as altered
|
||||
$query->addTag('i18n_select');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check table exists in query and get alias for it.
|
||||
*
|
||||
* @param $query
|
||||
* Query object
|
||||
* @param $table_name
|
||||
* Table name to find.
|
||||
* @param $field_name
|
||||
* field to join the table if needed.
|
||||
*
|
||||
* @return
|
||||
* Table alias if found, none if not.
|
||||
*/
|
||||
function i18n_select_check_table($query, $table_name, $field_name) {
|
||||
$tables = $query->getTables();
|
||||
foreach ($tables as $table) {
|
||||
if (!($table instanceof SelectQueryInterface) && $table['table'] == $table_name) {
|
||||
return _i18n_select_table_alias($table);
|
||||
}
|
||||
}
|
||||
// Join the table if we can find the key field on any of the tables
|
||||
// And all the conditions have a table alias (or there's a unique table).
|
||||
if (count($tables) == 1) {
|
||||
$table = reset($tables);
|
||||
$table_alias = _i18n_select_table_alias($table);
|
||||
if (i18n_select_check_conditions($query, $table_alias)) {
|
||||
$join_table = $table_alias;
|
||||
}
|
||||
}
|
||||
elseif (i18n_select_check_conditions($query)) {
|
||||
// Try to find the right field in the table schema.
|
||||
foreach ($tables as $table) {
|
||||
$schema = drupal_get_schema($table['table']);
|
||||
if ($schema && !empty($schema['fields'][$field_name])) {
|
||||
$join_table = _i18n_select_table_alias($table);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($join_table)) {
|
||||
return $query->join($table_name, $table_name, $join_table . '.' . $field_name . ' = %alias.' . $field_name);
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table alias
|
||||
*/
|
||||
function _i18n_select_table_alias($table) {
|
||||
return !empty($table['alias']) ? $table['alias'] : $table['table'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all query conditions have a table alias.
|
||||
*
|
||||
* @param $table_alias
|
||||
* Optional table alias for fields without table.
|
||||
*
|
||||
* @return boolean
|
||||
* TRUE if table conditions are ok, FALSE otherwise.
|
||||
*/
|
||||
function i18n_select_check_conditions($query, $table_alias = NULL) {
|
||||
$conditions =& $query->conditions();
|
||||
foreach ($conditions as $index => $condition) {
|
||||
if (is_array($condition) && isset($condition['field'])) {
|
||||
if (strpos($condition['field'], '.') === FALSE) {
|
||||
if ($table_alias) {
|
||||
// Change the condition to include a table alias.
|
||||
$conditions[$index]['field'] = $table_alias . '.' . $condition['field'];
|
||||
}
|
||||
else {
|
||||
// We won't risk joining anything here.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we should apply language conditions here:
|
||||
* - The query has not been tagged with 'i18n_select'
|
||||
* - The query doesn't have already a language condition
|
||||
* - All the conditions have a table alias or there's only one table.
|
||||
* - We are not loading specific objects (no condition for index field).
|
||||
*
|
||||
* @param $query
|
||||
* Query object.
|
||||
* @param $index_field
|
||||
* Object index field to check we don't have 'IN' conditions for it.
|
||||
*/
|
||||
function i18n_select_check_query($query, $index_field = NULL) {
|
||||
static $tags;
|
||||
// Skip queries with certain tags
|
||||
if (!isset($tags)) {
|
||||
$tags = ($skip = variable_get('i18n_select_skip_tags', 'views')) ? array_map('trim', explode(',', $skip)) : array();
|
||||
$tags[] = 'i18n_select';
|
||||
}
|
||||
foreach ($tags as $tag) {
|
||||
if ($query->hasTag($tag)) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
// Check all the conditions to see whether the query is suitable for altering.
|
||||
foreach ($query->conditions() as $condition) {
|
||||
if (is_array($condition)) {
|
||||
// @todo For some complex queries, like search ones, field is a DatabaseCondition object
|
||||
if (!isset($condition['field']) || !is_string($condition['field'])) {
|
||||
// There's a weird condition field, we won't take any chances.
|
||||
return FALSE;
|
||||
}
|
||||
else {
|
||||
// Just check the condition doesn't include the language field
|
||||
if (strpos($condition['field'], '.') === FALSE) {
|
||||
$field = $condition['field'];
|
||||
}
|
||||
else {
|
||||
list($table, $field) = explode('.', $condition['field']);
|
||||
}
|
||||
if ($field == 'language') {
|
||||
return FALSE;
|
||||
}
|
||||
// Check 'IN' conditions for index field, usually entity loading for specific objects.
|
||||
if ($field == $index_field && $condition['operator'] == 'IN') {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the language field is present we don't want to do any filtering.
|
||||
$fields = $query->getFields();
|
||||
if (isset($fields['language'])) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get main language for content selection
|
||||
*/
|
||||
function i18n_select_language() {
|
||||
return $GLOBALS[LANGUAGE_TYPE_CONTENT];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language codes for content selection to use in query conditions
|
||||
*/
|
||||
function i18n_select_langcodes() {
|
||||
return array(i18n_select_language()->language, LANGUAGE_NONE);
|
||||
}
|
||||
|
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Test language selection modes
|
||||
*/
|
||||
|
||||
class i18nSelectTestCase extends Drupali18nTestCase {
|
||||
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Content Selection',
|
||||
'group' => 'Internationalization',
|
||||
'description' => 'Internationalization Content Selection'
|
||||
);
|
||||
}
|
||||
|
||||
function setUp() {
|
||||
parent::setUp('translation', 'i18n_variable', 'i18n_select');
|
||||
parent::setUpLanguages();
|
||||
parent::setUpContentTranslation();
|
||||
}
|
||||
|
||||
function testIi18nSelect() {
|
||||
drupal_static_reset('language_list');
|
||||
$language_list = language_list();
|
||||
$language_count = count($language_list);
|
||||
// Set site name for each language and check pages later
|
||||
variable_set('i18n_variable_list', array('site_name'));
|
||||
foreach (i18n_language_list() as $langcode => $name) {
|
||||
i18n_variable_set('site_name', "Drupal-$name", $langcode);
|
||||
}
|
||||
|
||||
// Enable tags field for page content type.
|
||||
$edit = array(
|
||||
'fields[_add_existing_field][label]' => t('Tags'),
|
||||
'fields[_add_existing_field][field_name]' => 'field_tags',
|
||||
'fields[_add_existing_field][widget_type]' => 'taxonomy_autocomplete',
|
||||
);
|
||||
$this->drupalPost('admin/structure/types/manage/page/fields', $edit, t('Save'));
|
||||
$this->drupalPost(NULL, array(), t('Save settings'));
|
||||
|
||||
// Create some content and check selection modes
|
||||
$this->drupalLogin($this->content_editor);
|
||||
|
||||
// variable_set('language_content_type_story', 1);
|
||||
$neutral = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1));
|
||||
$source = $this->createNode('page', $this->randomName(), $this->randomString(20), language_default('language'), array('field_tags[und]' => $tag_name = $this->randomName()));
|
||||
$translations = $this->createNodeTranslationSet($source);
|
||||
|
||||
drupal_static_reset('translation_node_get_translations');
|
||||
$this->assertEqual(count(translation_node_get_translations($source->tnid)), $language_count, "Created $language_count $source->type translations.");
|
||||
|
||||
// Log in user with access content permission
|
||||
$user = $this->drupalCreateUser(array('access comments', 'access content'));
|
||||
$this->drupalLogin($user);
|
||||
// Default selection mode, only language neutral and current
|
||||
variable_set('i18n_select_nodes', TRUE);
|
||||
foreach (i18n_language_list() as $langcode => $name) {
|
||||
$this->i18nGet($langcode);
|
||||
$this->assertText("Drupal-$name", 'Checked translated site name: Drupal-' . $name);
|
||||
$display = array($translations[$langcode], $neutral);
|
||||
$hide = $translations;
|
||||
unset($hide[$langcode]);
|
||||
$this->assertContent($display, $hide);
|
||||
// Visit the taxonomy page of that node and try again. Only the translated
|
||||
// pages are tagged.
|
||||
unset($display[1]);
|
||||
$this->i18nGet($langcode, 'taxonomy/term/' . $source->field_tags[LANGUAGE_NONE][0]['tid']);
|
||||
$this->assertContent($display, $hide);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check some nodes are displayed, some are not
|
||||
*/
|
||||
function assertContent($display, $hide = array()) {
|
||||
$languages = language_list();
|
||||
foreach ($display as $node) {
|
||||
$this->assertText($node->title, 'Content displayed for ' . i18n_language_name($node->language));
|
||||
}
|
||||
foreach ($hide as $node) {
|
||||
$this->assertNoText($node->title, 'Content not displayed for ' . i18n_language_name($node->language));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* @file
|
||||
* Variable information
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_variable_info().
|
||||
*/
|
||||
function i18n_select_variable_info($options = array()) {
|
||||
$variables['i18n_select_nodes'] = array(
|
||||
'title' => t('Select nodes by language', array(), $options),
|
||||
'type' => 'boolean',
|
||||
'default' => TRUE,
|
||||
'group' => 'i18n',
|
||||
);
|
||||
$variables['i18n_select_taxonomy'] = array(
|
||||
'title' => t('Select taxonomy terms by language', array(), $options),
|
||||
'type' => 'boolean',
|
||||
'default' => TRUE,
|
||||
'group' => 'i18n',
|
||||
'element' => array('#disabled' => !module_exists('i18n_taxonomy')),
|
||||
);
|
||||
|
||||
// Enable / disable for specific pages
|
||||
$description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>'), $options);
|
||||
if (module_exists('php')) {
|
||||
$title = t('Pages or PHP code');
|
||||
$description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'), $options);
|
||||
}
|
||||
else {
|
||||
$title = t('Pages', array(), $options);
|
||||
}
|
||||
$variables['i18n_select_page_mode'] = array(
|
||||
'type' => 'select',
|
||||
'options callback' => 'i18n_select_variable_option_list',
|
||||
'default' => I18N_SELECT_PAGE_NOTLISTED,
|
||||
);
|
||||
$variables['i18n_select_page_list'] = array(
|
||||
'type' => 'text',
|
||||
'title' => $title,
|
||||
'default' => 'admin/*',
|
||||
'description' => $description,
|
||||
);
|
||||
$variables['i18n_select_page_block'] = array(
|
||||
'type' => 'boolean',
|
||||
'title' => t('Enable always for block content though it may be disabled for the page', array(), $options),
|
||||
'default' => TRUE,
|
||||
);
|
||||
$variables['i18n_select_skip_tags'] = array(
|
||||
'title' => t('Skip tags', array(), $options),
|
||||
'type' => 'string',
|
||||
'default' => 'views',
|
||||
'group' => 'i18n',
|
||||
'description' => t('Skip queries with these tags. Enter a list of tags separated by commas.'),
|
||||
'localize' => FALSE,
|
||||
);
|
||||
return $variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for page selection mode
|
||||
*/
|
||||
function i18n_select_variable_option_list($variable, $options = array()) {
|
||||
$options = array(
|
||||
I18N_SELECT_PAGE_NOTLISTED => t('All pages except those listed', array(), $options),
|
||||
I18N_SELECT_PAGE_LISTED => t('Only the listed pages', array(), $options),
|
||||
);
|
||||
if (module_exists('php')) {
|
||||
$options += array(I18N_SELECT_PAGE_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
|
||||
}
|
||||
return $options;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user