Permalink
Browse files

adds token extensions

  • Loading branch information...
1 parent 392abed commit 2ebe094dbabf7b277a028c02202027d83b921b9c @emjayess committed Mar 5, 2013
View
339 sites/all/modules/contrib/token/LICENSE.txt
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
View
2 sites/all/modules/contrib/token/README.txt
@@ -0,0 +1,2 @@
+
+Provides common and resuable token UI elements and missing core tokens.
View
BIN sites/all/modules/contrib/token/arrow-down.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN sites/all/modules/contrib/token/arrow-right.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
44 sites/all/modules/contrib/token/jquery.treeTable.css
@@ -0,0 +1,44 @@
+
+/* jQuery TreeTable Core 2.0 stylesheet
+ *
+ * This file contains styles that are used to display the tree table. Each tree
+ * table is assigned the +treeTable+ class.
+ * ========================================================================= */
+
+/* jquery.treeTable.collapsible
+ * ------------------------------------------------------------------------- */
+.treeTable tr td .expander {
+ background-position: left center;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ padding: 0;
+ zoom: 1; /* IE7 Hack */
+}
+
+.treeTable tr.collapsed td .expander {
+ background-image: url(arrow-right.png);
+}
+
+.treeTable tr.expanded td .expander {
+ background-image: url(arrow-down.png);
+}
+
+/* jquery.treeTable.sortable
+ * ------------------------------------------------------------------------- */
+.treeTable tr.selected, .treeTable tr.accept {
+ background-color: #3875d7;
+ color: #fff;
+}
+
+.treeTable tr.collapsed.selected td .expander, .treeTable tr.collapsed.accept td .expander {
+ background-image: url(../images/toggle-expand-light.png);
+}
+
+.treeTable tr.expanded.selected td .expander, .treeTable tr.expanded.accept td .expander {
+ background-image: url(../images/toggle-collapse-light.png);
+}
+
+.treeTable .ui-draggable-dragging {
+ color: #000;
+ z-index: 1;
+}
View
220 sites/all/modules/contrib/token/jquery.treeTable.js
@@ -0,0 +1,220 @@
+
+/*
+ * jQuery treeTable Plugin 2.3.0
+ * http://ludo.cubicphuse.nl/jquery-plugins/treeTable/
+ *
+ * Copyright 2010, Ludo van den Boom
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ */
+(function($) {
+ // Helps to make options available to all functions
+ // TODO: This gives problems when there are both expandable and non-expandable
+ // trees on a page. The options shouldn't be global to all these instances!
+ var options;
+ var defaultPaddingLeft;
+
+ $.fn.treeTable = function(opts) {
+ options = $.extend({}, $.fn.treeTable.defaults, opts);
+
+ return this.each(function() {
+ $(this).addClass("treeTable").find("tbody tr").each(function() {
+ // Initialize root nodes only if possible
+ if(!options.expandable || $(this)[0].className.search(options.childPrefix) == -1) {
+ // To optimize performance of indentation, I retrieve the padding-left
+ // value of the first root node. This way I only have to call +css+
+ // once.
+ if (isNaN(defaultPaddingLeft)) {
+ defaultPaddingLeft = parseInt($($(this).children("td")[options.treeColumn]).css('padding-left'), 10);
+ }
+
+ initialize($(this));
+ } else if(options.initialState == "collapsed") {
+ this.style.display = "none"; // Performance! $(this).hide() is slow...
+ }
+ });
+ });
+ };
+
+ $.fn.treeTable.defaults = {
+ childPrefix: "child-of-",
+ clickableNodeNames: false,
+ expandable: true,
+ indent: 19,
+ initialState: "collapsed",
+ treeColumn: 0
+ };
+
+ // Recursively hide all node's children in a tree
+ $.fn.collapse = function() {
+ $(this).addClass("collapsed");
+
+ childrenOf($(this)).each(function() {
+ if(!$(this).hasClass("collapsed")) {
+ $(this).collapse();
+ }
+
+ this.style.display = "none"; // Performance! $(this).hide() is slow...
+ });
+
+ return this;
+ };
+
+ // Recursively show all node's children in a tree
+ $.fn.expand = function() {
+ $(this).removeClass("collapsed").addClass("expanded");
+
+ childrenOf($(this)).each(function() {
+ initialize($(this));
+
+ if($(this).is(".expanded.parent")) {
+ $(this).expand();
+ }
+
+ // this.style.display = "table-row"; // Unfortunately this is not possible with IE :-(
+ $(this).show();
+ });
+
+ return this;
+ };
+
+ // Reveal a node by expanding all ancestors
+ $.fn.reveal = function() {
+ $(ancestorsOf($(this)).reverse()).each(function() {
+ initialize($(this));
+ $(this).expand().show();
+ });
+
+ return this;
+ };
+
+ // Add an entire branch to +destination+
+ $.fn.appendBranchTo = function(destination) {
+ var node = $(this);
+ var parent = parentOf(node);
+
+ var ancestorNames = $.map(ancestorsOf($(destination)), function(a) { return a.id; });
+
+ // Conditions:
+ // 1: +node+ should not be inserted in a location in a branch if this would
+ // result in +node+ being an ancestor of itself.
+ // 2: +node+ should not have a parent OR the destination should not be the
+ // same as +node+'s current parent (this last condition prevents +node+
+ // from being moved to the same location where it already is).
+ // 3: +node+ should not be inserted as a child of +node+ itself.
+ if($.inArray(node[0].id, ancestorNames) == -1 && (!parent || (destination.id != parent[0].id)) && destination.id != node[0].id) {
+ indent(node, ancestorsOf(node).length * options.indent * -1); // Remove indentation
+
+ if(parent) { node.removeClass(options.childPrefix + parent[0].id); }
+
+ node.addClass(options.childPrefix + destination.id);
+ move(node, destination); // Recursively move nodes to new location
+ indent(node, ancestorsOf(node).length * options.indent);
+ }
+
+ return this;
+ };
+
+ // Add reverse() function from JS Arrays
+ $.fn.reverse = function() {
+ return this.pushStack(this.get().reverse(), arguments);
+ };
+
+ // Toggle an entire branch
+ $.fn.toggleBranch = function() {
+ if($(this).hasClass("collapsed")) {
+ $(this).expand();
+ } else {
+ $(this).removeClass("expanded").collapse();
+ }
+
+ return this;
+ };
+
+ // === Private functions
+
+ function ancestorsOf(node) {
+ var ancestors = [];
+ while(node = parentOf(node)) {
+ ancestors[ancestors.length] = node[0];
+ }
+ return ancestors;
+ };
+
+ function childrenOf(node) {
+ return $(node).siblings("tr." + options.childPrefix + node[0].id);
+ };
+
+ function getPaddingLeft(node) {
+ var paddingLeft = parseInt(node[0].style.paddingLeft, 10);
+ return (isNaN(paddingLeft)) ? defaultPaddingLeft : paddingLeft;
+ }
+
+ function indent(node, value) {
+ var cell = $(node.children("td")[options.treeColumn]);
+ cell[0].style.paddingLeft = getPaddingLeft(cell) + value + "px";
+
+ childrenOf(node).each(function() {
+ indent($(this), value);
+ });
+ };
+
+ function initialize(node) {
+ if(!node.hasClass("initialized")) {
+ node.addClass("initialized");
+
+ var childNodes = childrenOf(node);
+
+ if(!node.hasClass("parent") && childNodes.length > 0) {
+ node.addClass("parent");
+ }
+
+ if(node.hasClass("parent")) {
+ var cell = $(node.children("td")[options.treeColumn]);
+ var padding = getPaddingLeft(cell) + options.indent;
+
+ childNodes.each(function() {
+ $(this).children("td")[options.treeColumn].style.paddingLeft = padding + "px";
+ });
+
+ if(options.expandable) {
+ cell.prepend('<span style="margin-left: -' + options.indent + 'px; padding-left: ' + options.indent + 'px" class="expander"></span>');
+ $(cell[0].firstChild).click(function() { node.toggleBranch(); });
+
+ if(options.clickableNodeNames) {
+ cell[0].style.cursor = "pointer";
+ $(cell).click(function(e) {
+ // Don't double-toggle if the click is on the existing expander icon
+ if (e.target.className != 'expander') {
+ node.toggleBranch();
+ }
+ });
+ }
+
+ // Check for a class set explicitly by the user, otherwise set the default class
+ if(!(node.hasClass("expanded") || node.hasClass("collapsed"))) {
+ node.addClass(options.initialState);
+ }
+
+ if(node.hasClass("expanded")) {
+ node.expand();
+ }
+ }
+ }
+ }
+ };
+
+ function move(node, destination) {
+ node.insertAfter(destination);
+ childrenOf(node).reverse().each(function() { move($(this), node[0]); });
+ };
+
+ function parentOf(node) {
+ var classNames = node[0].className.split(' ');
+
+ for(key in classNames) {
+ if(classNames[key].match(options.childPrefix)) {
+ return $(node).siblings("#" + classNames[key].substring(options.childPrefix.length));
+ }
+ }
+ };
+})(jQuery);
View
13 sites/all/modules/contrib/token/tests/token_test.info
@@ -0,0 +1,13 @@
+name = Token Test
+description = Testing module for token functionality.
+package = Testing
+core = 7.x
+files[] = token_test.module
+hidden = TRUE
+
+; Information added by drupal.org packaging script on 2013-02-24
+version = "7.x-1.5"
+core = "7.x"
+project = "token"
+datestamp = "1361665026"
+
View
33 sites/all/modules/contrib/token/tests/token_test.module
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Helper module for token tests.
+ */
+
+/**
+ * Implements hook_exit().
+ */
+function token_test_exit() {
+ if ($debug = variable_get('token_page_tokens', array())) {
+ $debug += array('tokens' => array(), 'data' => array(), 'options' => array());
+ foreach (array_keys($debug['tokens']) as $token) {
+ $debug['values'][$token] = token_replace($token, $debug['data'], $debug['options']);
+ }
+ variable_set('token_page_tokens', $debug);
+ }
+}
+
+/**
+ * Implements hook_date_format_types().
+ *
+ * @todo Remove when http://drupal.org/node/1173706 is fixed.
+ */
+function token_test_date_format_types() {
+ $info['token_test'] = t('Token test date format');
+
+ // Explicitly set the variable here as well.
+ variable_set('date_format_token_test', 'Y');
+
+ return $info;
+}
View
27 sites/all/modules/contrib/token/token.css
@@ -0,0 +1,27 @@
+
+.token-tree {
+ font-size: 0.85em;
+ margin-left: 19px;
+}
+
+.ui-dialog-content .token-tree {
+ margin-left: 0;
+}
+
+.token-tree td, .token-tree th {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.token-group {
+ font-weight: bold;
+}
+
+.js .token-group {
+ font-weight: normal;
+}
+
+/* Prevent the token columns from being wrapped. */
+.token-tree td.token-key {
+ white-space: nowrap;
+}
View
22 sites/all/modules/contrib/token/token.drush.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Drush integration for the Token module.
+ */
+
+/**
+ * Implements hook_drush_cache_clear().
+ */
+function token_drush_cache_clear(&$types) {
+ if (function_exists('module_exists') && module_exists('token')) {
+ $types['token'] = 'drush_token_cache_clear_token_info';
+ }
+}
+
+/**
+ * Clear caches internal to Token module.
+ */
+function drush_token_cache_clear_token_info() {
+ token_clear_cache();
+}
View
11 sites/all/modules/contrib/token/token.info
@@ -0,0 +1,11 @@
+name = Token
+description = Provides a user interface for the Token API and some missing core tokens.
+core = 7.x
+files[] = token.test
+
+; Information added by drupal.org packaging script on 2013-02-24
+version = "7.x-1.5"
+core = "7.x"
+project = "token"
+datestamp = "1361665026"
+
View
332 sites/all/modules/contrib/token/token.install
@@ -0,0 +1,332 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the token module.
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function token_requirements($phase = 'runtime') {
+ $requirements = array();
+ $t = get_t();
+
+ if ($phase == 'runtime') {
+ // Check for various token definition problems.
+ $token_problems = token_get_token_problems();
+ // Format and display each token problem.
+ foreach ($token_problems as $problem_key => $problem) {
+ if (!empty($problem['problems'])) {
+ $problems = array_unique($problem['problems']);
+ $problems = array_map('check_plain', $problems);
+ $token_problems[$problem_key] = $problem['label'] . theme('item_list', array('items' => $problems));
+ }
+ else {
+ unset($token_problems[$problem_key]);
+ }
+ }
+ if (!empty($token_problems)) {
+ $requirements['token_problems'] = array(
+ 'title' => $t('Tokens'),
+ 'value' => $t('Problems detected'),
+ 'severity' => REQUIREMENT_WARNING,
+ 'description' => '<p>' . implode('</p><p>', $token_problems) . '</p>', //theme('item_list', array('items' => $token_problems)),
+ );
+ }
+ }
+
+ return $requirements;
+}
+
+/**
+ * Implements hook_schema().
+ */
+function token_schema() {
+ $schema['cache_token'] = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['cache_token']['description'] = 'Cache table for token information.';
+ return $schema;
+}
+
+/**
+ * Add the cache_token table.
+ */
+function token_update_7000() {
+ if (!db_table_exists('cache_token')) {
+ $schema = drupal_get_schema_unprocessed('system', 'cache');
+ $schema['description'] = 'Cache table for token information.';
+ db_create_table('cache_token', $schema);
+ }
+}
+
+/**
+ * Deprecate and disable the token_actions module.
+ */
+function token_update_7001() {
+ module_disable(array('token_actions'));
+ return 'The Token actions module has been deprecated by the updated system module actions that support tokens.';
+}
+
+/**
+ * Migrate old token_actions module actions to system actions.
+ */
+//function token_update_700X() {
+// $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE type = 'system' AND callback IN ('token_actions_message_action', 'token_actions_send_email_action', 'token_actions_goto_action')")->fetchAll();
+// foreach ($actions as $action) {
+// $action->parameters = unserialize($action->parameters);
+// foreach ($action->parameters as $key => $value) {
+// if (is_string($value)) {
+// $action->parameters[$key] = token_update_token_text($value);
+// }
+// }
+// $action->callback = str_replace('token_actions_', '', $action->callback);
+// actions_save($action->callback, $action->type, $action->parameters, $action->label, $action->aid);
+// }
+// return 'Token actions module actions migrated to system module actions. You may still want to verify that the actions were upgraded correctly.';
+//}
+
+/**
+ * Build a list of Drupal 6 tokens and their Drupal 7 token names.
+ */
+function _token_upgrade_token_list() {
+ $tokens = array(
+ // Global tokens
+ 'user-name' => 'current-user:name',
+ 'user-id' => 'current-user:id',
+ 'user-mail' => 'current-user:mail',
+ 'site-url' => 'site:url',
+ 'site-name' => 'site:name',
+ 'site-slogan' => 'site:slogan',
+ 'site-mission' => 'site:mission',
+ 'site-mail' => 'site:mail',
+ 'site-date' => 'date:short',
+ //'site-date-' => '', // Date tokens expanded below
+ 'current-page-path' => 'current-page:path',
+ 'current-page-url' => 'current-page:url',
+ 'page-number' => 'current-page:page-number',
+
+ // Comment tokens
+ 'comment-cid' => 'comment:cid',
+ 'comment-nid' => 'comment:node:nid',
+ 'comment-title' => 'comment:title',
+ 'comment-body' => 'comment:body',
+ 'comment-author-name' => 'comment:author:name',
+ 'comment-author-mail' => 'comment:author:mail',
+ //'comment-body-format' => '',
+ //'comment-' => '', // Date tokens expanded below
+ 'comment-node-title' => 'comment:node',
+
+ // Node tokens
+ 'nid' => 'node:nid',
+ 'type' => 'node:type',
+ 'type-name' => 'node:type-name',
+ 'language' => 'node:language',
+ 'title' => 'node:title',
+ 'author-uid' => 'node:author:uid',
+ 'author-name' => 'node:author:name',
+ 'author-mail' => 'node:author:mail',
+ 'node_comment_count' => 'node:comment-count',
+ 'unread_comment_count' => 'node:comment-count-new',
+ 'log' => 'node:log',
+ //'' => '', // Date tokens expanded below
+ //'mod-' => '', // Date tokens expanded below
+ 'menupath' => 'node:menu-link:parent:path][node:menu-link',
+ 'menu' => 'node:menu-link:menu-name',
+ 'menu-link-title' => 'node:menu-link',
+ 'menu-link-mlid' => 'node:menu-link:mlid',
+ 'menu-link-plid' => 'node:menu-link:parent:mlid',
+ //'term' => 'node:term',
+ //'term-id' => 'node:term:tid',
+ //'vocab' => 'node:term:vocabulary',
+ //'vocab-id' => 'node:term:vocabulary:vid',
+
+ // Book tokens
+ //'book' => 'node:book',
+ //'book_id' => 'node:book:bid',
+ //'bookpath' => 'node:book:path',
+
+ // Taxonomy tokens
+ 'tid' => 'term:tid',
+ 'cat' => 'term:name',
+ 'cat-description' => 'term:description',
+ 'vid' => 'term:vocabulary:vid',
+ 'vocab' => 'term:vocabulary',
+ 'vocab-description' => 'term:vocabulary:description',
+
+ // User tokens
+ 'user' => 'user:name',
+ 'uid' => 'user:uid',
+ 'mail' => 'user:mail',
+ 'reg-date' => 'user:created',
+ 'reg-since' => 'user:created:since',
+ //'user-created' => '', // Date tokens expanded below
+ 'log-date' => 'user:last-login',
+ 'log-since' => 'user:last-login:since',
+ //'user-last-login' => '', // Date tokens expanded below
+ //'date-in-tz' => '',
+ 'account-url' => 'user:url',
+ 'account-edit' => 'user:edit-url',
+ );
+
+ // Account for date tokens which need to be expanded.
+ $tokens += _token_upgrade_token_date_list('site-', 'site:date');
+ $tokens += _token_upgrade_token_date_list('', 'node:created');
+ $tokens += _token_upgrade_token_date_list('mod-', 'node:changed');
+ //$tokens += _token_upgrade_token_date_list('node-revision-', 'node:changed');
+ $tokens += _token_upgrade_token_date_list('comment-', 'comment:created');
+ $tokens += _token_upgrade_token_date_list('user-register-', 'user:created');
+ $tokens += _token_upgrade_token_date_list('user-last-login-', 'user:last-login');
+
+ return $tokens;
+}
+
+/**
+ * Build a list of Drupal 6 date tokens and their Drupal 7 token names.
+ */
+function _token_upgrade_token_date_list($old_token, $new_token) {
+ $tokens = array();
+ $formats = array(
+ 'yyyy' => 'Y',
+ 'yy' => 'y',
+ 'month' => 'F',
+ 'mon' => 'M',
+ 'mm' => 'm',
+ 'm' => 'n',
+ 'ww' => 'W',
+ 'date' => 'N',
+ 'day' => 'l',
+ 'ddd' => 'D',
+ 'dd' => 'd',
+ 'd' => 'j',
+ );
+ foreach ($formats as $token_format => $date_format) {
+ $tokens[$old_token . $token_format] = "$new_token:custom:$date_format";
+ }
+ $tokens[$old_token . 'raw'] = "$new_token:raw";
+ $tokens[$old_token . 'since'] = "$new_token:since";
+ return $tokens;
+}
+
+/**
+ * Update a string containing Drupal 6 style tokens to Drupal 7 style tokens.
+ *
+ * @param $text
+ * A string containing tokens.
+ * @param $updates
+ * An optional array of Drupal 7 tokens keyed by their Drupal 6 token name.
+ * The default tokens will be merged into this array. Note neither the old
+ * or new token names should include the surrounding bracket ([ and ])
+ * characters.
+ * @return
+ * A string with the tokens upgraded
+ *
+ * @see _token_upgrade_token_list()
+ */
+function token_update_token_text($text, $updates = array(), $leading = '[', $trailing = ']') {
+ $updates += _token_upgrade_token_list();
+ $regex = '/' . preg_quote($leading, '/') . '([^\s]*)' . preg_quote($trailing, '/') . '/';
+ preg_match_all($regex, $text, $matches);
+
+ foreach ($matches[1] as $index => $old_token) {
+ if (isset($updates[$old_token])) {
+ $new_token = $updates[$old_token];
+ $text = str_replace("{$leading}{$old_token}{$trailing}", "[$new_token]", $text);
+ // Also replace any tokens that have a -raw suffix.
+ $text = str_replace("{$leading}{$old_token}-raw{$trailing}", "[$new_token]", $text);
+ }
+ }
+
+ return $text;
+}
+
+/**
+ * Get token problems.
+ */
+function token_get_token_problems() {
+ // @todo Improve the duplicate checking to report which modules are the offenders.
+ //$token_info = array();
+ //foreach (module_implements('token_info') as $module) {
+ // $module_token_info = module_invoke($module, 'token_info');
+ // if (in_array($module, _token_core_supported_modules())) {
+ // $module .= '/token';
+ // }
+ // if (isset($module_token_info['types'])) {
+ // if (is_array($module_token_info['types'])) {
+ // foreach (array_keys($module_token_info['types']) as $type) {
+ // if (is_array($module_token_info['types'][$type])) {
+ // $module_token_info['types'][$type] += array('module' => $module);
+ // }
+ // }
+ // }
+ // }
+ // if (isset($module_token_info['tokens'])) {
+ // if (is_array($module_token_info['tokens'])) {
+ //
+ // }
+ // }
+ // if (is_array($module_token_info)) {
+ // $token_info = array_merge_recursive($token_info, $module_token_info);
+ // }
+ //}
+
+ $token_info = token_info();
+ $token_problems = array(
+ 'not-array' => array(
+ 'label' => t('The following tokens or token types are not defined as arrays:'),
+ ),
+ 'missing-info' => array(
+ 'label' => t('The following tokens or token types are missing required name and/or description information:'),
+ ),
+ 'type-no-tokens' => array(
+ 'label' => t('The following token types do not have any tokens defined:'),
+ ),
+ 'tokens-no-type' => array(
+ 'label' => t('The following token types are not defined but have tokens:'),
+ ),
+ 'duplicate' => array(
+ 'label' => t('The following token or token types are defined by multiple modules:')
+ ),
+ );
+
+ // Check token types for problems.
+ foreach ($token_info['types'] as $type => $type_info) {
+ $real_type = !empty($type_info['type']) ? $type_info['type'] : $type;
+ if (!is_array($type_info)) {
+ $token_problems['not-array']['problems'][] = "\$info['types']['$type']";
+ continue;
+ }
+ elseif (!isset($type_info['name']) || !isset($type_info['description'])) {
+ $token_problems['missing-info']['problems'][] = "\$info['types']['$type']";
+ }
+ elseif (empty($token_info['tokens'][$real_type])) {
+ $token_problems['type-no-tokens']['problems'][] = "\$info['tokens']['$real_type']";
+ }
+ }
+
+ // Check tokens for problems.
+ foreach ($token_info['tokens'] as $type => $tokens) {
+ if (!is_array($tokens)) {
+ $token_problems['not-array']['problems'][] = "\$info['tokens']['$type']";
+ continue;
+ }
+ else {
+ foreach (array_keys($tokens) as $token) {
+ if (!is_array($tokens[$token])) {
+ $token_problems['not-array']['problems'][] = "\$info['tokens']['$type']['$token']";
+ continue;
+ }
+ elseif (!isset($tokens[$token]['name']) || !isset($tokens[$token]['description'])) {
+ $token_problems['missing-info']['problems'][] = "\$info['tokens']['$type']['$token']";
+ }
+ elseif (is_array($tokens[$token]['name']) || is_array($tokens[$token]['description'])) {
+ $token_problems['duplicate']['problems'][] = "\$info['tokens']['$type']['$token']";
+ }
+ }
+ }
+ if (!isset($token_info['types'][$type])) {
+ $token_problems['tokens-no-type']['problems'][] = "\$info['types']['$type']";
+ }
+ }
+
+ return $token_problems;
+}
View
87 sites/all/modules/contrib/token/token.js
@@ -0,0 +1,87 @@
+
+(function ($) {
+
+Drupal.behaviors.tokenTree = {
+ attach: function (context, settings) {
+ $('table.token-tree', context).once('token-tree', function () {
+ $(this).treeTable();
+ });
+ }
+};
+
+Drupal.behaviors.tokenDialog = {
+ attach: function (context, settings) {
+ $('a.token-dialog', context).once('token-dialog').click(function() {
+ var url = $(this).attr('href');
+ var dialog = $('<div style="display: none" class="loading">' + Drupal.t('Loading token browser...') + '</div>').appendTo('body');
+
+ // Emulate the AJAX data sent normally so that we get the same theme.
+ var data = {};
+ data['ajax_page_state[theme]'] = Drupal.settings.ajaxPageState.theme;
+ data['ajax_page_state[theme_token]'] = Drupal.settings.ajaxPageState.theme_token;
+
+ dialog.dialog({
+ title: $(this).attr('title') || Drupal.t('Available tokens'),
+ width: 700,
+ close: function(event, ui) {
+ dialog.remove();
+ }
+ });
+ // Load the token tree using AJAX.
+ dialog.load(
+ url,
+ data,
+ function (responseText, textStatus, XMLHttpRequest) {
+ dialog.removeClass('loading');
+ }
+ );
+ // Prevent browser from following the link.
+ return false;
+ });
+ }
+}
+
+Drupal.behaviors.tokenInsert = {
+ attach: function (context, settings) {
+ // Keep track of which textfield was last selected/focused.
+ $('textarea, input[type="text"]', context).focus(function() {
+ Drupal.settings.tokenFocusedField = this;
+ });
+
+ $('.token-click-insert .token-key', context).once('token-click-insert', function() {
+ var newThis = $('<a href="javascript:void(0);" title="' + Drupal.t('Insert this token into your form') + '">' + $(this).html() + '</a>').click(function(){
+ if (typeof Drupal.settings.tokenFocusedField == 'undefined') {
+ alert(Drupal.t('First click a text field to insert your tokens into.'));
+ }
+ else {
+ var myField = Drupal.settings.tokenFocusedField;
+ var myValue = $(this).text();
+
+ //IE support
+ if (document.selection) {
+ myField.focus();
+ sel = document.selection.createRange();
+ sel.text = myValue;
+ }
+
+ //MOZILLA/NETSCAPE support
+ else if (myField.selectionStart || myField.selectionStart == '0') {
+ var startPos = myField.selectionStart;
+ var endPos = myField.selectionEnd;
+ myField.value = myField.value.substring(0, startPos)
+ + myValue
+ + myField.value.substring(endPos, myField.value.length);
+ } else {
+ myField.value += myValue;
+ }
+
+ $('html,body').animate({scrollTop: $(myField).offset().top}, 500);
+ }
+ return false;
+ });
+ $(this).html(newThis);
+ });
+ }
+};
+
+})(jQuery);
View
1,283 sites/all/modules/contrib/token/token.module
@@ -0,0 +1,1283 @@
+<?php
+
+/**
+ * @file
+ * Enhances the token API in core: adds a browseable UI, missing tokens, etc.
+ */
+
+/**
+ * The maximum depth for token tree recursion.
+ */
+define('TOKEN_MAX_DEPTH', 9);
+
+/**
+ * Implements hook_help().
+ */
+function token_help($path, $arg) {
+ if ($path == 'admin/help#token') {
+ if (current_path() != 'admin/help/token') {
+ // Because system_modules() executes hook_help() for each module to 'test'
+ // if they will return anything, but not actually display it, we want to
+ // return a TRUE value if this is not actually the help page.
+ return TRUE;
+ }
+ $output = '<dl>';
+ $output .= '<dt>' . t('List of the currently available tokens on this site') . '</dt>';
+ $output .= '<dd>' . theme('token_tree', array('token_types' => 'all', 'click_insert' => FALSE, 'show_restricted' => TRUE)) . '</dd>';
+ $output .= '</dl>';
+ return $output;
+ }
+}
+
+/**
+ * Implements hook_system_info_alter().
+ *
+ * Prevent the token_actions module from being enabled since updates may have
+ * left the old module files still in the directory.
+ */
+function token_system_info_alter(&$info, $file, $type) {
+ if ($type == 'module' && $file->name == 'token_actions') {
+ $info['hidden'] = TRUE;
+ }
+}
+
+/**
+ * Return an array of the core modules supported by token.module.
+ */
+function _token_core_supported_modules() {
+ return array('book', 'field', 'menu', 'profile');
+}
+
+/**
+ * Implements hook_menu().
+ */
+function token_menu() {
+ /*$items['token/autocomplete/all/%menu_tail'] = array(
+ 'page callback' => 'token_autocomplete',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ 'file' => 'token.pages.inc',
+ );*/
+ $items['token/autocomplete/%token_type'] = array(
+ 'page callback' => 'token_autocomplete_token',
+ 'page arguments' => array(2),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ 'file' => 'token.pages.inc',
+ );
+ /*$items['token/autocomplete/%token_type/%menu_tail'] = array(
+ 'page callback' => 'token_autocomplete_token',
+ 'page arguments' => array(2, 3),
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ 'file' => 'token.pages.inc',
+ );*/
+
+ $items['token/tree'] = array(
+ 'page callback' => 'token_page_output_tree',
+ 'access callback' => TRUE,
+ 'type' => MENU_CALLBACK,
+ 'file' => 'token.pages.inc',
+ 'theme callback' => 'ajax_base_page_theme',
+ );
+
+ // Devel token pages.
+ if (module_exists('devel')) {
+ $items['node/%node/devel/token'] = array(
+ 'title' => 'Tokens',
+ 'page callback' => 'token_devel_token_object',
+ 'page arguments' => array('node', 1),
+ 'access arguments' => array('access devel information'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'token.pages.inc',
+ 'weight' => 5,
+ );
+ $items['comment/%comment/devel/token'] = array(
+ 'title' => 'Tokens',
+ 'page callback' => 'token_devel_token_object',
+ 'page arguments' => array('comment', 1),
+ 'access arguments' => array('access devel information'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'token.pages.inc',
+ 'weight' => 5,
+ );
+ $items['taxonomy/term/%taxonomy_term/devel/token'] = array(
+ 'title' => 'Tokens',
+ 'page callback' => 'token_devel_token_object',
+ 'page arguments' => array('taxonomy_term', 2),
+ 'access arguments' => array('access devel information'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'token.pages.inc',
+ 'weight' => 5,
+ );
+ $items['user/%user/devel/token'] = array(
+ 'title' => 'Tokens',
+ 'page callback' => 'token_devel_token_object',
+ 'page arguments' => array('user', 1),
+ 'access arguments' => array('access devel information'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'token.pages.inc',
+ 'weight' => 5,
+ );
+ }
+
+ // Admin menu callback to clear token caches.
+ $items['token/flush-cache'] = array(
+ 'page callback' => 'token_flush_cache_callback',
+ 'access arguments' => array('flush caches'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'token.pages.inc',
+ );
+
+ return $items;
+}
+
+/**
+ * Implements hook_admin_menu_output_alter().
+ */
+function token_admin_menu_output_alter(&$content) {
+ $content['icon']['icon']['flush-cache']['token'] = array(
+ '#title' => t('Token registry'),
+ '#href' => 'token/flush-cache',
+ '#options' => array(
+ 'query' => drupal_get_destination() + array('token' => drupal_get_token('token/flush-cache')),
+ ),
+ );
+}
+
+function token_type_load($token_type) {
+ $info = token_get_info();
+ return isset($info['types'][$token_type]) ? $info['types'][$token_type] : FALSE;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function token_theme() {
+ $info['tree_table'] = array(
+ 'variables' => array(
+ 'header' => array(),
+ 'rows' => array(),
+ 'attributes' => array(),
+ 'empty' => '',
+ 'caption' => '',
+ ),
+ 'file' => 'token.pages.inc',
+ );
+ $info['token_tree'] = array(
+ 'variables' => array(
+ 'token_types' => array(),
+ 'global_types' => TRUE,
+ 'click_insert' => TRUE,
+ 'show_restricted' => FALSE,
+ 'recursion_limit' => 3,
+ 'dialog' => FALSE,
+ ),
+ 'file' => 'token.pages.inc',
+ );
+ $info['token_tree_link'] = array(
+ 'variables' => array(
+ 'text' => NULL,
+ 'options' => array(),
+ 'dialog' => TRUE,
+ ),
+ 'file' => 'token.pages.inc',
+ );
+
+ return $info;
+}
+
+/**
+ * Implements hook_library().
+ */
+function token_library() {
+ // jQuery treeTable plugin.
+ $libraries['treeTable'] = array(
+ 'title' => 'jQuery treeTable',
+ 'website' => 'http://plugins.jquery.com/project/treetable',
+ 'version' => '2.3.0',
+ 'js' => array(
+ drupal_get_path('module', 'token') . '/jquery.treeTable.js' => array(),
+ ),
+ 'css' => array(
+ drupal_get_path('module', 'token') . '/jquery.treeTable.css' => array(),
+ ),
+ );
+
+ $libraries['dialog'] = array(
+ 'title' => 'Token dialog',
+ 'version' => '1.0',
+ 'js' => array(
+ drupal_get_path('module', 'token') . '/token.js' => array(),
+ ),
+ 'dependencies' => array(
+ array('system', 'ui.dialog'),
+ ),
+ );
+
+ return $libraries;
+}
+
+/**
+ * Implements hook_form_alter().
+ *
+ * Adds a submit handler to forms which could affect the tokens available on
+ * the site.
+ */
+function token_form_alter(&$form, $form_state, $form_id) {
+ switch ($form_id) {
+ // Profile field forms.
+ case 'profile_field_form':
+ case 'profile_field_delete':
+ // User picture form.
+ case 'user_admin_settings':
+ // System date type form.
+ // @todo Remove when http://drupal.org/node/1173706 is fixed.
+ case 'system_add_date_format_type_form':
+ case 'system_delete_date_format_type_form':
+ $form += array('#submit' => array());
+ array_unshift($form['#submit'], 'token_clear_cache');
+ break;
+ }
+}
+
+/**
+ * Implements hook_block_view_alter().
+ */
+function token_block_view_alter(&$data, $block) {
+ if (!empty($block->title) && $block->title != '<none>') {
+ // Perform unsanitized token replacement since _block_render_blocks() will
+ // call check_plain() on $block->title.
+ $block->title = token_replace($block->title, array(), array('sanitize' => FALSE));
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function token_form_block_add_block_form_alter(&$form, $form_state) {
+ token_form_block_admin_configure_alter($form, $form_state);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function token_form_block_admin_configure_alter(&$form, $form_state) {
+ $form['settings']['title']['#description'] .= ' ' . t('This field supports tokens.');
+ // @todo Figure out why this token validation does not seem to be working here.
+ $form['settings']['title']['#element_validate'][] = 'token_element_validate';
+ $form['settings']['title'] += array('#token_types' => array());
+}
+
+/**
+ * Implements hook_widget_form_alter().
+ */
+function token_field_widget_form_alter(&$element, &$form_state, $context) {
+ if (!empty($element['#description']) && is_string($element['#description'])) {
+ $element['#description'] = filter_xss_admin(token_replace($element['#description']));
+ }
+}
+
+/**
+ * Implements hook_field_info_alter().
+ */
+function token_field_info_alter(&$info) {
+ $defaults = array(
+ 'taxonomy_term_reference' => 'taxonomy_term_reference_plain',
+ 'number_integer' => 'number_unformatted',
+ 'number_decimal' => 'number_unformatted',
+ 'number_float' => 'number_unformatted',
+ 'file' => 'file_url_plain',
+ 'image' => 'file_url_plain',
+ 'text' => 'text_default',
+ 'text_long' => 'text_default',
+ 'text_with_summary' => 'text_default',
+ 'list_integer' => 'list_default',
+ 'list_float' => 'list_default',
+ 'list_text' => 'list_default',
+ 'list_boolean' => 'list_default',
+ );
+ foreach ($defaults as $field_type => $default_token_formatter) {
+ if (isset($info[$field_type])) {
+ $info[$field_type] += array('default_token_formatter' => $default_token_formatter);
+ }
+ }
+}
+
+/**
+ * Implements hook_field_display_alter().
+ */
+function token_field_display_alter(&$display, $context) {
+ if ($context['view_mode'] == 'token') {
+ $view_mode_settings = field_view_mode_settings($context['instance']['entity_type'], $context['instance']['bundle']);
+ // If the token view mode fell back to the 'default' view mode, then
+ // use the default token formatter.
+ if (empty($view_mode_settings[$context['view_mode']]['custom_settings'])) {
+ $field_type_info = field_info_field_types($context['field']['type']);
+
+ // If the field has specified a specific formatter to be used by default
+ // with tokens, use that, otherwise use the default formatter.
+ $formatter = !empty($field_type_info['default_token_formatter']) ? $field_type_info['default_token_formatter'] : $field_type_info['default_formatter'];
+
+ // Now that we have a formatter, fill in all the settings.
+ $display['type'] = $formatter;
+ $formatter_info = field_info_formatter_types($formatter);
+ $display['settings'] = isset($formatter_info['settings']) ? $formatter_info['settings'] : array();
+ $display['settings']['label'] = 'hidden';
+ $display['module'] = $formatter_info['module'];
+ }
+ }
+}
+
+/**
+ * Implements hook_field_create_instance().
+ */
+function token_field_create_instance($instance) {
+ token_clear_cache();
+}
+
+/**
+ * Implements hook_field_update_instance().
+ */
+function token_field_update_instance($instance) {
+ token_clear_cache();
+}
+
+/**
+ * Implements hook_field_delete_instance().
+ */
+function token_field_delete_instance($instance) {
+ token_clear_cache();
+}
+
+/**
+ * Clear token caches and static variables.
+ */
+function token_clear_cache() {
+ if (db_table_exists('cache_token')) {
+ cache_clear_all('*', 'cache_token', TRUE);
+ }
+ drupal_static_reset('token_get_info');
+ drupal_static_reset('token_get_global_token_types');
+ drupal_static_reset('token_get_entity_mapping');
+ drupal_static_reset('token_build_tree');
+ drupal_static_reset('_token_profile_fields');
+ drupal_static_reset('token_menu_link_load');
+ drupal_static_reset('token_book_link_load');
+ drupal_static_reset('token_node_menu_link_load');
+ drupal_static_reset('_token_field_info');
+}
+
+/**
+ * Return an array of entity type to token type mappings.
+ *
+ * Why do we need this? Because when the token API was moved to core we did not
+ * re-use the entity type as the base name for taxonomy terms and vocabulary
+ * tokens.
+ *
+ * @see token_entity_info_alter()
+ * @see http://drupal.org/node/737726
+ */
+function token_get_entity_mapping($value_type = 'token', $value = NULL, $fallback = FALSE) {
+ $mapping = &drupal_static(__FUNCTION__, array());
+
+ if (empty($mapping)) {
+ foreach (entity_get_info() as $entity_type => $info) {
+ $mapping[$entity_type] = !empty($info['token type']) ? $info['token type'] : $entity_type;
+ }
+ // Allow modules to alter the mapping array.
+ drupal_alter('token_entity_mapping', $mapping);
+ }
+
+ if (!isset($value)) {
+ return $value_type == 'token' ? array_flip($mapping) : $mapping;
+ }
+ elseif ($value_type == 'token') {
+ $return = array_search($value, $mapping);
+ return $return !== FALSE ? $return : ($fallback ? $value : FALSE);
+ }
+ elseif ($value_type == 'entity') {
+ return isset($mapping[$value]) ? $mapping[$value] : ($fallback ? $value : FALSE);
+ }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ *
+ * Because some token types to do not match their entity type names, we have to
+ * map them to the proper type. This is purely for other modules' benefit.
+ *
+ * @see token_get_entity_mapping()
+ * @see http://drupal.org/node/737726
+ */
+function token_entity_info_alter(&$info) {
+ foreach (array_keys($info) as $entity_type) {
+ // Add a token view mode if it does not already exist.
+ if (!empty($info[$entity_type]['view modes']) && !isset($info[$entity_type]['view modes']['token'])) {
+ $info[$entity_type]['view modes']['token'] = array(
+ 'label' => t('Tokens'),
+ 'custom settings' => FALSE,
+ );
+ }
+
+ if (!empty($info[$entity_type]['token type'])) {
+ // If the entity's token type is already defined, great!
+ continue;
+ }
+
+ // Fill in default token types for entities.
+ switch ($entity_type) {
+ case 'taxonomy_term':
+ case 'taxonomy_vocabulary':
+ // Stupid taxonomy token types...
+ $info[$entity_type]['token type'] = str_replace('taxonomy_', '', $entity_type);
+ break;
+ default:
+ // By default the token type is the same as the entity type.
+ $info[$entity_type]['token type'] = $entity_type;
+ break;
+ }
+ }
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ *
+ * Adds missing token support for core modules.
+ */
+function token_module_implements_alter(&$implementations, $hook) {
+ module_load_include('inc', 'token', 'token.tokens');
+
+ if ($hook == 'tokens' || $hook == 'token_info' || $hook == 'token_info_alter' || $hook == 'tokens_alter') {
+ foreach (_token_core_supported_modules() as $module) {
+ if (module_exists($module) && function_exists($module . '_' . $hook)) {
+ $implementations[$module] = FALSE;
+ }
+ }
+ // Move token.module to get included first since it is responsible for
+ // other modules.
+ unset($implementations['token']);
+ $implementations = array_merge(array('token' => 'tokens'), $implementations);
+ }
+}
+
+/**
+ * Implements hook_flush_caches().
+ */
+function token_flush_caches() {
+ if (db_table_exists('cache_token')) {
+ return array('cache_token');
+ }
+}
+
+/**
+ * Retrieve, sort, store token info from the cache.
+ *
+ * @param $token_type
+ * The optional token type that if specified will return
+ * $info['types'][$token_type].
+ * @param $token
+ * The optional token name if specified will return
+ * $info['tokens'][$token_type][$token].
+ *
+ * @return
+ * An array of all token information from hook_token_info(), or the array
+ * of a token type or specific token.
+ *
+ * @see hook_token_info()
+ * @see hook_token_info_alter()
+ */
+function token_get_info($token_type = NULL, $token = NULL) {
+ global $language;
+
+ // Use the advanced drupal_static() pattern, since this is called very often.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['token_info'] = &drupal_static(__FUNCTION__);
+ }
+ $token_info = &$drupal_static_fast['token_info'];
+
+ if (empty($token_info)) {
+ $cid = "info:{$language->language}";
+
+ if ($cache = cache_get($cid, 'cache_token')) {
+ $token_info = $cache->data;
+ }
+ else {
+ $token_info = token_info();
+
+ foreach (array_keys($token_info['types']) as $type_key) {
+ if (isset($token_info['types'][$type_key]['type'])) {
+ $base_type = $token_info['types'][$type_key]['type'];
+ // If this token type extends another token type, then merge in
+ // the base token type's tokens.
+ if (isset($token_info['tokens'][$base_type])) {
+ $token_info['tokens'] += array($type_key => array());
+ $token_info['tokens'][$type_key] += $token_info['tokens'][$base_type];
+ }
+ }
+ else {
+ // Add a 'type' value to each token type so we can properly use
+ // token_type_load().
+ $token_info['types'][$type_key]['type'] = $type_key;
+ }
+ }
+
+ // Pre-sort tokens.
+ uasort($token_info['types'], 'token_asort_tokens');
+ foreach (array_keys($token_info['tokens']) as $type) {
+ uasort($token_info['tokens'][$type], 'token_asort_tokens');
+ }
+
+ // Store info in cache for future use.
+ cache_set($cid, $token_info, 'cache_token');
+ }
+ }
+
+ if (isset($token_type) && isset($token)) {
+ return isset($token_info['tokens'][$token_type][$token]) ? $token_info['tokens'][$token_type][$token] : NULL;
+ }
+ elseif (isset($token_type)) {
+ return isset($token_info['types'][$token_type]) ? $token_info['types'][$token_type] : NULL;
+ }
+ else {
+ return $token_info;
+ }
+}
+
+/**
+ * Return the module responsible for a token.
+ *
+ * @param string $type
+ * The token type.
+ * @param string $name
+ * The token name.
+ *
+ * @return mixed
+ * The value of $info['tokens'][$type][$name]['module'] from token_get_info(),
+ * or NULL if the value does not exist.
+ */
+function _token_module($type, $name) {
+ $token_info = token_get_info($type, $name);
+ return isset($token_info['module']) ? $token_info['module'] : NULL;
+}
+
+/**
+ * uasort() callback to sort tokens by the 'name' property.
+ */
+function token_asort_tokens($token_a, $token_b) {
+ return strnatcmp($token_a['name'], $token_b['name']);
+}
+
+/**
+ * Get a list of token types that can be used without any context (global).
+ *
+ * @return
+ * An array of global token types.
+ */
+function token_get_global_token_types() {
+ $global_types = &drupal_static(__FUNCTION__, array());
+
+ if (empty($global_types)) {
+ $token_info = token_get_info();
+ foreach ($token_info['types'] as $type => $type_info) {
+ // If the token types has not specified that 'needs-data' => TRUE, then
+ // it is a global token type that will always be replaced in any context.
+ if (empty($type_info['needs-data'])) {
+ $global_types[] = $type;
+ }
+ }
+ }
+
+ return $global_types;
+}
+
+/**
+ * Validate an tokens in raw text based on possible contexts.
+ *
+ * @param $value
+ * A string with the raw text containing the raw tokens, or an array of
+ * tokens from token_scan().
+ * @param $tokens
+ * An array of token types that will be used when token replacement is
+ * performed.
+ * @return
+ * An array with the invalid tokens in their original raw forms.
+ */
+function token_get_invalid_tokens_by_context($value, $valid_types = array()) {
+ if (in_array('all', $valid_types)) {
+ $info = token_get_info();
+ $valid_types = array_keys($info['types']);
+ }
+ else {
+ // Add the token types that are always valid in global context.
+ $valid_types = array_merge($valid_types, token_get_global_token_types());
+ }
+
+ $invalid_tokens = array();
+ $value_tokens = is_string($value) ? token_scan($value) : $value;
+
+ foreach ($value_tokens as $type => $tokens) {
+ if (!in_array($type, $valid_types)) {
+ // If the token type is not a valid context, its tokens are invalid.
+ $invalid_tokens = array_merge($invalid_tokens, array_values($tokens));
+ }
+ else {
+ // Check each individual token for validity.
+ $invalid_tokens = array_merge($invalid_tokens, token_get_invalid_tokens($type, $tokens));
+ }
+ }
+
+ array_unique($invalid_tokens);
+ return $invalid_tokens;
+}
+
+/**
+ * Validate an array of tokens based on their token type.
+ *
+ * @param $type
+ * The type of tokens to validate (e.g. 'node', etc.)
+ * @param $tokens
+ * A keyed array of tokens, and their original raw form in the source text.
+ * @return
+ * An array with the invalid tokens in their original raw forms.
+ */
+function token_get_invalid_tokens($type, $tokens) {
+ $token_info = token_get_info();
+ $invalid_tokens = array();
+
+ foreach ($tokens as $token => $full_token) {
+ // Split token up if it has chains.
+ $parts = explode(':', $token, 2);
+
+ if (!isset($token_info['tokens'][$type][$parts[0]])) {
+ // This is an invalid token (not defined).
+ $invalid_tokens[] = $full_token;
+ }
+ elseif (count($parts) == 2) {
+ $sub_token_info = $token_info['tokens'][$type][$parts[0]];
+ if (!empty($sub_token_info['dynamic'])) {
+ // If this token has been flagged as a dynamic token, skip it.
+ continue;
+ }
+ elseif (empty($sub_token_info['type'])) {
+ // If the token has chains, but does not support it, it is invalid.
+ $invalid_tokens[] = $full_token;
+ }
+ else {
+ // Resursively check the chained tokens.
+ $sub_tokens = token_find_with_prefix(array($token => $full_token), $parts[0]);
+ $invalid_tokens = array_merge($invalid_tokens, token_get_invalid_tokens($sub_token_info['type'], $sub_tokens));
+ }
+ }
+ }
+
+ return $invalid_tokens;
+}
+
+/**
+ * Validate a form element that should have tokens in it.
+ *
+ * Form elements that want to add this validation should have the #token_types
+ * parameter defined.
+ *
+ * For example:
+ * @code
+ * $form['my_node_text_element'] = array(
+ * '#type' => 'textfield',
+ * '#title' => t('Some text to token-ize that has a node context.'),
+ * '#default_value' => 'The title of this node is [node:title].',
+ * '#element_validate' => array('token_element_validate'),
+ * '#token_types' => array('node'),
+ * '#min_tokens' => 1,
+ * '#max_tokens' => 10,
+ * );
+ * @endcode
+ */
+function token_element_validate(&$element, &$form_state) {
+ $value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];
+
+ if (!drupal_strlen($value)) {
+ // Empty value needs no further validation since the element should depend
+ // on using the '#required' FAPI property.
+ return $element;
+ }
+
+ $tokens = token_scan($value);
+ $title = empty($element['#title']) ? $element['#parents'][0] : $element['#title'];
+ // @todo Find old Drupal 6 style tokens and add them to invalid tokens.
+
+ // Validate if an element must have a minimum number of tokens.
+ if (isset($element['#min_tokens']) && count($tokens) < $element['#min_tokens']) {
+ // @todo Change this error message to include the minimum number.
+ $error = format_plural($element['#min_tokens'], 'The %element-title cannot contain fewer than one token.', 'The %element-title must contain at least @count tokens.', array('%element-title' => $title));
+ form_error($element, $error);
+ }
+
+ // Validate if an element must have a maximum number of tokens.
+ if (isset($element['#max_tokens']) && count($tokens) > $element['#max_tokens']) {
+ // @todo Change this error message to include the maximum number.
+ $error = format_plural($element['#max_tokens'], 'The %element-title must contain as most one token.', 'The %element-title must contain at most @count tokens.', array('%element-title' => $title));
+ form_error($element, $error);
+ }
+
+ // Check if the field defines specific token types.
+ if (isset($element['#token_types'])) {
+ $invalid_tokens = token_get_invalid_tokens_by_context($tokens, $element['#token_types']);
+ if ($invalid_tokens) {
+ form_error($element, t('The %element-title is using the following invalid tokens: @invalid-tokens.', array('%element-title' => $title, '@invalid-tokens' => implode(', ', $invalid_tokens))));
+ }
+ }
+
+ return $element;
+}
+
+/**
+ * Deprecated. Use token_element_validate() instead.
+ */
+function token_element_validate_token_context(&$element, &$form_state) {
+ return token_element_validate($element, $form_state);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function token_form_field_ui_field_edit_form_alter(&$form, $form_state) {
+ if (!isset($form['instance']) || !empty($form['#field']['locked'])) {
+ return;
+ }
+
+ if (($form['#field']['type'] == 'file' || $form['#field']['type'] == 'image') && isset($form['instance']['settings']['file_directory']) && !module_exists('filefield_paths')) {
+ // GAH! We can only support global tokens in the upload file directory path.
+ $form['instance']['settings']['file_directory']['#element_validate'][] = 'token_element_validate';
+ $form['instance']['settings']['file_directory'] += array('#token_types' => array());
+ $form['instance']['settings']['file_directory']['#description'] .= ' ' . t('This field supports tokens.');
+ }
+
+ // Note that the description is tokenized via token_field_widget_form_alter().
+ $form['instance']['description']['#description'] .= '<br />' . t('This field supports tokens.');
+ $form['instance']['description']['#element_validate'][] = 'token_element_validate';
+ $form['instance']['description'] += array('#token_types' => array());
+
+ $form['instance']['settings']['token_tree'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => array(),
+ '#dialog' => TRUE,
+ '#weight' => $form['instance']['description']['#weight'] + 0.5,
+ );
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Alters the configure action form to add token context validation and
+ * adds the token tree for a better token UI and selection.
+ */
+function token_form_system_actions_configure_alter(&$form, $form_state) {
+ $action = actions_function_lookup($form['actions_action']['#value']);
+
+ switch ($action) {
+ case 'system_message_action':
+ case 'system_send_email_action':
+ case 'system_goto_action':
+ $form['token_tree'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => 'all',
+ '#dialog' => TRUE,
+ '#weight' => 100,
+ );
+ // @todo Add token validation to the action fields that can use tokens.
+ break;
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * Alters the user e-mail fields to add token context validation and
+ * adds the token tree for a better token UI and selection.
+ */
+function token_form_user_admin_settings_alter(&$form, &$form_state) {
+ $email_token_help = t('Available variables are: [site:name], [site:url], [user:name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].');
+
+ foreach (element_children($form) as $key) {
+ $element = &$form[$key];
+
+ // Remove the crummy default token help text.
+ if (!empty($element['#description'])) {
+ $element['#description'] = trim(str_replace($email_token_help, t('The list of available tokens that can be used in e-mails is provided below.'), $element['#description']));
+ }
+
+ switch ($key) {
+ case 'email_admin_created':
+ case 'email_pending_approval':
+ case 'email_no_approval_required':
+ case 'email_password_reset':
+ case 'email_cancel_confirm':
+ // Do nothing, but allow execution to continue.
+ break;
+ case 'email_activated':
+ case 'email_blocked':
+ case 'email_canceled':
+ // These fieldsets have their e-mail elements inside a 'settings'
+ // sub-element, so switch to that element instead.
+ $element = &$form[$key]['settings'];
+ break;
+ default:
+ continue 2;
+ }
+
+ foreach (element_children($element) as $sub_key) {
+ if (!isset($element[$sub_key]['#type'])) {
+ continue;
+ }
+ elseif ($element[$sub_key]['#type'] == 'textfield' && substr($sub_key, -8) === '_subject') {
+ // Add validation to subject textfields.
+ $element[$sub_key]['#element_validate'][] = 'token_element_validate';
+ $element[$sub_key] += array('#token_types' => array('user'));
+ }
+ elseif ($element[$sub_key]['#type'] == 'textarea' && substr($sub_key, -5) === '_body') {
+ // Add validation to body textareas.
+ $element[$sub_key]['#element_validate'][] = 'token_element_validate';
+ $element[$sub_key] += array('#token_types' => array('user'));
+ }
+ }
+ }
+
+ // Add the token tree UI.
+ $form['email']['token_tree'] = array(
+ '#theme' => 'token_tree',
+ '#token_types' => array('user'),
+ '#show_restricted' => TRUE,
+ '#dialog' => TRUE,
+ '#weight' => 90,
+ );
+}
+
+/**
+ * Build a tree array of tokens used for themeing or information.
+ *
+ * @param $token_type
+ * The token type.
+ * @param $flat_tree
+ * A boolean if TRUE will only make a flat array of tokens, otherwise
+ * child tokens will be inside the 'children' parameter of a token.
+ * @param $show_restricted
+ * A boolean if TRUE will show restricted tokens. Otherwise they will be
+ * hidden. Default is FALSE.
+ * @param $recursion_limit
+ * An integer with the maximum number of token levels to recurse.
+ * @param $parents
+ * An optional array with the current parents of the tokens.
+ */
+function token_build_tree($token_type, array $options = array()) {
+ global $language;
+
+ // Static cache of already built token trees.
+ $trees = &drupal_static(__FUNCTION__, array());
+
+ $options += array(
+ 'restricted' => FALSE,
+ 'depth' => 4,
+ 'data' => array(),
+ 'values' => FALSE,
+ 'flat' => FALSE,
+ );
+
+ // Do not allow past the maximum token information depth.
+ $options['depth'] = min($options['depth'], TOKEN_MAX_DEPTH);
+
+ // If $token_type is an entity, make sure we are using the actual token type.
+ if ($entity_token_type = token_get_entity_mapping('entity', $token_type)) {
+ $token_type = $entity_token_type;
+ }
+
+ $tree_cid = "tree:{$token_type}:{$language->language}:{$options['depth']}";
+
+ // If we do not have this base tree in the static cache, check {cache_token}
+ // otherwise generate and store it in the cache.
+ if (!isset($trees[$tree_cid])) {
+ if ($cache = cache_get($tree_cid, 'cache_token')) {
+ $trees[$tree_cid] = $cache->data;
+ }
+ else {
+ $options['parents'] = array();
+ $trees[$tree_cid] = _token_build_tree($token_type, $options);
+ cache_set($tree_cid, $trees[$tree_cid], 'cache_token');
+ }
+ }
+
+ $tree = $trees[$tree_cid];
+
+ // If the user has requested a flat tree, convert it.
+ if (!empty($options['flat'])) {
+ $tree = token_flatten_tree($tree);
+ }
+
+ // Fill in token values.
+ if (!empty($options['values'])) {
+ $token_values = array();
+ foreach ($tree as $token => $token_info) {
+ if (!empty($token_info['dynamic']) || !empty($token_info['restricted'])) {
+ continue;
+ }
+ elseif (!isset($token_info['value'])) {
+ $token_values[$token_info['token']] = $token;
+ }
+ }
+ if (!empty($token_values)) {
+ $token_values = token_generate($token_type, $token_values, $options['data']);
+ foreach ($token_values as $token => $replacement) {
+ $tree[$token]['value'] = $replacement;
+ }
+ }
+ }
+
+ return $tree;
+}
+
+/**
+ * Flatten a token tree.
+ */
+function token_flatten_tree($tree) {
+ $result = array();
+ foreach ($tree as $token => $token_info) {
+ $result[$token] = $token_info;
+ if (isset($token_info['children']) && is_array($token_info['children'])) {
+ $result += token_flatten_tree($token_info['children']);
+ // unset($result[$token]['children']);
+ }
+ }
+ return $result;
+}
+
+/**
+ * Generate a token tree.
+ */
+function _token_build_tree($token_type, array $options) {
+ $options += array(
+ 'parents' => array(),
+ );
+
+ $info = token_get_info();
+ if ($options['depth'] <= 0 || !isset($info['types'][$token_type]) || !isset($info['tokens'][$token_type])) {
+ return array();
+ }
+
+ $tree = array();
+ foreach ($info['tokens'][$token_type] as $token => $token_info) {
+ // Build the raw token string.
+ $token_parents = $options['parents'];
+ if (empty($token_parents)) {
+ // If the parents array is currently empty, assume the token type is its
+ // parent.
+ $token_parents[] = $token_type;
+ }
+ elseif (in_array($token, array_slice($token_parents, 1))) {
+ // Prevent duplicate recursive tokens. For example, this will prevent
+ // the tree from generating the following tokens or deeper:
+ // [comment:parent:parent]
+ // [comment:parent:root:parent]
+ continue;
+ }
+
+ $token_parents[] = $token;
+ if (!empty($token_info['dynamic'])) {
+ $token_parents[] = '?';
+ }
+ $raw_token = '[' . implode(':', $token_parents) . ']';
+ $tree[$raw_token] = $token_info;
+ $tree[$raw_token]['raw token'] = $raw_token;
+
+ // Add the token's real name (leave out the base token type).
+ $tree[$raw_token]['token'] = implode(':', array_slice($token_parents, 1));
+
+ // Add the token's parent as its raw token value.
+ if (!empty($options['parents'])) {
+ $tree[$raw_token]['parent'] = '[' . implode(':', $options['parents']) . ']';
+ }
+
+ // Fetch the child tokens.
+ if (!empty($token_info['type'])) {
+ $child_options = $options;
+ $child_options['depth']--;
+ $child_options['parents'] = $token_parents;
+ $tree[$raw_token]['children'] = _token_build_tree($token_info['type'], $child_options);
+ }
+ }
+
+ return $tree;
+}
+
+/**
+ * Get a translated menu link by its mlid, without access checking.
+ *
+ * This function is a copy of menu_link_load() but with its own cache and a
+ * simpler query to load the link. This also skips normal menu link access
+ * checking by using _token_menu_link_translate().
+ *
+ * @param $mlid
+ * The mlid of the menu item.
+ *
+ * @return
+ * A menu link translated for rendering.
+ *
+ * @see menu_link_load()
+ * @see _token_menu_link_translate()
+ */
+function token_menu_link_load($mlid) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ if (!is_numeric($mlid)) {
+ return FALSE;
+ }
+
+ if (!isset($cache[$mlid])) {
+ $item = db_query("SELECT * FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
+ if (!empty($item)) {
+ _token_menu_link_translate($item);
+ }
+ $cache[$mlid] = $item;
+ }
+
+ return $cache[$mlid];
+}
+
+/**
+ * Get a translated book menu link by its mlid, without access checking.
+ *
+ * This function is a copy of book_link_load() but with its own cache and a
+ * simpler query to load the link. This also skips normal menu link access
+ * checking by using _token_menu_link_translate().
+ *
+ * @param $mlid
+ * The mlid of the book menu item.
+ *
+ * @return
+ * A book menu link translated for rendering.
+ *
+ * @see book_link_load()
+ * @see _token_menu_link_translate()
+ */
+function token_book_link_load($mlid) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ if (!is_numeric($mlid)) {
+ return FALSE;
+ }
+
+ if (!isset($cache[$mlid])) {
+ $item = db_query("SELECT * FROM {menu_links} ml INNER JOIN {book} b ON b.mlid = ml.mlid LEFT JOIN {menu_router} m ON m.path = ml.router_path WHERE ml.mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc();
+ if (!empty($item)) {
+ _token_menu_link_translate($item);
+ }
+ $cache[$mlid] = $item;
+ }
+
+ return $cache[$mlid];
+}
+
+function _token_menu_link_translate(&$item) {
+ $map = array();
+
+ if (!is_array($item['options'])) {
+ $item['options'] = unserialize($item['options']);
+ }
+
+ if ($item['external']) {
+ $item['access'] = 1;
+ $item['href'] = $item['link_path'];
+ $item['title'] = $item['link_title'];
+ $item['localized_options'] = $item['options'];
+ }
+ else {
+ // Complete the path of the menu link with elements from the current path,
+ // if it contains dynamic placeholders (%).
+ $map = explode('/', $item['link_path']);
+ if (strpos($item['link_path'], '%') !== FALSE) {
+ // Invoke registered to_arg callbacks.
+ if (!empty($item['to_arg_functions'])) {
+ _menu_link_map_translate($map, $item['to_arg_functions']);
+ }
+ }
+ $item['href'] = implode('/', $map);
+
+ // Skip links containing untranslated arguments.
+ if (strpos($item['href'], '%') !== FALSE) {
+ $item['access'] = FALSE;
+ return FALSE;
+ }
+
+ $item['access'] = TRUE;
+ _menu_item_localize($item, $map, TRUE);
+ }
+
+ // Allow other customizations - e.g. adding a page-specific query string to the
+ // options array. For performance reasons we only invoke this hook if the link
+ // has the 'alter' flag set in the options array.
+ if (!empty($item['options']['alter'])) {
+ drupal_alter('translated_menu_link', $item, $map);
+ }
+
+ return $map;
+}
+
+/**
+ * Prepare a string for use as a valid token name.
+ *
+ * @param $name
+ * The token name to clean.
+ * @return
+ * The cleaned token name.
+ */
+function token_clean_token_name($name) {
+ static $names = array();
+
+ if (!isset($names[$name])) {
+ $cleaned_name = strtr($name, array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => ''));
+ $cleaned_name = preg_replace('/[^\w\-]/i', '', $cleaned_name);
+ $cleaned_name = trim($cleaned_name, '-');
+ $names[$name] = $cleaned_name;
+ }
+
+ return $names[$name];
+}
+
+/**
+ * Do not use this function yet. Its API has not been finalized.
+ */
+function token_render_array(array $array, array $options = array()) {
+ $rendered = array();
+ foreach (element_children($array) as $key) {
+ $value = $array[$key];
+ $rendered[] = is_array($value) ? render($value) : (string) $value;
+ }
+ if (!empty($options['sanitize'])) {
+ $rendered = array_map('check_plain', $rendered);
+ }
+ $join = isset($options['join']) ? $options['join'] : ', ';
+ return implode($join, $rendered);
+}
+
+/**
+ * Do not use this function yet. Its API has not been finalized.
+ */
+function token_render_array_value($value, array $options = array()) {
+ $rendered = is_array($value) ? render($value) : (string) $value;
+ if (!empty($options['sanitize'])) {
+ $rendered = check_plain($rendered);
+ }
+ return $rendered;
+}
+
+/**
+ * Copy of drupal_render_cache_get() that does not care about request method.
+ */
+function token_render_cache_get($elements) {
+ if (!$cid = drupal_render_cid_create($elements)) {
+ return FALSE;
+ }
+ $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
+
+ if (!empty($cid) && $cache = cache_get($cid, $bin)) {
+ // Add additional libraries, JavaScript, CSS and other data attached
+ // to this element.
+ if (isset($cache->data['#attached'])) {
+ drupal_process_attached($cache->data);
+ }
+ // Return the rendered output.
+ return $cache->data['#markup'];
+ }
+ return FALSE;
+}
+
+/**
+ * Coyp of drupal_render_cache_set() that does not care about request method.
+ */
+function token_render_cache_set(&$markup, $elements) {
+ // This should only run of drupal_render_cache_set() did not.
+ if (in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD'))) {
+ return FALSE;
+ }
+
+ $original_method = $_SERVER['REQUEST_METHOD'];
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+ drupal_render_cache_set($markup, $elements);
+ $_SERVER['REQUEST_METHOD'] = $original_method;
+}
+
+function token_menu_link_load_all_parents($mlid) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ if (!is_numeric($mlid)) {
+ return array();
+ }
+
+ if (!isset($cache[$mlid])) {
+ $cache[$mlid] = array();
+ $plid = db_query("SELECT plid FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchField();
+ while ($plid && $parent = token_menu_link_load($plid)) {
+ $cache[$mlid] = array($plid => $parent['title']) + $cache[$mlid];
+ $plid = $parent['plid'];
+ }
+ }
+
+ return $cache[$mlid];
+}
+
+function token_taxonomy_term_load_all_parents($tid) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ if (!is_numeric($tid)) {
+ return array();
+ }
+
+ if (!isset($cache[$tid])) {
+ $cache[$tid] = array();
+ $parents = taxonomy_get_parents_all($tid);
+ array_shift($parents); // Remove this term from the array.
+ $parents = array_reverse($parents);
+ foreach ($parents as $term) {
+ $cache[$tid][$term->tid] = entity_label('taxonomy_term', $term);
+ }
+ }
+
+ return $cache[$tid];
+}
+
+/**
+ * Load the preferred menu link associated with a node.
+ *
+ * @param $node
+ * A node object.
+ *
+ * @return
+ * A menu link array from token_menu_link_load() or FALSE if a menu link was
+ * not found.
+ *
+ * @see menu_node_prepare()
+ * @see token_menu_link_load()
+ */
+function token_node_menu_link_load($node) {
+ $cache = &drupal_static(__FUNCTIon__, array());
+
+ if (!isset($cache[$node->nid])) {
+ $mlid = FALSE;
+
+ // Nodes do not have their menu links loaded via menu_node_load().
+ if (!isset($node->menu)) {
+ // We need to clone the node as menu_node_prepare() may cause data loss.
+ // @see http://drupal.org/node/1317926
+ $menu_node = clone $node;
+ menu_node_prepare($menu_node);
+ $mlid = !empty($menu_node->menu['mlid']) ? $menu_node->menu['mlid'] : FALSE;
+ }
+ else {
+ $mlid = !empty($node->menu['mlid']) ? $node->menu['mlid'] : FALSE;
+ }
+
+ $cache[$node->nid] = $mlid;
+ }
+
+ return $cache[$node->nid] ? token_menu_link_load($cache[$node->nid]) : FALSE;
+}
View
364 sites/all/modules/contrib/token/token.pages.inc
@@ -0,0 +1,364 @@
+<?php
+
+/**
+ * @file
+ * User page callbacks for the token module.
+ */
+
+/**
+ * Theme a link to a token tree either as a regular link or a dialog.
+ */
+function theme_token_tree_link($variables) {
+ if (empty($variables['text'])) {
+ $variables['text'] = t('Browse available tokens.');
+ }
+
+ if (!empty($variables['dialog'])) {
+ drupal_add_library('token', 'dialog');
+ $variables['options']['attributes']['class'][] = 'token-dialog';
+ }
+
+ $info = token_theme();
+ $variables['options']['query']['options'] = array_intersect_key($variables, $info['token_tree']['variables']);
+
+ // We should never pass the dialog option to theme_token_tree(). It is only
+ // used for this function.
+ unset($variables['options']['query']['options']['dialog']);
+
+ // Add a security token so that the tree page should only work when used
+ // when the dialog link is output with theme('token_tree_link').
+ $variables['options']['query']['token'] = drupal_get_token('token-tree:' . serialize($variables['options']['query']['options']));
+
+ // Because PHP converts query strings with arrays into a different syntax on
+ // the next request, the options have to be encoded with JSON in the query
+ // string so that we can reliably decode it for token comparison.
+ $variables['options']['query']['options'] = drupal_json_encode($variables['options']['query']['options']);
+
+ // Set the token tree to open in a separate window.
+ $variables['options']['attributes'] + array('target' => '_blank');
+
+ return l($variables['text'], 'token/tree', $variables['options']);
+}
+
+/**
+ * Page callback to output a token tree as an empty page.
+ */
+function token_page_output_tree() {
+ $options = isset($_GET['options']) ? drupal_json_decode($_GET['options']) : array();
+
+ // Check the token against the serialized options to prevent random access to
+ // the token browser page.
+ if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], 'token-tree:' . serialize($options))) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ // Force the dialog option to be false so we're not creating a dialog within
+ // a dialog.
+ $options['dialog'] = FALSE;
+
+ $output = theme('token_tree', $options);
+ print '<html><head>' . drupal_get_css() . drupal_get_js() . '</head>';
+ print '<body class="token-tree">' . $output . '</body></html>';
+ drupal_exit();
+}
+
+/**
+ * Theme a tree table.
+ *
+ * @ingroup themeable
+ */
+function theme_tree_table($variables) {
+ foreach ($variables['rows'] as &$row) {
+ $row += array('class' => array());
+ if (!empty($row['parent'])) {
+ $row['class'][] = 'child-of-' . $row['parent'];
+ unset($row['parent']);
+ }
+ }
+
+ if (!empty($variables['rows'])) {
+ drupal_add_library('token', 'treeTable');
+ }
+
+ return theme('table', $variables);
+}
+
+/**
+ * Provide a 'tree' display of nested tokens.
+ *
+ * @ingroup themeable
+ */
+function theme_token_tree($variables) {
+ if (!empty($variables['dialog'])) {
+ return theme_token_tree_link($variables);
+ }
+
+ $token_types = $variables['token_types'];
+ $info = token_get_info();
+
+ if ($token_types == 'all') {
+ $token_types = array_keys($info['types']);
+ }
+ elseif ($variables['global_types']) {
+ $token_types = array_merge($token_types, token_get_global_token_types());
+ }
+
+ $element = array(
+ '#cache' => array(
+ 'cid' => 'tree-rendered:' . hash('sha256', serialize(array('token_types' => $token_types, 'global_types' => NULL) + $variables)),
+ 'bin' => 'cache_token',
+ ),
+ );
+ if ($cached_output = token_render_cache_get($element)) {
+ return $cached_output;
+ }
+
+ $options = array(
+ 'flat' => TRUE,
+ 'restricted' => $variables['show_restricted'],
+ 'depth' => $variables['recursion_limit'],
+ );
+ $multiple_token_types = (count($token_types) > 1);
+ $rows = array();
+
+ foreach ($info['types'] as $type => $type_info) {
+ if (!in_array($type, $token_types)) {
+ continue;
+ }
+
+ if ($multiple_token_types) {
+ $row = _token_token_tree_format_row($type, $type_info, TRUE);
+ unset($row['data']['value']);
+ $rows[] = $row;
+ }
+
+ $tree = token_build_tree($type, $options);
+ foreach ($tree as $token => $token_info) {
+ if (!empty($token_info['restricted']) && empty($variables['show_restricted'])) {
+ continue;
+ }
+ if ($multiple_token_types && !isset($token_info['parent'])) {
+ $token_info['parent'] = $type;