diff --git a/sites/all/modules/pathauto/API.txt b/sites/all/modules/pathauto/API.txt new file mode 100644 index 0000000000000000000000000000000000000000..5feafa08029d6e691bd1f1bd6fa0a89f4bc95f46 --- /dev/null +++ b/sites/all/modules/pathauto/API.txt @@ -0,0 +1,155 @@ +$Id: API.txt,v 1.10 2011/01/13 03:27:24 davereid Exp $ + +This document explains how to provide "Pathauto integration" in a +module. You need this if you would like to provide additional tokens +or if your module has paths and you wish to have them automatically +aliased. The simplest integration is just to provide tokens so we +cover that first. More advanced integration requires an +implementation of hook_pathauto to provide a settings form. + +It may be helpful to review some examples of integration from the +pathauto_node.inc, pathauto_taxonomy.inc, and pathauto_user.inc files. + + +================== +1 - Providing additional tokens +================== + +If all you want is to enable tokens for your module you will simply +need to implement two functions: + + hook_token_values + hook_token_list + +See the token.module and it's API.txt for more information about this +process. + +If the token is intended to generate a path expected to contain slashes, +the token name must end in 'path', 'path-raw' or 'alias'. This indicates to +Pathauto that the slashes should not be removed from the replacement value. + +When an object is created (whether it is a node or a user or a +taxonomy term) the data that Pathauto hands to the token_values in the +$object is in a specific format. This is the format that most people +write code to handle. However, during edits and bulk updates the data +may be in a totally different format. So, if you are writing a +hook_token_values implementation to add special tokens, be sure to +test creation, edit, and bulk update cases to make sure your code will +handle it. + +================== +2 - Settings hook - To create aliases for your module +================== +You must implement hook_pathauto($op), where $op is always (at this +time) 'settings'. Return an object (NOT an array) containing the +following members, which will be used by pathauto to build a group +of settings for your module and define the variables for saving your +settings: + +module - The name of your module (e.g., 'node') +groupheader - The translated label for the settings group (e.g., + t('Content path settings') +patterndescr - The translated label for the default pattern (e.g., + t('Default path pattern (applies to all content types with blank patterns below)') +patterndefault - A translated default pattern (e.g., t('[cat]/[title].html')) +token_type - The token type (e.g. 'node', 'user') that can be used. +patternitems - For modules which need to express multiple patterns + (for example, the node module supports a separate pattern for each + content type), an array whose keys consist of identifiers for each + pattern (e.g., the content type name) and values consist of the + translated label for the pattern +supportsfeeds - Modules which support RSS feeds should set this to the + string that's appended to a path for its feed (usually 'feed') , so + when administrators enable "Create feed aliases" an alias for this + content type's feed will be generated in addition to the base alias. +bulkname - For modules which support a bulk update operation, the + translated label for the action (e.g., t('Bulk update content paths')) +bulkdescr - For modules which support a bulk update operation, a + translated, more thorough description of what the operation will do + (e.g., t('Generate aliases for all existing content items which do not already have aliases.')) + + +================== +2 - $alias = pathauto_create_alias($module, $op, $placeholders, $src, $type=NULL) +================== + +At the appropriate time (usually when a new item is being created for +which a generated alias is desired), call pathauto_create_alias() to +generate and create the alias. See the user, taxonomy, and nodeapi hook +implementations in pathauto.module for examples. + +$module - The name of your module (e.g., 'node') +$op - Operation being performed on the item ('insert', 'update', or + 'bulkupdate') +$placeholders - An array whose keys consist of the translated placeholders + which appear in patterns and values are the "clean" values to be + substituted into the pattern. Call pathauto_cleanstring() on any + values which you do not know to be purely alphanumeric, to substitute + any non-alphanumerics with the user's designated separator. Note that + if the pattern has multiple slash-separated components (e.g., [term:path]), + pathauto_cleanstring() should be called for each component, not the + complete string. + Example: $placeholders[t('[title]')] = pathauto_cleanstring($node->title); +$src - The "real" URI of the content to be aliased (e.g., "node/$node->nid") +$type - For modules which provided patternitems in hook_autopath(), + the relevant identifier for the specific item to be aliased (e.g., + $node->type) + +pathauto_create_alias() returns the alias that was created. + + +================== +3 - Bulk update function +================== + +If a module supports bulk updating of aliases, it must provide a +function of this form, to be called by pathauto when the corresponding +checkbox is selected and the settings page submitted: + +function <module>_pathauto_bulkupdate() + +The function should iterate over the content items controlled by the +module, calling pathauto_create_alias() for each one. It is +recommended that the function report on its success (e.g., with a +count of created aliases) via drupal_set_message(). + + +================== +4 - Bulk delete hook_path_alias_types() +================== + +For modules that create new types of pages that can be aliased with pathauto, a +hook implementation is needed to allow the user to delete them all at once. + +function hook_path_alias_types() + +This hook returns an array whose keys match the beginning of the source paths +(e.g.: "node/", "user/", etc.) and whose values describe the type of page (e.g.: +"content", "users"). Like all displayed strings, these descriptionsshould be +localized with t(). Use % to match interior pieces of a path; "user/%/track". This +is a database wildcard, so be careful. + + +================== +Modules that extend node and/or taxonomy +================== + +NOTE: this is basically not true any more. If you feel you need this file an issue. + +Many contributed Drupal modules extend the core node and taxonomy +modules. To extend pathauto patterns to support their extensions, they +may implement the pathauto_node and pathauto_taxonomy hooks. + +To do so, implement the function <modulename>_pathauto_node (or _taxonomy), +accepting the arguments $op and $node (or $term). Two operations are +supported: + +$op = 'placeholders' - return an array keyed on placeholder strings +(e.g., t('[eventyyyy]')) valued with descriptions (e.g. t('The year the +event starts.')). +$op = 'values' - return an array keyed on placeholder strings, valued +with the "clean" actual value for the passed node or category (e.g., +pathauto_cleanstring(date('M', $eventstart))); + +See contrib/pathauto_node_event.inc for an example of extending node +patterns. diff --git a/sites/all/modules/pathauto/INSTALL.txt b/sites/all/modules/pathauto/INSTALL.txt new file mode 100644 index 0000000000000000000000000000000000000000..2de60e6fdcbb3c4464e267cd2240868686e6bdc1 --- /dev/null +++ b/sites/all/modules/pathauto/INSTALL.txt @@ -0,0 +1,50 @@ + +**Installation: + +Pathauto is an extension to the path module, which must be enabled. + +Pathauto also relies on the Token module, which must be downloaded and +enabled separately. + +1. Unpack the Pathauto folder and contents in the appropriate modules +directory of your Drupal installation. This is probably + sites/all/modules/ +2. Enable the Pathauto module in the administration tools. +3. If you're not using Drupal's default administrative account, make +sure "administer pathauto" is enabled through access control administration. +4. Visit the Pathauto settings page and make appropriate configurations + For 5.x: Administer > Site configuration > Pathauto + For 6.x: Administer > Site building > URL alias > Automated alias settings + +**Transliteration support: +If you desire transliteration support in the creation of URLs (e.g. the +replacement of Á with A) then you will need to install the Transliteration +module, which can be found at http://drupal.org/project/transliteration + +Once you've installed and enabled the module, simply go to +admin/config/search/path/settings and check the "Transliterate prior to +creating alias" box and path aliases should now be transliterated automagically. + +**Upgrading from previous versions: +If you are upgrading from Pathauto 5.x-1.x to 5.x-2.x (or 6.x-2.x) then you +will probably need to change your patterns. + +For content patterns: + [user] is now [author-name] + [cat] is now [term] + +There may be other changes as well. Please review the pattern examples on + Administration > Site Configuration > Pathauto + +If you upgraded from Pathauto 5.x-1.x directly without enabling Token +first then you will need to + 1) download/install the Token module + 2) disable the Pathauto module + 3) re-enable the Pathauto module + +Upgrade to 6.x: +Note that the settings page has moved so that it is more logically grouped with +other URL alias related items under + Administer > Site building > URL alias > Automated alias settings + +$Id: INSTALL.txt,v 1.6 2010/03/15 18:24:56 davereid Exp $ diff --git a/sites/all/modules/pathauto/LICENSE.txt b/sites/all/modules/pathauto/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/pathauto/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, 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 Library 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 diff --git a/sites/all/modules/pathauto/README.txt b/sites/all/modules/pathauto/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..4c00ec323e961265772dfb4c49c6b825a7f3162f --- /dev/null +++ b/sites/all/modules/pathauto/README.txt @@ -0,0 +1,95 @@ +Please read this file and also the INSTALL.txt. +They contain answers to many common questions. +If you are developing for this module, the API.txt may be interesting. +If you are upgrading, check the CHANGELOG.txt for major changes. + +**Description: +The Pathauto module provides support functions for other modules to +automatically generate aliases based on appropriate criteria, with a +central settings path for site administrators. + +Implementations are provided for core content types: nodes, taxonomy +terms, and users (including blogs and tracker pages). + +Pathauto also provides a way to delete large numbers of aliases. This feature +is available at Administer > Site building > URL aliases > Delete aliases + +**Benefits: +Besides making the page address more reflective of its content than +"node/138", it's important to know that modern search engines give +heavy weight to search terms which appear in a page's URL. By +automatically using keywords based directly on the page content in the URL, +relevant search engine hits for your page can be significantly +enhanced. + +**Installation AND Upgrades: +See the INSTALL.txt file. + +**Notices: +Pathauto just adds URL aliases to content, users, and taxonomy terms. +Because it's an alias, the standard Drupal URL (for example node/123 or +taxonomy/term/1) will still function as normal. If you have external links +to your site pointing to standard Drupal URLs, or hardcoded links in a module, +template, content or menu which point to standard Drupal URLs it will bypass +the alias set by Pathauto. + +There are reasons you might not want two URLs for the same content on your +site. If this applies to you, please note that you will need to update any +hard coded links in your content or blocks. + +If you use the "system path" (i.e. node/10) for menu items and settings like +that, Drupal will replace it with the url_alias. + +For external links, you might want to consider the Path Redirect or +Global Redirect modules, which allow you to set forwarding either per item or +across the site to your aliased URLs. + +URLs (not) Getting Replaced With Aliases: +Please bear in mind that only URLs passed through Drupal's l() or url() +functions will be replaced with their aliases during page output. If a module +or your template contains hardcoded links, such as 'href="node/$node->nid"' +those won't get replaced with their corresponding aliases. Use the +Drupal API instead: + +* 'href="'. url("node/$node->nid") .'"' or +* l("Your link title", "node/$node->nid") + +See http://api.drupal.org/api/HEAD/function/url and +http://api.drupal.org/api/HEAD/function/l for more information. + +** Disabling Pathauto for a specific content type (or taxonomy) +When the pattern for a content type is left blank, the default pattern will be +used. But if the default pattern is also blank, Pathauto will be disabled +for that content type. + +** Bulk Updates Must be Run Multiple Times: +As of 5.x-2.x Pathauto now performs bulk updates in a manner which is more +likely to succeed on large sites. The drawback is that it needs to be run +multiple times. If you want to reduce the number of times that you need to +run Pathauto you can increase the "Maximum number of objects to alias in a +bulk update:" setting under General Settings. + +**WYSIWYG Conflicts - FCKEditor, TinyMCE, etc. +If you use a WYSIWYG editor, please disable it for the Pathauto admin page. +Failure to do so may cause errors about "preg_replace" problems due to the <p> +tag being added to the "strings to replace". See http://drupal.org/node/175772 + +**Credits: +The original module combined the functionality of Mike Ryan's autopath with +Tommy Sundstrom's path_automatic. + +Significant enhancements were contributed by jdmquin @ www.bcdems.net. + +Matt England added the tracker support. + +Other suggestions and patches contributed by the Drupal community. + +Current maintainers: + Greg Knaddison - http://growingventuresolutions.com + Mike Ryan - http://mikeryan.name + Frederik 'Freso' S. Olesen - http://freso.dk + +**Changes: +See the CHANGELOG.txt file. + +$Id: README.txt,v 1.17 2011/01/13 03:27:24 davereid Exp $ diff --git a/sites/all/modules/pathauto/pathauto.admin.inc b/sites/all/modules/pathauto/pathauto.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..a9269c78e8cfe66e592b00da39e2764adb542a60 --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.admin.inc @@ -0,0 +1,443 @@ +<?php +// $Id: pathauto.admin.inc,v 1.46 2011/01/11 19:55:39 davereid Exp $ + +/** + * @file + * Admin page callbacks for the Pathauto module. + * + * @ingroup pathauto + */ + +/** + * Form builder; Configure the URL alias patterns. + * + * @ingroup forms + * @see system_settings_form() + */ +function pathauto_patterns_form($form, $form_state) { + // Call the hook on all modules - an array of 'settings' objects is returned + $all_settings = module_invoke_all('pathauto', 'settings'); + foreach ($all_settings as $settings) { + $module = $settings->module; + $patterndescr = $settings->patterndescr; + $patterndefault = $settings->patterndefault; + $groupheader = $settings->groupheader; + + $form[$module] = array( + '#type' => 'fieldset', + '#title' => $groupheader, + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + + // Prompt for the default pattern for this module + $variable = 'pathauto_' . $module . '_pattern'; + $form[$module][$variable] = array( + '#type' => 'textfield', + '#title' => $patterndescr, + '#default_value' => variable_get($variable, $patterndefault), + '#size' => 65, + '#maxlength' => 1280, + '#element_validate' => array('token_element_validate'), + '#after_build' => array('token_element_validate'), + '#token_types' => array($settings->token_type), + '#min_tokens' => 1, + '#parents' => array($variable), + ); + + // If the module supports a set of specialized patterns, set + // them up here + if (isset($settings->patternitems)) { + foreach ($settings->patternitems as $itemname => $itemlabel) { + $variable = 'pathauto_' . $module . '_' . $itemname . '_pattern'; + $form[$module][$variable] = array( + '#type' => 'textfield', + '#title' => $itemlabel, + '#default_value' => variable_get($variable, ''), + '#size' => 65, + '#maxlength' => 1280, + '#element_validate' => array('token_element_validate'), + '#after_build' => array('token_element_validate'), + '#token_types' => array($settings->token_type), + '#min_tokens' => 1, + '#parents' => array($variable), + ); + } + } + + // Display the user documentation of placeholders supported by + // this module, as a description on the last pattern + $form[$module]['token_help'] = array( + '#title' => t('Replacement patterns'), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form[$module]['token_help']['help'] = array( + '#theme' => 'token_tree', + '#token_types' => array($settings->token_type), + ); + } + + return system_settings_form($form); +} + +/** + * Form builder; Configure the Pathauto settings. + * + * @ingroup forms + * @see system_settings_form() + */ +function pathauto_settings_form($form) { + module_load_include('inc', 'pathauto'); + + $form['pathauto_verbose'] = array( + '#type' => 'checkbox', + '#title' => t('Verbose'), + '#default_value' => variable_get('pathauto_verbose', FALSE), + '#description' => t('Display alias changes (except during bulk updates).'), + ); + + $form['pathauto_separator'] = array( + '#type' => 'textfield', + '#title' => t('Separator'), + '#size' => 1, + '#maxlength' => 1, + '#default_value' => variable_get('pathauto_separator', '-'), + '#description' => t('Character used to separate words in titles. This will replace any spaces and punctuation characters. Using a space or + character can cause unexpected results.'), + ); + + $form['pathauto_case'] = array( + '#type' => 'radios', + '#title' => t('Character case'), + '#default_value' => variable_get('pathauto_case', PATHAUTO_CASE_LOWER), + '#options' => array( + PATHAUTO_CASE_LEAVE_ASIS => t('Leave case the same as source token values.'), + PATHAUTO_CASE_LOWER => t('Change to lower case'), + ), + ); + + $max_length = _pathauto_get_schema_alias_maxlength(); + + $form['pathauto_max_length'] = array( + '#type' => 'textfield', + '#title' => t('Maximum alias length'), + '#size' => 3, + '#maxlength' => 3, + '#default_value' => variable_get('pathauto_max_length', 100), + '#min_value' => 1, + '#max_value' => $max_length, + '#description' => t('Maximum length of aliases to generate. 100 is the recommended length. @max is the maximum possible length. See <a href="@pathauto-help">Pathauto help</a> for details.', array('@pathauto-help' => url('admin/help/pathauto'), '@max' => $max_length)), + '#element_validate' => array('_pathauto_validate_numeric_element'), + ); + $form['pathauto_max_component_length'] = array( + '#type' => 'textfield', + '#title' => t('Maximum component length'), + '#size' => 3, + '#maxlength' => 3, + '#default_value' => variable_get('pathauto_max_component_length', 100), + '#min_value' => 1, + '#max_value' => $max_length, + '#description' => t('Maximum text length of any component in the alias (e.g., [title]). 100 is the recommended length. @max is the maximum possible length. See <a href="@pathauto-help">Pathauto help</a> for details.', array('@pathauto-help' => url('admin/help/pathauto'), '@max' => $max_length)), + '#element_validate' => array('_pathauto_validate_numeric_element'), + ); + + $form['pathauto_update_action'] = array( + '#type' => 'radios', + '#title' => t('Update action'), + '#default_value' => variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE), + '#options' => array( + PATHAUTO_UPDATE_ACTION_NO_NEW => t('Do nothing. Leave the old alias intact.'), + PATHAUTO_UPDATE_ACTION_LEAVE => t('Create a new alias. Leave the existing alias functioning.'), + PATHAUTO_UPDATE_ACTION_DELETE => t('Create a new alias. Delete the old alias.'), + PATHAUTO_UPDATE_ACTION_REDIRECT => t('Create a new alias. Redirect from old alias.'), + ), + '#description' => t('What should Pathauto do when updating an existing content item which already has an alias?'), + ); + if (!module_exists('path_redirect')) { + // Remove the redirection option if Path redirect is not enabled. + unset($form['pathauto_update_action']['#options'][PATHAUTO_UPDATE_ACTION_REDIRECT]); + if (variable_get('pathauto_update_action', NULL) == PATHAUTO_UPDATE_ACTION_REDIRECT) { + // Fix the current update action variable as well. + variable_set('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE); + } + } + + $form['pathauto_transliterate'] = array( + '#type' => 'checkbox', + '#title' => t('Transliterate prior to creating alias'), + '#default_value' => variable_get('pathauto_transliterate', FALSE), + '#description' => t('When a pattern includes certain characters (such as those with accents) should Pathauto attempt to transliterate them into the ASCII-96 alphabet? Transliteration is handled by the Transliteration module.'), + ); + if (!module_exists('transliteration')) { + // Remove the redirection option if Transliteration is not enabled. + $form['pathauto_transliterate']['#access'] = FALSE; + variable_set('pathauto_transliterate', FALSE); + } + + $form['pathauto_reduce_ascii'] = array( + '#type' => 'checkbox', + '#title' => t('Reduce strings to letters and numbers'), + '#default_value' => variable_get('pathauto_reduce_ascii', FALSE), + '#description' => t('Filters the new alias to only letters and numbers found in the ASCII-96 set.'), + ); + + $form['pathauto_ignore_words'] = array( + '#type' => 'textarea', + '#title' => t('Strings to Remove'), + '#default_value' => variable_get('pathauto_ignore_words', PATHAUTO_IGNORE_WORDS), + '#description' => t('Words to strip out of the URL alias, separated by commas. Do not use this to remove punctuation.'), + '#wysiwyg' => FALSE, + ); + + $form['punctuation'] = array( + '#type' => 'fieldset', + '#title' => t('Punctuation'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $punctuation = pathauto_punctuation_chars(); + foreach ($punctuation as $name => $details) { + $details['default'] = PATHAUTO_PUNCTUATION_REMOVE; + if ($details['value'] == variable_get('pathauto_separator', '-')) { + $details['default'] = PATHAUTO_PUNCTUATION_REPLACE; + } + $form['punctuation']['pathauto_punctuation_' . $name] = array( + '#type' => 'select', + '#title' => $details['name'], + '#default_value' => variable_get('pathauto_punctuation_' . $name, $details['default']), + '#options' => array( + PATHAUTO_PUNCTUATION_REMOVE => t('Remove'), + PATHAUTO_PUNCTUATION_REPLACE => t('Replace by separator'), + PATHAUTO_PUNCTUATION_DO_NOTHING => t('No action (do not replace)'), + ), + ); + } + + return system_settings_form($form); +} + +/** + * Validate a form element that should have an numeric value. + */ +function _pathauto_validate_numeric_element($element, &$form_state) { + $value = $element['#value']; + + if (!is_numeric($value)) { + form_error($element, t('The field %name is not a valid number.', array('%name' => $element['#title']))); + } + elseif (isset($element['#max_value']) && $value > $element['#max_value']) { + form_error($element, t('The field %name cannot be greater than @max.', array('%name' => $element['#title'], '@max' => $element['#max_value']))); + } + elseif (isset($element['#min_value']) && $value < $element['#min_value']) { + form_error($element, t('The field %name cannot be less than @min.', array('%name' => $element['#title'], '@min' => $element['#min_value']))); + } +} + +/** + * Validate pathauto_settings_form form submissions. + */ +function pathauto_settings_form_validate($form, &$form_state) { + module_load_include('inc', 'pathauto'); + + // Perform a basic check for HTML characters in the strings to remove field. + if (strip_tags($form_state['values']['pathauto_ignore_words']) != $form_state['values']['pathauto_ignore_words']) { + form_set_error('pathauto_ignore_words', t('The <em>Strings to remove</em> field must not contain HTML. Make sure to disable any WYSIWYG editors for this field.')); + } + + // Validate that the separator is not set to be removed per http://drupal.org/node/184119 + // This isn't really all that bad so warn, but still allow them to save the value. + $separator = $form_state['values']['pathauto_separator']; + $punctuation = pathauto_punctuation_chars(); + foreach ($punctuation as $name => $details) { + if ($details['value'] == $separator) { + $action = $form_state['values']['pathauto_punctuation_' . $name]; + if ($action == PATHAUTO_PUNCTUATION_REMOVE) { + drupal_set_message(t('You have configured the @name to be the separator and to be removed when encountered in strings. This can cause problems with your patterns and especially with the term:path token. You should probably set the action for @name to be "replace by separator".', array('@name' => $details['name'])), 'error'); + } + } + } +} + +/** + * Form contructor for path alias bulk update form. + * + * @see pathauto_bulk_update_form_submit() + * @ingroup forms + */ +function pathauto_bulk_update_form() { + $form['#update_callbacks'] = array(); + + $form['update'] = array( + '#type' => 'checkboxes', + '#title' => t('Select the types of un-aliased paths for which to generate URL aliases'), + '#options' => array(), + '#default_value' => array(), + ); + + $pathauto_settings = module_invoke_all('pathauto', 'settings'); + foreach ($pathauto_settings as $settings) { + if (!empty($settings->batch_update_callback)) { + $form['#update_callbacks'][$settings->batch_update_callback] = $settings; + $form['update']['#options'][$settings->batch_update_callback] = $settings->groupheader; + } + } + + $form['actions']['#type'] = 'actions'; + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Update'), + ); + + return $form; +} + +/** + * Form submit handler for path alias bulk update form. + * + * @see pathauto_batch_update_form() + * @see pathauto_bulk_update_batch_finished() + */ +function pathauto_bulk_update_form_submit($form, &$form_state) { + $batch = array( + 'title' => t('Bulk updating URL aliases'), + 'operations' => array( + array('pathauto_bulk_update_batch_start', array()), + ), + 'finished' => 'pathauto_bulk_update_batch_finished', + 'file' => drupal_get_path('module', 'pathauto') . '/pathauto.admin.inc', + ); + + foreach ($form_state['values']['update'] as $callback) { + if (!empty($callback)) { + $settings = $form['#update_callbacks'][$callback]; + if (!empty($settings->batch_file)) { + $batch['operations'][] = array('pathauto_bulk_update_batch_process', array($callback, $settings)); + } + else { + $batch['operations'][] = array($callback, array()); + } + } + } + + batch_set($batch); +} + +/** + * Batch callback; count the current number of URL aliases for comparison later. + */ +function pathauto_bulk_update_batch_start(&$context) { + $context['results']['count_before'] = db_select('url_alias')->countQuery()->execute()->fetchField(); +} + +/** + * Common batch processing callback for all operations. + * + * Required to load our include the proper batch file. + */ +function pathauto_bulk_update_batch_process($callback, $settings, &$context) { + if (!empty($settings->batch_file)) { + require_once DRUPAL_ROOT . '/' . $settings->batch_file; + } + return $callback($context); +} + +/** + * Batch finished callback. + */ +function pathauto_bulk_update_batch_finished($success, $results, $operations) { + if ($success) { + // Count the current number of URL aliases after the batch is completed + // and compare to the count before the batch started. + $results['count_after'] = db_select('url_alias')->countQuery()->execute()->fetchField(); + $results['count_changed'] = max($results['count_after'] - $results['count_before'], 0); + if ($results['count_changed']) { + drupal_set_message(format_plural($results['count_changed'], 'Generated 1 URL alias.', 'Generated @count URL aliases.')); + } + else { + drupal_set_message('No new URL aliases to generate.'); + } + } + else { + $error_operation = reset($operations); + drupal_set_message(t('An error occurred while processing @operation with arguments : @args', array('@operation' => $error_operation[0], '@args' => print_r($error_operation[0], TRUE)))); + } +} + +/** + * Menu callback; select certain alias types to delete. + */ +function pathauto_admin_delete() { + /* TODO: + 1) all - DONE + 2) all node aliases - DONE + 4) all user aliases - DONE + 5) all taxonomy aliases - DONE + 6) by node type + 7) by taxonomy vocabulary + 8) no longer existing aliases (see http://drupal.org/node/128366 ) + 9) where src like 'pattern' - DON'T DO + 10) where dst like 'pattern' - DON'T DO + */ + + $form['delete'] = array( + '#type' => 'fieldset', + '#title' => t('Choose aliases to delete'), + '#collapsible' => FALSE, + '#collapsed' => FALSE, + ); + + // First we do the "all" case + $total_count = db_query('SELECT count(1) FROM {url_alias}')->fetchField(); + $form['delete']['all_aliases'] = array( + '#type' => 'checkbox', + '#title' => t('All aliases'), + '#default_value' => FALSE, + '#description' => t('Delete all aliases. Number of aliases which will be deleted: %count.', array('%count' => $total_count)), + ); + + // Next, iterate over an array of objects/alias types which can be deleted and provide checkboxes + $objects = module_invoke_all('path_alias_types'); + foreach ($objects as $internal_name => $label) { + $count = db_query("SELECT count(1) FROM {url_alias} WHERE source LIKE :src", array(':src' => "$internal_name%"))->fetchField(); + $form['delete'][$internal_name] = array( + '#type' => 'checkbox', + '#title' => $label, // This label is sent through t() in the hard coded function where it is defined + '#default_value' => FALSE, + '#description' => t('Delete aliases for all @label. Number of aliases which will be deleted: %count.', array('@label' => $label, '%count' => $count)), + ); + } + + // Warn them and give a button that shows we mean business + $form['warning'] = array('#value' => '<p>' . t('<strong>Note:</strong> there is no confirmation. Be sure of your action before clicking the "Delete aliases now!" button.<br />You may want to make a backup of the database and/or the url_alias table prior to using this feature.') . '</p>'); + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete aliases now!'), + ); + + return $form; +} + +/** + * Process pathauto_admin_delete form submissions. + */ +function pathauto_admin_delete_submit($form, &$form_state) { + foreach ($form_state['values'] as $key => $value) { + if ($value) { + if ($key === 'all_aliases') { + db_delete('url_alias') + ->execute(); + drupal_set_message(t('All of your path aliases have been deleted.')); + } + $objects = module_invoke_all('path_alias_types'); + if (array_key_exists($key, $objects)) { + db_delete('url_alias') + ->condition('source', db_like($key) . '%', 'LIKE') + ->execute(); + drupal_set_message(t('All of your %type path aliases have been deleted.', array('%type' => $objects[$key]))); + } + } + } + $form_state['redirect'] = 'admin/config/search/path/delete_bulk'; +} diff --git a/sites/all/modules/pathauto/pathauto.api.php b/sites/all/modules/pathauto/pathauto.api.php new file mode 100644 index 0000000000000000000000000000000000000000..1b09ae99f208f3a8b6935d3b17396d5b1261456a --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.api.php @@ -0,0 +1,19 @@ +<?php +// $Id: pathauto.api.php,v 1.2 2010/08/08 23:46:36 davereid Exp $ + +/** + * @file + * Documentation for pathauto API. + * + * @see hook_token_info + * @see hook_tokens + */ + +function hook_path_alias_types() { +} + +function hook_pathauto($op) { +} + +function hook_pathauto_alias_alter(&$alias, array $context) { +} diff --git a/sites/all/modules/pathauto/pathauto.inc b/sites/all/modules/pathauto/pathauto.inc new file mode 100644 index 0000000000000000000000000000000000000000..9faf3ece18811fed404bdfeac69406cdccb264ad --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.inc @@ -0,0 +1,626 @@ +<?php +// $Id: pathauto.inc,v 1.95 2011/01/11 19:55:39 davereid Exp $ + +/** + * @file + * Miscellaneous functions for Pathauto. + * + * This also contains some constants giving human readable names to some numeric + * settings; they're included here as they're only rarely used outside this file + * anyway. Use module_load_include('inc', 'pathauto') if the constants need to + * be available. + * + * @ingroup pathauto + */ + +/** + * Case should be left as is in the generated path. + */ +define('PATHAUTO_CASE_LEAVE_ASIS', 0); + +/** + * Case should be lowercased in the generated path. + */ +define('PATHAUTO_CASE_LOWER', 1); + +/** + * "Do nothing. Leave the old alias intact." + */ +define('PATHAUTO_UPDATE_ACTION_NO_NEW', 0); + +/** + * "Create a new alias. Leave the existing alias functioning." + */ +define('PATHAUTO_UPDATE_ACTION_LEAVE', 1); + +/** + * "Create a new alias. Delete the old alias." + */ +define('PATHAUTO_UPDATE_ACTION_DELETE', 2); + +/** + * "Create a new alias. Redirect from old alias." + * + * This is only available when the Path Redirect module is. + */ +define('PATHAUTO_UPDATE_ACTION_REDIRECT', 3); + +/** + * Remove the punctuation from the alias. + */ +define('PATHAUTO_PUNCTUATION_REMOVE', 0); + +/** + * Replace the punctuation with the separator in the alias. + */ +define('PATHAUTO_PUNCTUATION_REPLACE', 1); + +/** + * Leave the punctuation as it is in the alias. + */ +define('PATHAUTO_PUNCTUATION_DO_NOTHING', 2); + +/** + * Check to see if there is already an alias pointing to a different item. + * + * @param $alias + * A string alias. + * @param $source + * A string that is the internal path. + * @param $language + * A string indicating the path's language. + * @return + * TRUE if an alias exists, FALSE if not. + */ +function _pathauto_alias_exists($alias, $source, $language = LANGUAGE_NONE) { + $pid = db_query_range("SELECT pid FROM {url_alias} WHERE source <> :source AND alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", 0, 1, array( + ':source' => $source, + ':alias' => $alias, + ':language' => $language, + ':language_none' => LANGUAGE_NONE, + ))->fetchField(); + + if (module_exists('path_redirect') && function_exists('path_redirect_delete_multiple')) { + // Delete from path_redirect the exact same alias to the same node. + path_redirect_delete_multiple(NULL, array('source' => $alias, 'redirect' => $source)); + + // If there still is this alias used in path_redirect, then create a different alias. + $redirects = path_redirect_load_multiple(NULL, array('source' => $alias)); + } + + if ($pid || !empty($redirects)) { + return TRUE; + } + else { + return FALSE; + } +} + +/** + * Fetches an existing URL alias given a path and optional language. + * + * @param $source + * An internal Drupal path. + * @param $language + * An optional language code to look up the path in. + * @return + * FALSE if no alias was found or an associative array containing the + * following keys: + * - pid: Unique path alias identifier. + * - alias: The URL alias. + */ +function _pathauto_existing_alias_data($source, $language = LANGUAGE_NONE) { + return db_query_range("SELECT pid, alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", 0, 1, array(':source' => $source, ':language' => $language, ':language_none' => LANGUAGE_NONE))->fetchAssoc(); +} + +/** + * Clean up a string segment to be used in an URL alias. + * + * Performs the following possible alterations: + * - Process the string through the transliteration module. + * - Replace or remove punctuation with the separator character. + * - Remove back-slashes. + * - Replace non-ascii and non-numeric characters with the separator. + * - Remove common words. + * - Replace whitespace with the separator character. + * - Trim duplicate, leading, and trailing separators. + * - Convert to lower-case. + * - Shorten to a desired length and logical position based on word boundaries. + * + * This function should *not* be called on URL alias or path strings because it + * is assumed that they are already clean. + * + * @param $string + * A string to clean. + * @return + * The cleaned string. + */ +function pathauto_cleanstring($string) { + $output = $string; + + // Optionally transliterate (by running through the Transliteration module) + if (variable_get('pathauto_transliterate', FALSE)) { + if (module_exists('transliteration')) { + $output = transliteration_get($output); + } + else { + drupal_set_message(t('Pathauto could not transliterate the path, as the Transliteration module has been disabled.'), 'error'); + } + } + + // Replace or drop punctuation based on user settings + $separator = variable_get('pathauto_separator', '-'); + $punctuation = pathauto_punctuation_chars(); + foreach ($punctuation as $name => $details) { + $action = variable_get('pathauto_punctuation_' . $name, PATHAUTO_PUNCTUATION_REMOVE); + if ($action != PATHAUTO_PUNCTUATION_DO_NOTHING) { + // Slightly tricky inline if which either replaces with the separator or nothing + $output = str_replace($details['value'], ($action ? $separator : ''), $output); + } + } + + // Reduce strings to letters and numbers + if (variable_get('pathauto_reduce_ascii', FALSE)) { + $pattern = '/[^a-zA-Z0-9\/]+/'; + $output = preg_replace($pattern, $separator, $output); + } + + // Calculate and statically cache the ignored words regex expression. + $ignore_words_regex = &drupal_static('pathauto_cleanstring:ignore_words_regex'); + if (!isset($ignore_words_regex)) { + $ignore_words = variable_get('pathauto_ignore_words', PATHAUTO_IGNORE_WORDS); + $ignore_words_regex = preg_replace(array('/^[,\s]+|[,\s]+$/', '/[,\s]+/'), array('', '\b|\b'), $ignore_words); + if ($ignore_words_regex) { + $ignore_words_regex = '\b' . $ignore_words_regex . '\b'; + } + } + + // Get rid of words that are on the ignore list + if ($ignore_words_regex) { + if (function_exists('mb_eregi_replace')) { + $words_removed = mb_eregi_replace($ignore_words_regex, '', $output); + } + else { + $words_removed = preg_replace("/$ignore_words_regex/i", '', $output); + } + if (drupal_strlen(trim($words_removed)) > 0) { + $output = $words_removed; + } + } + + // Always replace whitespace with the separator. + $output = preg_replace('/\s+/', $separator, $output); + + // Trim duplicates and remove trailing and leading separators. + $output = _pathauto_clean_separators($output); + + // Optionally convert to lower case. + if (variable_get('pathauto_case', PATHAUTO_CASE_LOWER)) { + $output = drupal_strtolower($output); + } + + // Shorten to a logical place based on word boundaries. + $maxlength = min(variable_get('pathauto_max_component_length', 100), _pathauto_get_schema_alias_maxlength()); + $output = truncate_utf8($output, $maxlength, TRUE); + + return $output; +} + +/** + * Trims duplicate, leading, and trailing separators from a string. + * + * @param $string + * The string to clean path separators from. + * @param $separator + * The path separator to use when cleaning. + * @return + * The cleaned version of the string. + * + * @see pathauto_cleanstring() + * @see pathauto_clean_alias() + */ +function _pathauto_clean_separators($string, $separator = NULL) { + $output = $string; + + if (!isset($separator)) { + $separator = variable_get('pathauto_separator', '-'); + } + + // Clean duplicate or trailing separators. + if (strlen($separator)) { + // Escape the separator. + $seppattern = preg_quote($separator, '/'); + + // Trim any leading or trailing separators. + $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output); + + // Replace trailing separators around slashes. + if ($separator !== '/') { + $output = preg_replace("/$seppattern+\/|\/$seppattern+/", "/", $output); + } + + // Replace multiple separators with a single one. + $output = preg_replace("/$seppattern+/", $separator, $output); + } + + return $output; +} + +/** + * Clean up an URL alias. + * + * Performs the following alterations: + * - Trim duplicate, leading, and trailing back-slashes. + * - Trim duplicate, leading, and trailing separators. + * - Shorten to a desired length and logical position based on word boundaries. + * + * @param $alias + * A string with the URL alias to clean up. + * @return + * The cleaned URL alias. + */ +function pathauto_clean_alias($alias) { + $output = $alias; + + // Trim duplicate, leading, and trailing back-slashes. + $output = _pathauto_clean_separators($output, '/'); + + // Trim duplicate, leading, and trailing separators. + $output = _pathauto_clean_separators($output); + + // Shorten to a logical place based on word boundaries. + $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength()); + $output = truncate_utf8($output, $maxlength, TRUE); + + return $output; +} + +/** + * Apply patterns to create an alias. + * + * @param $module + * The name of your module (e.g., 'node'). + * @param $op + * Operation being performed on the content being aliased + * ('insert', 'update', 'return', or 'bulkupdate'). + * @param $source + * An internal Drupal path to be aliased. + * @param $data + * An array of keyed objects to pass to token_replace(). For simple + * replacement scenarios 'node', 'user', and others are common keys, with an + * accompanying node or user object being the value. Some token types, like + * 'site', do not require any explicit information from $data and can be + * replaced even if it is empty. + * @param $type + * For modules which provided pattern items in hook_pathauto(), + * the relevant identifier for the specific item to be aliased + * (e.g., $node->type). + * @param $language + * A string specify the path's language. + * @return + * The alias that was created. + * + * @see _pathauto_set_alias() + * @see token_replace() + */ +function pathauto_create_alias($module, $op, $source, $data, $type = NULL, $language = LANGUAGE_NONE) { + // Retrieve and apply the pattern for this content type. + $pattern = pathauto_pattern_load_by_entity($module, $type, $language); + if (empty($pattern)) { + // No pattern? Do nothing (otherwise we may blow away existing aliases...) + return ''; + } + + // Special handling when updating an item which is already aliased. + $existing_alias = NULL; + if ($op == 'update' || $op == 'bulkupdate') { + if ($existing_alias = _pathauto_existing_alias_data($source, $language)) { + switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) { + case PATHAUTO_UPDATE_ACTION_NO_NEW: + // If an alias already exists, and the update action is set to do nothing, + // then gosh-darn it, do nothing. + return ''; + } + } + } + + // Replace any tokens in the pattern. Uses callback option to clean replacements. No sanitization. + $alias = token_replace($pattern, $data, array( + 'sanitize' => FALSE, + 'clear' => TRUE, + 'callback' => 'pathauto_clean_token_values', + 'language' => (object) array('language' => $language), + 'pathauto' => TRUE, + )); + + // Check if the token replacement has not actually replaced any values. If + // that is the case, then stop because we should not generate an alias. + // @see token_scan() + $pattern_tokens_removed = preg_replace('/\[[^\s\]:]*:[^\s\]]*\]/', '', $pattern); + if ($alias === $pattern_tokens_removed) { + return ''; + } + + $alias = pathauto_clean_alias($alias); + + // Allow other modules to alter the alias. + $context = array( + 'module' => $module, + 'op' => $op, + 'source' => $source, + 'data' => $data, + 'type' => $type, + 'language' => $language, + 'pattern' => $pattern, + ); + drupal_alter('pathauto_alias', $alias, $context); + + // If we have arrived at an empty string, discontinue. + if (!drupal_strlen($alias)) { + return ''; + } + + // If the alias already exists, generate a new, hopefully unique, variant + if (_pathauto_alias_exists($alias, $source, $language)) { + $maxlength = min(variable_get('pathauto_max_length', 100), _pathauto_get_schema_alias_maxlength()); + $separator = variable_get('pathauto_separator', '-'); + $original_alias = $alias; + + $i = 0; + do { + // Append an incrementing numeric suffix until we find a unique alias. + $unique_suffix = $separator . $i; + $alias = truncate_utf8($original_alias, $maxlength - drupal_strlen($unique_suffix, TRUE)) . $unique_suffix; + $i++; + } while (_pathauto_alias_exists($alias, $source, $language)); + + // Alert the user why this happened. + _pathauto_verbose(t('The automatically generated alias %original_alias conflicted with an existing alias. Alias changed to %alias.', array( + '%original_alias' => $original_alias, + '%alias' => $alias, + )), $op); + } + + // Return the generated alias if requested. + if ($op == 'return') { + return $alias; + } + + // Build the new path alias array and send it off to be created. + $path = array( + 'source' => $source, + 'alias' => $alias, + 'language' => $language, + ); + $path = _pathauto_set_alias($path, $existing_alias, $op); + return $path; +} + +/** + * Verify if the given path is a valid menu callback. + * + * Taken from menu_execute_active_handler(). + * + * @param $path + * A string containing a relative path. + * @return + * TRUE if the path already exists. + */ +function _pathauto_path_is_callback($path) { + $menu = menu_get_item($path); + if (isset($menu['path']) && $menu['path'] == $path) { + return TRUE; + } + return FALSE; +} + +/** + * Private function for Pathauto to create an alias. + * + * @param $path + * An associative array containing the following keys: + * - source: The internal system path. + * - alias: The URL alias. + * - pid: (optional) Unique path alias identifier. + * - language: (optional) The language of the alias. + * @param $existing_alias + * (optional) An associative array of the existing path alias. + * @param $op + * An optional string with the operation being performed. + * + * @return + * The saved path from path_save() or NULL if the path was not saved. + * + * @see path_save() + */ +function _pathauto_set_alias(array $path, $existing_alias = NULL, $op = NULL) { + $verbose = _pathauto_verbose(NULL, $op); + + // Alert users that an existing callback cannot be overridden automatically + if (_pathauto_path_is_callback($path['alias'])) { + if ($verbose) { + _pathauto_verbose(t('Ignoring alias %alias due to existing path conflict.', array('%alias' => $path['alias']))); + } + return; + } + // Alert users if they are trying to create an alias that is the same as the internal path + if ($path['source'] == $path['alias']) { + if ($verbose) { + _pathauto_verbose(t('Ignoring alias %alias because it is the same as the internal path.', array('%alias' => $path['alias']))); + } + return; + } + + // Skip replacing the current alias with an identical alias + if (empty($existing_alias) || $existing_alias['alias'] != $path['alias']) { + $path += array('pathauto' => TRUE); + + // If there is already an alias, respect some update actions. + if (!empty($existing_alias)) { + switch (variable_get('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE)) { + case PATHAUTO_UPDATE_ACTION_NO_NEW: + // Do not create the alias. + return; + case PATHAUTO_UPDATE_ACTION_LEAVE: + // Create a new alias instead of overwriting the existing by leaving + // $path['pid'] empty. + break; + case PATHAUTO_UPDATE_ACTION_REDIRECT: + // Create a redirect + if (module_exists('path_redirect') && function_exists('path_redirect_save')) { + $redirect = array( + 'source' => $existing_alias['alias'], + 'redirect' => $path['source'], + ); + path_redirect_save($redirect); + } + // Intentionally fall through to the next condition since we still + // want to replace the existing alias. + case PATHAUTO_UPDATE_ACTION_DELETE: + // Both the redirect and delete actions should overwrite the existing + // alias. + $path['pid'] = $existing_alias['pid']; + break; + } + } + + // Save the path array. + path_save($path); + + if ($verbose) { + if (!empty($redirect)) { + _pathauto_verbose(t('Created new alias %alias for %source, replacing %old_alias. %old_alias now redirects to %alias.', array('%alias' => $path['alias'], '%source' => $path['source'], '%old_alias' => $existing_alias['alias']))); + } + elseif (!empty($existing_alias['pid'])) { + _pathauto_verbose(t('Created new alias %alias for %source, replacing %old_alias.', array('%alias' => $path['alias'], '%source' => $path['source'], '%old_alias' => $existing_alias['alias']))); + } + else { + _pathauto_verbose(t('Created new alias %alias for %source.', array('%alias' => $path['alias'], '%source' => $path['source']))); + } + } + + return $path; + } +} + +/** + * Output a helpful message if verbose output is enabled. + * + * Verbose output is only enabled when: + * - The 'pathauto_verbose' setting is enabled. + * - The current user has the 'notify of path changes' permission. + * - The $op parameter is anything but 'bulkupdate' or 'return'. + * + * @param $message + * An optional string of the verbose message to display. This string should + * already be run through t(). + * @param $op + * An optional string with the operation being performed. + * @return + * TRUE if verbose output is enabled, or FALSE otherwise. + */ +function _pathauto_verbose($message = NULL, $op = NULL) { + static $verbose; + + if (!isset($verbose)) { + $verbose = variable_get('pathauto_verbose', FALSE) && user_access('notify of path changes'); + } + + if (!$verbose || (isset($op) && in_array($op, array('bulkupdate', 'return')))) { + return FALSE; + } + + if ($message) { + drupal_set_message($message); + } + + return $verbose; +} + +/** + * Clean tokens so they are URL friendly. + * + * @param $replacements + * An array of token replacements that need to be "cleaned" for use in the URL. + * @param $data + * An array of objects used to generate the replacements. + * @param $options + * An array of options used to generate the replacements. + */ +function pathauto_clean_token_values(&$replacements, $data = array(), $options = array()) { + foreach ($replacements as $token => $value) { + // Only clean non-path tokens. + if (!preg_match('/(path|alias|url|url-brief)\]$/', $token)) { + $replacements[$token] = pathauto_cleanstring($value); + } + // @todo Clean up 'tree-style' tokens that are not actually URLs. + } +} + +/** + * Return an array of arrays for punctuation values. + * + * Returns an array of arrays for punctuation values keyed by a name, including + * the value and a textual description. + * Can and should be expanded to include "all" non text punctuation values. + * + * @return + * An array of arrays for punctuation values keyed by a name, including the + * value and a textual description. + */ +function pathauto_punctuation_chars() { + static $punctuation; + + if (!isset($punctuation)) { + $punctuation = array(); + $punctuation['double_quotes'] = array('value' => '"', 'name' => t('Double quotes "')); + $punctuation['quotes'] = array('value' => "'", 'name' => t("Single quotes (apostrophe) '")); + $punctuation['backtick'] = array('value' => '`', 'name' => t('Back tick `')); + $punctuation['comma'] = array('value' => ',', 'name' => t('Comma ,')); + $punctuation['period'] = array('value' => '.', 'name' => t('Period .')); + $punctuation['hyphen'] = array('value' => '-', 'name' => t('Hyphen -')); + $punctuation['underscore'] = array('value' => '_', 'name' => t('Underscore _')); + $punctuation['colon'] = array('value' => ':', 'name' => t('Colon :')); + $punctuation['semicolon'] = array('value' => ';', 'name' => t('Semicolon ;')); + $punctuation['pipe'] = array('value' => '|', 'name' => t('Pipe |')); + $punctuation['left_curly'] = array('value' => '{', 'name' => t('Left curly bracket {')); + $punctuation['left_square'] = array('value' => '[', 'name' => t('Left square bracket [')); + $punctuation['right_curly'] = array('value' => '}', 'name' => t('Right curly bracket }')); + $punctuation['right_square'] = array('value' => ']', 'name' => t('Right square bracket ]')); + $punctuation['plus'] = array('value' => '+', 'name' => t('Plus +')); + $punctuation['equal'] = array('value' => '=', 'name' => t('Equal =')); + $punctuation['asterisk'] = array('value' => '*', 'name' => t('Asterisk *')); + $punctuation['ampersand'] = array('value' => '&', 'name' => t('Ampersand &')); + $punctuation['percent'] = array('value' => '%', 'name' => t('Percent %')); + $punctuation['caret'] = array('value' => '^', 'name' => t('Caret ^')); + $punctuation['dollar'] = array('value' => '$', 'name' => t('Dollar $')); + $punctuation['hash'] = array('value' => '#', 'name' => t('Hash #')); + $punctuation['at'] = array('value' => '@', 'name' => t('At @')); + $punctuation['exclamation'] = array('value' => '!', 'name' => t('Exclamation !')); + $punctuation['tilde'] = array('value' => '~', 'name' => t('Tilde ~')); + $punctuation['left_parenthesis'] = array('value' => '(', 'name' => t('Left parenthesis (')); + $punctuation['right_parenthesis'] = array('value' => ')', 'name' => t('Right parenthesis )')); + $punctuation['question_mark'] = array('value' => '?', 'name' => t('Question mark ?')); + $punctuation['less_than'] = array('value' => '<', 'name' => t('Less than <')); + $punctuation['greater_than'] = array('value' => '>', 'name' => t('Greater than >')); + $punctuation['slash'] = array('value' => '/', 'name' => t('Slash /')); + $punctuation['back_slash'] = array('value' => '\\', 'name' => t('Backslash \\')); + } + + return $punctuation; +} + +/** + * Fetch the maximum length of the {url_alias}.alias field from the schema. + * + * @return + * An integer of the maximum URL alias length allowed by the database. + */ +function _pathauto_get_schema_alias_maxlength() { + $maxlength = &drupal_static(__FUNCTION__); + if (!isset($maxlength)) { + $schema = drupal_get_schema('url_alias'); + $maxlength = $schema['fields']['alias']['length']; + } + return $maxlength; +} diff --git a/sites/all/modules/pathauto/pathauto.info b/sites/all/modules/pathauto/pathauto.info new file mode 100644 index 0000000000000000000000000000000000000000..cca81c4923cc208834d4a6cf247cef4dc6da6337 --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.info @@ -0,0 +1,21 @@ +; $Id: pathauto.info,v 1.13 2010/08/11 17:30:01 davereid Exp $ +name = Pathauto +description = Provides a mechanism for modules to automatically generate aliases for the content they manage. +dependencies[] = path +dependencies[] = token +core = 7.x +files[] = pathauto.admin.inc +files[] = pathauto.inc +files[] = pathauto.module +files[] = pathauto.install +files[] = pathauto.test +files[] = pathauto.pathauto.inc +configure = admin/config/search/path/patterns +recommends[] = path_redirect + +; Information added by drupal.org packaging script on 2011-01-13 +version = "7.x-1.0-beta1" +core = "7.x" +project = "pathauto" +datestamp = "1294891601" + diff --git a/sites/all/modules/pathauto/pathauto.install b/sites/all/modules/pathauto/pathauto.install new file mode 100644 index 0000000000000000000000000000000000000000..58ad22ac3423188028260f7d83aeef8eee37c3a0 --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.install @@ -0,0 +1,148 @@ +<?php +// $Id: pathauto.install,v 1.30 2011/01/11 19:55:39 davereid Exp $ + +/** + * @file + * Install, update, and uninstall functions for Pathauto. + * + * @ingroup pathauto + */ + +/** + * Implements hook_install(). + */ +function pathauto_install() { + // Set some default variables necessary for the module to perform. + variable_set('pathauto_node_pattern', 'content/[node:title]'); + variable_set('pathauto_taxonomy_term_pattern', '[term:vocabulary]/[term:name]'); + variable_set('pathauto_forum_pattern', '[term:vocabulary]/[term:name]'); + variable_set('pathauto_user_pattern', 'users/[user:name]'); + variable_set('pathauto_blog_pattern', 'blogs/[user:name]'); + + // Set the default separator character to replace instead of remove (default). + variable_set('pathauto_punctuation_hyphen', 1); + + // Set the weight to 1 + db_update('system') + ->fields(array('weight' => 1)) + ->condition('type', 'module') + ->condition('name', 'pathauto') + ->execute(); +} + +/** + * Implements hook_uninstall(). + */ +function pathauto_uninstall() { + // Delete all the pathauto variables and then clear the variable cache + db_query("DELETE FROM {variable} WHERE name LIKE 'pathauto_%'"); + cache_clear_all('variables', 'cache'); +} + +/** + * Remove the unsupported user/%/contact and user/%/tracker pattern variables. + */ +function pathauto_update_6200() { + variable_del('pathauto_contact_bulkupdate'); + variable_del('pathauto_contact_pattern'); + variable_del('pathauto_contact_supportsfeeds'); + variable_del('pathauto_contact_applytofeeds'); + variable_del('pathauto_tracker_bulkupdate'); + variable_del('pathauto_tracker_pattern'); + variable_del('pathauto_tracker_supportsfeeds'); + variable_del('pathauto_tracker_applytofeeds'); +} + +/** + * Empty update since it is handled by pathauto_update_7000(). + */ +function pathauto_update_6201() { +} + +/** + * Remove obsolete variables since batch API is now used. + */ +function pathauto_update_7000() { + variable_del('pathauto_max_bulk_update'); + variable_del('pathauto_node_bulkupdate'); + variable_del('pathauto_taxonomy_bulkupdate'); + variable_del('pathauto_forum_bulkupdate'); + variable_del('pathauto_user_bulkupdate'); + variable_del('pathauto_blog_bulkupdate'); + variable_del('pathauto_modulelist'); + variable_del('pathauto_indexaliases'); + variable_del('pathauto_indexaliases_bulkupdate'); +} + +/** + * Taxonomy and forum feed suffixes changed from '0\/feed' to 'feed'. + */ +function pathauto_update_7001() { + if (variable_get('pathauto_taxonomy_applytofeeds', '') == '0/feed') { + variable_set('pathauto_taxonomy_applytofeeds', 'feed'); + } + if (variable_get('pathauto_forum_applytofeeds', '') == '0/feed') { + variable_set('pathauto_forum_applytofeeds', 'feed'); + } +} + +/** + * Update pathauto_taxonomy_[vid]_pattern variables to pathauto_taxonomy_[machinename]_pattern. + */ +function pathauto_update_7002() { + if (module_exists('taxonomy')) { + $vocabularies = taxonomy_get_vocabularies(); + foreach ($vocabularies as $vid => $vocabulary) { + if ($vid == variable_get('forum_nav_vocabulary', '')) { + // Skip the forum vocabulary. + continue; + } + if ($pattern = variable_get('pathauto_taxonomy_' . $vid . '_pattern', '')) { + variable_set('pathauto_taxonomy_' . $vocabulary->machine_name . '_pattern', $pattern); + } + variable_del('pathauto_taxonomy_' . $vid . '_pattern'); + } + } +} + +/** + * Rename 'taxonomy' variables to use the entity type 'taxonomy_term'. + */ +function pathauto_update_7003() { + $variables = db_query("SELECT name FROM {variable} WHERE name LIKE :pattern AND name NOT LIKE :pattern2", array(':pattern' => db_like("pathauto_taxonomy_") . '%', ':pattern2' => db_like("pathauto_taxonomy_term_") . '%'))->fetchCol(); + foreach ($variables as $variable) { + $value = variable_get($variable); + variable_del($variable); + $variable = strtr($variable, array('pathauto_taxonomy_' => 'pathauto_taxonomy_term_')); + variable_set($variable, $value); + } +} + +/** + * Remove obsolete variables for removed feed handling. + */ +function pathauto_update_7004() { + variable_del('pathauto_node_supportsfeeds'); + variable_del('pathauto_node_applytofeeds'); + variable_del('pathauto_taxonomy_supportsfeeds'); + variable_del('pathauto_taxonomy_applytofeeds'); + variable_del('pathauto_forum_supportsfeeds'); + variable_del('pathauto_forum_applytofeeds'); + variable_del('pathauto_user_supportsfeeds'); + variable_del('pathauto_user_applytofeeds'); + variable_del('pathauto_blog_supportsfeeds'); + variable_del('pathauto_blog_applytofeeds'); +} + +/** + * Build a list of Drupal 6 tokens and their Drupal 7 token names. + */ +function _pathauto_upgrade_token_list() { + $tokens = array( + //'catpath' => 'node:term-lowest:parent:path][node:term-lowest', + //'catalias' => 'node:term-lowest:path', + 'termpath' => 'term:parent:path][term:name', + 'termalias' => 'term:path', + 'bookpathalias' => 'node:book:parent:path', + ); +} diff --git a/sites/all/modules/pathauto/pathauto.js b/sites/all/modules/pathauto/pathauto.js new file mode 100644 index 0000000000000000000000000000000000000000..0e09db31578b99d16e6470b2320e9c04893933c4 --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.js @@ -0,0 +1,23 @@ +// $Id: pathauto.js,v 1.9 2010/11/11 17:34:54 davereid Exp $ +(function ($) { + +Drupal.behaviors.pathFieldsetSummaries = { + attach: function (context) { + $('fieldset.path-form', context).drupalSetSummary(function (context) { + var path = $('.form-item-path-alias input').val(); + var automatic = $('.form-item-path-pathauto input').attr('checked'); + + if (automatic) { + return Drupal.t('Automatic alias'); + } + if (path) { + return Drupal.t('Alias: @alias', { '@alias': path }); + } + else { + return Drupal.t('No alias'); + } + }); + } +}; + +})(jQuery); diff --git a/sites/all/modules/pathauto/pathauto.module b/sites/all/modules/pathauto/pathauto.module new file mode 100644 index 0000000000000000000000000000000000000000..696d14f28e2bb5878af196795d33d7de28366ee7 --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.module @@ -0,0 +1,674 @@ +<?php +// $Id: pathauto.module,v 1.169 2011/01/13 03:27:24 davereid Exp $ + +/** + * @defgroup pathauto Pathauto: Automatically generates aliases for content + * + * The Pathauto module automatically generates path aliases for various kinds of + * content (nodes, categories, users) without requiring the user to manually + * specify the path alias. This allows you to get aliases like + * /category/my-node-title.html instead of /node/123. The aliases are based upon + * a "pattern" system which the administrator can control. + */ + +/** + * @file + * Main file for the Pathauto module, which automatically generates aliases for content. + * + * @ingroup pathauto + */ + +/** + * The default ignore word list. + */ +define('PATHAUTO_IGNORE_WORDS', 'a, an, as, at, before, but, by, for, from, is, in, into, like, of, off, on, onto, per, since, than, the, this, that, to, up, via, with'); + +/** + * Implements hook_hook_info(). + */ +function pathauto_hook_info() { + $info['pathauto'] = array('group' => 'pathauto'); + $info['path_alias_types'] = array('group' => 'pathauto'); + return $info; +} + +/** + * Implements hook_module_implements_alter(). + * + * Adds pathauto support for core modules. + */ +function pathauto_module_implements_alter(&$implementations, $hook) { + $hooks = pathauto_hook_info(); + if (isset($hooks[$hook])) { + $modules = array('node', 'taxonomy', 'user', 'forum', 'blog'); + foreach ($modules as $module) { + if (module_exists($module)) { + $implementations[$module] = TRUE; + } + } + // Move pathauto.module to get included first since it is responsible for + // other modules. + unset($implementations['pathauto']); + $implementations = array_merge(array('pathauto' => 'pathauto'), $implementations); + } +} + +/** + * Implements hook_help(). + */ +function pathauto_help($path, $arg) { + switch ($path) { + case 'admin/help#pathauto': + module_load_include('inc', 'pathauto'); + $output = '<h3>' . t('About') . '</h3>'; + $output .= '<p>' . t('Provides a mechanism for modules to automatically generate aliases for the content they manage.') . '</p>'; + $output .= '<h3>' . t('Settings') . '</h3>'; + $output .= '<dl>'; + $output .= '<dt>' . t('Maximum alias and component length') . '</dt>'; + $output .= '<dd>' . t('The <strong>maximum alias length</strong> and <strong>maximum component length</strong> values default to 100 and have a limit of @max from Pathauto. This length is limited by the length of the "alias" column of the url_alias database table. The default database schema for this column is @max. If you set a length that is equal to that of the one set in the "alias" column it will cause problems in situations where the system needs to append additional words to the aliased URL. For example, URLs generated for feeds will have "/feed" added to the end. You should enter a value that is the length of the "alias" column minus the length of any strings that might get added to the end of the URL. The length of strings that might get added to the end of your URLs depends on which modules you have enabled and on your Pathauto settings. The recommended and default value is 100.', array('@max' => _pathauto_get_schema_alias_maxlength())) . '</dd>'; + $output .= '</dl>'; + return $output; + } +} + +/** + * Implements hook_permission(). + */ +function pathauto_permission() { + return array( + 'administer pathauto' => array( + 'title' => t('Administer pathauto'), + 'description' => t('Allows a user to configure patterns for automated aliases and bulk delete URL-aliases.'), + ), + 'notify of path changes' => array( + 'title' => t('Notify of Path Changes'), + 'description' => t('Determines whether or not users are notified.'), + ), + ); +} + +/** + * Implements hook_menu(). + */ +function pathauto_menu() { + $items['admin/config/search/path/patterns'] = array( + 'title' => 'Patterns', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('pathauto_patterns_form'), + 'access arguments' => array('administer pathauto'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + 'file' => 'pathauto.admin.inc', + ); + $items['admin/config/search/path/settings'] = array( + 'title' => 'Settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('pathauto_settings_form'), + 'access arguments' => array('administer pathauto'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 20, + 'file' => 'pathauto.admin.inc', + ); + $items['admin/config/search/path/update_bulk'] = array( + 'title' => 'Bulk update', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('pathauto_bulk_update_form'), + 'access arguments' => array('administer url aliases'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 30, + 'file' => 'pathauto.admin.inc', + ); + $items['admin/config/search/path/delete_bulk'] = array( + 'title' => 'Delete aliases', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('pathauto_admin_delete'), + 'access arguments' => array('administer url aliases'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 40, + 'file' => 'pathauto.admin.inc', + ); + + return $items; +} + +/** + * Load an URL alias pattern by entity, bundle, and language. + * + * @param $entity + * An entity (e.g. node, taxonomy, user, etc.) + * @param $bundle + * A bundle (e.g. content type, vocabulary ID, etc.) + * @param $language + * A language code, defaults to the LANGUAGE_NONE constant. + */ +function pathauto_pattern_load_by_entity($entity, $bundle = '', $language = LANGUAGE_NONE) { + $patterns = &drupal_static(__FUNCTION__, array()); + + $pattern_id = "$entity:$bundle:$language"; + if (!isset($patterns[$pattern_id])) { + $variables = array(); + if ($language != LANGUAGE_NONE) { + $variables[] = "pathauto_{$entity}_{$bundle}_{$language}_pattern"; + } + if ($bundle) { + $variables[] = "pathauto_{$entity}_{$bundle}_pattern"; + } + $variables[] = "pathauto_{$entity}_pattern"; + + foreach ($variables as $variable) { + if ($pattern = trim(variable_get($variable, ''))) { + break; + } + } + + $patterns[$pattern_id] = $pattern; + } + + return $patterns[$pattern_id]; +} + +/** + * Return the proper SQL to perform cross-db and field-type concatenation. + * + * @return + * A string of SQL with the concatenation. + */ +function _pathauto_sql_concat() { + $args = func_get_args(); + switch (db_driver()) { + case 'mysql': + return 'CONCAT(' . implode(', ', $args) . ')'; + case 'mssql': + return '(' . implode(' + ', $args) . ')'; + default: + // The ANSI standard of concatentation uses the double-pipe. + return '(' . implode(' || ', $args) . ')'; + } +} + +/** + * Delete multiple URL aliases. + * + * Intent of this is to abstract a potential path_delete_multiple() function + * for Drupal 7 or 8. + * + * @param $pids + * An array of path IDs to delete. + */ +function pathauto_path_delete_multiple($pids) { + foreach ($pids as $pid) { + path_delete(array('pid' => $pid)); + } +} + +/** + * Delete an URL alias and any of its sub-paths. + * + * Given a source like 'node/1' this function will delete any alias that have + * that specific source or any sources that match 'node/1/%'. + * + * @param $source + * An string with a source URL path. + */ +function pathauto_path_delete_all($source) { + $sql = "SELECT pid FROM {url_alias} WHERE source = :source OR source LIKE :source_wildcard"; + $pids = db_query($sql, array(':source' => $source, ':source_wildcard' => $source . '/%'))->fetchCol(); + if ($pids) { + pathauto_path_delete_multiple($pids); + } +} + +/** + * Delete an entity URL alias and any of its sub-paths. + * + * This function also checks to see if the default entity URI is different from + * the current entity URI and will delete any of the default aliases. + * + * @param $entity_type + * A string with the entity type. + * @param $entity + * An entity object. + * @param $default_uri + * The optional default uri path for the entity. + */ +function pathauto_entity_path_delete_all($entity_type, $entity, $default_uri = NULL) { + $uri = entity_uri($entity_type, $entity); + pathauto_path_delete_all($uri['path']); + if (isset($default_uri) && $uri['path'] != $default_uri) { + pathauto_path_delete_all($default_uri); + } +} + +/** + * Implements hook_field_attach_rename_bundle(). + * + * Respond to machine name changes for pattern variables. + */ +function pathauto_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { + $variables = db_query("SELECT name FROM {variable} WHERE name LIKE :pattern", array(':pattern' => db_like("pathauto_{$entity_type}_{$bundle_old}_") . '%'))->fetchCol(); + foreach ($variables as $variable) { + $value = variable_get($variable, ''); + variable_del($variable); + $variable = strtr($variable, array("{$entity_type}_{$bundle_old}" => "{$entity_type}_{$bundle_new}")); + variable_set($variable, $value); + } +} + +/** + * Implements hook_field_attach_delete_bundle(). + * + * Respond to sub-types being deleted, their patterns can be removed. + */ +function pathauto_field_attach_delete_bundle($entity_type, $bundle, $instances) { + $variables = db_query("SELECT name FROM {variable} WHERE name LIKE :pattern", array(':pattern' => db_like("pathauto_{$entity_type}_{$bundle}_") . '%'))->fetchCol(); + foreach ($variables as $variable) { + variable_del($variable); + } +} + +//============================================================================== +// Some node related functions. + +/** + * Implements hook_node_presave(). + */ +function pathauto_node_presave($node) { + // About to be saved (before insert/update) + if (!empty($node->path['pathauto']) && isset($node->path['old_alias']) + && $node->path['alias'] == '' && $node->path['old_alias'] != '') { + /** + * There was an old alias, but when pathauto_perform_alias was checked + * the javascript disabled the textbox which led to an empty value being + * submitted. Restoring the old path-value here prevents the Path module + * from deleting any old alias before Pathauto gets control. + */ + $node->path['alias'] = $node->path['old_alias']; + } +} + +/** + * Implements hook_node_insert(). + */ +function pathauto_node_insert($node) { + pathauto_node_update_alias($node, 'insert'); +} + +/** + * Implements hook_node_update(). + */ +function pathauto_node_update($node) { + pathauto_node_update_alias($node, 'update'); +} + +/** + * Implements hook_node_delete(). + */ +function pathauto_node_delete($node) { + pathauto_entity_path_delete_all('node', $node, "node/{$node->nid}"); +} + +/** + * Implements hook_form_alter(). + * + * This allows alias creators to override Pathauto and specify their + * own aliases (Pathauto will be invisible to other users). Inserted + * into the path module's fieldset in the node form. + */ +function pathauto_form_alter(&$form, &$form_state, $form_id) { + // Process only node forms. + if (!empty($form['#node_edit_form'])) { + $node = $form['#node']; + + // Find if there is an automatic alias pattern for this content type. + $language = isset($node->language) ? $node->language : LANGUAGE_NONE; + $pattern = pathauto_pattern_load_by_entity('node', $node->type, $language); + + // If there is a pattern, show the automatic alias checkbox. + if ($pattern) { + if (!isset($node->path['pathauto'])) { + if (!empty($node->nid)) { + // If this is not a new node, compare it's current alias to the + // alias that would be genereted by pathauto. If they are the same, + // then keep the automatic alias enabled. + module_load_include('inc', 'pathauto'); + $uri = entity_uri('node', $node); + $path = drupal_get_path_alias($uri['path'], $language); + $pathauto_alias = pathauto_create_alias('node', 'return', $uri['path'], array('node' => $node), $node->type, $node->language); + $node->path['pathauto'] = $path != $uri['path'] && $path == $pathauto_alias; + } + else { + // If this is a new node, enable the automatic alias. + $node->path['pathauto'] = TRUE; + } + } + + // Add JavaScript that will disable the path textfield when the automatic + // alias checkbox is checked. + $form['path']['alias']['#states']['!enabled']['input[name="path[pathauto]"]'] = array('checked' => TRUE); + + // Override path.module's vertical tabs summary. + $form['path']['#attached']['js'] = array( + 'vertical-tabs' => drupal_get_path('module', 'pathauto') . '/pathauto.js' + ); + + $form['path']['pathauto'] = array( + '#type' => 'checkbox', + '#title' => t('Automatic alias'), + '#default_value' => $node->path['pathauto'], + '#description' => t('An alias will be generated for you. If you wish to create your own alias below, uncheck this option.'), + '#weight' => -1, + ); + + if (user_access('administer pathauto')) { + $form['path']['pathauto']['#description'] .= ' ' . t('To control the format of the generated aliases, see the <a href="@url-patterns">URL alias patterns</a>.', array('@url-patterns' => url('admin/config/search/path/patterns'))); + } + + if ($node->path['pathauto'] && !empty($node->old_alias) && empty($path['alias'])) { + $form['path']['alias']['#default_value'] = $node->old_alias; + $path['alias'] = $node->old_alias; + } + + // For Pathauto to remember the old alias and prevent the Path-module from deleteing it when Pathauto wants to preserve it + if (!empty($path['alias'])) { + $form['path']['old_alias'] = array( + '#type' => 'value', + '#value' => $path['alias'], + ); + } + } + } +} + +/** + * Implements hook_node_operations(). + */ +function pathauto_node_operations() { + $operations['pathauto_update_alias'] = array( + 'label' => t('Update URL alias'), + 'callback' => 'pathauto_node_update_alias_multiple', + 'callback arguments' => array('bulkupdate', array('message' => TRUE)), + ); + return $operations; +} + +/** + * Update the URL aliases for an individual node. + * + * @param $node + * A node object. + * @param $op + * Operation being performed on the node ('insert', 'update' or 'bulkupdate'). + * @param $options + * An optional array of additional options. + */ +function pathauto_node_update_alias(stdClass $node, $op, array $options = array()) { + // Skip processing if the user has disabled pathauto for the node. + if (isset($node->path['pathauto']) && empty($node->path['pathauto'])) { + return; + } + + // Skip processing if the node has no pattern. + $language = isset($node->language) ? $node->language : LANGUAGE_NONE; + if (!pathauto_pattern_load_by_entity('node', $node->type, $language)) { + return; + } + + module_load_include('inc', 'pathauto'); + $uri = entity_uri('node', $node); + pathauto_create_alias('node', $op, $uri['path'], array('node' => $node), $node->type, $language); +} + +/** + * Update the URL aliases for multiple nodes. + * + * @param $nids + * An array of node IDs. + * @param $op + * Operation being performed on the nodes ('insert', 'update' or + * 'bulkupdate'). + * @param $options + * An optional array of additional options. + */ +function pathauto_node_update_alias_multiple(array $nids, $op, array $options = array()) { + $options += array('message' => FALSE); + + $nodes = node_load_multiple($nids); + foreach ($nodes as $node) { + pathauto_node_update_alias($node, $op, $options); + } + + if (!empty($options['message'])) { + drupal_set_message(format_plural(count($nids), 'Updated URL alias for 1 node.', 'Updated URL aliases for @count nodes.')); + } +} + +//============================================================================== +// Taxonomy related functions. + +/** + * Implements hook_taxonomy_term_insert(). + */ +function pathauto_taxonomy_term_insert($term) { + pathauto_taxonomy_term_update_alias($term, 'insert'); +} + +/** + * Implements hook_taxonomy_term_update(). + */ +function pathauto_taxonomy_term_update($term) { + pathauto_taxonomy_term_update_alias($term, 'update', array('alias children' => TRUE)); +} + +/** + * Implements hook_taxonomy_term_delete(). + */ +function pathauto_taxonomy_term_delete($term) { + pathauto_entity_path_delete_all('taxonomy_term', $term, "taxonomy/term/{$term->tid}"); +} + +/** + * Update the URL aliases for an individual taxonomy term. + * + * @param $term + * A taxonomy term object. + * @param $op + * Operation being performed on the term ('insert', 'update' or 'bulkupdate'). + * @param $options + * An optional array of additional options. + */ +function pathauto_taxonomy_term_update_alias(stdClass $term, $op, array $options = array()) { + // Skip processing if the user has disabled pathauto for the term. + if (isset($term->path['pathauto']) && empty($term->path['pathauto'])) { + return; + } + + $options += array('alias children' => FALSE); + + $module = 'taxonomy_term'; + if ($term->vid == variable_get('forum_nav_vocabulary', '')) { + if (module_exists('forum')) { + $module = 'forum'; + } + else { + return; + } + } + + // Check that the term has its bundle, which is the vocabulary's machine name. + if (!isset($term->vocabulary_machine_name)) { + $vocabulary = taxonomy_vocabulary_load($term->vid); + $term->vocabulary_machine_name = $vocabulary->machine_name; + } + + // Skip processing if the term has no pattern. + if (!pathauto_pattern_load_by_entity($module, $term->vocabulary_machine_name)) { + return; + } + + module_load_include('inc', 'pathauto'); + $uri = entity_uri('taxonomy_term', $term); + pathauto_create_alias($module, $op, $uri['path'], array('term' => $term), $term->vocabulary_machine_name); + + if (!empty($options['alias children'])) { + // For all children generate new alias. + $options['alias children'] = FALSE; + foreach (taxonomy_get_tree($term->vid, $term->tid) as $subterm) { + pathauto_taxonomy_term_update_alias($subterm, $op, $options); + } + } +} + +/** + * Update the URL aliases for multiple taxonomy terms. + * + * @param $tids + * An array of term IDs. + * @param $op + * Operation being performed on the nodes ('insert', 'update' or + * 'bulkupdate'). + * @param $options + * An optional array of additional options. + */ +function pathauto_taxonomy_term_update_alias_multiple(array $tids, $op, array $options = array()) { + $options += array('message' => FALSE); + + $terms = taxonomy_term_load_multiple($tids); + foreach ($terms as $term) { + pathauto_taxonomy_term_update_alias($term, $op, $options); + } + + if (!empty($options['message'])) { + drupal_set_message(format_plural(count($tids), 'Updated URL alias for 1 term.', 'Updated URL aliases for @count terms.')); + } +} + +//============================================================================== +// User related functions. For users, trackers, and blogs. + +/** + * Implements hook_user_insert(). + */ +function pathauto_user_insert(&$edit, $account, $category) { + pathauto_user_update_alias($account, 'insert'); +} + +/** + * Implements hook_user_update(). + */ +function pathauto_user_update(&$edit, $account, $category) { + pathauto_user_update_alias($account, 'update'); +} + +/** + * Implements hook_user_cancel(). + */ +function pathauto_user_cancel($edit, $account, $method) { + switch ($method) { + case 'user_cancel_block': + case 'user_cancel_block_unpublish': + // Don't remove aliases because the user may become active again later. + break; + case 'user_cancel_reassign': + case 'user_cancel_delete': + // Do remove aliases since the account will be deleted. + pathauto_entity_path_delete_all('user', $account, "user/{$account->uid}"); + pathauto_path_delete_all("blog/{$account->uid}"); + break; + } +} + +/** + * Implements hook_user_operations(). + */ +function pathauto_user_operations() { + $operations['pathauto_update_alias'] = array( + 'label' => t('Update URL alias'), + 'callback' => 'pathauto_user_update_alias_multiple', + 'callback arguments' => array('bulkupdate', array('message' => TRUE)), + ); + return $operations; +} + +/** + * Update the URL aliases for an individual user account. + * + * @param $account + * A user account object. + * @param $op + * Operation being performed on the account ('insert', 'update' or + * 'bulkupdate'). + * @param $options + * An optional array of additional options. + */ +function pathauto_user_update_alias(stdClass $account, $op, array $options = array()) { + // Skip processing if the user has disabled pathauto for the account. + if (isset($account->path['pathauto']) && empty($account->path['pathauto'])) { + return; + } + + $options += array('alias blog' => module_exists('blog')); + + // Skip processing if the account has no pattern. + if (!pathauto_pattern_load_by_entity('user')) { + return; + } + + module_load_include('inc', 'pathauto'); + $uri = entity_uri('user', $account); + pathauto_create_alias('user', $op, $uri['path'], array('user' => $account)); + + // Because blogs are also associated with users, also generate the blog paths. + if (!empty($options['alias blog'])) { + pathauto_blog_update_alias($account, $op); + } +} + +/** + * Update the URL aliases for multiple user accounts. + * + * @param $uids + * An array of user account IDs. + * @param $op + * Operation being performed on the accounts ('insert', 'update' or + * 'bulkupdate'). + * @param $options + * An optional array of additional options. + */ +function pathauto_user_update_alias_multiple(array $uids, $op, array $options = array()) { + $options += array('message' => FALSE); + + $accounts = user_load_multiple($uids); + foreach ($accounts as $account) { + pathauto_user_update_alias($account, $op, $options); + } + + if (!empty($options['message'])) { + drupal_set_message(format_plural(count($uids), 'Updated URL alias for 1 user account.', 'Updated URL aliases for @count user accounts.')); + } +} + +/** + * Update the blog URL aliases for an individual user account. + * + * @param $account + * A user account object. + * @param $op + * Operation being performed on the blog ('insert', 'update' or + * 'bulkupdate'). + * @param $options + * An optional array of additional options. + */ +function pathauto_blog_update_alias(stdClass $account, $op, array $options = array()) { + // Skip processing if the blog has no pattern. + if (!pathauto_pattern_load_by_entity('blog')) { + return; + } + + module_load_include('inc', 'pathauto'); + if (node_access('create', 'blog', $account)) { + pathauto_create_alias('blog', $op, "blog/{$account->uid}", array('user' => $account)); + } + else { + pathauto_path_delete_all("blog/{$account->uid}"); + } +} diff --git a/sites/all/modules/pathauto/pathauto.pathauto.inc b/sites/all/modules/pathauto/pathauto.pathauto.inc new file mode 100644 index 0000000000000000000000000000000000000000..f95e7a86fd12b1282e1a3e2a75cefb089952078c --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.pathauto.inc @@ -0,0 +1,396 @@ +<?php +// $Id: pathauto.pathauto.inc,v 1.7 2011/01/13 03:27:23 davereid Exp $ + +/** + * @file + * Pathauto integration for core modules. + * + * @ingroup pathauto + */ + +/** + * Implements hook_path_alias_types(). + * + * Used primarily by the bulk delete form. + */ +function pathauto_path_alias_types() { + $objects['user/'] = t('Users'); + $objects['node/'] = t('Content'); + if (module_exists('blog')) { + $objects['blog/'] = t('User blogs'); + } + if (module_exists('taxonomy')) { + $objects['taxonomy/term/'] = t('Taxonomy terms'); + } + if (module_exists('forum')) { + $objects['forum/'] = t('Forums'); + } + return $objects; +} + +/** + * Implements hook_pathauto(). + * + * This function is empty so that the other core module implementations can be + * defined in this file. This is because in pathauto_module_implements_alter() + * we add pathauto to be included first. The module system then peforms a + * check on any subsequent run if this function still exists. If this does not + * exist, than this file will not get included and the core implementations + * will never get run. + * + * @see pathauto_module_implements_alter(). + */ +function pathauto_pathauto() { + // Empty hook; see the above comment. +} + +/** + * Implements hook_pathauto(). + */ +function node_pathauto($op) { + switch ($op) { + case 'settings': + $settings = array(); + $settings['module'] = 'node'; + $settings['token_type'] = 'node'; + $settings['groupheader'] = t('Content paths'); + $settings['patterndescr'] = t('Default path pattern (applies to all content types with blank patterns below)'); + $settings['patterndefault'] = 'content/[node:title]'; + $settings['batch_update_callback'] = 'node_pathauto_bulk_update_batch_process'; + $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; + + $languages = array(); + if (module_exists('locale')) { + $languages = array(LANGUAGE_NONE => t('language neutral')) + locale_language_list('name'); + } + + foreach (node_type_get_names() as $node_type => $node_name) { + if (count($languages) && variable_get('language_content_type_' . $node_type, 0)) { + $settings['patternitems'][$node_type] = t('Default path pattern for @node_type (applies to all @node_type content types with blank patterns below)', array('@node_type' => $node_name)); + foreach ($languages as $lang_code => $lang_name) { + $settings['patternitems'][$node_type . '_' . $lang_code] = t('Pattern for all @language @node_type paths', array('@node_type' => $node_name, '@language' => $lang_name)); + } + } + else { + $settings['patternitems'][$node_type] = t('Pattern for all @node_type paths', array('@node_type' => $node_name)); + } + } + return (object) $settings; + default: + break; + } +} + +/** + * Batch processing callback; Generate aliases for nodes. + */ +function node_pathauto_bulk_update_batch_process(&$context) { + if (!isset($context['sandbox']['current'])) { + $context['sandbox']['count'] = 0; + $context['sandbox']['current'] = 0; + } + + $query = db_select('node', 'n'); + $concat = _pathauto_sql_concat("'node/'", 'n.nid'); + $query->leftJoin('url_alias', 'ua', "$concat = ua.source"); + $query->addField('n', 'nid'); + $query->isNull('ua.source'); + $query->condition('n.nid', $context['sandbox']['current'], '>'); + $query->orderBy('n.nid'); + $query->addTag('pathauto_bulk_update'); + $query->addMetaData('entity', 'node'); + + // Get the total amount of items to process. + if (!isset($context['sandbox']['total'])) { + $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); + + // If there are no nodes to update, the stop immediately. + if (!$context['sandbox']['total']) { + $context['finished'] = 1; + return; + } + } + + $query->range(0, 25); + $nids = $query->execute()->fetchCol(); + + pathauto_node_update_alias_multiple($nids, 'bulkupdate'); + $context['sandbox']['count'] += count($nids); + $context['sandbox']['current'] = max($nids); + $context['message'] = t('Updated alias for node @nid.', array('@nid' => end($nids))); + + if ($context['sandbox']['count'] != $context['sandbox']['total']) { + $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; + } +} + +/** + * Implements hook_pathauto(). + */ +function taxonomy_pathauto($op) { + switch ($op) { + case 'settings': + $settings = array(); + $settings['module'] = 'taxonomy_term'; + $settings['token_type'] = 'term'; + $settings['groupheader'] = t('Taxonomy term paths'); + $settings['patterndescr'] = t('Default path pattern (applies to all vocabularies with blank patterns below)'); + $settings['patterndefault'] = '[term:vocabulary]/[term:name]'; + $settings['batch_update_callback'] = 'taxonomy_pathauto_bulk_update_batch_process'; + $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; + + $vocabularies = taxonomy_get_vocabularies(); + if (count($vocabularies)) { + $settings['patternitems'] = array(); + foreach ($vocabularies as $vid => $vocabulary) { + if ($vid == variable_get('forum_nav_vocabulary', '')) { + // Skip the forum vocabulary. + continue; + } + $settings['patternitems'][$vocabulary->machine_name] = t('Pattern for all %vocab-name paths', array('%vocab-name' => $vocabulary->name)); + } + } + return (object) $settings; + default: + break; + } +} + +/** + * Batch processing callback; Generate aliases for taxonomy terms. + */ +function taxonomy_pathauto_bulk_update_batch_process(&$context) { + if (!isset($context['sandbox']['current'])) { + $context['sandbox']['count'] = 0; + $context['sandbox']['current'] = 0; + } + + $query = db_select('taxonomy_term_data', 'td'); + $concat = _pathauto_sql_concat("'taxonomy/term/'", 'td.tid'); + $query->leftJoin('url_alias', 'ua', "$concat = ua.source"); + $query->addField('td', 'tid'); + $query->isNull('ua.source'); + // Exclude the forums terms. + if ($forum_vid = variable_get('forum_nav_vocabulary', '')) { + $query->condition('td.vid', $forum_vid, '<>'); + } + $query->orderBy('td.tid'); + $query->addTag('pathauto_bulk_update'); + $query->addMetaData('entity', 'taxonomy_term'); + + // Get the total amount of items to process. + if (!isset($context['sandbox']['total'])) { + $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); + + // If there are no nodes to update, the stop immediately. + if (!$context['sandbox']['total']) { + $context['finished'] = 1; + return; + } + } + + $query->range(0, 25); + $tids = $query->execute()->fetchCol(); + + pathauto_taxonomy_term_update_alias_multiple($tids, 'bulkupdate'); + $context['sandbox']['count'] += count($tids); + $context['sandbox']['current'] = max($tids); + $context['message'] = t('Updated alias for term @tid.', array('@tid' => end($tids))); + + if ($context['sandbox']['count'] != $context['sandbox']['total']) { + $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; + } +} + +/** + * Implements hook_pathauto() for forum module. + */ +function forum_pathauto($op) { + switch ($op) { + case 'settings': + $settings = array(); + $settings['module'] = 'forum'; + $settings['token_type'] = 'term'; + $settings['groupheader'] = t('Forum paths'); + $settings['patterndescr'] = t('Pattern for forums and forum containers'); + $settings['patterndefault'] = '[term:vocabulary]/[term:name]'; + $settings['batch_update_callback'] = 'forum_pathauto_bulk_update_batch_process'; + $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; + return (object) $settings; + default: + break; + } +} + +/** + * Batch processing callback; Generate aliases for forums. + */ +function forum_pathauto_bulk_update_batch_process(&$context) { + if (!isset($context['sandbox']['current'])) { + $context['sandbox']['count'] = 0; + $context['sandbox']['current'] = 0; + } + + $query = db_select('taxonomy_term_data', 'td'); + $concat = _pathauto_sql_concat("'forum/'", 'td.tid'); + $query->leftJoin('url_alias', 'ua', "$concat = ua.source"); + $query->addField('td', 'tid'); + $query->isNull('ua.source'); + $query->condition('vid', variable_get('forum_nav_vocabulary', '')); + $query->orderBy('td.tid'); + $query->addTag('pathauto_bulk_update'); + $query->addMetaData('entity', 'taxonomy_term'); + + // Get the total amount of items to process. + if (!isset($context['sandbox']['total'])) { + $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); + + // If there are no nodes to update, the stop immediately. + if (!$context['sandbox']['total']) { + $context['finished'] = 1; + return; + } + } + + $query->range(0, 25); + $tids = $query->execute()->fetchCol(); + + pathauto_taxonomy_term_update_alias_multiple($tids, 'bulkupdate'); + $context['sandbox']['count'] += count($tids); + $context['sandbox']['current'] = max($tids); + $context['message'] = t('Updated alias for fourm @tid.', array('@tid' => end($tids))); + + if ($context['sandbox']['count'] != $context['sandbox']['total']) { + $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; + } +} + +/** + * Implements hook_pathauto(). + */ +function user_pathauto($op) { + switch ($op) { + case 'settings': + $settings = array(); + $settings['module'] = 'user'; + $settings['token_type'] = 'user'; + $settings['groupheader'] = t('User paths'); + $settings['patterndescr'] = t('Pattern for user account page paths'); + $settings['patterndefault'] = 'users/[user:name]'; + $settings['batch_update_callback'] = 'user_pathauto_bulk_update_batch_process'; + $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; + return (object) $settings; + default: + break; + } +} + +/** + * Batch processing callback; Generate aliases for users. + */ +function user_pathauto_bulk_update_batch_process(&$context) { + if (!isset($context['sandbox']['current'])) { + $context['sandbox']['count'] = 0; + $context['sandbox']['current'] = 0; + } + + $query = db_select('users', 'u'); + $concat = _pathauto_sql_concat("'user/'", 'u.uid'); + $query->leftJoin('url_alias', 'ua', "$concat = ua.source"); + $query->addField('u', 'uid'); + $query->isNull('ua.source'); + $query->condition('u.uid', $context['sandbox']['current'], '>'); + $query->orderBy('u.uid'); + $query->addTag('pathauto_bulk_update'); + $query->addMetaData('entity', 'user'); + + // Get the total amount of items to process. + if (!isset($context['sandbox']['total'])) { + $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); + + // If there are no nodes to update, the stop immediately. + if (!$context['sandbox']['total']) { + $context['finished'] = 1; + return; + } + } + + $query->range(0, 25); + $uids = $query->execute()->fetchCol(); + + pathauto_user_update_alias_multiple($uids, 'bulkupdate', array('alias blog' => FALSE)); + $context['sandbox']['count'] += count($uids); + $context['sandbox']['current'] = max($uids); + $context['message'] = t('Updated alias for user @uid.', array('@uid' => end($uids))); + + if ($context['sandbox']['count'] != $context['sandbox']['total']) { + $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; + } +} + +/** + * Implements hook_pathauto(). + */ +function blog_pathauto($op) { + switch ($op) { + case 'settings': + $settings = array(); + $settings['module'] = 'blog'; + $settings['token_type'] = 'user'; + $settings['groupheader'] = t('Blog paths'); + $settings['patterndescr'] = t('Pattern for blog page paths'); + $settings['patterndefault'] = 'blogs/[user:name]'; + $settings['batch_update_callback'] = 'blog_pathauto_bulk_update_batch_process'; + $settings['batch_file'] = drupal_get_path('module', 'pathauto') . '/pathauto.pathauto.inc'; + return (object) $settings; + default: + break; + } +} + +/** + * Batch processing callback; Generate aliases for blogs. + */ +function blog_pathauto_bulk_update_batch_process(&$context) { + if (!isset($context['sandbox']['current'])) { + $context['sandbox']['count'] = 0; + $context['sandbox']['current'] = 0; + } + + $query = db_select('users', 'u'); + $concat = _pathauto_sql_concat("'blog/'", 'u.uid'); + $query->leftJoin('url_alias', 'ua', "$concat = ua.source"); + $query->addField('u', 'uid'); + $query->isNull('ua.source'); + $query->condition('u.uid', $context['sandbox']['current'], '>'); + $query->orderBy('u.uid'); + $query->addTag('pathauto_bulk_update'); + $query->addMetaData('entity', 'user'); + + // Get the total amount of items to process. + if (!isset($context['sandbox']['total'])) { + $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField(); + + // If there are no nodes to update, the stop immediately. + if (!$context['sandbox']['total']) { + $context['finished'] = 1; + return; + } + } + + $query->range(0, 25); + $uids = $query->execute()->fetchCol(); + + $accounts = user_load_multiple($uids); + foreach ($accounts as $account) { + pathauto_blog_update_alias($account, 'bulkupdate'); + } + + $context['sandbox']['count'] += count($uids); + $context['sandbox']['current'] = max($uids); + $context['message'] = t('Updated alias for blog user @uid.', array('@uid' => end($uids))); + + if ($context['sandbox']['count'] != $context['sandbox']['total']) { + $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total']; + } +} diff --git a/sites/all/modules/pathauto/pathauto.test b/sites/all/modules/pathauto/pathauto.test new file mode 100644 index 0000000000000000000000000000000000000000..a5a2b8ecbb1884ff8dbaf76ee94ffc32d33414ed --- /dev/null +++ b/sites/all/modules/pathauto/pathauto.test @@ -0,0 +1,587 @@ +<?php +// $Id: pathauto.test,v 1.38 2011/01/13 03:27:24 davereid Exp $ + +/** + * @file + * Functionality tests for Pathauto. + * + * @ingroup pathauto + */ + +/** + * Helper test class with some added functions for testing. + */ +class PathautoTestHelper extends DrupalWebTestCase { + function setUp(array $modules = array()) { + $modules[] = 'path'; + $modules[] = 'token'; + $modules[] = 'pathauto'; + parent::setUp($modules); + } + + function assertToken($type, $object, $token, $expected) { + $tokens = token_generate($type, array($token => $token), array($type => $object)); + $tokens += array($token => ''); + $this->assertIdentical($tokens[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $tokens[$token], '@expected' => $expected))); + } + + function saveAlias($source, $alias, $language = LANGUAGE_NONE) { + $alias = array( + 'source' => $source, + 'alias' => $alias, + 'language' => $language, + ); + path_save($alias); + return $alias; + } + + function saveEntityAlias($entity_type, $entity, $alias, $language = LANGUAGE_NONE) { + $uri = entity_uri($entity_type, $entity); + return $this->saveAlias($uri['path'], $alias, $language); + } + + function assertEntityAlias($entity_type, $entity, $expected_alias, $language = LANGUAGE_NONE) { + $uri = entity_uri($entity_type, $entity); + $this->assertAlias($uri['path'], $expected_alias, $language); + } + + function assertEntityAliasExists($entity_type, $entity) { + $uri = entity_uri($entity_type, $entity); + return $this->assertAliasExists(array('source' => $uri['path'])); + } + + function assertNoEntityAlias($entity_type, $entity, $language = LANGUAGE_NONE) { + $uri = entity_uri($entity_type, $entity); + $this->assertEntityAlias($entity_type, $entity, $uri['path'], $language); + } + + function assertNoEntityAliasExists($entity_type, $entity) { + $uri = entity_uri($entity_type, $entity); + $this->assertNoAliasExists(array('source' => $uri['path'])); + } + + function assertAlias($source, $expected_alias, $language = '') { + drupal_clear_path_cache($source); + $alias = drupal_get_path_alias($source, $language); + $this->assertIdentical($alias, $expected_alias, t("Alias for %source with language '@language' was %actual, expected %expected.", array('%source' => $source, '%actual' => $alias, '%expected' => $expected_alias, '@language' => $language))); + } + + function assertAliasExists($conditions) { + $path = path_load($conditions); + $this->assertTrue($path, t('Alias with conditions @conditions found.', array('@conditions' => var_export($conditions, TRUE)))); + return $path; + } + + function assertNoAliasExists($conditions) { + $alias = path_load($conditions); + $this->assertFalse($alias, t('Alias with conditions @conditions not found.', array('@conditions' => var_export($conditions, TRUE)))); + } + + function deleteAllAliases() { + db_delete('url_alias')->execute(); + drupal_clear_path_cache(); + } + + function addVocabulary(array $vocabulary = array()) { + $name = drupal_strtolower($this->randomName(5)); + $vocabulary += array( + 'name' => $name, + 'machine_name' => $name, + 'nodes' => array('article' => 'article'), + ); + $vocabulary = (object) $vocabulary; + taxonomy_vocabulary_save($vocabulary); + return $vocabulary; + } + + function addTerm(stdClass $vocabulary, array $term = array()) { + $term += array( + 'name' => drupal_strtolower($this->randomName(5)), + 'vocabulary_machine_name' => $vocabulary->machine_name, + 'vid' => $vocabulary->vid, + ); + $term = (object) $term; + taxonomy_term_save($term); + return $term; + } + + function assertEntityPattern($entity_type, $bundle, $language = LANGUAGE_NONE, $expected) { + drupal_static_reset('pathauto_pattern_load_by_entity'); + $pattern = pathauto_pattern_load_by_entity($entity_type, $bundle, $language); + $this->assertIdentical($expected, $pattern); + } +} + +/** + * Unit tests for Pathauto functions. + */ +class PathautoUnitTestCase extends PathautoTestHelper { + public static function getInfo() { + return array( + 'name' => 'Pathauto unit tests', + 'description' => 'Unit tests for Pathauto functions.', + 'group' => 'Pathauto', + 'dependencies' => array('token'), + ); + } + + function setUp(array $modules = array()) { + parent::setUp($modules); + module_load_include('inc', 'pathauto'); + } + + /** + * Test _pathauto_get_schema_alias_maxlength(). + */ + function testGetSchemaAliasMaxLength() { + $this->assertIdentical(_pathauto_get_schema_alias_maxlength(), 255); + } + + /** + * Test pathauto_pattern_load_by_entity(). + */ + function testPatternLoadByEntity() { + variable_set('pathauto_node_story_en_pattern', ' story/en/[node:title] '); + variable_set('pathauto_node_story_pattern', 'story/[node:title]'); + variable_set('pathauto_node_pattern', 'content/[node:title]'); + variable_set('pathauto_user_pattern', 'users/[user:name]'); + + $tests = array( + array('entity' => 'node', 'bundle' => 'story', 'language' => 'fr', 'expected' => 'story/[node:title]'), + array('entity' => 'node', 'bundle' => 'story', 'language' => 'en', 'expected' => 'story/en/[node:title]'), + array('entity' => 'node', 'bundle' => 'story', 'language' => LANGUAGE_NONE, 'expected' => 'story/[node:title]'), + array('entity' => 'node', 'bundle' => 'page', 'language' => 'en', 'expected' => 'content/[node:title]'), + array('entity' => 'user', 'bundle' => 'user', 'language' => LANGUAGE_NONE, 'expected' => 'users/[user:name]'), + array('entity' => 'invalid-entity', 'bundle' => '', 'language' => LANGUAGE_NONE, 'expected' => ''), + ); + foreach ($tests as $test) { + $actual = pathauto_pattern_load_by_entity($test['entity'], $test['bundle'], $test['language']); + $this->assertIdentical($actual, $test['expected'], t("pathauto_pattern_load_by_entity('@entity', '@bundle', '@language') returned '@actual', expected '@expected'", array('@entity' => $test['entity'], '@bundle' => $test['bundle'], '@language' => $test['language'], '@actual' => $actual, '@expected' => $test['expected']))); + } + } + + /** + * Test pathauto_cleanstring(). + */ + function testCleanString() { + $tests = array(); + variable_set('pathauto_ignore_words', ', in, is,that, the , this, with, '); + variable_set('pathauto_max_component_length', 20); + + // Test the 'ignored words' removal. + $tests['this'] = 'this'; + $tests['this with that'] = 'this-with-that'; + $tests['this thing with that thing'] = 'thing-thing'; + + // Test length truncation and duplicate separator removal. + $tests[' - Pathauto is the greatest - module ever in Drupal history - '] = 'pathauto-greatest'; + + foreach ($tests as $input => $expected) { + $output = pathauto_cleanstring($input); + $this->assertEqual($output, $expected, t("pathauto_cleanstring('@input') expected '@expected', actual '@output'", array('@input' => $input, '@expected' => $expected, '@output' => $output))); + } + } + + /** + * Test pathauto_path_delete_multiple(). + */ + function testPathDeleteMultiple() { + $this->saveAlias('node/1', 'node-1-alias'); + $this->saveAlias('node/1/view', 'node-1-alias/view'); + $this->saveAlias('node/1', 'node-1-alias-en', 'en'); + $this->saveAlias('node/1', 'node-1-alias-fr', 'fr'); + $this->saveAlias('node/2', 'node-2-alias'); + + pathauto_path_delete_all('node/1'); + $this->assertNoAliasExists(array('source' => "node/1")); + $this->assertNoAliasExists(array('source' => "node/1/view")); + $this->assertAliasExists(array('source' => "node/2")); + } + + /** + * Test the different update actions in pathauto_create_alias(). + */ + function testUpdateActions() { + // Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'insert'. + variable_set('pathauto_update_action', PATHAUTO_UPDATE_ACTION_NO_NEW); + $node = $this->drupalCreateNode(array('title' => 'First title')); + $this->assertEntityAlias('node', $node, 'content/first-title'); + + // Default action is PATHAUTO_UPDATE_ACTION_DELETE. + variable_set('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE); + $node->title = 'Second title'; + pathauto_node_update($node); + $this->assertEntityAlias('node', $node, 'content/second-title'); + $this->assertNoAliasExists(array('alias' => 'content/first-title')); + + // Test PATHAUTO_UPDATE_ACTION_LEAVE + variable_set('pathauto_update_action', PATHAUTO_UPDATE_ACTION_LEAVE); + $node->title = 'Third title'; + pathauto_node_update($node); + $this->assertEntityAlias('node', $node, 'content/third-title'); + $this->assertAliasExists(array('source' => "node/{$node->nid}", 'alias' => 'content/second-title')); + + variable_set('pathauto_update_action', PATHAUTO_UPDATE_ACTION_DELETE); + $node->title = 'Fourth title'; + pathauto_node_update($node); + $this->assertEntityAlias('node', $node, 'content/fourth-title'); + $this->assertNoAliasExists(array('alias' => 'content/third-title')); + // The older second alias is not deleted yet. + $older_path = $this->assertAliasExists(array('source' => "node/{$node->nid}", 'alias' => 'content/second-title')); + path_delete($older_path); + + variable_set('pathauto_update_action', PATHAUTO_UPDATE_ACTION_NO_NEW); + $node->title = 'Fifth title'; + pathauto_node_update($node); + $this->assertEntityAlias('node', $node, 'content/fourth-title'); + $this->assertNoAliasExists(array('alias' => 'content/fith-title')); + + // Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'update'. + $this->deleteAllAliases(); + pathauto_node_update($node); + $this->assertEntityAlias('node', $node, 'content/fifth-title'); + + // Test PATHAUTO_UPDATE_ACTION_NO_NEW with unaliased node and 'bulkupdate'. + $this->deleteAllAliases(); + $node->title = 'Sixth title'; + pathauto_node_update_alias($node, 'bulkupdate'); + $this->assertEntityAlias('node', $node, 'content/sixth-title'); + } + + /** + * Test that pathauto_create_alias() will not create an alias for a pattern + * that does not get any tokens replaced. + */ + function testNoTokensNoAlias() { + $node = $this->drupalCreateNode(array('title' => '')); + $this->assertNoEntityAliasExists('node', $node); + + $node->title = 'hello'; + pathauto_node_update($node); + $this->assertEntityAlias('node', $node, 'content/hello'); + } + + /** + * Test the handling of path vs non-path tokens in pathauto_clean_token_values(). + */ + function testPathTokens() { + variable_set('pathauto_taxonomy_term_pattern', '[term:parent:url:alias]/[term:name]'); + $vocab = $this->addVocabulary(); + + $term1 = $this->addTerm($vocab, array('name' => 'Parent term')); + $this->assertEntityAlias('taxonomy_term', $term1, 'parent-term'); + + $term2 = $this->addTerm($vocab, array('name' => 'Child term', 'parent' => $term1->tid)); + $this->assertEntityAlias('taxonomy_term', $term2, 'parent-term/child-term'); + + $this->saveEntityAlias('taxonomy_term', $term1, 'My Crazy/Alias/'); + pathauto_taxonomy_term_update($term2); + $this->assertEntityAlias('taxonomy_term', $term2, 'My Crazy/Alias/child-term'); + } + + function testEntityBundleRenamingDeleting() { + // Create a vocabulary and test that it's pattern variable works. + $vocab = $this->addVocabulary(array('machine_name' => 'old_name')); + variable_set('pathauto_taxonomy_term_pattern', 'base'); + variable_set("pathauto_taxonomy_term_old_name_pattern", 'bundle'); + $this->assertEntityPattern('taxonomy_term', 'old_name', LANGUAGE_NONE, 'bundle'); + + // Rename the vocabulary's machine name, which should cause its pattern + // variable to also be renamed. + $vocab->machine_name = 'new_name'; + taxonomy_vocabulary_save($vocab); + $this->assertEntityPattern('taxonomy_term', 'new_name', LANGUAGE_NONE, 'bundle'); + $this->assertEntityPattern('taxonomy_term', 'old_name', LANGUAGE_NONE, 'base'); + + // Delete the vocabulary, which should cause its pattern variable to also + // be deleted. + taxonomy_vocabulary_delete($vocab->vid); + $this->assertEntityPattern('taxonomy_term', 'new_name', LANGUAGE_NONE, 'base'); + } +} + +/** + * Helper test class with some added functions for testing. + */ +class PathautoFunctionalTestHelper extends PathautoTestHelper { + protected $admin_user; + + function setUp(array $modules = array()) { + parent::setUp($modules); + + // Set pathauto settings we assume to be as-is in this test. + variable_set('pathauto_node_page_pattern', 'content/[node:title]'); + + // Allow other modules to add additional permissions for the admin user. + $permissions = array( + 'administer pathauto', + 'administer url aliases', + 'create url aliases', + 'administer nodes', + 'bypass node access', + 'access content overview', + 'administer users', + ); + $args = func_get_args(); + if (isset($args[1]) && is_array($args[1])) { + $permissions = array_merge($permissions, $args[1]); + } + $this->admin_user = $this->drupalCreateUser($permissions); + + $this->drupalLogin($this->admin_user); + } +} + +/** + * Test basic pathauto functionality. + */ +class PathautoFunctionalTestCase extends PathautoFunctionalTestHelper { + public static function getInfo() { + return array( + 'name' => 'Pathauto basic tests', + 'description' => 'Test basic pathauto functionality.', + 'group' => 'Pathauto', + 'dependencies' => array('token'), + ); + } + + /** + * Basic functional testing of Pathauto. + */ + function testNodeEditing() { + // Create node for testing. + $random_title = $this->randomName(10); + $title = ' Simpletest title ' . $random_title . ' ['; + $automatic_alias = 'content/simpletest-title-' . strtolower($random_title); + $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'page')); + + // Look for alias generated in the form. + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertFieldChecked('edit-path-pathauto'); + $this->assertFieldByName('path[alias]', $automatic_alias, 'Proper automated alias generated.'); + + // Check whether the alias actually works. + $this->drupalGet($automatic_alias); + $this->assertText($title, 'Node accessible through automatic alias.'); + + // Manually set the node's alias. + $manual_alias = 'content/' . $node->nid; + $edit = array( + 'path[pathauto]' => FALSE, + 'path[alias]' => $manual_alias, + ); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertText(t('@type @title has been updated', array('@type' => 'Basic page', '@title' => $title))); + + // Check that the automatic alias checkbox is now unchecked by default. + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertNoFieldChecked('edit-path-pathauto'); + $this->assertFieldByName('path[alias]', $manual_alias); + + // Submit the node form with the default values. + $this->drupalPost(NULL, array(), t('Save')); + $this->assertText(t('@type @title has been updated', array('@type' => 'Basic page', '@title' => $title))); + + // Test that the old (automatic) alias has been deleted and only accessible + // through the new (manual) alias. + $this->drupalGet($automatic_alias); + $this->assertResponse(404, 'Node not accessible through automatic alias.'); + $this->drupalGet($manual_alias); + $this->assertText($title, 'Node accessible through manual alias.'); + } + + /** + * Test node operations. + */ + function testNodeOperations() { + $node1 = $this->drupalCreateNode(array('title' => 'node1')); + $node2 = $this->drupalCreateNode(array('title' => 'node2')); + + // Delete all current URL aliases. + $this->deleteAllAliases(); + + $edit = array( + 'operation' => 'pathauto_update_alias', + "nodes[{$node1->nid}]" => TRUE, + ); + $this->drupalPost('admin/content', $edit, t('Update')); + $this->assertText('Updated URL alias for 1 node.'); + + $this->assertEntityAlias('node', $node1, 'content/' . $node1->title); + $this->assertEntityAlias('node', $node2, 'node/' . $node2->nid); + } + + /** + * Test user operations. + */ + function testUserOperations() { + $account = $this->drupalCreateUser(); + + // Delete all current URL aliases. + $this->deleteAllAliases(); + + $edit = array( + 'operation' => 'pathauto_update_alias', + "accounts[{$account->uid}]" => TRUE, + ); + $this->drupalPost('admin/people', $edit, t('Update')); + $this->assertText('Updated URL alias for 1 user account.'); + + $this->assertEntityAlias('user', $account, 'users/' . drupal_strtolower($account->name)); + $this->assertEntityAlias('user', $this->admin_user, 'user/' . $this->admin_user->uid); + } + + function testSettingsValidation() { + $edit = array(); + $edit['pathauto_max_length'] = 'abc'; + $edit['pathauto_max_component_length'] = 'abc'; + $this->drupalPost('admin/config/search/path/settings', $edit, 'Save configuration'); + $this->assertText('The field Maximum alias length is not a valid number.'); + $this->assertText('The field Maximum component length is not a valid number.'); + $this->assertNoText('The configuration options have been saved.'); + + $edit['pathauto_max_length'] = '0'; + $edit['pathauto_max_component_length'] = '0'; + $this->drupalPost('admin/config/search/path/settings', $edit, 'Save configuration'); + $this->assertText('The field Maximum alias length cannot be less than 1.'); + $this->assertText('The field Maximum component length cannot be less than 1.'); + $this->assertNoText('The configuration options have been saved.'); + + $edit['pathauto_max_length'] = '999'; + $edit['pathauto_max_component_length'] = '999'; + $this->drupalPost('admin/config/search/path/settings', $edit, 'Save configuration'); + $this->assertText('The field Maximum alias length cannot be greater than 255.'); + $this->assertText('The field Maximum component length cannot be greater than 255.'); + $this->assertNoText('The configuration options have been saved.'); + + $edit['pathauto_max_length'] = '50'; + $edit['pathauto_max_component_length'] = '50'; + $this->drupalPost('admin/config/search/path/settings', $edit, 'Save configuration'); + $this->assertText('The configuration options have been saved.'); + } + + function testPatternsValidation() { + $edit = array(); + $edit['pathauto_node_pattern'] = '[node:title]/[user:name]/[term:name]'; + $edit['pathauto_node_page_pattern'] = 'page'; + $this->drupalPost('admin/config/search/path/patterns', $edit, 'Save configuration'); + $this->assertText('The Default path pattern (applies to all content types with blank patterns below) is using the following invalid tokens: [user:name], [term:name].'); + $this->assertText('The Pattern for all Basic page paths cannot contain fewer than one token.'); + $this->assertNoText('The configuration options have been saved.'); + + $edit['pathauto_node_pattern'] = '[node:title]'; + $edit['pathauto_node_page_pattern'] = 'page/[node:title]'; + $edit['pathauto_node_article_pattern'] = ''; + $this->drupalPost('admin/config/search/path/patterns', $edit, 'Save configuration'); + $this->assertText('The configuration options have been saved.'); + } +} + +class PathautoLocaleTestCase extends PathautoFunctionalTestHelper { + public static function getInfo() { + return array( + 'name' => 'Pathauto localization tests', + 'description' => 'Test pathauto functionality with localization and translation.', + 'group' => 'Pathauto', + 'dependencies' => array('token'), + ); + } + + function setUp(array $modules = array()) { + $modules[] = 'locale'; + $modules[] = 'translation'; + parent::setUp($modules, array('administer languages')); + + // Add predefined French language and reset the locale cache. + require_once DRUPAL_ROOT . '/includes/locale.inc'; + locale_add_language('fr', NULL, NULL, LANGUAGE_LTR, '', 'fr'); + drupal_language_initialize(); + } + + /** + * Test that when an English node is updated, its old English alias is + * updated and its newer French alias is left intact. + */ + function testLanguageAliases() { + $node = array( + 'title' => 'English node', + 'language' => 'en', + 'body' => array('en' => array(array())), + 'path' => array( + 'alias' => 'english-node', + 'pathauto' => FALSE, + ), + ); + $node = $this->drupalCreateNode($node); + $english_alias = path_load(array('alias' => 'english-node', 'language' => 'en')); + $this->assertTrue($english_alias, 'Alias created with proper language.'); + + // Also save a French alias that should not be left alone, even though + // it is the newer alias. + $this->saveEntityAlias('node', $node, 'french-node', 'fr'); + + // Add an alias with the soon-to-be generated alias, causing the upcoming + // alias update to generate a unique alias with the '-0' suffix. + $this->saveAlias('node/invalid', 'content/english-node', LANGUAGE_NONE); + + // Update the node, triggering a change in the English alias. + $node->path['pathauto'] = TRUE; + pathauto_node_update($node); + + // Check that the new English alias replaced the old one. + $this->assertEntityAlias('node', $node, 'content/english-node-0', 'en'); + $this->assertEntityAlias('node', $node, 'french-node', 'fr'); + $this->assertAliasExists(array('pid' => $english_alias['pid'], 'alias' => 'content/english-node-0')); + } +} + +/** + * Bulk update functionality tests. + */ +class PathautoBulkUpdateTestCase extends PathautoFunctionalTestHelper { + private $nodes; + + public static function getInfo() { + return array( + 'name' => 'Pathauto bulk updating', + 'description' => 'Tests bulk updating of URL aliases.', + 'group' => 'Pathauto', + 'dependencies' => array('token'), + ); + } + + function testBulkUpdate() { + // Create some nodes. + $this->nodes = array(); + for ($i = 1; $i <= 5; $i++) { + $node = $this->drupalCreateNode(); + $this->nodes[$node->nid] = $node; + } + + // Clear out all aliases. + $this->deleteAllAliases(); + + // Bulk create aliases. + $edit = array( + 'update[node_pathauto_bulk_update_batch_process]' => TRUE, + 'update[user_pathauto_bulk_update_batch_process]' => TRUE, + ); + $this->drupalPost('admin/config/search/path/update_bulk', $edit, t('Update')); + $this->assertText('Generated 7 URL aliases.'); // 5 nodes + 2 users + + // Check that aliases have actually been created. + foreach ($this->nodes as $node) { + $this->assertEntityAliasExists('node', $node); + } + $this->assertEntityAliasExists('user', $this->admin_user); + + // Add a new node. + $new_node = $this->drupalCreateNode(array('path' => array('alias' => '', 'pathauto' => FALSE))); + + // Run the update again which should only run against the new node. + $this->drupalPost('admin/config/search/path/update_bulk', $edit, t('Update')); + $this->assertText('Generated 1 URL alias.'); // 1 node + 0 users + + $this->assertEntityAliasExists('node', $new_node); + } +} diff --git a/sites/all/modules/pathauto/translations/ru.po b/sites/all/modules/pathauto/translations/ru.po new file mode 100644 index 0000000000000000000000000000000000000000..11d02761813565e6ff2d1a262421136772201117 --- /dev/null +++ b/sites/all/modules/pathauto/translations/ru.po @@ -0,0 +1,620 @@ +msgid "" +msgstr "" +"Project-Id-Version: Pathauto 5.x-1.1\n" +"POT-Creation-Date: \n" +"PO-Revision-Date: 2007-03-20 12:30+0300\n" +"Last-Translator: SadhooKlay <sadhoo@mail.ru>\n" +"Language-Team: SadhooKlay <klay@sadhoo.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: Plural-Forms: nplurals=3; plural=((((n%10)==1)&&((n%100)!=11))?(0):(((((n%10)>=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));\\n\n" +"X-Poedit-Language: Russian\n" +"X-Poedit-Country: RUSSIAN FEDERATION\n" +"X-Poedit-SourceCharset: utf-8\n" + +#: pathauto.module:19 +msgid "" +"<p>Provides a mechanism for modules to automatically generate aliases for the content they manage.</p>\n" +" <h2>Settings</h2>\n" +" <p>The <strong>Maximum Alias Length</strong> and <strong>Maximum component length</strong> values\n" +" default to 100 and have a limit of 128 from pathauto. This length is limited by the length of the dst \n" +" column of the url_alias database table. The default database schema for this column is 128. If you \n" +" set a length that is equal to that of the one set in the dst column it will cause problems in situations \n" +" where the system needs to append additional words to the aliased URL. For example... URLs generated\n" +" for feeds will have '/feed' added to the end. You should enter a value that is the length of the dst\n" +" column minus the length of any strings that might get added to the end of the URL. The length of \n" +" strings that might get added to the end of your URLs depends on which modules you have enabled and \n" +" on your Pathauto settings. The recommended and default value is 100.</p>" +msgstr "" +"<p>Модуль pathauto позволяет использовать функцию автоматического создания синонимов (alias) для других модулей, на основании указанных критериев, с централизованной настройкой путей для администраторов сайта.</p><p>Поддерживаются базовые типы материалов: узлы, термы таксономии, пользователи, и блоги.</p><p>Помимо создания более осмысленного адреса страницы, говорящего о его содержимом, чем просто \"node/138\", также важно знать, что современные механизмы поиска придают большое значение словам, появляющимся в URL страницы. Автоматическое использование ключевых слов, берущихся из самого содержания страницы в URL, значительно увеличивают соответствующие индексы популярности в механизмах поиска для вашей страницы.</p>\n" +"<h2>Настройки</h2>\n" +"<p><strong>Максимальная длина синонима</strong> и <strong>Максимальная длина компонента</strong> по умолчанию установленны в 100 символов, лимит этих значений равен 128 символам. Эти значения ограничены величиной колонки dts в таблице url_alias. Если вы установите это значение равное максимальному в базе данных могут появиться проблемы в ситуации, когда системе потребуется добавить дополнительные слова в адресную строку, построенную из синонимаа. Например, адрес сгенерированный для лент новостей будет иметь добавленное окончание '/feed'. Вы должны указать это значение исходя из простой формулы: <em>Длина поля dst минус длина любой строки, которая может быть добавлена в конце, как в случае с окончанием '/feed'.</em> Длина строки, которая может быть добавлена в конце, зависит от того, какие модули у вас включены и от настроек Pathauto.</p><p><strong>Рекомендуется оставить значение по умолчанию в виде 100 символов.</strong></p>" + +#: pathauto.module:44 +msgid "You are not authorized to access the pathauto settings." +msgstr "Авторизируйтесь, чтобы получить доступ к настройкам синонимов." + +#: pathauto.module:64 +msgid "General settings" +msgstr "Основные настройки" + +#: pathauto.module:69 +msgid "Verbose" +msgstr "Подробности" + +#: pathauto.module:71 +msgid "Display alias changes (except during bulk updates)." +msgstr "Отобразить изменения синонимов (за исключением ситуации больших объемов обновлений)" + +#: pathauto.module:74 +msgid "Separator" +msgstr "Разделитель" + +#: pathauto.module:76 +msgid "Character used to separate words in titles. This will replace any spaces and punctuation characters." +msgstr "Символ для разделения слов в строке. Им будут заменены пробелы и символы пунктуации." + +#: pathauto.module:79 +msgid "Quotation marks" +msgstr "Использование кавычек" + +#: pathauto.module:81 +msgid "Remove" +msgstr "Удалить" + +#: pathauto.module:81 +msgid "Replace by separator" +msgstr "Заменять разделителем" + +#: pathauto.module:85 +msgid "Maximum alias length" +msgstr "Максимальная длина синонима" + +#: pathauto.module:87 +msgid "Maximum length of aliases to generate. 100 is recommended. See <a href=\"@pathauto-help\">Pathauto help</a> for details." +msgstr "Максимальная длина генерируемого синонима. Рекомендуемое значение 100. Подробнее смотрите в <a href=\"@pathauto-help\">справке по автопсевдонимам</a>." + +#: pathauto.module:90 +msgid "Maximum component length" +msgstr "Максимальная длина компонента" + +#: pathauto.module:92 +msgid "Maximum text length of any component in the alias (e.g., [title]). 100 is recommended. See <a href=\"@pathauto-help\">Pathauto help</a> for details." +msgstr "Максимальная длина текста любого компонента в синониме (например [title]).Рекомендуемое значение 100. Подробнее смотрите в <a href=\"@pathauto-help\">справке по синонимам</a>." + +#: pathauto.module:95 +msgid "Create index aliases" +msgstr "Создать индекс синонимов" + +#: pathauto.module:97 +msgid "When a pattern generates a hierarchical alias (i.e., any alias containing a slash), generate aliases for each step of the hierarchy which can be used to list content within that hierarchy. For example, if a node alias \"music/concert/beethoven\" is created, also create an alias \"music/concert\" which will list all concert nodes, and an alias \"music\" which will list all music nodes." +msgstr "Если шаблон генерирует иерархические синонимы (т.е. каждый синоним разделен слэшем), будут созданы синонимы для каждой ступени иерархии, которая может быть использована для отображения в списке этой иерархии. Например, если синоним материала создан как \"music/concert/beethoven\", так же создастся синоним \"music/concert\", в котором будут перечислены все материалы типа \"концерт\" , и синоним \"music\", который будет адресовать всё перечисление музыкальных материалов." + +#: pathauto.module:103 +msgid "Bulk generate index aliases" +msgstr "Массовое создание индекса синонимов" + +#: pathauto.module:105 +msgid "Generate index aliases based on all pre-existing aliases." +msgstr "Создать индекс синонимов на основе всех ранее существовавших." + +#: pathauto.module:108 +msgid "Update action" +msgstr "Обновить действие" + +#: pathauto.module:109 +msgid "Do nothing, leaving the old alias intact" +msgstr "Ничего не делать, оставив в старый синоним без изменений" + +#: pathauto.module:110 +msgid "Create a new alias in addition to the old alias" +msgstr "Создать новый синоним в дополнение к уже существующим" + +#: pathauto.module:111 +msgid "Create a new alias, replacing the old one" +msgstr "Создать новый синоним, заменяя старый" + +#: pathauto.module:112 +msgid "What should pathauto do when updating an existing content item which already has an alias?" +msgstr "Что предпринять при обновлении существующего материала, если он имеет синоним?" + +#: pathauto.module:115 +msgid "Strings to Remove" +msgstr "Строки для удаления" + +#: pathauto.module:116 +msgid "Words to strip out of the URL alias, separated by commas" +msgstr "Слова, разделенные запятой, не включаемые в синоним адреса." + +#: pathauto.module:193 +msgid "Create feed aliases" +msgstr "Создавать синонимы лент" + +#: pathauto.module:195 +msgid "Also generate aliases for RSS feeds." +msgstr "Генерировать синонимы также и для RSS лент" + +#: pathauto.module:423 +msgid "Ignoring alias " +msgstr "Игнорирование синонима" + +#: pathauto.module:423 +msgid " due to existing path conflict" +msgstr " из-за существующих конфликтов путей" + +#: pathauto.module:433 +msgid "Created new alias %dst for %src, replacing %oldalias" +msgstr "Создан новый синоним %dst для %src, заменя %oldalias" + +#: pathauto.module:436 +msgid "Created new alias %dst for %src" +msgstr "Создан новый синоним %dst для %src" + +#: pathauto.module:471 +#: ;486 pathauto.info:0 +msgid "Pathauto" +msgstr "Синонимы" + +#: pathauto.module:472 +msgid "Configure how pathauto generates clean URLs for your content." +msgstr "Настроить как будут генерироваться чистые ссылки для содержимого сайта." + +#: pathauto.module:201 +#, fuzzy +msgid "Bulk update of index aliases completed, one alias generated." +msgid_plural "Bulk update of index aliases completed, @count aliases generated." +msgstr[0] "Массовое обновление индекса синонимов завершено, @count синоним сгенерирован." +msgstr[1] "Массовое обновление индекса синонимов завершено, @count синонима сгенерировано." +msgstr[2] "Массовое обновление индекса синонимов завершено, @count[2] синонимов сгенерировано." + +#: pathauto.module:37 +msgid "administer pathauto" +msgstr "управлять синонимами" + +#: pathauto.module:0 +#: pathauto.info:0 +msgid "pathauto" +msgstr "pathauto" + +#: pathauto.info:0 +msgid "Provides a mechanism for modules to automatically generate aliases for the content they manage." +msgstr "Предоставляет модулям механизм автоматического генерирования синонимов (alias) для управляемого модулями содержимого." + +#: pathauto_node.inc:13 +msgid "Node path settings" +msgstr "Настройки адреса материала" + +#: pathauto_node.inc:14 +msgid "Default path pattern (applies to all node types with blank patterns below)" +msgstr "Шаблон адреса по умолчанию (будет применено ко всем типам материалов ниже, для которых шаблоны не определены)" + +#: ;21;116 +msgid "[title]" +msgstr "[title]" + +#: pathauto_node.inc:17 +msgid "Bulk update node paths" +msgstr "Массовое обновление адресов материалов" + +#: pathauto_node.inc:19 +msgid "Generate aliases for all existing nodes which do not already have aliases." +msgstr "Сгенерировать синонимы для всех существующих материалов не имеющих синонимы" + +#: pathauto_node.inc:21 +msgid "The title of the node, with spaces and punctuation replaced by the separator." +msgstr "Заголовок материала. Пробельные символы и знаки пунктуации будут заменены разделителем." + +#: pathauto_node.inc:22 +#: ;126 +msgid "[nid]" +msgstr "[nid]" + +#: pathauto_node.inc:22 +msgid "The id number of the node." +msgstr "Идентификационный номер (ID) материала." + +#: pathauto_node.inc:23 +msgid "The name of the user who created the node." +msgstr "Имя пользователя, создавшего материал." + +#: pathauto_node.inc:24 +#: ;127 +msgid "[type]" +msgstr "[type]" + +#: pathauto_node.inc:24 +msgid "The node type (e.g., \"page\", \"story\", etc.)." +msgstr "Тип материала (например \"page\", \"story\" и т.д.)" + +#: pathauto_node.inc:25 +#: ;117 +msgid "[yyyy]" +msgstr "[yyyy]" + +#: pathauto_node.inc:25 +msgid "The year the node was created." +msgstr "Год создания материала" + +#: pathauto_node.inc:26 +#: ;118 +msgid "[mm]" +msgstr "[mm]" + +#: pathauto_node.inc:26 +msgid "The two-digit month (01-12) the node was created." +msgstr "Месяц создания материала, состоящий из двух цифр (01-12)" + +#: pathauto_node.inc:27 +#: ;119 +msgid "[mon]" +msgstr "[mon]" + +#: pathauto_node.inc:27 +msgid "The three-letter month (jan-dec) the node was created." +msgstr "Трехбуквенный (янв-дек) месяц создания материала." + +#: pathauto_node.inc:28 +#: ;120 +msgid "[dd]" +msgstr "[dd]" + +#: pathauto_node.inc:28 +msgid "The two-digit day of the month (00-31) the node was created." +msgstr "День создания материала, состоящий из двух цифр (00-31)" + +#: pathauto_node.inc:29 +#: ;121 +msgid "[day]" +msgstr "[day]" + +#: pathauto_node.inc:29 +msgid "The three-letter day of the week (sun-sat) that the node was created." +msgstr "Трехбуквенный (пон-вос) день недели создания материала." + +#: pathauto_node.inc:30 +#: ;122 +msgid "[hour]" +msgstr "[hour]" + +#: pathauto_node.inc:30 +msgid "The two-digit hour (00-23) the node was created." +msgstr "Час создания материала, состоящий из двух цифр (00-23)" + +#: pathauto_node.inc:31 +#: ;123 +msgid "[min]" +msgstr "[min]" + +#: pathauto_node.inc:31 +msgid "The two-digit minute (00-59) the node was created." +msgstr "Минута создания материала, состоящяя из двух цифр (00-59)" + +#: pathauto_node.inc:32 +#: ;124 +msgid "[sec]" +msgstr "[sec]" + +#: pathauto_node.inc:32 +msgid "The two-digit second (00-59) the node was created." +msgstr "Секунда создания материала, состоящяя из двух цифр (00-59)" + +#: pathauto_node.inc:33 +#: ;125 +msgid "[week]" +msgstr "[week]" + +#: pathauto_node.inc:33 +msgid "The week number (1-52) of the year the node was created." +msgstr "Номер недели (1-52) создания материала." + +#: pathauto_node.inc:42 +msgid "The name of the lowest-weight category that the page belongs to." +msgstr "Синоним для самой легкой по весу категории, к которой принадлежит страница." + +#: pathauto_node.inc:45 +#: ;221;223;237 +msgid "[catalias]" +msgstr "[catalias]" + +#: pathauto_node.inc:46 +msgid "The alias for the lowest-weight category that the page belongs to. This is useful for long category names. You must first set up aliases for your categories." +msgstr "Синоним для самой легкой по весу категории, к которой принадлежит страница. Это полезно для категорий с длинными именами. Сперва вы должны назначить вес для категорий." + +#: pathauto_node.inc:51 +#: ;137;151 +msgid "[book]" +msgstr "[book]" + +#: pathauto_node.inc:52 +msgid "For book pages, the title of the top-level book." +msgstr "Для страниц подшивок, заголовок верхнего уровня подшивки." + +#: pathauto_node.inc:53 +#: ;148;152 +msgid "[bookpath]" +msgstr "[bookpath]" + +#: pathauto_node.inc:54 +msgid "For book pages, the full hierarchy from the top-level book." +msgstr "Для страниц подшивок, полная иерархия с верхнего уровня подшивки." + +#: pathauto_node.inc:63 +msgid "Pattern for all " +msgstr "Шаблон адреса для всех материалов типа \"" + +#: pathauto_node.inc:63 +msgid " paths" +msgstr "\"" + +#: pathauto_node.inc:101 +msgid "An alias will be automatically generated from the title and other node attributes, in addition to any alias manually provided above." +msgstr "Синоним будет автоматически сгенерирован из заголовка и атрибутов материала, в дополнение к всем синонимам настраиваемым вручную выше." + +#: pathauto_node.inc:103 +msgid " To control the format of the generated aliases, see the <a href=\"admin/settings/pathauto\">pathauto settings</a>." +msgstr "Для контролирования и понимания формата генерирования синонимов смотрите <a href=\"admin/settings/pathauto\">справку по автопсевдонимам</a>." + +#: pathauto_node.inc:273 +msgid "Bulk update of nodes completed, one alias generated." +msgid_plural "Bulk update of nodes completed, @count aliases generated." +msgstr[0] "Массовое обновление материалов завершено, @count синоним сгенерирован." +msgstr[1] "Массовое обновление материалов завершено, @count синонима сгенерированно." +msgstr[2] "Массовое обновление материалов завершено, @count[2] синонимов сгенерированно." + +#: contrib/pathauto_node_event.inc:12 +#: ;22 +msgid "[eventyyyy]" +msgstr "[eventyyyy]" + +#: contrib/pathauto_node_event.inc:12 +msgid "The year the event starts." +msgstr "Год старта события." + +#: contrib/pathauto_node_event.inc:13 +#: ;23 +msgid "[eventmm]" +msgstr "[eventmm]" + +#: contrib/pathauto_node_event.inc:13 +msgid "The two-digit month (01-12) the event starts." +msgstr "Месяц старта события, состоящий из двух цифр (01-12)." + +#: contrib/pathauto_node_event.inc:14 +#: ;24 +msgid "[eventmon]" +msgstr "[eventmon]" + +#: contrib/pathauto_node_event.inc:14 +msgid "The three-letter month (jan-dec) the event starts." +msgstr "Трехбуквенный месяц (янв-дек) старта события." + +#: contrib/pathauto_node_event.inc:15 +#: ;25 +msgid "[eventdd]" +msgstr "[eventdd]" + +#: contrib/pathauto_node_event.inc:15 +msgid "The two-digit day of the month (00-31) the event starts." +msgstr "День месяца старта события, состоящий из двух цифр (01-12)." + +#: contrib/pathauto_node_event.inc:16 +#: ;26 +msgid "[eventday]" +msgstr "[eventday]" + +#: contrib/pathauto_node_event.inc:16 +msgid "The three-letter day of the week (sun-sat) the event starts." +msgstr "Трехбуквенный день недели (пон-вос) старта события." + +#: contrib/pathauto_node_event.inc:17 +#: ;27 +msgid "[eventweek]" +msgstr "[eventweek]" + +#: contrib/pathauto_node_event.inc:17 +msgid "The week number (1-52) of the year the event starts." +msgstr "Номер недели (1-52) старта события." + +#: pathauto_taxonomy.inc:13 +msgid "Category path settings" +msgstr "Настройки адреса категории" + +#: pathauto_taxonomy.inc:14 +msgid "Default path pattern (applies to all vocabularies with blank patterns below)" +msgstr "Шаблон адреса по умолчанию (применяется ко всем словарям ниже, для которых шаблоны не определены)" + +#: pathauto_taxonomy.inc:15 +msgid "[vocab]/[catpath]" +msgstr "[vocab]/[catpath]" + +#: pathauto_taxonomy.inc:18 +msgid "The name of the category." +msgstr "Имя категории" + +#: pathauto_taxonomy.inc:20 +#: ;65;111 +msgid "[tid]" +msgstr "[tid]" + +#: pathauto_taxonomy.inc:20 +msgid "The id number of the category." +msgstr "Идентификационный номер категории" + +#: pathauto_taxonomy.inc:26 +msgid "Bulk update category paths" +msgstr "Массовое обновление адресов категорий" + +#: pathauto_taxonomy.inc:27 +msgid "Generate aliases for all existing categories which do not already have aliases." +msgstr "Сгенерировать синонимы для всех существующих категорий у которых еще нет синонимов." + +#: pathauto_taxonomy.inc:34 +msgid "Pattern for all %vocab-name paths" +msgstr "Шаблон для все адресов типа \"%vocab-name\"" + +#: pathauto_taxonomy.inc:145 +msgid "Bulk update of terms completed, one alias generated." +msgid_plural "Bulk update of terms completed, @count aliases generated." +msgstr[0] "Массовое обновление терминов завершено, @count синоним сгенерирован." +msgstr[1] "Массовое обновление терминов завершено, @count синонима сгенерированно." +msgstr[2] "Массовое обновление терминов завершено, @count синонимов сгенерированно." +#: pathauto_user.inc:12 +msgid "User path settings" +msgstr "Настройки адреса пользователя" + +#: pathauto_user.inc:13 +msgid "Pattern for user account page paths" +msgstr "Шаблон для адресов пользовательских страниц" + +#: pathauto_user.inc:14 +msgid "user/[user]" +msgstr "user/[user]" + +#: pathauto_user.inc:16 +#: ;39;63 +msgid "The name of the user." +msgstr "Имя пользователя." + +#: pathauto_user.inc:17 +#: ;40;64;89;131;154;177 +msgid "[uid]" +msgstr "[uid]" + +#: pathauto_user.inc:17 +#: ;40;64 +msgid "The id number of the user." +msgstr "Идентификационный номер пользователя." + +#: pathauto_user.inc:19 +msgid "Bulk update user paths" +msgstr "Массовое обновление адресов пользователей" + +#: pathauto_user.inc:20 +msgid "Generate aliases for all existing user account pages which do not already have aliases." +msgstr "Сгенерировать синонимы для всех существующих пользоветелей которые не имеют синонимов." + +#: pathauto_user.inc:35 +msgid "Blog path settings" +msgstr "Настройки адреса блога" + +#: pathauto_user.inc:36 +msgid "Pattern for blog page paths" +msgstr "Шаблон для адресов страницы блога" + +#: pathauto_user.inc:37 +msgid "blog/[user]" +msgstr "blog/[user]" + +#: pathauto_user.inc:43 +msgid "Bulk update blog paths" +msgstr "Массовое обновление адресов блогов" + +#: pathauto_user.inc:44 +msgid "Generate aliases for all existing blog pages which do not already have aliases." +msgstr "Сгенирировать синонимы для всех существующих страниц блогов которые не имеют синонимов." + +#: pathauto_user.inc:59 +msgid "User-tracker path settings" +msgstr "Настройки адреса пользовательского трекера" + +#: pathauto_user.inc:60 +msgid "Pattern for user-tracker page paths" +msgstr "Шаблон для адреса страницы пользовательского трекера" + +#: pathauto_user.inc:61 +msgid "user/[user]/track" +msgstr "user/[user]/track" + +#: pathauto_user.inc:67 +msgid "Bulk update user-tracker paths" +msgstr "Массовое обновление адресов пользовательских трекеров" + +#: pathauto_user.inc:68 +msgid "Generate aliases for all existing user-tracker pages which do not already have aliases." +msgstr "Сгенерировать синонимы для всех существующих пользоветельских трекеров которые не имеют синонимов." + +#: pathauto_user.inc:139 +#, fuzzy +msgid "Bulk update of users completed, one alias generated." +msgid_plural "Bulk update of users completed, @count aliases generated." +msgstr[0] "Массовое обновление пользователей завершено, @count синоним сгенерирован." +msgstr[1] "Массовое обновление пользователей завершено, @count синонима сгенерировано." +msgstr[2] "Массовое обновление пользователей завершено, @count синонимов сгенерировано." + +#: pathauto_user.inc:162 +#, fuzzy +msgid "Bulk update of user blogs completed, one alias generated." +msgid_plural "Bulk update of user blogs completed, @count aliases generated." +msgstr[0] "Массовое обновление пользовательских блогов завершено, @count синоним сгенерирован." +msgstr[1] "Массовое обновление пользовательских блогов завершено, @count синонима сгенерировано." +msgstr[2] "Массовое обновление пользовательских блогов завершено, @count синонимов сгенерировано." + +#: pathauto_user.inc:185 +msgid "Bulk update of user tracker pages completed, one alias generated." +msgid_plural "Bulk update of user tracker pages completed, @count aliases generated." +msgstr[0] "Массовое обновление страниц пользовательских трекеров завершено, @count синоним сгенерирован." +msgstr[1] "Массовое обновление страниц пользовательских трекеров завершено, @count синонима сгенерировано." +msgstr[2] "Массовое обновление страниц пользовательских трекеров завершено, @count синонимов сгенерировано." + +#: pathauto_node.inc:23 +#: ;132 pathauto_user.inc:16 +#: ;39;63;88;130;153;176 +msgid "[user]" +msgstr "[user]" + +#: pathauto_node.inc:39 +#: ;219;236 pathauto_taxonomy.inc:17 +#: ;62;109 +msgid "[vocab]" +msgstr "[vocab]" + +#: pathauto_node.inc:40 +#: pathauto_taxonomy.inc:17 +msgid "The vocabulary that the page's first category belongs to." +msgstr "Словарь страницы, к которой принадлежит первая категория." + +#: pathauto_node.inc:41 +#: ;215;234 pathauto_taxonomy.inc:18 +#: ;64;110 +msgid "[cat]" +msgstr "[cat]" + +#: pathauto_node.inc:43 +#: ;231;235 pathauto_taxonomy.inc:19 +#: ;72 +msgid "[catpath]" +msgstr "[catpath]" + +#: pathauto_node.inc:44 +#: pathauto_taxonomy.inc:19 +msgid "As [cat], but including its supercategories." +msgstr "Как и [cat], но в том числе с супер категориями." + +#: pathauto_menu.inc:18 +#: ;53;56 +msgid "[menu]" +msgstr "[menu]" + +#: pathauto_menu.inc:19 +msgid "The name of the menu the node belongs to." +msgstr "Имя меню к которому пренадлежит материал." + +#: pathauto_menu.inc:20 +#: ;60;63 +msgid "[menupath]" +msgstr "[menupath]" + +#: pathauto_menu.inc:21 +msgid "The menu path (as reflected in the breadcrumb), not including Home or [menu]." +msgstr "Путь меню (как это отражено в breadcrumb), исключая главную страницу сайта или [menu]." + +#: contrib/pathauto_node_i18n.inc:12 +#: ;16 +msgid "[lang]" +msgstr "[lang]" + +#: contrib/pathauto_node_i18n.inc:12 +msgid "Language code of the document" +msgstr "Языковой код документа" + diff --git a/sites/all/modules/pathauto/translations/sk.po b/sites/all/modules/pathauto/translations/sk.po new file mode 100644 index 0000000000000000000000000000000000000000..b395429906ed145444fed14ceeaeaeb2372f5b7e --- /dev/null +++ b/sites/all/modules/pathauto/translations/sk.po @@ -0,0 +1,1173 @@ +# Slovak translation of Pathauto (all releases) +# Copyright (c) 2008 by the Slovak translation team +# Generated from files: +# pathauto.module,v 1.44.4.101 2008/06/24 16:04:11 greggles +# pathauto.module,v 1.118 2008/06/20 20:01:01 greggles +# pathauto.module,v 1.44.4.88 2008/04/08 21:32:45 greggles +# pathauto.module,v 1.44.4.83 2008/02/20 10:24:27 greggles +# pathauto.admin.inc,v 1.10.2.1 2008/06/24 16:07:51 greggles +# pathauto_node.inc,v 1.47 2008/06/10 21:41:53 freso +# pathauto.inc,v 1.1.2.51 2008/06/18 20:05:08 greggles +# pathauto.inc,v 1.45 2008/06/18 20:02:40 greggles +# pathauto.inc,v 1.1.2.38 2008/04/05 18:51:02 greggles +# pathauto.inc,v 1.1.2.36 2008/02/19 16:52:27 greggles +# pathauto.info,v 1.3.4.4 2007/06/18 23:17:13 dww +# pathauto.info,v 1.4 2007/10/28 21:06:13 greggles +# pathauto_node.inc,v 1.29.4.32 2008/06/12 18:51:38 freso +# pathauto_node.inc,v 1.29.4.27 2008/04/08 12:29:59 greggles +# pathauto_node.inc,v 1.29.4.25 2008/02/03 12:31:32 greggles +# pathauto_taxonomy.inc,v 1.20.4.32 2008/06/12 18:51:38 freso +# pathauto_taxonomy.inc,v 1.20.4.24 2008/04/08 12:29:59 greggles +# pathauto_taxonomy.inc,v 1.20.4.20 2007/12/16 21:57:13 greggles +# pathauto_taxonomy.inc,v 1.39 2008/06/10 21:41:53 freso +# pathauto_user.inc,v 1.17.4.22 2008/05/30 00:58:55 freso +# pathauto_user.inc,v 1.28 2008/05/28 16:04:35 freso +# pathauto_user.inc,v 1.17.4.19 2008/04/08 12:29:59 greggles +# pathauto_user.inc,v 1.17.4.17 2007/12/16 21:57:13 greggles +# pathauto.install,v 1.5 2008/02/13 13:05:45 greggles +# pathauto_node_event.inc,v 1.3 2006/10/12 14:54:15 greggles +# pathauto_node_i18n.inc,v 1.3 2006/10/12 14:54:15 greggles +# +msgid "" +msgstr "" +"Project-Id-Version: Pathauto (all releases)\n" +"POT-Creation-Date: 2008-12-01 09:37+0100\n" +"PO-Revision-Date: 2008-12-01 09:29+0100\n" +"Language-Team: Slovak\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=((n==1)?(0):(((n>=2)&&(n<=4))?(1):2));\n" + +#: pathauto.module:191,543,188,549,553,553 +msgid "content" +msgstr "obsah" + +#: pathauto.module:434,96,96,99,99; pathauto.admin.inc:38 +msgid "General settings" +msgstr "Hlavné nastavenia" + +#: pathauto.module:557,191,191,194,194; pathauto.admin.inc:161 +msgid "Remove" +msgstr "Odstrániť" + +#: pathauto_node.inc:79,37 +msgid "Language neutral" +msgstr "Nezávislé od jazyka" + +#: pathauto.inc:374,361,381,354,356,356 +msgid "Ignoring alias %dst due to existing path conflict." +msgstr "" +"Alias %dst bude ignorovaný, pretože má konflikt s existujúcou " +"cestou." + +#: pathauto.inc:368,361,363,363 +msgid "Ignoring alias %dst because it is the same as the internal path." +msgstr "Alias %dst bude ignorovaný, pretože je totožný s internou cestou." + +#: pathauto.inc:401,388,408,381,383,383 +msgid "" +"Created new alias %dst for %src, replacing %old_alias. %old_alias now " +"redirects to %dst" +msgstr "" +"Vytvorený nový alias %dst pre %src, prepísaný %old_alias. " +"%old_alias je teraz presmerovaný na %dst" + +#: pathauto.inc:404,391,411,384,386,386 +msgid "Created new alias %dst for %src, replacing %old_alias" +msgstr "Vytvorený nový alias %dst pre %src, prepísaný %old_alias" + +#: pathauto.inc:407,394,414,387,389,389 +msgid "Created new alias %dst for %src" +msgstr "Vytvorený nový alias %dst pre %src" + +#: pathauto.inc:430,418,437,411,413,413 +msgid "" +"It appears that you have installed Pathauto, which depends on token, " +"but token is either not installed or not installed properly." +msgstr "" +"Zdá sa že máte nainštalované Pathauto, ktoré zavisí na module " +"token, ale buď token nieje nainštalovaný alebo je nainštalovaný " +"nesprávne." + +#: pathauto.inc:470,454,477,447,449,449 +msgid "Double quotes \"" +msgstr "Úvodzovky \"" + +#: pathauto.inc:471,455,478,448,450,450 +msgid "Single quotes (apostrophe) '" +msgstr "Jednoduché úvodzovky (apostrof) '" + +#: pathauto.inc:472,456,479,449,451,451 +msgid "Back tick `" +msgstr "Spätná úvodzovka `" + +#: pathauto.inc:473,457,480,450,452,452 +msgid "Comma ," +msgstr "Čiarka ," + +#: pathauto.inc:474,458,481,451,453,453 +msgid "Period ." +msgstr "Bodka ." + +#: pathauto.inc:475,459,482,452,454,454 +msgid "Hyphen -" +msgstr "Pomlčka -" + +#: pathauto.inc:476,460,483,453,455,455 +msgid "Underscore _" +msgstr "Podčiarkovník _" + +#: pathauto.inc:477,461,484,454,456,456 +msgid "Colon :" +msgstr "Dvojbodka :" + +#: pathauto.inc:478,462,485,455,457,457 +msgid "Semicolon ;" +msgstr "Bodkočiarka ;" + +#: pathauto.inc:479,463,486,456,458,458 +msgid "Pipe |" +msgstr "Zvislý oddelovač |" + +#: pathauto.inc:480,464,487,457,459,459 +msgid "Left curly bracket {" +msgstr "Ľavá zložená zátvorka {" + +#: pathauto.inc:481,465,488,458,460,460 +msgid "Left square bracket [" +msgstr "Ľavá hranatá zátvorka [" + +#: pathauto.inc:482,466,489,459,461,461 +msgid "Right curly bracket }" +msgstr "Pravá zložená zátvorka {" + +#: pathauto.inc:483,467,490,460,462,462 +msgid "Right square bracket ]" +msgstr "Pravá hranatá zátvorka ]" + +#: pathauto.inc:484,468,491,461,463,463 +msgid "Plus +" +msgstr "Plus +" + +#: pathauto.inc:485,469,492,462,464,464 +msgid "Equal =" +msgstr "Rovná sa =" + +#: pathauto.inc:486,470,493,463,465,465 +msgid "Asterisk *" +msgstr "Hviezdička *" + +#: pathauto.inc:487,471,494,464,466,466 +msgid "Ampersand &" +msgstr "Značka &" + +#: pathauto.inc:488,472,495,465,467,467 +msgid "Percent %" +msgstr "Percento %" + +#: pathauto.inc:489,473,496,466,468,468 +msgid "Caret ^" +msgstr "Strieška ^" + +#: pathauto.inc:490,474,497,467,469,469 +msgid "Dollar $" +msgstr "Dolár $" + +#: pathauto.inc:491,475,498,468,470,470 +msgid "Hash #" +msgstr "Mriežka #" + +#: pathauto.inc:492,476,499,469,471,471 +msgid "At @" +msgstr "Zavináč @" + +#: pathauto.inc:493,477,500,470,472,472 +msgid "Exclamation !" +msgstr "Výkričník !" + +#: pathauto.inc:494,478,501,471,473,473 +msgid "Tilde ~" +msgstr "Vlnovka ~" + +#: pathauto.inc:495,479,502,472,474,474 +msgid "Left parenthesis (" +msgstr "Ľavá zátvorka (" + +#: pathauto.inc:496,480,503,473,475,475 +msgid "right parenthesis )" +msgstr "pravá zátvorka )" + +#: pathauto.inc:497,481,504,474,476,476 +msgid "Question mark ?" +msgstr "Otáznik ?" + +#: pathauto.inc:498,482,505,475,477,477 +msgid "Less than <" +msgstr "Menej ako <" + +#: pathauto.inc:499,483,506,476,478,478 +msgid "Greater than >" +msgstr "Viac ako >" + +#: pathauto.inc:500,484,507,477,479,479 +msgid "Back slash \\" +msgstr "Opačné lomítko \\" + +#: pathauto.inc:430,437,413,413; pathauto.module:60,41,44,44; pathauto.info:0,0,0,0 +msgid "Pathauto" +msgstr "Pathauto" + +#: pathauto_node.inc:20,13,20,13,13 +msgid "Node path settings" +msgstr "Nastavenia ciest uzlov" + +#: pathauto_node.inc:21,14,21,14,14 +msgid "" +"Default path pattern (applies to all node types with blank patterns " +"below)" +msgstr "" +"Predvolený vzor cesty (aplikovaný na všetky typy obsahu, ktoré " +"majú prázdny vzor)" + +#: pathauto_node.inc:22,15,22,15,15 +msgid "content/[title-raw]" +msgstr "content/[title-raw]" + +#: pathauto_node.inc:23,16,23,16,16 +msgid "Bulk generate aliases for nodes that are not aliased" +msgstr "Hromadné generovanie aliasov pre obsah, ktorý zatiaľ nemá alias" + +#: pathauto_node.inc:24,17,24,17,17 +msgid "" +"Generate aliases for all existing nodes which do not already have " +"aliases." +msgstr "" +"Generovať aliasy pre všetok existujúci obsah ktorý zatiaľ nemá " +"aliasy." + +#: pathauto_node.inc:36,31,55,30,30 +msgid "Pattern for all @node_type paths" +msgstr "Vzor pre všetky cesty typu: @node_type" + +#: pathauto_node.inc:93,97,121,88,88 +msgid "Bulk generation of nodes completed, one alias generated." +msgid_plural "Bulk generation of nodes completed, @count aliases generated." +msgstr[0] "Hromadná generácia pre obsah ukončená, @count alias vygenerovaný." +msgstr[1] "" +"Hromadná generácia pre obsah ukončená, @count aliasy " +"vygenerované." +msgstr[2] "" +"Hromadná generácia pre obsah ukončená, @count aliasov " +"vygenerovaných." + +#: pathauto_taxonomy.inc:20,13,13 +msgid "Category path settings" +msgstr "Nastavenia ciest kategorií" + +#: pathauto_taxonomy.inc:21,14,21,14,14 +msgid "" +"Default path pattern (applies to all vocabularies with blank patterns " +"below)" +msgstr "" +"Predvolený vzor cesty (aplikovaný na všetky typy slovníkov, ktoré " +"majú prázdny vzor)" + +#: pathauto_taxonomy.inc:22,15,22,15,15 +msgid "category/[vocab-raw]/[catpath-raw]" +msgstr "category/[vocab-raw]/[catpath-raw]" + +#: pathauto_taxonomy.inc:32,25,25 +msgid "Bulk generate aliases for categories that are not aliased" +msgstr "Hromadné generovanie aliasov pre kategórie, ktoré nemajú aliasy" + +#: pathauto_taxonomy.inc:33,26,26 +msgid "" +"Generate aliases for all existing categories which do not already have " +"aliases." +msgstr "" +"Generovať aliasy pre všetky existujúce kategórie, ktoré zatiaľ " +"nemajú aliasy." + +#: pathauto_taxonomy.inc:42,35,42,35,35 +msgid "Pattern for all %vocab-name paths" +msgstr "Vzor pre všetky cesty %vocab-name" + +#: pathauto_taxonomy.inc:134,105,134,105,105 +msgid "Forum path settings" +msgstr "Nastavenia ciest fóra" + +#: pathauto_taxonomy.inc:135,106,135,106,106 +msgid "Pattern for forums and forum containers" +msgstr "Vzor pre cesty fóra a kontajnery fóra" + +#: pathauto_taxonomy.inc:136,107,136,107,107 +msgid "[vocab-raw]/[catpath-raw]" +msgstr "[vocab-raw]/[catpath-raw]" + +#: pathauto_taxonomy.inc:146,117,146,117,117 +msgid "Bulk generate forum paths" +msgstr "Hromadné generovanie ciest fóra" + +#: pathauto_taxonomy.inc:147,118,147,118,118 +msgid "" +"Generate aliases for all existing forums and forum containers which do " +"not already have aliases." +msgstr "" +"Generovať aliasy pre všetky existujúce fóra a kontajnery fór " +"ktoré zatiaľ nemajú aliasy." + +#: pathauto_taxonomy.inc:92,61,92,61,61 +msgid "Bulk generation of terms completed, one alias generated." +msgid_plural "Bulk generation of terms completed, @count aliases generated." +msgstr[0] "Hromadná generácia pre výrazy kompletná, @count alias generovaný." +msgstr[1] "" +"Hromadná generácia pre výrazy kompletná, @count aliasy " +"generované." +msgstr[2] "" +"Hromadná generácia pre výrazy kompletná, @count aliasov " +"generovaných." + +#: pathauto_taxonomy.inc:168,140,168,140,140 +msgid "" +"Bulk update of forums and forum containers completed, one alias " +"generated." +msgid_plural "" +"Bulk update of forums and forum containers completed, @count aliases " +"generated." +msgstr[0] "" +"Hromadná generácia pre fóra a kontajnery fór kompletná, @count " +"alias generovaný." +msgstr[1] "" +"Hromadná generácia pre fóra a kontajnery fór kompletná, @count " +"aliasy generované." +msgstr[2] "" +"Hromadná generácia pre fóra a kontajnery fór kompletná, @count " +"aliasov generovaných." + +#: pathauto_user.inc:20,13,20,13,13 +msgid "User path settings" +msgstr "Nastavenia ciest užívateľa" + +#: pathauto_user.inc:21,14,21,14,14 +msgid "Pattern for user account page paths" +msgstr "Vzor pre cesty k uživaťeľským účtom" + +#: pathauto_user.inc:22,15,22,15,15 +msgid "users/[user-raw]" +msgstr "users/[user-raw]" + +#: pathauto_user.inc:32,25,32,25,25 +msgid "Bulk generate aliases for users that are not aliased" +msgstr "" +"Hromadné generovanie aliasov pre používateľov, ktorý nemajú " +"aliasy" + +#: pathauto_user.inc:33,26,33,26,26 +msgid "" +"Generate aliases for all existing user account pages which do not " +"already have aliases." +msgstr "" +"Generovanie aliasov pre všetky existujúce stránky " +"používateľských účtov ktoré zatiaľ nemajú alias." + +#: pathauto_user.inc:49,42,49,42,42 +msgid "Blog path settings" +msgstr "Nastavenia ciest blogu" + +#: pathauto_user.inc:50,43,50,43,43 +msgid "Pattern for blog page paths" +msgstr "Vzor pre cesty stránok blogu" + +#: pathauto_user.inc:51,44,51,44,44 +msgid "blogs/[user-raw]" +msgstr "blogs/[user-raw]" + +#: pathauto_user.inc:57,50,57,50,50 +msgid "Bulk generate aliases for blogs that are not aliased" +msgstr "Hromadné generovanie aliasov pre blogy, ktoré nemajú aliasy" + +#: pathauto_user.inc:58,51,58,51,51 +msgid "" +"Generate aliases for all existing blog pages which do not already have " +"aliases." +msgstr "" +"Generovanie aliasov pre všetky existujúce blogy ktoré zatiaľ " +"nemajú alias." + +#: pathauto_user.inc:74,67,74,67,67 +msgid "User-tracker path settings" +msgstr "Nastavenia používateľských ciest" + +#: pathauto_user.inc:75,68,75,68,68 +msgid "Pattern for user-tracker page paths" +msgstr "Vzor pre cesty používateľom sledovaných stránok" + +#: pathauto_user.inc:76,69,76,69,69 +msgid "users/[user-raw]/track" +msgstr "users/[user-raw]/track" + +#: pathauto_user.inc:82,75,82,75,75 +msgid "Bulk generate aliases for user-tracker paths that are not aliased" +msgstr "" +"Hromadné generovanie aliasov pre user-tracker cesty, ktoré nemajú " +"aliasy." + +#: pathauto_user.inc:83,76,83,76,76 +msgid "" +"Generate aliases for all existing user-tracker pages which do not " +"already have aliases." +msgstr "" +"Generovanie aliasov pre všetky existujúce user-tracker stránky, " +"ktoré zatiaľ nemajú alias." + +#: pathauto_user.inc:107,100,107,100,100 +msgid "Bulk generation of users completed, one alias generated." +msgid_plural "Bulk generation of users completed, @count aliases generated." +msgstr[0] "" +"Hromadné generovanie užívateľov kompletné, @count alias " +"generovaný" +msgstr[1] "" +"Hromadné generovanie užívateľov kompletné, @count aliasy " +"generované" +msgstr[2] "" +"Hromadné generovanie užívateľov kompletné, @count aliasov " +"generovaných" + +#: pathauto_user.inc:129,123,129,123,123 +msgid "Bulk generation of user blogs completed, one alias generated." +msgid_plural "Bulk generation of user blogs completed, @count aliases generated." +msgstr[0] "Hromadné generovanie blogov kompletné, @count alias generovaný" +msgstr[1] "Hromadné generovanie blogov kompletné, @count aliasy generované" +msgstr[2] "Hromadné generovanie blogov kompletné, @count aliasov generovaných" + +#: pathauto_user.inc:153,147,153,145,147,145 +msgid "Bulk generation of user tracker pages completed, one alias generated." +msgid_plural "" +"Bulk generation of user tracker pages completed, @count aliases " +"generated." +msgstr[0] "Hromadné generovanie stránok kompletné, @count alias generovaný" +msgstr[1] "Hromadné generovanie stránok kompletné, @count aliasy generované" +msgstr[2] "" +"Hromadné generovanie stránok kompletné, @count aliasov " +"generovaných" + +#: pathauto.module:11,11,11 +msgid "" +"<p>Provides a mechanism for modules to automatically generate aliases " +"for the content they manage.</p>\n" +" <h2>Settings</h2>\n" +" <p>The <strong>Maximum Alias Length</strong> and " +"<strong>Maximum component length</strong> values\n" +" default to 100 and have a limit of 128 from " +"pathauto. This length is limited by the length of the dst \n" +" column of the url_alias database table. The default " +"database schema for this column is 128. If you \n" +" set a length that is equal to that of the one set in " +"the dst column it will cause problems in situations \n" +" where the system needs to append additional words to " +"the aliased URL. For example... URLs generated\n" +" for feeds will have '/feed' added to the end. You " +"should enter a value that is the length of the dst\n" +" column minus the length of any strings that might " +"get added to the end of the URL. The length of \n" +" strings that might get added to the end of your URLs " +"depends on which modules you have enabled and \n" +" on your Pathauto settings. The recommended and " +"default value is 100.</p>\n" +" <p><strong>Raw Tokens</strong> In Pathauto it is " +"appropriate to use the -raw form of tokens. Paths are sent through a " +"filtering\n" +" system which ensures that raw user content is " +"filtered. Failure to use -raw tokens can cause problems\n" +" with the Pathauto punctuation filtering system.</p>" +msgstr "" +"<p>Poskytuje mechanizmus pre moduly na spravovanie automatického " +"generovania aliasov pre obsah</p>\r\n" +"<h2>Nastavenia</h2>\r\n" +"<p>Hodnoty: <strong>Maximálny počet znakov aliasu</strong> a " +"<strong>Maximálny počet znakov komponentu</strong>\r\n" +"sú Pathautom nastavené na 100 a limit je 128. Tento počet znakov je " +"limitovaný dst\r\n" +"stĺpcom v databázovej tabuľke url_alias. Predvolený limit počtu " +"znakov databázovej schémy je 128. Ak vložíte\r\n" +"počet znakov, ktorý sa rovná počtu znakov v stĺpčeku dst " +"spôsobí to problémy v situáciach,\r\n" +"kde systém potrebuje doplniť ďalšie slovo do aliasu URL. Napr. URL " +"generované\r\n" +"pre feed bude mať na na konci '/feed'. Mali by ste vložiť hodnotu, " +"ktorá bude rovná počtu znakov dst\r\n" +"stĺpca mínus počet znakov reťazca, ktorý možno bude vložený do " +"URL. Počet znakov reťazcov,\r\n" +"ktoré budú vložené na koniec URL závisí od modulov, ktoré mate " +"aktivované a od nastavení\r\n" +"Pathauto. Odporúčaná a základná hodnota je 100.</p>\r\n" +"<p><strong>Raw znaky</strong> V Pathauto je vhodné používať -raw " +"formy symbolov. Cesty sú prevedené cez filtračný systém, ktorý " +"zabezpečuje, že raw obsah používateľa bude filtrovaný.\r\n" +"Chyby pri používaní -raw znakov môžu spôsobiť problémy " +"systému Pathauto na filtráciu interpunkcie.</p>" + +#: pathauto.module:61,46,45,45 +msgid "Configure how pathauto generates clean URLs for your content." +msgstr "Nastavte ako má Pathauto generovať čisté URL pre váš obsah." + +#: pathauto.module:69,51,68,51,53,53 +msgid "Delete aliases" +msgstr "Vymazať aliasy" + +#: pathauto.module:82,82,85,85 +msgid "" +"It appears that the <a href=\"@token_link\">Token module</a> is not " +"installed. Please ensure that you have it installed so that Pathauto " +"can work properly. You may need to disable Pathauto and re-enable it " +"to get Token enabled." +msgstr "" +"Zdá sa, že <a href=\"@token_link\">modul Token</a> nie je " +"nainštalovaný. Uistite sa prosím, že ho máte nainštalovaný, aby " +"Pathauto mohlo správne fungovať. Možno budete potrebovať zapnúť " +"a vypnúť Pathauto, aby sa Token zapol." + +#: pathauto.module:442,101,101,104,104; pathauto.admin.inc:46 +msgid "Verbose" +msgstr "Sledovanie zmien" + +#: pathauto.module:444,103,103,106,106; pathauto.admin.inc:48 +msgid "Display alias changes (except during bulk updates)." +msgstr "" +"Zobraziť zmeny aliasov (okrem prebiehajúcich hromadných " +"aktualizácií)" + +#: pathauto.module:449,106,106,109,109; pathauto.admin.inc:53 +msgid "Separator" +msgstr "Oddeľovač" + +#: pathauto.module:453,108,108,111,111; pathauto.admin.inc:57 +msgid "" +"Character used to separate words in titles. This will replace any " +"spaces and punctuation characters. Using a space or + character can " +"cause unexpected results." +msgstr "" +"Znak používaný na oddeľovanie slov v názvoch. Nahradí všetky " +"medzery a interpunkciu. Ak použijete medzeru alebo + bude to mať " +"nepriaznivé následky." + +#: pathauto.module:458,111,111,114,114; pathauto.admin.inc:62 +msgid "Character case" +msgstr "Veľkosť znakov" + +#: pathauto.module:460,113,113,116,116; pathauto.admin.inc:64 +msgid "Leave case the same as source token values." +msgstr "Ponechať rovnakú veľkosť písmen ako sú v zdrojových reťazcoch." + +#: pathauto.module:460,113,113,116,116; pathauto.admin.inc:64 +msgid "Change to lower case" +msgstr "Previesť na malé znaky" + +#: pathauto.module:465,117,117,120,120; pathauto.admin.inc:69 +msgid "Maximum alias length" +msgstr "Maximálna dĺžka aliasu" + +#: pathauto.module:119,119,122,122 +msgid "" +"Maximum length of aliases to generate. 100 is recommended. See <a " +"href=\"@pathauto-help\">Pathauto help</a> for details." +msgstr "" +"Maximálny počet znakov pre vygenerovanie aliasu - odporúčané je " +"100. Viac detailov sa dozviete v <a " +"href=\"@pathauto-help\">Pomocníkovi pre Pathauto</a>." + +#: pathauto.module:474,122,122,125,125; pathauto.admin.inc:78 +msgid "Maximum component length" +msgstr "Maximálna dĺžka komponentu" + +#: pathauto.module:478,124,124,127,127; pathauto.admin.inc:82 +msgid "" +"Maximum text length of any component in the alias (e.g., [title]). 100 " +"is recommended. See <a href=\"@pathauto-help\">Pathauto help</a> for " +"details." +msgstr "" +"Maximálny počet znakov pre ľubovoľný komponent v aliase (napr., " +"[title]). Odporúčané je 100. Viac informácii nájdete v <a " +"href=\"@pathauto-help\">nápovede Pathauto</a>." + +#: pathauto.module:483,127,127,130,130; pathauto.admin.inc:87 +msgid "Maximum number of objects to alias in a bulk update" +msgstr "Maximálny počet objektov v aliase pri hromadnej aktualizácii" + +#: pathauto.module:129,129,132,132 +msgid "" +"Maximum number of objects of a given type which should be aliased " +"during a a bulk update. The default is 50 and the recommended number " +"depends on the speed of your server. If bulk updates \"time out\" or " +"result in a \"white screen\" then reduce the number." +msgstr "" +"Maximálny počet objektov určitého typu, ktoré môžu dostať " +"alias pri hromadnej aktualizácií. Prednastavená hodnota je 50 ale " +"vhodný počet závisí od rýchlosti vášho servera. Ak hromadnej " +"aktualizácii \"vyprší čas\" alebo sa objaví \"biela obrazovka\" " +"znížte číslo." + +#: pathauto.module:491,132,132,135,135; pathauto.admin.inc:95 +msgid "Do nothing. Leave the old alias intact." +msgstr "Nerobte nič. Nechajte starý alias nedotknutý." + +#: pathauto.module:492,133,133,136,136; pathauto.admin.inc:96 +msgid "Create a new alias. Leave the existing alias functioning." +msgstr "Vytvoriť nový alias. Aktuálny alias ponechať funkčný." + +#: pathauto.module:493,134,134,137,137; pathauto.admin.inc:97 +msgid "Create a new alias. Delete the old alias." +msgstr "Vytvoriť nový alias. Vymazať starý alias." + +#: pathauto.module:496,137,137,140,140; pathauto.admin.inc:100 +msgid "Create a new alias. Redirect from old alias." +msgstr "Vytvoriť nový alias. Presmerovať zo starého aliasu." + +#: pathauto.module:506,146,146,149,149; pathauto.admin.inc:110 +msgid "Update action" +msgstr "Aktualizovať akciu" + +#: pathauto.module:509,148,148,151,151; pathauto.admin.inc:113 +msgid "" +"What should pathauto do when updating an existing content item which " +"already has an alias?" +msgstr "" +"Čo má pathauto urobiť, ak sa aktualizuje existujúci obsah, ktorý " +"už má alias?" + +#: pathauto.module:153,153,156,156 +msgid "" +"When a pattern includes certain characters (such as those with " +"accents) should Pathauto attempt to transliterate them into the " +"ASCII-96 alphabet? Transliteration is determined by the " +"i18n-ascii.txt file in the Pathauto directory. <strong>This option is " +"disabled on your site because you do not have an i18n-ascii.txt file " +"in the Pathauto directory.</strong>" +msgstr "" +"Keď vzor obsahuje určité znaky (ako znaky s diakritikou ) má sa " +"Pathauto pokúsiť prepísať ich do abecedy typu ASCII-96? Preklad " +"znakov sa robí na základe pravidiel uložených v súbore " +"i18n-ascii.txt v zložke Pathauto. <strong>Táto možnosť je " +"vypnutá pretože v zložke Pathauto nie je žiadny súbor s názvom " +"i18n-ascii.txt.</strong>" + +#: pathauto.module:156,156,159,159 +msgid "" +"When a pattern includes certain characters (such as those with " +"accents) should Pathauto attempt to transliterate them into the " +"ASCII-96 alphabet? Transliteration is determined by the " +"i18n-ascii.txt file in the Pathauto directory." +msgstr "" +"Keď vzor obsahuje určité znaky (ako znaky s diakritikou ) má sa " +"Pathauto pokúsiť prepísať ich do abecedy typu ASCII-96? Preklad " +"znakov sa robí na základe pravidiel uložených v súbore " +"i18n-ascii.txt v zložke Pathauto." + +#: pathauto.module:525,163,163,166,166; pathauto.admin.inc:129 +msgid "Transliterate prior to creating alias" +msgstr "Prepísať pred vytvorením aliasu" + +#: pathauto.module:532,169,169,172,172; pathauto.admin.inc:136 +msgid "Reduce strings to letters and numbers from ASCII-96" +msgstr "Redukovať reťazce na písmená a čísla z ASCII-96" + +#: pathauto.module:534,171,171,174,174; pathauto.admin.inc:138 +msgid "" +"Filters the new alias to only letters and numbers found in the " +"ASCII-96 set." +msgstr "" +"Filtrovať nový alias len použitím písmen a čísel " +"nachádzajúcich sa v ASCII-96 tabuľke." + +#: pathauto.module:539,176,176,179,179; pathauto.admin.inc:143 +msgid "Strings to Remove" +msgstr "Reťazce na odstránenie" + +#: pathauto.module:177,177,180,180 +msgid "" +"Words to strip out of the URL alias, separated by commas. Do not " +"place punctuation in here and do not use WYSIWYG editors on this " +"field." +msgstr "" +"Slová, ktoré majú byť odstránené z URL aliasu, oddelené " +"čiarkou. Nepoužívajte interpunkciu a WYSIWYG editory na toto pole." + +#: pathauto.module:546,182,182,185,185; pathauto.admin.inc:150 +msgid "Punctuation settings" +msgstr "Nastavenie interpunkcie" + +#: pathauto.module:557,191,191,194,194; pathauto.admin.inc:161 +msgid "Replace by separator" +msgstr "Nahradiť oddeľovačom" + +#: pathauto.module:557,191,191,194,194; pathauto.admin.inc:161 +msgid "No action (do not replace)" +msgstr "Žiadna akcia (nenahrádzať)" + +#: pathauto.module:635,262,262,266,266; pathauto.admin.inc:239 +msgid "Replacement patterns" +msgstr "Vzory na zámenu" + +#: pathauto.module:639,266,266,270,270; pathauto.admin.inc:243 +msgid "Use -raw replacements for text to avoid problems with HTML entities." +msgstr "" +"Použiť -raw náhrady pre text, aby sa vyhlo problémom s HTML " +"entitami." + +#: pathauto.module:672,297,297,301,301; pathauto.admin.inc:276 +msgid "Internal feed alias text (leave blank to disable)" +msgstr "" +"Vnútorný textový alias pre kanály správ (ponechajte prázdne ak " +"to chcete vypnúť)" + +#: pathauto.module:676,301,301,305,305; pathauto.admin.inc:280 +msgid "" +"The text to use for aliases for RSS feeds. Examples are \"feed\" and " +"\"0/feed\"." +msgstr "" +"Text, ktorý sa použije pri vytváraní aliasov pre RSS kanály " +"správ. Napríklad sú \"kanale\" a \"0/kanale\"" + +#: pathauto.module:731,344,344,348,348; pathauto.admin.inc:335 +msgid "" +"You are using the token [%token] which is not valid within the scope " +"of tokens where you are using it." +msgstr "" +"Používate symbol [%token], ktorý nie je platný s rozsahom znakov, " +"ktoré teraz používate." + +#: pathauto.module:352,352,356,356 +msgid "" +"You are using the token [%token] which has a -raw companion available " +"[%raw_token]. For Pathauto patterns you should use the -raw version " +"of tokens unless you really know what you are doing. See the <a " +"href=\"@pathauto-help\">Pathauto help</a> for more details." +msgstr "" +"Používate znak, ktorý má dostupnú -raw náhradu [%raw_token]. Pre " +"vzory Pathauto by ste mali používať -raw verzie znakov, ale pokiaľ " +"naozaj viete čo robíte, môžete používať aj iné hodnoty. Viac " +"informácií nájdete v <a href=\"@pathauto-help\">nápovede " +"Pathauto</a>." + +#: pathauto.module:749,362,362,366,366; pathauto.admin.inc:353 +msgid "NOTE: This field contains potentially incorrect patterns. " +msgstr "Poznámka: Toto pole obsahuje potencionálne nesprávne vzory. " + +#: pathauto.module:751,364,364,368,368; pathauto.admin.inc:355 +msgid "%problems" +msgstr "%problems" + +#: pathauto.module:380,384 +msgid "" +"You have configured the @name to be the separator and to be removed " +"when encountered in strings. This can cause problems with your " +"patterns and especially with the catpath and termpath patterns." +msgstr "" +"Máte nastavený znak @name ako oddeľovač a má sa vymazať keď sa " +"stretne s rovnakým znakom v reťazci. Toto môže spôsobovať " +"problémy s vašimi vzormi - hlavne s catpath a termpath." + +#: pathauto.module:167,456,164,462,466,466 +msgid "As [cat], but including its supercategories separated by /." +msgstr "Ako [cat], ale s vloženými superkategóriami a oddelené /." + +#: pathauto.module:168,457,165,463,467,467 +msgid "" +"As [cat-raw], but including its supercategories separated by /. " +"WARNING - raw user input." +msgstr "" +"Ako [cat-raw], ale s vloženými superkategóriami a oddelené /. " +"UPOZORNENIE - nespracovaný používateľský vstup." + +#: pathauto.module:169,464,468,468 +msgid "URL alias for the category." +msgstr "URL alias pre kategóriu" + +#: pathauto.module:172,461,169,467,471,471 +msgid "As [term], but including its supercategories separated by /." +msgstr "Ako [term], ale s vloženými superkategóriami a oddelené /." + +#: pathauto.module:173,462,170,468,472,472 +msgid "" +"As [term-raw], but including its supercategories separated by /. " +"WARNING - raw user input." +msgstr "" +"Ako [term-raw], ale s vloženými superkategóriami a oddelené /. " +"UPOZORNENIE - nespracovaný používateľský vstup." + +#: pathauto.module:174,463,458,171,166,469,473,473 +msgid "URL alias for the term." +msgstr "URL alias pre termín" + +#: pathauto.module:179,468,176,474,478,478 +msgid "URL alias for the parent book." +msgstr "URL alias pre koreňovú knihu." + +#: pathauto.module:795,492,498,502,502; pathauto.admin.inc:399 +msgid "Choose Aliases to Delete" +msgstr "Vybrať aliasy na vymazanie" + +#: pathauto.module:804,498,504,508,508; pathauto.admin.inc:408 +msgid "all aliases" +msgstr "všetky aliasy" + +#: pathauto.module:500,506,510,510 +msgid "Delete all aliases. Number of aliases which will be deleted: %count." +msgstr "" +"Vymazať všetky aliasy. Počet aliasov ktoré budú vymazané: " +"%count." + +#: pathauto.module:817,509,515,519,519; pathauto.admin.inc:421 +msgid "" +"Delete aliases for all @label. Number of aliases which will be " +"deleted: %count." +msgstr "" +"Vymazať aliasy pre všetky @label. Počet aliasov, ktoré budú " +"vymazané: %count." + +#: pathauto.module:513,519,523,523 +msgid "" +"<p><strong>Note:</strong> there is no confirmation. Be sure of your " +"action before clicking the \"Delete aliases now!\" button.<br /> You " +"may want to make a backup of the database and/or the url_alias table " +"prior to using this feature.</p>" +msgstr "" +"<p><strong>Poznámka:</strong> nie je žiadne potvrdzovanie. Buďte si " +"istý pred kliknutím na tlačidlo \"Vymazať aliasy!\".<br> Možno by " +"ste si skôr ako použijete túto možnosť mali spraviť zálohu " +"databázy a/alebo tabuľky url_alias.</p>" + +#: pathauto.module:825,516,522,526,526; pathauto.admin.inc:429 +msgid "Delete aliases now!" +msgstr "Vymazať aliasy!" + +#: pathauto.module:839,530,536,540,540; pathauto.admin.inc:443 +msgid "All of your path aliases have been deleted." +msgstr "Všetky vaše aliasy ciest boli vymazané." + +#: pathauto.module:844,535,541,545,545; pathauto.admin.inc:448 +msgid "All of your %type path aliases have been deleted." +msgstr "Všetky vaše aliasy ciest typu: %type boli zmazané" + +#: pathauto.module:191,543,188,549,553,553 +msgid "users" +msgstr "používatelia" + +#: pathauto.module:193,545,190,551,555,555 +msgid "user blogs" +msgstr "blogy používateľa" + +#: pathauto.module:196,548,193,554,558,558 +msgid "vocabularies and terms" +msgstr "slovníky a termíny" + +#: pathauto.module:199,551,196,557,561,561 +msgid "user trackers" +msgstr "sledovania používateľa" + +#: pathauto.module:261,639,284,619,624,623 +msgid "" +"An alias will be generated for you. If you wish to create your own " +"alias below, untick this option." +msgstr "" +"Bude vám vygenerovaný alias. Pokiaľ chcete vygenerovať svoj " +"vlastný alias, odškrtnite túto možnosť." + +#: pathauto.module:263,641,286,621,626,625 +msgid "" +" To control the format of the generated aliases, see the <a " +"href=\"@pathauto\">Pathauto settings</a>." +msgstr "" +" Pre kontrolovanie formátu generovaných aliasov pozrite <a " +"href=\"@pathauto\">nastavenia Pathauto</a>." + +#: pathauto.module:271,648,294,628,632,631 +msgid "Automatic alias" +msgstr "Automatický alias" + +#: pathauto.module:286,667,319,643,647,646 +msgid "Update path alias" +msgstr "Aktualizovať alias cesty" + +#: pathauto.module:683,307,307,311,311; pathauto.admin.inc:287 +msgid "Bulk generation of index aliases completed, one alias generated." +msgid_plural "Bulk generation of index aliases completed, @count aliases generated." +msgstr[0] "" +"Hromadné generovanie indexov aliasov kompletné, @count alias " +"vygenerovaný." +msgstr[1] "" +"Hromadné generovanie indexov aliasov kompletné, @count aliasy " +"vygenerované." +msgstr[2] "" +"Hromadné generovanie indexov aliasov kompletné, @count aliasov " +"vygenerovaných." + +#: pathauto.module:750,363,363,367,367; pathauto.admin.inc:354 +msgid "Problem token: " +msgid_plural "Problem tokens: " +msgstr[0] "Problémový znak:" +msgstr[1] "Problémové znaky:" +msgstr[2] "Problémové znaky: " + +#: pathauto.module:49,33,49,33,33 +msgid "administer pathauto" +msgstr "Administrovať pathauto" + +#: pathauto.module:49,33,49,33,33 +msgid "notify of path changes" +msgstr "oznamuj zmeny cesty" + +#: pathauto.module:0,0,0,0 +msgid "pathauto" +msgstr "pathauto" + +#: pathauto.info:0,0,0,0 +msgid "" +"Provides a mechanism for modules to automatically generate aliases for " +"the content they manage." +msgstr "" +"Poskytuje mechanizmus pre moduly na automatické generovanie aliasov " +"pre obsah ktorý manažujú." + +#: pathauto.install:17,17 +msgid "Extra table to help Pathauto interact with the core url_alias table" +msgstr "" +"Tabuľka, ktorá pomáha modulu Pathauto spolupracovať s tabuľkou " +"základných url aliasov" + +#: contrib/pathauto_node_event.inc:22,12,22,12 +msgid "[eventyyyy]" +msgstr "[eventyyyy]" + +#: contrib/pathauto_node_event.inc:12,12 +msgid "The year the event starts." +msgstr "Rok, v ktorom začala udalosť." + +#: contrib/pathauto_node_event.inc:23,13,23,13 +msgid "[eventmm]" +msgstr "[eventmm]" + +#: contrib/pathauto_node_event.inc:13,13 +msgid "The two-digit month (01-12) the event starts." +msgstr "Dvojčíselné označenie mesiaca (01-12), v ktorom začala udalosť." + +#: contrib/pathauto_node_event.inc:24,14,24,14 +msgid "[eventmon]" +msgstr "[eventmon]" + +#: contrib/pathauto_node_event.inc:14,14 +msgid "The three-letter month (jan-dec) the event starts." +msgstr "Trojpísmenový názov mesiaca (jan-dec), v ktorom začala udalosť." + +#: contrib/pathauto_node_event.inc:25,15,25,15 +msgid "[eventdd]" +msgstr "[eventdd]" + +#: contrib/pathauto_node_event.inc:15,15 +msgid "The two-digit day of the month (00-31) the event starts." +msgstr "Dvojčíselné označenie dňa (00-31), v ktorom začala udalosť." + +#: contrib/pathauto_node_event.inc:26,16,26,16 +msgid "[eventday]" +msgstr "[eventday]" + +#: contrib/pathauto_node_event.inc:16,16 +msgid "The three-letter day of the week (sun-sat) the event starts." +msgstr "" +"Trojpísmenový názov dňa v týždni (pon-ned), v ktorom začala " +"udalosť." + +#: contrib/pathauto_node_event.inc:27,17,27,17 +msgid "[eventweek]" +msgstr "[eventweek]" + +#: contrib/pathauto_node_event.inc:17,17 +msgid "The week number (1-52) of the year the event starts." +msgstr "Číslo týždňa (1-52), v ktorom začala udalosť." + +#: contrib/pathauto_node_i18n.inc:16,12,16,12 +msgid "[lang]" +msgstr "[lang]" + +#: contrib/pathauto_node_i18n.inc:12,12 +msgid "Language code of the document" +msgstr "Kód jazyka dokumentu" + +#: pathauto.module:380,384 +msgid "" +"You have configured the @name to be the separator and to be removed " +"when encountered in strings. This can cause problems with your " +"patterns and especially with the catpath and termpath patterns. You " +"should probably set the action for @name to be \"replace by " +"separator\"" +msgstr "" +"Máte nastavený znak @name ako oddeľovač a má sa vymazať keď sa " +"stretne s rovnakým znakom v reťazci. Toto môže spôsobovať " +"problémy s vašimi vzormi - hlavne s catpath a termpath. Vhodnejšie " +"bude, ak nastavíte znak @name do poľa \"nahradiť oddeľovačom\"." + +#: pathauto.module:202,554,199,564 +msgid "forums" +msgstr "fóra" + +#: pathauto_node.inc:34,47 +msgid "Pattern for all @node_type paths in @language" +msgstr "Vzor pre všetky cesty @node_type v @language" + +#: pathauto_taxonomy.inc:13,20 +msgid "Taxonomy term path settings" +msgstr "Nastavenia cesty taxonomického výrazu" + +#: pathauto_taxonomy.inc:25,32 +msgid "Bulk generate aliases for terms that are not aliased" +msgstr "Hromadné generovanie aliasov pre pojmy, ktoré nemajú aliasy" + +#: pathauto_taxonomy.inc:26,33 +msgid "" +"Generate aliases for all existing terms which do not already have " +"aliases." +msgstr "Vytvoernie aliasov pre všetky termíny, ktoré ešte aliasy nemajú." + +#: pathauto.module:41,57 +msgid "Automated alias settings" +msgstr "Nastavenia automatických aliasov" + +#: pathauto.module:418; pathauto.admin.inc:22 +msgid "" +"It appears that the <a href=\"@token_link\">Token module</a> is not " +"installed. Please ensure that you have it installed so that Pathauto " +"can work properly. You may need to disable Pathauto and re-enable it " +"to get Token enabled." +msgstr "" +"Zdá sa, že <a href=\"@token_link\">modul Token</a> nie je " +"nainštalovaný. Uistite sa prosím, že ho máte nainštalovaný, aby " +"Pathauto mohlo správne fungovať. Možno budete potrebovať zapnúť " +"a vypnúť Pathauto, aby sa Token zapol." + +#: pathauto.module:469; pathauto.admin.inc:73 +msgid "" +"Maximum length of aliases to generate. 100 is recommended. See <a " +"href=\"@pathauto-help\">Pathauto help</a> for details." +msgstr "" +"Maximálny počet znakov pre vygenerovanie aliasu - odporúčané je " +"100. Viac detailov sa dozviete v <a " +"href=\"@pathauto-help\">Pomocníkovi pre Pathauto</a>." + +#: pathauto.module:487; pathauto.admin.inc:91 +msgid "" +"Maximum number of objects of a given type which should be aliased " +"during a bulk update. The default is 50 and the recommended number " +"depends on the speed of your server. If bulk updates \"time out\" or " +"result in a \"white screen\" then reduce the number." +msgstr "" +"Maximálny počet objektov určitého typu, ktoré môžu dostať " +"alias pri hromadnej aktualizácií. Prednastavená hodnota je 50 ale " +"vhodný počet závisí od rýchlosti vášho servera. Ak hromadnej " +"aktualizácii \"vyprší čas\" alebo sa objaví \"biela obrazovka\" " +"znížte číslo." + +#: pathauto.module:514; pathauto.admin.inc:118 +msgid "" +"When a pattern includes certain characters (such as those with " +"accents) should Pathauto attempt to transliterate them into the " +"ASCII-96 alphabet? Transliteration is determined by the i18n-ascii.txt " +"file in the Pathauto directory. <strong>This option is disabled on " +"your site because you do not have an i18n-ascii.txt file in the " +"Pathauto directory.</strong>" +msgstr "" +"Keď vzor obsahuje určité znaky (ako znaky s diakritikou ) má sa " +"Pathauto pokúsiť prepísať ich do abecedy typu ASCII-96? Preklad " +"znakov sa robí na základe pravidiel uložených v súbore " +"i18n-ascii.txt v zložke Pathauto. <strong>Táto možnosť je vypnutá " +"pretože v zložke Pathauto nie je žiadny súbor s názvom " +"i18n-ascii.txt.</strong>" + +#: pathauto.module:517; pathauto.admin.inc:121 +msgid "" +"When a pattern includes certain characters (such as those with " +"accents) should Pathauto attempt to transliterate them into the " +"ASCII-96 alphabet? Transliteration is determined by the i18n-ascii.txt " +"file in the Pathauto directory." +msgstr "" +"Keď vzor obsahuje určité znaky (ako znaky s diakritikou ) má sa " +"Pathauto pokúsiť prepísať ich do abecedy typu ASCII-96? Preklad " +"znakov sa robí na základe pravidiel uložených v súbore " +"i18n-ascii.txt v zložke Pathauto." + +#: pathauto.module:540; pathauto.admin.inc:144 +msgid "" +"Words to strip out of the URL alias, separated by commas. Do not place " +"punctuation in here and do not use WYSIWYG editors on this field." +msgstr "" +"Slová, ktoré majú byť odstránené z URL aliasu, oddelené " +"čiarkou. Nepoužívajte interpunkciu a WYSIWYG editory na toto pole." + +#: pathauto.module:739; pathauto.admin.inc:343 +msgid "" +"You are using the token [%token] which has a -raw companion available " +"[%raw_token]. For Pathauto patterns you should use the -raw version of " +"tokens unless you really know what you are doing. See the <a " +"href=\"@pathauto-help\">Pathauto help</a> for more details." +msgstr "" +"Používate znak, ktorý má dostupnú -raw náhradu [%raw_token]. Pre " +"vzory Pathauto by ste mali používať -raw verzie znakov, ale pokiaľ " +"naozaj viete čo robíte, môžete používať aj iné hodnoty. Viac " +"informácií nájdete v <a href=\"@pathauto-help\">nápovede " +"Pathauto</a>." + +#: pathauto.module:770; pathauto.admin.inc:374 +msgid "" +"You have configured the @name to be the separator and to be removed " +"when encountered in strings. This can cause problems with your " +"patterns and especially with the catpath and termpath patterns. You " +"should probably set the action for @name to be \"replace by " +"separator\"" +msgstr "" +"Máte nastavený znak @name ako oddeľovač a má sa vymazať keď sa " +"stretne s rovnakým znakom v reťazci. Toto môže spôsobovať " +"problémy s vašimi vzormi - hlavne s catpath a termpath. Vhodnejšie " +"bude, ak nastavíte znak @name do poľa \"nahradiť oddeľovačom\"." + +#: pathauto.module:806; pathauto.admin.inc:410 +msgid "Delete all aliases. Number of aliases which will be deleted: %count." +msgstr "" +"Odstrániť všetky aliasy. Počet aliasov, ktoré budú odstránené: " +"%count." + +#: pathauto.module:822; pathauto.admin.inc:426 +msgid "" +"<p><strong>Note:</strong> there is no confirmation. Be sure of your " +"action before clicking the \"Delete aliases now!\" button.<br />You " +"may want to make a backup of the database and/or the url_alias table " +"prior to using this feature.</p>" +msgstr "" +"<p><strong>Poznámka:</strong> nie je žiadne potvrdzovanie. Buďte si " +"istý pred kliknutím na tlačidlo \"Vymazať aliasy!\".<br /> Možno " +"by ste si skôr ako použijete túto možnosť mali spraviť zálohu " +"databázy a/alebo tabuľky url_alias.</p>" + +#: pathauto.inc:308,315 +msgid "" +"The automatically generated alias %original_alias conflicted with an " +"existing alias. Alias changed to %alias." +msgstr "" +"Automaticky generovaný alias %original_alias je už použitý. Alias " +"bol zmenený na %alias." + +#: pathauto.inc:381,388 +msgid "Ignoring alias %dst because it is the same as the internal path." +msgstr "Alias %dst bude ignorovaný, pretože je totožný s internou cestou." + +#: pathauto_node.inc:44 +msgid "" +"Default path pattern for @node_type (applies to all @node_type node " +"types with blank patterns below)" +msgstr "" +"Predvolený vzor cesty pre @node_type (aplikovaný na všetky typy " +"slovníkov @node_type, ktoré majú prázdny vzor)" + +#: pathauto_node.inc:50 +msgid "Pattern for all language neutral @node_type paths" +msgstr "Vzor pre všetky jazykovo neutrálne cesty @node_type" + diff --git a/sites/all/modules/pathauto/translations/uk-ua.po b/sites/all/modules/pathauto/translations/uk-ua.po new file mode 100644 index 0000000000000000000000000000000000000000..f33ec00a36e05bb7f7e9c2bdf4841a3ac2d4e2e4 --- /dev/null +++ b/sites/all/modules/pathauto/translations/uk-ua.po @@ -0,0 +1,791 @@ +# Ukrainian translation of Pathauto (all releases) +# Copyright (c) 2009 by the Ukrainian translation team +# +msgid "" +msgstr "" +"Project-Id-Version: Pathauto (all releases)\n" +"POT-Creation-Date: 2009-11-12 01:30+0000\n" +"PO-Revision-Date: 2009-11-12 01:30+0000\n" +"Language-Team: Ukrainian\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=((((n%10)==1)&&((n%100)!=11))?(0):(((((n%10)" +">=2)&&((n%10)<=4))&&(((n%100)<10)||((n%100)>=20)))?(1):2));\n" + +msgid "" +" To control the format of the generated aliases, see the <a href=\"@pathauto" +"\">Pathauto settings</a>." +msgstr "" +" Для керування форматом згенерованих псевдонимів, перегляньте <a href=" +"\"@pathauto\">Настройки автошляху</a>." + +msgid " due to existing path conflict" +msgstr " через конфлікт із існуючими посиланнями" + +msgid "%problems" +msgstr "%problems" + +msgid "" +"<p><strong>Note:</strong> there is no confirmation. Be sure of your action " +"before clicking the \"Delete aliases now!\" button.<br />You may want to " +"make a backup of the database and/or the url_alias table prior to using this " +"feature.</p>" +msgstr "" +"<p><strong>Увага:</strong> операція видалення виконується без підтвердження. " +"Будьте впевнені в своїх діях, перед тим як клікнути на кнопку \"Видалити " +"псевдоніми зараз!\".<br /> Ви можете попередньо зробити резервну копію вашої " +"бази даних та/або таблиці url_alias.</p>" + +msgid "All of your %type path aliases have been deleted." +msgstr "Усі ваші синоніми шляхів %type були вилучені." + +msgid "All of your path aliases have been deleted." +msgstr "Усі ваші синоніми шляхів були вилучені." + +msgid "Also generate aliases for RSS feeds." +msgstr "Генерувати синоніми так само й для RSS стрічок" + +msgid "Ampersand &" +msgstr "Амперсанд &" + +msgid "" +"An alias will be generated for you. If you wish to create your own alias " +"below, untick this option." +msgstr "" +"Буде згенеровано альтернативний псевдонім шляху до цієї публікації. Якщо Ви " +"бажаєте створити власний псевдонім, вимкніть цю опцію та введіть свій шлях в " +"поле, що розташоване вище." + +msgid "" +"As [cat-raw], but including its supercategories separated by /. WARNING - " +"raw user input." +msgstr "" +"Як і [cat-raw], але включаючи категорії верхнього рівня, розділені /. УВАГА: " +"прямий ввід користувача." + +msgid "As [cat], but including its supercategories separated by /." +msgstr "Як і [cat], але включаючи категорії верхнього рівня, розділені /." + +msgid "As [cat], but including its supercategories." +msgstr "Як і [cat], але в тому числі із супер категоріями." + +msgid "" +"As [term-raw], but including its supercategories separated by /. WARNING - " +"raw user input." +msgstr "" +"Як [term-raw], але включає категорію верхнього рівня відділену /. " +"ПОПЕРЕДЖЕННЯ: прямий ввід користувача." + +msgid "As [term], but including its supercategories separated by /." +msgstr "Як і [term], але включаючи категорії верхнього рівня, розділені /." + +msgid "Asterisk *" +msgstr "Зірочка *" + +msgid "At @" +msgstr "«Собачка» @" + +msgid "Automated alias settings" +msgstr "Автоматичний формат псевдонімів" + +msgid "Automatic alias" +msgstr "Автоматичні синоніми" + +msgid "Back slash \\" +msgstr "Зворотна коса риса \\" + +msgid "Back tick `" +msgstr "Зворотний апостроф `" + +msgid "Blog path settings" +msgstr "Настроювання адреси блога" + +msgid "Bulk generate aliases for blogs that are not aliased" +msgstr "Масове формування синонімів для блогів, у яких ще немає синонімів" + +msgid "Bulk generate aliases for categories that are not aliased" +msgstr "Масове формування синонімів для категорій, у яких ще немає синонімів" + +msgid "Bulk generate aliases for nodes that are not aliased" +msgstr "Масова генерація псевдонімів до нод, які не мають псевдонімів" + +msgid "Bulk generate aliases for terms that are not aliased" +msgstr "Масове створення синонімів для термінів, які не мають посилань" + +msgid "Bulk generate aliases for user-tracker paths that are not aliased" +msgstr "" +"Масове формування синонімів для сторінок історії користувачів, у яких ще " +"немає синонімів" + +msgid "Bulk generate aliases for users that are not aliased" +msgstr "" +"Масова генерація псевдонімів до профілів користувачів, які не мають " +"псевдонімів" + +msgid "Bulk generate forum paths" +msgstr "Масове формування адрес форуму" + +msgid "Bulk generate index aliases" +msgstr "Масова генерація списку псевдонімів." + +msgid "Bulk update blog paths" +msgstr "Масове відновлення псевдонімів шляхів для блогів" + +msgid "Bulk update category paths" +msgstr "Масове оновлення адрес категорій" + +msgid "Bulk update node paths" +msgstr "Масове оновлення адрес матеріалів" + +msgid "Bulk update user paths" +msgstr "Масове оновлення адрес користувачів" + +msgid "Bulk update user-tracker paths" +msgstr "Масове відновлення адрес користувацьких трекеров" + +msgid "Category path settings" +msgstr "Настроювання адреси категорії" + +msgid "Change to lower case" +msgstr "Перевести в нижній регістр" + +msgid "Character case" +msgstr "Регістр символів" + +msgid "" +"Character used to separate words in titles. This will replace any spaces and " +"punctuation characters." +msgstr "" +"Символ для поділу слів у рядку. Їм будуть замінені пробіли символи " +"пунктуації." + +msgid "" +"Character used to separate words in titles. This will replace any spaces and " +"punctuation characters. Using a space or + character can cause unexpected " +"results." +msgstr "" +"Символ, що розділяє слова в заголовках. Їм заміняються всі пробіли й символи " +"пунктуації. Використання пробілу або символу + може привести до " +"непередбачених результатів." + +msgid "Choose Aliases to Delete" +msgstr "Оберіть псевдоніми для видалення" + +msgid "Colon :" +msgstr "Двокрапка :" + +msgid "Comma ," +msgstr "Кома ," + +msgid "Create a new alias in addition to the old alias" +msgstr "Створити новий синонім на додаток до вже існуючих" + +msgid "Create a new alias, replacing the old one" +msgstr "Створити новий псевдонім, замінивши старий." + +msgid "Create a new alias. Delete the old alias." +msgstr "Створювати новий синонім. Видаляти старий синонім." + +msgid "Create a new alias. Leave the existing alias functioning." +msgstr "Створювати новий синонім. Залишати існуючий синонім функціонуючим." + +msgid "Create a new alias. Redirect from old alias." +msgstr "Створювати новий синонім. Переадресувати зі старого синоніма." + +msgid "Create feed aliases" +msgstr "Створювати синоніми стрічок" + +msgid "Create index aliases" +msgstr "Створення списку псевдонімів" + +msgid "Created new alias %dst for %src" +msgstr "Створений новий синонім %dst для %src" + +msgid "Created new alias %dst for %src, replacing %old_alias" +msgstr "Створений новий синонім %dst для %src, що заміняє %old_alias" + +msgid "Created new alias %dst for %src, replacing %oldalias" +msgstr "Створений новий псевдонім %dst для %src замість %oldalias" + +msgid "" +"Default path pattern (applies to all node types with blank patterns below)" +msgstr "" +"Шаблон адреси базово (буде пременено до всіх типів матеріалів нижче, для " +"яких шаблони не визначені)" + +msgid "" +"Default path pattern (applies to all vocabularies with blank patterns below)" +msgstr "" +"Шаблон адреси базово (застосовується до всіх словників нижче, для яких " +"шаблони не визначені)" + +msgid "Delete aliases" +msgstr "Вилучити синоніми" + +msgid "" +"Delete aliases for all @label. Number of aliases which will be deleted: %" +"count." +msgstr "" +"Вилучити синоніми для всіх @label. Кількість синонімів, які будуть вилучені: " +"%count." + +msgid "Delete aliases now!" +msgstr "Видалити псевдоніми зараз!" + +msgid "Delete all aliases. Number of aliases which will be deleted: %count." +msgstr "Вилучити всі синоніми. Число синонімів, які будуть вилучені: %count." + +msgid "Display alias changes (except during bulk updates)." +msgstr "Відображати зміни синонімів ( крім масового відновлення)." + +msgid "Do nothing, leaving the old alias intact" +msgstr "Нічого не робити, залишити старий псевдонім діючим." + +msgid "Do nothing. Leave the old alias intact." +msgstr "Нічого не робити. Залишати старий синонім недоторканим." + +msgid "Dollar $" +msgstr "Долар $" + +msgid "Equal =" +msgstr "Рівно =" + +msgid "Exclamation !" +msgstr "Знак оклику !" + +msgid "" +"Filters the new alias to only letters and numbers found in the ASCII-96 set." +msgstr "" +"Залишати в створюваних синонімах тільки букви й цифри, що входять у набір " +"ASCII-96." + +msgid "For book pages, the full hierarchy from the top-level book." +msgstr "Для сторінок підшивок, повна ієрархія з верхнього рівня підшивки." + +msgid "For book pages, the title of the top-level book." +msgstr "Для сторінок підшивок, заголовок верхнього рівня підшивки." + +msgid "Forum path settings" +msgstr "Настроювання адреси форуму" + +msgid "General settings" +msgstr "Загальні налаштування" + +msgid "" +"Generate aliases for all existing blog pages which do not already have " +"aliases." +msgstr "" +"Сформувати синоніми для всіх існуючих сторінок блогів, які ще не мають " +"синонімів." + +msgid "" +"Generate aliases for all existing categories which do not already have " +"aliases." +msgstr "" +"Сформувати синоніми для всіх існуючих категорій, які ще не мають синонімів." + +msgid "" +"Generate aliases for all existing forums and forum containers which do not " +"already have aliases." +msgstr "" +"Сформувати синоніми для всіх існуючих форумів і контейнерів форумів, які не " +"ще мають синонімів." + +msgid "" +"Generate aliases for all existing nodes which do not already have aliases." +msgstr "" +"Сформувати синоніми для всіх існуючих матеріалів, які ще не мають синонімів." + +msgid "" +"Generate aliases for all existing terms which do not already have aliases." +msgstr "Створювати синоніми для всіх існуючих термінів, які їх не мають." + +msgid "" +"Generate aliases for all existing user account pages which do not already " +"have aliases." +msgstr "" +"Сформувати синоніми для всіх існуючих сторінок облікових записів " +"користувачів, які ще не мають синонімів." + +msgid "" +"Generate aliases for all existing user-tracker pages which do not already " +"have aliases." +msgstr "" +"Сформувати синоніми для всіх існуючих сторінок історії користувачів, які ще " +"не мають синонімів." + +msgid "Generate index aliases based on all pre-existing aliases." +msgstr "Генерувати список псевдонімів на основі існуючих." + +msgid "Greater than >" +msgstr "Більше ніж >" + +msgid "Hash #" +msgstr "Хеш #" + +msgid "Hyphen -" +msgstr "Дефіс -" + +msgid "Ignoring alias " +msgstr "Ігнорувати синонім " + +msgid "Ignoring alias %dst because it is the same as the internal path." +msgstr "" +"Ігнорування синоніма %dst тому що він збігається із внутрішньою адресою." + +msgid "Ignoring alias %dst due to existing path conflict." +msgstr "Ігнорування синоніма %dst через конфлікт із існуючою адресою." + +msgid "Internal feed alias text (leave blank to disable)" +msgstr "" +"Внутрішній текст для синонімів стрічок (залиште поле порожнім, щоб " +"відключити)" + +msgid "" +"It appears that you have installed Pathauto, which depends on token, but " +"token is either not installed or not installed properly." +msgstr "" +"Схоже, що ви встановили Автосинонім, який залежить від модуля Маркер, але " +"модуль Маркер не встановлений або встановлений неправильно." + +msgid "Language code of the document" +msgstr "Мовний код документа" + +msgid "Language neutral" +msgstr "Нейтральна мова" + +msgid "Leave case the same as source token values." +msgstr "Залишити регістр таким, який він є у вихіднім значенні маркера." + +msgid "Left curly bracket {" +msgstr "Ліва фігурна дужка {" + +msgid "Left parenthesis (" +msgstr "Ліва кругла дужка (" + +msgid "Left square bracket [" +msgstr "Ліва квадратна дужка [" + +msgid "Less than <" +msgstr "Менше ніж <" + +msgid "Maximum alias length" +msgstr "Максимальна довжина синоніма" + +msgid "Maximum component length" +msgstr "Максимальна довжина компонента" + +msgid "" +"Maximum length of aliases to generate. 100 is recommended. See <a href=" +"\"@pathauto-help\">Pathauto help</a> for details." +msgstr "" +"Максимальна довжина згенерованого псевдоніму адреси. Рекомендовано 100. " +"Дивиться подробиці: <a href=\"@pathauto-help\">Pathauto help</a>." + +msgid "" +"Maximum length of aliases to generate. 100 is recommended. See <a href=" +"\"@pathauto-help\">Pathauto help</a> for details." +msgstr "" +"Максимальна довжина згенерованого псевдоніму адреси. Рекомендовано 100. " +"Дивиться подробиці: <a href=\"@pathauto-help\">Pathauto help</a>." + +msgid "Maximum number of objects to alias in a bulk update" +msgstr "Максимальна кількість синонімів при масовім відновленні" + +msgid "NOTE: This field contains potentially incorrect patterns. " +msgstr "ЗАУВАЖЕННЯ: Це поле містить потенційно невірні шаблони. " + +msgid "No action (do not replace)" +msgstr "Немає дії (не заміняти)" + +msgid "Node path settings" +msgstr "Настроювання адреси матеріалу" + +msgid "Pathauto" +msgstr "Автоcинонім" + +msgid "Pattern for all %nodetypename paths" +msgstr "Шаблон для всіх адрес %nodetypename" + +msgid "Pattern for all %vocab-name paths" +msgstr "Шаблон для всіх адрес словника %vocab-name" + +msgid "Pattern for all @node_type paths" +msgstr "Шаблон для всіх шляхів типу нод @node_type" + +msgid "Pattern for all @node_type paths in @language" +msgstr "Шаблон для всіх шляхів типу нод @node_type в мові @language" + +msgid "Pattern for blog page paths" +msgstr "Шаблон до сторінок журналів користувачів" + +msgid "Pattern for forums and forum containers" +msgstr "Шаблон для форумів і контейнерів форумів" + +msgid "Pattern for user account page paths" +msgstr "Шаблон до шляхів профілів користувачів" + +msgid "Pattern for user-tracker page paths" +msgstr "Шаблон до сторінок стеження за діями користувачів" + +msgid "Percent %" +msgstr "Відсоток %" + +msgid "Period ." +msgstr "Період" + +msgid "Pipe |" +msgstr "Вертикальна риса |" + +msgid "Plus +" +msgstr "Плюс +" + +msgid "" +"Provides a mechanism for modules to automatically generate aliases for the " +"content they manage." +msgstr "" +"Надає модулям механізм автоматичного формування синонімів для керованого " +"цими модулями вмісту." + +msgid "Punctuation settings" +msgstr "Пунктуація" + +msgid "Question mark ?" +msgstr "Знак питання ?" + +msgid "Quotation marks" +msgstr "Знак лапок" + +msgid "Reduce strings to letters and numbers from ASCII-96" +msgstr "Забирати з рядків символи, що не входять у набір ASCII-96" + +msgid "Remove" +msgstr "Вилучити" + +msgid "Replace by separator" +msgstr "Замінити на роздільник" + +msgid "Replacement patterns" +msgstr "Шаблони заміни" + +msgid "Right curly bracket }" +msgstr "Права фігурна дужка }" + +msgid "Right square bracket ]" +msgstr "Права квадратна дужка ]" + +msgid "Semicolon ;" +msgstr "Крапка з комою ;" + +msgid "Separator" +msgstr "Розділювач" + +msgid "Single quotes (apostrophe) '" +msgstr "Одинарні лапки (апостроф) '" + +msgid "Taxonomy term path settings" +msgstr "Настроювання для термінів таксономії" + +msgid "" +"The automatically generated alias %original_alias conflicted with an " +"existing alias. Alias changed to %alias." +msgstr "" +"Автоматично сформований %original_alias конфліктує з існуючим адресою. " +"Синонім змінений на %alias." + +msgid "The id number of the category." +msgstr "ID категорії" + +msgid "The id number of the node." +msgstr "Ідентифікатор матеріалу id." + +msgid "The id number of the user." +msgstr "Номер ID користувача." + +msgid "" +"The menu path (as reflected in the breadcrumb), not including Home or [menu]." +msgstr "" +"Шлях меню ( як це відбите в breadcrumb), крім головної сторінки сайту або " +"[menu]." + +msgid "The name of the category." +msgstr "Ім'я категорії" + +msgid "The name of the menu the node belongs to." +msgstr "Ім'я меню, пов'язаного з матеріалом." + +msgid "The name of the user who created the node." +msgstr "Ім'я користувача, який створив матеріал." + +msgid "The name of the user." +msgstr "Ім'я користувача." + +msgid "The three-letter day of the week (sun-sat) that the node was created." +msgstr "Три букви дня тижня (sun-sat), у який матеріал був створений." + +msgid "The three-letter day of the week (sun-sat) the event starts." +msgstr "Три букви дня тижня початку події (sun-sat)." + +msgid "The three-letter month (jan-dec) the event starts." +msgstr "Трибуквений місяць (jan-dec) старту події." + +msgid "The three-letter month (jan-dec) the node was created." +msgstr "Трибуквене позначення місяця (jan-dec), у якім матеріал був створений." + +msgid "" +"The title of the node, with spaces and punctuation replaced by the separator." +msgstr "" +"Заголовок матеріалу. Символи пробіл і знаки пунктуації будуть замінені " +"роздільником." + +msgid "The two-digit day of the month (00-31) the event starts." +msgstr "Порядковий номер дня місяця початку події (00-31)." + +msgid "The two-digit day of the month (00-31) the node was created." +msgstr "Порядковий номер дня (00-31), у якім матеріал був створений." + +msgid "The two-digit hour (00-23) the node was created." +msgstr "Порядковий номер години (00-23), у якій матеріал був створений." + +msgid "The two-digit minute (00-59) the node was created." +msgstr "Порядковий номер хвилини (00-59), у яку матеріал був створений." + +msgid "The two-digit month (01-12) the event starts." +msgstr "Місяць старту події, що полягає із двох цифр (01-12)." + +msgid "The two-digit month (01-12) the node was created." +msgstr "Порядковий номер місяця (01-12), у якім матеріал був створений." + +msgid "The two-digit second (00-59) the node was created." +msgstr "Порядковий номер секунди (00-59), у яку матеріал був створений." + +msgid "The vocabulary that the page's first category belongs to." +msgstr "Словник, до якого відноситься перша категорія сторінки." + +msgid "The week number (1-52) of the year the event starts." +msgstr "Номер тижня (1-52) старту події." + +msgid "The week number (1-52) of the year the node was created." +msgstr "Номер тижня (1-52) створення матеріалу." + +msgid "The year the event starts." +msgstr "Рік старту події." + +msgid "The year the node was created." +msgstr "Рік створення матеріалу" + +msgid "Tilde ~" +msgstr "Тильда ~" + +msgid "Transliterate prior to creating alias" +msgstr "Транслітерувати перед створенням синоніма" + +msgid "URL alias for the category." +msgstr "Синонім URL для категорії." + +msgid "URL alias for the parent book." +msgstr "Синонім URL для батьківської книги." + +msgid "URL alias for the term." +msgstr "Синонім URL для терміна." + +msgid "Underscore _" +msgstr "Підкреслення _" + +msgid "Update action" +msgstr "Оновлення Дії" + +msgid "Update path alias" +msgstr "Обновити синонім адреси" + +msgid "Use -raw replacements for text to avoid problems with HTML entities." +msgstr "" +"Використовуйте -raw замінники для тексту, щоб уникнути проблем з Html-" +"Елементами." + +msgid "User path settings" +msgstr "Шлях до профіля користувача" + +msgid "User-tracker path settings" +msgstr "Настроювання адреси сторінки історії користувача" + +msgid "Verbose" +msgstr "Подробиці" + +msgid "" +"What should pathauto do when updating an existing content item which already " +"has an alias?" +msgstr "" +"Що слід зробити Автосиноніму при відновленні існуючого елемента вмісту, який " +"уже має синонім?" + +msgid "Words to strip out of the URL alias, separated by commas" +msgstr "Слова для виключення із псевдонімів URL, розділені комами" + +msgid "" +"Words to strip out of the URL alias, separated by commas. Do not place " +"punctuation in here and do not use WYSIWYG editors on this field." +msgstr "" +"Слова, роздільники для синоніма URL, відділені комами. Не поміщайте сюди " +"знаки пунктуації й не використовуйте режим WYSIWYG для цього поля." + +msgid "You are not authorized to access the pathauto settings." +msgstr "У вас немає прав для установки автошляхів." + +msgid "" +"You are using the token [%token] which is not valid within the scope of " +"tokens where you are using it." +msgstr "" +"Ви використовуєте маркер [%token], який не входить у той набір маркерів, для " +"якого ви його використовуєте." + +msgid "[book]" +msgstr "[book]" + +msgid "[bookpath]" +msgstr "[bookpath]" + +msgid "[cat]" +msgstr "[cat]" + +msgid "[catalias]" +msgstr "[catalias]" + +msgid "[catpath]" +msgstr "[catpath]" + +msgid "[day]" +msgstr "[day]" + +msgid "[dd]" +msgstr "[dd]" + +msgid "[eventday]" +msgstr "[eventday]" + +msgid "[eventdd]" +msgstr "[eventdd]" + +msgid "[eventmm]" +msgstr "[eventmm]" + +msgid "[eventmon]" +msgstr "[eventmon]" + +msgid "[eventweek]" +msgstr "[eventweek]" + +msgid "[eventyyyy]" +msgstr "[eventyyyy]" + +msgid "[hour]" +msgstr "[hour]" + +msgid "[lang]" +msgstr "[lang]" + +msgid "[menu]" +msgstr "[menu]" + +msgid "[menupath]" +msgstr "[menupath]" + +msgid "[min]" +msgstr "[min]" + +msgid "[mm]" +msgstr "[mm]" + +msgid "[mon]" +msgstr "[mon]" + +msgid "[nid]" +msgstr "[nid]" + +msgid "[sec]" +msgstr "[sec]" + +msgid "[tid]" +msgstr "[tid]" + +msgid "[title]" +msgstr "[title]" + +msgid "[type]" +msgstr "[type]" + +msgid "[uid]" +msgstr "[uid]" + +msgid "[user]" +msgstr "[user]" + +msgid "[vocab-raw]/[catpath-raw]" +msgstr "[vocab-raw]/[catpath-raw]" + +msgid "[vocab]" +msgstr "[vocab]" + +msgid "[vocab]/[catpath]" +msgstr "[vocab]/[catpath]" + +msgid "[week]" +msgstr "[week]" + +msgid "[yyyy]" +msgstr "[yyyy]" + +msgid "administer pathauto" +msgstr "керування автошляхів" + +msgid "all aliases" +msgstr "усі синоніми" + +msgid "blog/[user]" +msgstr "blog/[user]" + +msgid "blogs/[user-raw]" +msgstr "blogs/[user-raw]" + +msgid "category/[vocab-raw]/[catpath-raw]" +msgstr "category/[vocab-raw]/[catpath-raw]" + +msgid "content" +msgstr "матеріал" + +msgid "content/[title-raw]" +msgstr "content/[title-raw]" + +msgid "forums" +msgstr "форуми" + +msgid "notify of path changes" +msgstr "повідомляти про зміни адреси" + +msgid "right parenthesis )" +msgstr "Права кругла дужка )" + +msgid "user blogs" +msgstr "блоги користувачів" + +msgid "user trackers" +msgstr "історія користувачів" + +msgid "user/[user]" +msgstr "user/[user]" + +msgid "user/[user]/track" +msgstr "user/[user]/track" + +msgid "users" +msgstr "користувачі" + +msgid "users/[user-raw]" +msgstr "users/[user-raw]" + +msgid "users/[user-raw]/track" +msgstr "users/[user-raw]/track" + +msgid "vocabularies and terms" +msgstr "словники й терміни" diff --git a/sites/all/modules/token/LICENSE.txt b/sites/all/modules/token/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/token/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, 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 Library 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 diff --git a/sites/all/modules/token/README.txt b/sites/all/modules/token/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..eeacce976f1ac869fa10a371e900b79c06b4c45b --- /dev/null +++ b/sites/all/modules/token/README.txt @@ -0,0 +1,3 @@ +$Id: README.txt,v 1.2 2010/03/06 22:12:11 davereid Exp $ + +Provides common and resuable token UI elements and missing core tokens. diff --git a/sites/all/modules/token/arrow-down.png b/sites/all/modules/token/arrow-down.png new file mode 100644 index 0000000000000000000000000000000000000000..2edbb17d7768f01251d9862ae4c0d093b2e402fc Binary files /dev/null and b/sites/all/modules/token/arrow-down.png differ diff --git a/sites/all/modules/token/arrow-right.png b/sites/all/modules/token/arrow-right.png new file mode 100644 index 0000000000000000000000000000000000000000..6f2f48238bf9cf15d2a83eda46935f2ceaac504a Binary files /dev/null and b/sites/all/modules/token/arrow-right.png differ diff --git a/sites/all/modules/token/jquery.treeTable.css b/sites/all/modules/token/jquery.treeTable.css new file mode 100644 index 0000000000000000000000000000000000000000..017a5ddefadc0257c60c11bbb63d1a1503001094 --- /dev/null +++ b/sites/all/modules/token/jquery.treeTable.css @@ -0,0 +1,45 @@ +/* $Id: jquery.treeTable.css,v 1.2 2010/07/10 13:57:47 davereid Exp $ */ + +/* 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; +} diff --git a/sites/all/modules/token/jquery.treeTable.js b/sites/all/modules/token/jquery.treeTable.js new file mode 100644 index 0000000000000000000000000000000000000000..00fb8107a445c751e835ca8c472f73f6d9911813 --- /dev/null +++ b/sites/all/modules/token/jquery.treeTable.js @@ -0,0 +1,221 @@ +// $Id: jquery.treeTable.js,v 1.1 2010/03/16 23:48:58 davereid Exp $ + +/* + * 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 $("table.treeTable tbody 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 $("#" + classNames[key].substring(9)); + } + } + }; +})(jQuery); diff --git a/sites/all/modules/token/tests/token_test.info b/sites/all/modules/token/tests/token_test.info new file mode 100644 index 0000000000000000000000000000000000000000..f0324c240e9675f2853118948aa5ab60cd4a6d69 --- /dev/null +++ b/sites/all/modules/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 2011-01-12 +version = "7.x-1.0-beta1" +core = "7.x" +project = "token" +datestamp = "1294805790" + diff --git a/sites/all/modules/token/tests/token_test.module b/sites/all/modules/token/tests/token_test.module new file mode 100644 index 0000000000000000000000000000000000000000..1b2f9aa87d78a16e8d0fa7530310d586a260633c --- /dev/null +++ b/sites/all/modules/token/tests/token_test.module @@ -0,0 +1,14 @@ +<?php + +/** + * 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); + } +} diff --git a/sites/all/modules/token/token.css b/sites/all/modules/token/token.css new file mode 100644 index 0000000000000000000000000000000000000000..351bb7c5027811494df9219b9f37caacc3a4a1a4 --- /dev/null +++ b/sites/all/modules/token/token.css @@ -0,0 +1,11 @@ +/* $Id: token.css,v 1.3 2010/09/06 14:55:41 davereid Exp $ */ + +.token-tree { + font-size: 0.85em; + margin-left: 19px; +} + +.token-tree td, .token-tree th { + padding-top: 0; + padding-bottom: 0; +} diff --git a/sites/all/modules/token/token.info b/sites/all/modules/token/token.info new file mode 100644 index 0000000000000000000000000000000000000000..1bcb425ad9d538d4fa703f240f068d34fd274bb0 --- /dev/null +++ b/sites/all/modules/token/token.info @@ -0,0 +1,16 @@ +; $Id: token.info,v 1.12 2010/11/18 00:20:33 davereid Exp $ +name = Token +description = Provides a user interface for the Token API and some missing core tokens. +core = 7.x +files[] = token.module +files[] = token.install +files[] = token.tokens.inc +files[] = token.pages.inc +files[] = token.test + +; Information added by drupal.org packaging script on 2011-01-12 +version = "7.x-1.0-beta1" +core = "7.x" +project = "token" +datestamp = "1294805790" + diff --git a/sites/all/modules/token/token.install b/sites/all/modules/token/token.install new file mode 100644 index 0000000000000000000000000000000000000000..b4466bc98322aa74935894e5b21223eb8327d27f --- /dev/null +++ b/sites/all/modules/token/token.install @@ -0,0 +1,225 @@ +<?php +// $Id: token.install,v 1.15 2010/12/08 16:25:09 davereid Exp $ + +/** + * Implements hook_requirements(). + */ +function token_requirements($phase = 'runtime') { + $requirements = array(); + $t = get_t(); + + // Check for duplicate tokens. + if ($phase == 'runtime') { + if ($duplicate_tokens = token_find_duplicate_tokens()) { + foreach ($duplicate_tokens as $token => $modules) { + $duplicate_tokens[$token] = $t('@token (defined by modules: @modules)', array('@token' => $token, '@modules' => implode(', ', $modules))); + } + $requirements['token_duplicates'] = array( + 'title' => $t('Duplicate tokens'), + 'value' => $t('The following tokens are defined by multiple modules and may cause problems when performing token replacement.'), + 'severity' => REQUIREMENT_WARNING, + 'description' => theme('item_list', array('items' => $duplicate_tokens)), + ); + } + } + + 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() { + $schema['cache_token'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_token']['description'] = 'Cache table for token information.'; + db_create_table('cache_token', $schema['cache_token']); +} + +/** + * 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; +} diff --git a/sites/all/modules/token/token.js b/sites/all/modules/token/token.js new file mode 100644 index 0000000000000000000000000000000000000000..304063227bff253e252c73625192a47df4c3260a --- /dev/null +++ b/sites/all/modules/token/token.js @@ -0,0 +1,56 @@ +// $Id: token.js,v 1.3 2010/03/20 19:48:25 davereid Exp $ + +(function ($) { + +Drupal.behaviors.tokenTree = { + attach: function (context, settings) { + $('table.token-tree', context).once('token-tree', function () { + $(this).treeTable(); + }); + } +}; + +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); diff --git a/sites/all/modules/token/token.module b/sites/all/modules/token/token.module new file mode 100644 index 0000000000000000000000000000000000000000..f0e8692e5d7b3f405b717483bcffcc198346986e --- /dev/null +++ b/sites/all/modules/token/token.module @@ -0,0 +1,918 @@ +<?php +// $Id: token.module,v 1.61 2011/01/12 03:26:59 davereid Exp $ + +/** + * The maximum depth for token tree recursion. + */ +define('TOKEN_MAX_DEPTH', 9); + +/** + * Impelements hook_help(). + */ +function token_help($path, $arg) { + if ($path == 'admin/help#token') { + $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', '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', + );*/ + + // 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, + ); + } + + return $items; +} + +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() { + return array( + 'tree_table' => array( + 'variables' => array('header' => array(), 'rows' => array(), 'attributes' => array(), 'empty' => '', 'caption' => ''), + 'file' => 'token.pages.inc', + ), + 'token_tree' => array( + 'variables' => array('token_types' => array(), 'global_types' => TRUE, 'click_insert' => TRUE, 'show_restricted' => FALSE, 'recursion_limit' => 4), + 'file' => 'token.pages.inc', + ), + ); +} + +/** + * 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(), + ), + ); + + 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': + $form += array('#submit' => array()); + array_unshift($form['#submit'], 'token_clear_cache'); + break; + } +} + +/** + * Clear token caches and static variables. + */ +function token_clear_cache() { + cache_clear_all(NULL, 'cache_token'); + drupal_static_reset('token_get_info'); + drupal_static_reset('token_get_global_token_types'); + drupal_static_reset('token_build_tree'); + drupal_static_reset('_token_profile_fields'); +} + +/** + * 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) { + $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; + } + } + + if (!isset($value)) { + return $mapping; + } + elseif ($value_type == 'token') { + return array_search($value, $mapping); + } + elseif ($value_type == 'entity') { + return isset($mapping[$value]) ? $mapping[$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) { + 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) { + if ($hook == 'tokens' || $hook == 'token_info') { + foreach (_token_core_supported_modules() as $module) { + if (module_exists($module)) { + $implementations[$module] = TRUE; + } + } + // 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() { + 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'])) { + // If this token type extends another token type, then merge in + // the base token type's tokens. + $token_info['tokens'] += array($type_key => array()); + $token_info['tokens'][$type_key] += $token_info['tokens'][$token_info['types'][$type_key]['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 if defined in + * $info['tokens']['type']['module']. + */ +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 (!empty($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 (($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']['#token_types'] = array(); + $form['instance']['settings']['token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array(), + '#weight' => $form['instance']['settings']['file_directory']['#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', + '#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, '', $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. + $element['token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array('user'), + '#show_restricted' => TRUE, + '#weight' => 100, + ); + } +} + +/** + * 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])) { + 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; + } + $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; +} + +/** + * Find tokens that have been declared twice by different modules. + */ +function token_find_duplicate_tokens() { + $all_tokens = 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 (!empty($module_token_info['types'])) { + foreach (array_keys($module_token_info['types']) as $type) { + $all_tokens['type:' . $type][] = $module; + } + } + if (!empty($module_token_info['tokens'])) { + foreach ($module_token_info['tokens'] as $type => $tokens) { + foreach (array_keys($tokens) as $token) { + $all_tokens[$type . ':' . $token][] = $module; + } + } + } + } + + foreach ($all_tokens as $token => $modules) { + if (count($modules) < 2) { + unset($all_tokens[$token]); + } + } + + return $all_tokens; +} + +/** + * 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; +} diff --git a/sites/all/modules/token/token.pages.inc b/sites/all/modules/token/token.pages.inc new file mode 100644 index 0000000000000000000000000000000000000000..8d2935ab964553fabed33d0ed30bbc5c86473cf6 --- /dev/null +++ b/sites/all/modules/token/token.pages.inc @@ -0,0 +1,262 @@ +<?php +// $Id: token.pages.inc,v 1.13 2010/12/18 01:53:33 davereid Exp $ + +/** + * @file + * User page callbacks for the token module. + */ + +/** + * 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-' . drupal_clean_css_identifier($row['parent']); + unset($row['parent']); + } + } + + if (count($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) { + $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()); + } + + $header = array( + t('Name'), + t('Token'), + t('Description'), + ); + $rows = array(); + + foreach ($info['types'] as $type => $type_info) { + if (!in_array($type, $token_types)) { + continue; + } + + if (count($token_types) > 1) { + $row = _token_token_tree_format_row($type, $type_info, TRUE); + unset($row['data']['value']); + $rows[] = $row; + } + + $options = array( + 'flat' => TRUE, + 'restricted' => $variables['show_restricted'], + 'depth' => $variables['recursion_limit'], + ); + $tree = token_build_tree($type, $options); + foreach ($tree as $token => $token_info) { + if (!empty($token_info['restricted']) && empty($variables['show_restricted'])) { + continue; + } + if (count($token_types) > 1 && !isset($token_info['parent'])) { + $token_info['parent'] = $type; + } + $row = _token_token_tree_format_row($token, $token_info); + unset($row['data']['value']); + $rows[] = $row; + } + } + + drupal_add_js(drupal_get_path('module', 'token') . '/token.js'); + drupal_add_css(drupal_get_path('module', 'token') . '/token.css'); + + $table_options = array( + 'header' => $header, + 'rows' => $rows, + 'attributes' => array('class' => array('token-tree')), + 'empty' => t('No tokens available.'), + ); + if ($variables['click_insert']) { + $table_options['caption'] = t('Click a token to insert it into the field you\'ve last clicked.'); + $table_options['attributes']['class'][] = 'token-click-insert'; + } + + return theme('tree_table', $table_options); +} + +/** + * Build a row in the token tree. + */ +function _token_token_tree_format_row($token, array $token_info, $is_group = FALSE) { + $row = array( + 'id' => _token_clean_css_identifier($token), + 'class' => array(), + 'data' => array( + 'name' => $token_info['name'], + 'token' => '', + 'value' => '', + 'description' => $token_info['description'], + ), + ); + + if ($is_group) { + // This is a token type/group. + $row['class'][] = 'token-group'; + } + else { + // This is a token. + $row['data']['token'] = array( + 'data' => $token, + 'class' => array('token-key'), + ); + if (isset($token_info['value'])) { + $row['data']['value'] = $token_info['value']; + } + if (!empty($token_info['parent'])) { + $row['parent'] = _token_clean_css_identifier($token_info['parent']); + } + } + + return $row; +} + +/** + * Wrapper function for drupal_clean_css_identifier() for use with tokens. + * + * This trims any brackets from the token and also cleans the colon character + * to a hyphen. + * + * @see drupal_clean_css_identifier(). + */ +function _token_clean_css_identifier($id) { + return drupal_clean_css_identifier('token-' . trim($id, '[]'), array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '', ':' => '-')); +} + +/** + * Menu callback; prints the available tokens and values for an object. + */ +function token_devel_token_object($entity_type, $entity) { + $header = array( + t('Token'), + t('Value'), + ); + $rows = array(); + + $options = array( + 'flat' => TRUE, + 'values' => TRUE, + 'data' => array($entity_type => $entity), + ); + $tree = token_build_tree($entity_type, $options); + foreach ($tree as $token => $token_info) { + if (!empty($token_info['restricted'])) { + continue; + } + if (!isset($token_info['value']) && !empty($token_info['parent']) && !isset($tree[$token_info['parent']]['value'])) { + continue; + } + $row = _token_token_tree_format_row($token, $token_info); + unset($row['data']['description']); + unset($row['data']['name']); + $rows[] = $row; + } + + drupal_add_js(drupal_get_path('module', 'token') . '/token.js'); + drupal_add_css(drupal_get_path('module', 'token') . '/token.css'); + + $table_options = array( + 'header' => $header, + 'rows' => $rows, + 'attributes' => array('class' => array('token-tree')), + 'empty' => t('No tokens available.'), + ); + return theme('tree_table', $table_options); +} + +function token_autocomplete() { + $args = func_get_args(); + $string = implode('/', $args); + + $token_info = token_info(); + + preg_match_all('/\[([^\s\]:]*):?([^\s\]]*)?\]?/', $string, $matches); + $types = $matches[1]; + $tokens = $matches[2]; + + foreach ($types as $index => $type) { + if (!empty($tokens[$index]) || isset($token_info['types'][$type])) { + token_autocomplete_token($type, $tokens[$index]); + } + else { + token_autocomplete_type($type); + } + } + +} + +function token_autocomplete_type($string = '') { + $token_info = token_info(); + $types = $token_info['types']; + $matches = array(); + + foreach ($types as $type => $info) { + if (!$string || strpos($type, $string) === 0) { + $type_key = "[{$type}:"; + $matches[$type_key] = levenshtein($type, $string); + } + } + + if ($string) { + asort($matches); + } + else { + ksort($matches); + } + + $matches = drupal_map_assoc(array_keys($matches)); + drupal_json_output($matches); +} + +function token_autocomplete_token($token_type) { + $args = func_get_args(); + array_shift($args); + $string = trim(implode('/', $args)); + $string = substr($string, strrpos($string, '[')); + + $token_type = $token_type['type']; + $matches = array(); + + if (!drupal_strlen($string)) { + $matches["[{$token_type}:"] = 0; + } + else { + $depth = max(1, substr_count($string, ':')); + $tree = token_build_tree($token_type, array('flat' => TRUE, 'depth' => $depth)); + foreach (array_keys($tree) as $token) { + if (strpos($token, $string) === 0) { + $matches[$token] = levenshtein($token, $string); + if (isset($tree[$token]['children'])) { + $token = rtrim($token, ':]') . ':'; + $matches[$token] = levenshtein($token, $string); + } + } + } + } + + asort($matches); + $matches = drupal_map_assoc(array_keys($matches)); + drupal_json_output($matches); +} diff --git a/sites/all/modules/token/token.test b/sites/all/modules/token/token.test new file mode 100644 index 0000000000000000000000000000000000000000..ef1c817ec6a00ca54168c0fb41773b9254992dbf --- /dev/null +++ b/sites/all/modules/token/token.test @@ -0,0 +1,741 @@ +<?php +// $Id: token.test,v 1.32 2011/01/12 03:26:59 davereid Exp $ + +/** + * Helper test class with some added functions for testing. + */ +class TokenTestHelper extends DrupalWebTestCase { + function setUp($modules = array()) { + $modules[] = 'path'; + $modules[] = 'token'; + $modules[] = 'token_test'; + parent::setUp($modules); + } + + function assertToken($type, $object, $token, $expected, array $options = array()) { + $this->assertTokens($type, $object, array($token => $expected), $options); + } + + function assertTokens($type, $object, array $tokens, array $options = array()) { + $token_input = drupal_map_assoc(array_keys($tokens)); + $values = token_generate($type, $token_input, array($type => $object), $options); + foreach ($tokens as $token => $expected) { + if (!isset($expected)) { + $this->assertTrue(!isset($values[$token]), t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token))); + } + elseif (!isset($values[$token])) { + $this->fail(t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token))); + } + else { + $this->assertIdentical($values[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $values[$token], '@expected' => $expected))); + } + } + } + + function assertNoTokens($type, $object, array $tokens, array $options = array()) { + $token_input = drupal_map_assoc($tokens); + $values = token_generate($type, $token_input, array($type => $object), $options); + foreach ($tokens as $token) { + $this->assertTrue(!isset($values[$token]), t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token))); + } + } + + function saveAlias($source, $alias, $language = LANGUAGE_NONE) { + $alias = array( + 'source' => $source, + 'alias' => $alias, + 'language' => $language, + ); + path_save($alias); + return $alias; + } + + function saveEntityAlias($entity_type, $entity, $alias, $language = LANGUAGE_NONE) { + $uri = entity_uri($entity_type, $entity); + return $this->saveAlias($uri['path'], $alias, $language); + } + + /** + * Make a page request and test for token generation. + */ + function assertPageTokens($url, array $tokens, array $data = array(), array $options = array()) { + if (empty($tokens)) { + return TRUE; + } + + $token_page_tokens = array( + 'tokens' => $tokens, + 'data' => $data, + 'options' => $options, + ); + variable_set('token_page_tokens', $token_page_tokens); + + $options += array('url_options' => array()); + $this->drupalGet($url, $options['url_options']); + $this->refreshVariables(); + $result = variable_get('token_page_tokens', array()); + + if (!isset($result['values']) || !is_array($result['values'])) { + return $this->fail('Failed to generate tokens.'); + } + + foreach ($tokens as $token => $expected) { + if (!isset($expected)) { + $this->assertTrue(!isset($result['values'][$token]) || $result['values'][$token] === $token, t("Token value for @token was not generated.", array('@token' => $token))); + } + elseif (!isset($result['values'][$token])) { + $this->fail(t('Failed to generate token @token.', array('@token' => $token))); + } + else { + $this->assertIdentical($result['values'][$token], (string) $expected, t("Token value for @token was '@actual', expected value '@expected'.", array('@token' => $token, '@actual' => $result['values'][$token], '@expected' => $expected))); + } + } + } +} + +class TokenUnitTestCase extends TokenTestHelper { + public static function getInfo() { + return array( + 'name' => 'Token unit tests', + 'description' => 'Test basic, low-level token functions.', + 'group' => 'Token', + ); + } + + /** + * Test token_get_invalid_tokens() and token_get_invalid_tokens_by_context(). + */ + public function testGetInvalidTokens() { + $tests = array(); + $tests[] = array( + 'valid tokens' => array( + '[node:title]', + '[node:created:short]', + '[node:created:custom:invalid]', + '[node:created:custom:mm-YYYY]', + '[site:name]', + '[site:slogan]', + '[current-date:short]', + '[current-user:uid]', + '[current-user:ip-address]', + ), + 'invalid tokens' => array( + '[node:title:invalid]', + '[node:created:invalid]', + '[node:created:short:invalid]', + '[invalid:title]', + '[site:invalid]', + '[user:ip-address]', + '[user:uid]', + '[comment:cid]', + // Deprecated tokens + '[node:tnid]', + '[node:type]', + '[node:type-name]', + '[date:short]', + ), + 'types' => array('node'), + ); + $tests[] = array( + 'valid tokens' => array( + '[node:title]', + '[node:created:short]', + '[node:created:custom:invalid]', + '[node:created:custom:mm-YYYY]', + '[site:name]', + '[site:slogan]', + '[user:uid]', + '[comment:cid]', + '[current-date:short]', + '[current-user:uid]', + ), + 'invalid tokens' => array( + '[node:title:invalid]', + '[node:created:invalid]', + '[node:created:short:invalid]', + '[invalid:title]', + '[site:invalid]', + '[user:ip-address]', + // Deprecated tokens + '[node:tnid]', + '[node:type]', + '[node:type-name]', + ), + 'types' => array('all'), + ); + + foreach ($tests as $test) { + $tokens = array_merge($test['valid tokens'], $test['invalid tokens']); + shuffle($tokens); + + $invalid_tokens = token_get_invalid_tokens_by_context(implode(' ', $tokens), $test['types']); + + sort($invalid_tokens); + sort($test['invalid tokens']); + $this->assertEqual($invalid_tokens, $test['invalid tokens'], 'Invalid tokens detected properly: ' . implode(', ', $invalid_tokens)); + } + } +} + +class TokenCommentTestCase extends TokenTestHelper { + public static function getInfo() { + return array( + 'name' => 'Comment token tests', + 'description' => 'Test the comment tokens.', + 'group' => 'Token', + ); + } + + function testCommentTokens() { + $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN)); + + $parent_comment = new stdClass; + $parent_comment->nid = $node->nid; + $parent_comment->pid = 0; + $parent_comment->cid = NULL; + $parent_comment->uid = 0; + $parent_comment->name = 'anonymous user'; + $parent_comment->mail = 'anonymous@example.com'; + $parent_comment->subject = $this->randomName(); + $parent_comment->timestamp = mt_rand($node->created, REQUEST_TIME); + $parent_comment->language = LANGUAGE_NONE; + $parent_comment->body[LANGUAGE_NONE][0] = $this->randomName(); + comment_save($parent_comment); + + $tokens = array( + 'url' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => TRUE)), + 'url:absolute' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => TRUE)), + 'url:relative' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => FALSE)), + 'url:path' => 'comment/' . $parent_comment->cid, + 'url:alias' => 'comment/' . $parent_comment->cid, + 'parent:url:absolute' => NULL, + ); + $this->assertTokens('comment', $parent_comment, $tokens); + + $comment = new stdClass(); + $comment->nid = $node->nid; + $comment->pid = $parent_comment->cid; + $comment->cid = NULL; + $comment->uid = 1; + $comment->subject = $this->randomName(); + $comment->timestamp = mt_rand($parent_comment->created, REQUEST_TIME); + $comment->language = LANGUAGE_NONE; + $comment->body[LANGUAGE_NONE][0] = $this->randomName(); + comment_save($comment); + + $tokens = array( + 'url' => url('comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid, 'absolute' => TRUE)), + 'url:absolute' => url('comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid, 'absolute' => TRUE)), + 'url:relative' => url('comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid, 'absolute' => FALSE)), + 'url:path' => 'comment/' . $comment->cid, + 'url:alias' => 'comment/' . $comment->cid, + 'parent:url:absolute' => url('comment/' . $parent_comment->cid, array('fragment' => 'comment-' . $parent_comment->cid, 'absolute' => TRUE)), + ); + $this->assertTokens('comment', $comment, $tokens); + } +} + +class TokenNodeTestCase extends TokenTestHelper { + public static function getInfo() { + return array( + 'name' => 'Node and content type token tests', + 'description' => 'Test the node and content type tokens.', + 'group' => 'Token', + ); + } + + function testNodeTokens() { + $source_node = $this->drupalCreateNode(array('log' => $this->randomName(), 'path' => array('alias' => 'content/source-node'))); + $tokens = array( + 'source' => NULL, + 'source:nid' => NULL, + 'log' => $source_node->log, + 'url:absolute' => url("node/{$source_node->nid}", array('absolute' => TRUE)), + 'url:relative' => url("node/{$source_node->nid}", array('absolute' => FALSE)), + 'url:path' => "node/{$source_node->nid}", + 'url:alias' => 'content/source-node', + 'content-type' => 'Basic page', + 'content-type:name' => 'Basic page', + 'content-type:machine-name' => 'page', + 'content-type:description' => "Use <em>basic pages</em> for your static content, such as an 'About us' page.", + 'content-type:node-count' => 1, + 'content-type:edit-url' => url('admin/structure/types/manage/page', array('absolute' => TRUE)), + // Deprecated tokens. + 'tnid' => 0, + 'type' => 'page', + 'type-name' => 'Basic page', + ); + $this->assertTokens('node', $source_node, $tokens); + + $translated_node = $this->drupalCreateNode(array('tnid' => $source_node->nid, 'type' => 'article')); + $tokens = array( + 'source' => $source_node->title, + 'source:nid' => $source_node->nid, + 'log' => '', + 'url:absolute' => url("node/{$translated_node->nid}", array('absolute' => TRUE)), + 'url:relative' => url("node/{$translated_node->nid}", array('absolute' => FALSE)), + 'url:path' => "node/{$translated_node->nid}", + 'url:alias' => "node/{$translated_node->nid}", + 'content-type' => 'Article', + 'content-type:name' => 'Article', + 'content-type:machine-name' => 'article', + 'content-type:description' => "Use <em>articles</em> for time-sensitive content like news, press releases or blog posts.", + 'content-type:node-count' => 1, + 'content-type:edit-url' => url('admin/structure/types/manage/article', array('absolute' => TRUE)), + // Deprecated tokens. + 'type' => 'article', + 'type-name' => 'Article', + 'tnid' => $source_node->nid, + ); + $this->assertTokens('node', $translated_node, $tokens); + } +} + +class TokenMenuTestCase extends TokenTestHelper { + public static function getInfo() { + return array( + 'name' => 'Menu link and menu token tests', + 'description' => 'Test the menu tokens.', + 'group' => 'Token', + ); + } + + function setUp($modules = array()) { + $modules[] = 'menu'; + parent::setUp($modules); + } + + function testMenuTokens() { + // Add a root link. + $root_link = array( + 'link_path' => 'root', + 'link_title' => 'Root link', + 'menu_name' => 'main-menu', + ); + menu_link_save($root_link); + + // Add another link with the root link as the parent + $parent_link = array( + 'link_path' => 'root/parent', + 'link_title' => 'Parent link', + 'menu_name' => 'main-menu', + 'plid' => $root_link['mlid'], + ); + menu_link_save($parent_link); + + // Test menu link tokens. + $tokens = array( + 'mlid' => $parent_link['mlid'], + 'title' => 'Parent link', + 'menu' => 'Main menu', + 'menu:name' => 'Main menu', + 'menu:machine-name' => 'main-menu', + 'menu:description' => 'The <em>Main</em> menu is used on many sites to show the major sections of the site, often in a top navigation bar.', + 'menu:menu-link-count' => 3, // Standard install creates one link. + 'menu:edit-url' => url("admin/structure/menu/manage/main-menu", array('absolute' => TRUE)), + 'url' => url('root/parent', array('absolute' => TRUE)), + 'url:absolute' => url('root/parent', array('absolute' => TRUE)), + 'url:relative' => url('root/parent', array('absolute' => FALSE)), + 'url:path' => 'root/parent', + 'url:alias' => 'root/parent', + 'edit-url' => url("admin/structure/menu/item/{$parent_link['mlid']}/edit", array('absolute' => TRUE)), + 'parent' => 'Root link', + 'parent:mlid' => $root_link['mlid'], + 'parent:title' => 'Root link', + 'parent:menu' => 'Main menu', + 'parent:parent' => NULL, + 'root' => 'Root link', + 'root:mlid' => $root_link['mlid'], + 'root:parent' => NULL, + 'root:root' => NULL, + ); + $this->assertTokens('menu-link', $parent_link, $tokens); + + // Add a node menu link + $node_link = array( + 'enabled' => TRUE, + 'link_title' => 'Node link', + 'plid' => $parent_link['mlid'], + 'customized' => 0, + 'description' => '', + ); + $node = $this->drupalCreateNode(array('menu' => $node_link)); + + // Test [node:menu] tokens. + $tokens = array( + 'menu-link' => 'Node link', + 'menu-link:mlid' => $node->menu['mlid'], + 'menu-link:title' => 'Node link', + 'menu-link:menu' => 'Main menu', + 'menu-link:url' => url('node/' . $node->nid, array('absolute' => TRUE)), + 'menu-link:url:path' => 'node/' . $node->nid, + 'menu-link:edit-url' => url("admin/structure/menu/item/{$node->menu['mlid']}/edit", array('absolute' => TRUE)), + 'menu-link:parent' => 'Parent link', + 'menu-link:parent:mlid' => $node->menu['plid'], + 'menu-link:parent:mlid' => $parent_link['mlid'], + 'menu-link:root' => 'Root link', + 'menu-link:root:mlid' => $root_link['mlid'], + ); + $this->assertTokens('node', $node, $tokens); + } +} + +class TokenTaxonomyTestCase extends TokenTestHelper { + protected $vocab; + + public static function getInfo() { + return array( + 'name' => 'Taxonomy and vocabulary token tests', + 'description' => 'Test the taxonomy tokens.', + 'group' => 'Token', + ); + } + + function setUp($modules = array()) { + $modules[] = 'taxonomy'; + parent::setUp($modules); + // Use the default 'Tags' taxonomy included with the standard install profile. + $this->vocab = taxonomy_vocabulary_machine_name_load('tags'); + } + + /** + * Test the additional taxonomy term tokens. + */ + function testTaxonomyTokens() { + $parent_term = $this->addTerm($this->vocab, array('path' => array('alias' => 'parent-term'))); + $parent_term_tokens = array( + 'url' => url("taxonomy/term/{$parent_term->tid}", array('absolute' => TRUE)), + 'url:absolute' => url("taxonomy/term/{$parent_term->tid}", array('absolute' => TRUE)), + 'url:relative' => url("taxonomy/term/{$parent_term->tid}", array('absolute' => FALSE)), + 'url:path' => "taxonomy/term/{$parent_term->tid}", + 'url:alias' => 'parent-term', + 'edit-url' => url("taxonomy/term/{$parent_term->tid}/edit", array('absolute' => TRUE)), + ); + $this->assertTokens('term', $parent_term, $parent_term_tokens); + + $child_term = $this->addTerm($this->vocab, array('parent' => $parent_term->tid)); + $child_term_tokens = array( + 'url' => url("taxonomy/term/{$child_term->tid}", array('absolute' => TRUE)), + 'url:absolute' => url("taxonomy/term/{$child_term->tid}", array('absolute' => TRUE)), + 'url:relative' => url("taxonomy/term/{$child_term->tid}", array('absolute' => FALSE)), + 'url:path' => "taxonomy/term/{$child_term->tid}", + 'url:alias' => "taxonomy/term/{$child_term->tid}", + 'edit-url' => url("taxonomy/term/{$child_term->tid}/edit", array('absolute' => TRUE)), + ); + $this->assertTokens('term', $child_term, $child_term_tokens); + } + + /** + * Test the additional vocabulary tokens. + */ + function testVocabularyTokens() { + $vocabulary = $this->vocab; + $tokens = array( + 'machine-name' => 'tags', + 'edit-url' => url("admin/structure/taxonomy/{$vocabulary->machine_name}/edit", array('absolute' => TRUE)), + ); + $this->assertTokens('vocabulary', $vocabulary, $tokens); + } + + function addVocabulary(array $vocabulary = array()) { + $vocabulary += array( + 'name' => drupal_strtolower($this->randomName(5)), + 'nodes' => array('article' => 'article'), + ); + $vocabulary = (object) $vocabulary; + taxonomy_vocabulary_save($vocabulary); + return $vocabulary; + } + + function addTerm(stdClass $vocabulary, array $term = array()) { + $term += array( + 'name' => drupal_strtolower($this->randomName(5)), + 'vid' => $vocabulary->vid, + ); + $term = (object) $term; + taxonomy_term_save($term); + return $term; + } +} + +class TokenUserTestCase extends TokenTestHelper { + protected $account = NULL; + + public static function getInfo() { + return array( + 'name' => 'User token tests', + 'description' => 'Test the user tokens.', + 'group' => 'Token', + ); + } + + function setUp($modules = array()) { + parent::setUp($modules); + + // Enable user pictures. + variable_set('user_pictures', 1); + variable_set('user_picture_file_size', 0); + + // Set up the pictures directory. + $picture_path = file_default_scheme() . '://' . variable_get('user_picture_path', 'pictures'); + if (!file_prepare_directory($picture_path, FILE_CREATE_DIRECTORY)) { + $this->fail('Could not create directory ' . $picture_path . '.'); + } + + $this->account = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($this->account); + } + + function testUserTokens() { + // Add a user picture to the account. + $image = current($this->drupalGetTestFiles('image')); + $edit = array('files[picture_upload]' => drupal_realpath($image->uri)); + $this->drupalPost('user/' . $this->account->uid . '/edit', $edit, t('Save')); + + // Load actual user data from database. + $this->account = user_load($this->account->uid, TRUE); + $this->assertTrue(!empty($this->account->picture->fid), 'User picture uploaded.'); + + $user_tokens = array( + 'picture' => theme('user_picture', array('account' => $this->account)), + 'picture:fid' => $this->account->picture->fid, + 'ip-address' => NULL, + ); + $this->assertTokens('user', $this->account, $user_tokens); + + $edit = array('user_pictures' => FALSE); + $this->drupalPost('admin/config/people/accounts', $edit, 'Save configuration'); + $this->assertText('The configuration options have been saved.'); + + $user_tokens = array( + 'picture' => NULL, + 'picture:fid' => NULL, + 'ip-address' => NULL, + ); + $this->assertTokens('user', $this->account, $user_tokens); + + // The ip address token should work for the current user token type. + $tokens = array( + 'ip-address' => ip_address(), + ); + $this->assertTokens('current-user', NULL, $tokens); + } +} + +class TokenEntityTestCase extends TokenTestHelper { + public static function getInfo() { + return array( + 'name' => 'Entity token tests', + 'description' => 'Test the entity tokens.', + 'group' => 'Token', + ); + } + + function setUp($modules = array()) { + $modules[] = 'taxonomy'; + parent::setUp($modules); + } + + function testEntityMapping() { + $this->assertIdentical(token_get_entity_mapping('token', 'node'), 'node'); + $this->assertIdentical(token_get_entity_mapping('token', 'term'), 'taxonomy_term'); + $this->assertIdentical(token_get_entity_mapping('token', 'vocabulary'), 'taxonomy_vocabulary'); + $this->assertIdentical(token_get_entity_mapping('token', 'invalid'), FALSE); + $this->assertIdentical(token_get_entity_mapping('entity', 'node'), 'node'); + $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_term'), 'term'); + $this->assertIdentical(token_get_entity_mapping('entity', 'taxonomy_vocabulary'), 'vocabulary'); + $this->assertIdentical(token_get_entity_mapping('entity', 'invalid'), FALSE); + + // Test that when we send the mis-matched entity type into token_replace() + // that we still get the tokens replaced. + $vocabulary = taxonomy_vocabulary_machine_name_load('tags'); + $term = $this->addTerm($vocabulary); + $this->assertIdentical(token_replace('[vocabulary:name]', array('taxonomy_vocabulary' => $vocabulary)), $vocabulary->name); + $this->assertIdentical(token_replace('[term:name][term:vocabulary:name]', array('taxonomy_term' => $term)), $term->name . $vocabulary->name); + } + + function addTerm(stdClass $vocabulary, array $term = array()) { + $term += array( + 'name' => drupal_strtolower($this->randomName(5)), + 'vid' => $vocabulary->vid, + ); + $term = (object) $term; + taxonomy_term_save($term); + return $term; + } +} + +/** + * Test the profile tokens. + */ +class TokenProfileTestCase extends TokenTestHelper { + private $account; + + public static function getInfo() { + return array( + 'name' => 'Profile token tests', + 'description' => 'Test the profile tokens.', + 'group' => 'Token', + ); + } + + function setUp($modules = array()) { + $modules[] = 'profile'; + parent::setUp($modules); + $this->account = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($this->account); + } + + /** + * Test the profile tokens. + */ + function testProfileTokens() { + $field_types = _profile_field_types(); + foreach (array_keys($field_types) as $field_type) { + $field = array(); + switch ($field_type) { + case 'checkbox': + $field['title'] = 'This is a checkbox'; + break; + case 'selection': + $field['options'] = implode("\n", array('Red', 'Blue', 'Green')); + break; + } + $this->addProfileField($field_type, $field); + } + + // Submit the profile fields for the user. + $edit = array( + 'profile_textfield' => 'This is a text field', + 'profile_textarea' => "First paragraph.\n\nSecond paragraph.", + 'profile_checkbox' => TRUE, + 'profile_selection' => 'Red', + 'profile_list' => ' Drupal , Joomla ', + 'profile_url' => 'http://www.example.com/', + 'profile_date[month]' => 5, + 'profile_date[day]' => 20, + 'profile_date[year]' => 1984, + ); + $this->drupalPost("user/{$this->account->uid}/edit/SimpleTest", $edit, 'Save'); + $account = user_load($this->account->uid, TRUE); + + // Test the profile token values. + $tokens = array( + 'profile-textfield' => 'This is a text field', + 'profile-textarea' => "<p>First paragraph.</p>\n<p>Second paragraph.</p>\n", + 'profile-checkbox' => 'This is a checkbox', + 'profile-selection' => 'Red', + 'profile-list' => 'Drupal, Joomla', + 'profile-url' => 'http://www.example.com/', + 'profile-date' => format_date(453859200, 'medium', '', NULL), + 'profile-date:raw' => '453859200', + 'profile-date:custom:Y' => '1984', + ); + $this->assertTokens('user', $account, $tokens); + + // 'Un-select' the checkbox and select profile fields. + $edit = array( + 'profile_checkbox' => FALSE, + 'profile_selection' => '0', + ); + $this->drupalPost("user/{$this->account->uid}/edit/SimpleTest", $edit, 'Save'); + $account = user_load($this->account->uid, TRUE); + + // The checkbox and select profile tokens should no longer return a value. + $tokens = array( + 'profile-checkbox' => NULL, + 'profile-selection' => NULL, + ); + $this->assertTokens('user', $account, $tokens); + } + + /** + * Add a profile field. + * + * @param $type + * The profile field type. + * @param $field + * (optional) An array of the profile field properties. + * + * @return + * The saved profile field record object. + * + * @see drupal_form_submit() + */ + function addProfileField($type, array $field = array()) { + $field += array( + 'type' => $type, + 'category' => 'SimpleTest', + 'title' => $this->randomName(8), + 'name' => 'profile_' . $type, + 'explanation' => $this->randomName(50), + 'autocomplete' => 0, + 'required' => 0, + 'register' => 0, + ); + + // Submit the profile field using drupal_form_submit(). + $form_state = array(); + $form_state['values'] = $field; + $form_state['values']['op'] = 'Save field'; + $form_state['build_info']['args'] = array($type); + drupal_form_submit('profile_field_form', $form_state); + + // Verify the profile field was created successfully. + $saved_field = db_query("SELECT * FROM {profile_field} WHERE type = :type AND name = :name", array(':type' => $type, ':name' => $field['name']))->fetchObject(); + $this->assertTrue($saved_field, t('Profile field @name created.', array('@name' => $saved_field->name))); + + // Why in the hell these fields get saved with not the explict values + // for autocomplete, register and required that we tell it, I have no idea. + // So we need to manually save it again. Stupid profile module... + $saved_field->autocomplete = $field['autocomplete']; + $saved_field->required = $field['required']; + $saved_field->register = $field['register']; + drupal_write_record('profile_field', $saved_field, array('fid')); + + return $saved_field; + } +} + +/** + * Test the current page tokens. + */ +class TokenCurrentPageTestCase extends TokenTestHelper { + public static function getInfo() { + return array( + 'name' => 'Current page token tests', + 'description' => 'Test the [current-page:*] tokens.', + 'group' => 'Token', + ); + } + + function testCurrentPageTokens() { + $tokens = array( + '[current-page:title]' => 'Welcome to Drupal', + '[current-page:url]' => url('node', array('absolute' => TRUE)), + '[current-page:url:absolute]' => url('node', array('absolute' => TRUE)), + '[current-page:url:relative]' => url('node', array('absolute' => FALSE)), + '[current-page:url:path]' => 'node', + '[current-page:url:alias]' => 'node', + '[current-page:page-number]' => 1, + '[current-page:arg:0]' => 'node', + '[current-page:arg:1]' => NULL, + ); + $this->assertPageTokens('', $tokens); + + $node = $this->drupalCreateNode(array('title' => 'Node title', 'path' => array('alias' => 'node-alias'))); + $tokens = array( + '[current-page:title]' => 'Node title', + '[current-page:url]' => url("node/{$node->nid}", array('absolute' => TRUE)), + '[current-page:url:absolute]' => url("node/{$node->nid}", array('absolute' => TRUE)), + '[current-page:url:relative]' => url("node/{$node->nid}", array('absolute' => FALSE)), + '[current-page:url:path]' => "node/{$node->nid}", + '[current-page:url:alias]' => 'node-alias', + '[current-page:page-number]' => 1, + '[current-page:arg:0]' => 'node', + '[current-page:arg:1]' => $node->nid, + '[current-page:arg:2]' => NULL, + ); + $this->assertPageTokens("node/{$node->nid}", $tokens); + } +} diff --git a/sites/all/modules/token/token.tokens.inc b/sites/all/modules/token/token.tokens.inc new file mode 100644 index 0000000000000000000000000000000000000000..e85ab6792eef658899621e4d8ed1129181c16d42 --- /dev/null +++ b/sites/all/modules/token/token.tokens.inc @@ -0,0 +1,862 @@ +<?php +// $Id: token.tokens.inc,v 1.37 2011/01/09 20:49:07 davereid Exp $ + +/** + * @file + * Token callbacks for the token module. + */ + +/** + * Implements hook_token_info_alter(). + */ +function token_token_info_alter(&$info) { + // Force 'date' type tokens to require input and add a 'current-date' type. + // @todo Remove when http://drupal.org/node/943028 is fixed. + $info['types']['date']['needs-data'] = 'date'; + $info['types']['current-date'] = array( + 'name' => t('Current date'), + 'description' => t('Tokens related to the current date and time.'), + 'type' => 'date', + ); + + // Add a 'dynamic' key to any tokens that have chained but dynamic tokens. + $info['tokens']['date']['custom']['dynamic'] = TRUE; + + // Remove deprecated tokens from being listed. + unset($info['tokens']['node']['tnid']); + unset($info['tokens']['node']['type']); + unset($info['tokens']['node']['type-name']); + + // Add [token:path] tokens for any URI-able entities. + $entities = entity_get_info(); + foreach ($entities as $entity => $entity_info) { + if (!isset($entity_info['token type'])) { + continue; + } + $token_type = $entity_info['token type']; + + // Add [entity:url] tokens if they do not already exist. + // @todo Support entity:label + if (!isset($info['tokens'][$token_type]['url']) && (!empty($entity_info['uri callback']) || !empty($entity_info['bundles'][$entity]['uri callback']))) { + $info['tokens'][$token_type]['url'] = array( + 'name' => t('URL'), + 'description' => t('The URL of the @entity.', array('@entity' => drupal_strtolower($entity_info['label']))), + 'module' => 'token', + 'type' => 'url', + ); + } + } + + $info['tokens']['comment']['url']['type'] = 'url'; + //$info['tokens']['file']['url']['type'] = 'url'; + $info['tokens']['node']['url']['type'] = 'url'; + $info['tokens']['term']['url']['type'] = 'url'; + $info['tokens']['user']['url']['type'] = 'url'; + // @todo Support other tokens not named 'url' +} + +/** + * Implements hook_token_info(). + */ +function token_token_info() { + // Node tokens. + $info['tokens']['node']['source'] = array( + 'name' => t('Translation source node'), + 'description' => t("The source node for this current node's translation set."), + 'type' => 'node', + ); + $info['tokens']['node']['log'] = array( + 'name' => t('Revision log message'), + 'description' => t('The explanation of the most recent changes made to the node.'), + ); + $info['tokens']['node']['content-type'] = array( + 'name' => t('Content type'), + 'description' => t('The content type of the node.'), + 'type' => 'content-type', + ); + + // Content type tokens. + $info['types']['content-type'] = array( + 'name' => t('Content types'), + 'description' => t('Tokens related to content types.'), + 'needs-data' => 'content-type', + ); + $info['tokens']['content-type']['name'] = array( + 'name' => t('Name'), + 'description' => t('The name of the content type.'), + ); + $info['tokens']['content-type']['machine-name'] = array( + 'name' => t('Machine-readable name'), + 'description' => t('The unique machine-readable name of the content type.'), + ); + $info['tokens']['content-type']['description'] = array( + 'name' => t('Description'), + 'description' => t('The optional description of the content type.'), + ); + $info['tokens']['content-type']['node-count'] = array( + 'name' => t('Node count'), + 'description' => t('The number of nodes belonging to the content type.'), + ); + $info['tokens']['content-type']['edit-url'] = array( + 'name' => t('Edit URL'), + 'description' => t("The URL of the content type's edit page."), + //'type' => 'url', + ); + + // Taxonomy term and vocabulary tokens. + if (module_exists('taxonomy')) { + $info['tokens']['term']['edit-url'] = array( + 'name' => t('Edit URL'), + 'description' => t("The URL of the taxonomy term's edit page."), + //'type' => 'url', + ); + + $info['tokens']['vocabulary']['machine-name'] = array( + 'name' => t('Machine-readable name'), + 'description' => t('The unique machine-readable name of the vocabulary.'), + ); + $info['tokens']['vocabulary']['edit-url'] = array( + 'name' => t('Edit URL'), + 'description' => t("The URL of the vocabulary's edit page."), + //'type' => 'url', + ); + } + + // File tokens. + $info['tokens']['file']['extension'] = array( + 'name' => t('Extension'), + 'description' => t('The extension of the file.'), + ); + + // User tokens. + // Add information on the restricted user tokens. + $info['tokens']['user']['cancel-url'] = array( + 'name' => t('Account cancellation URL'), + 'description' => t('The URL of the confirm delete page for the user account.'), + 'restricted' => TRUE, + //'type' => 'url', + ); + $info['tokens']['user']['one-time-login-url'] = array( + 'name' => t('One-time login URL'), + 'description' => t('The URL of the one-time login page for the user account.'), + 'restricted' => TRUE, + //'type' => 'url', + ); + if (variable_get('user_pictures', 0)) { + $info['tokens']['user']['picture'] = array( + 'name' => t('Picture'), + 'description' => t('The picture of the user.'), + 'type' => 'file', + ); + } + + // Current user tokens. + $info['tokens']['current-user']['ip-address'] = array( + 'name' => t('IP address'), + 'description' => 'The IP address of the current user.', + ); + + // Menu link tokens (work regardless if menu module is enabled or not). + $info['types']['menu-link'] = array( + 'name' => t('Menu links'), + 'description' => t('Tokens related to menu links.'), + 'needs-data' => 'menu-link', + ); + $info['tokens']['menu-link']['mlid'] = array( + 'name' => t('Link ID'), + 'description' => t('The unique ID of the menu link.'), + ); + $info['tokens']['menu-link']['title'] = array( + 'name' => t('Title'), + 'description' => t('The title of the menu link.'), + ); + $info['tokens']['menu-link']['url'] = array( + 'name' => t('URL'), + 'description' => t('The URL of the menu link.'), + 'type' => 'url', + ); + $info['tokens']['menu-link']['parent'] = array( + 'name' => t('Parent'), + 'description' => t("The menu link's parent."), + 'type' => 'menu-link', + ); + $info['tokens']['menu-link']['root'] = array( + 'name' => t('Root'), + 'description' => t("The menu link's root."), + 'type' => 'menu-link', + ); + + // Current page tokens. + $info['types']['current-page'] = array( + 'name' => t('Current page'), + 'description' => t('Tokens related to the current page request.'), + ); + $info['tokens']['current-page']['title'] = array( + 'name' => t('Title'), + 'description' => t('The title of the current page.'), + ); + $info['tokens']['current-page']['url'] = array( + 'name' => t('URL'), + 'description' => t('The URL of the current page.'), + 'type' => 'url', + ); + $info['tokens']['current-page']['page-number'] = array( + 'name' => t('Page number'), + 'description' => t('The page number of the current page when viewing paged lists.'), + ); + $info['tokens']['current-page']['arg'] = array( + 'name' => t('Page argument'), + 'description' => t("The specific argument of the current page (e.g. 'arg:1' on the page 'node/1' returns '1')."), + 'dynamic' => TRUE, + ); + + $info['types']['url'] = array( + 'name' => t('URL'), + 'description' => t('Tokens related to URLs.'), + 'needs-data' => 'url', + ); + $info['tokens']['url']['path'] = array( + 'name' => t('Internal path'), + 'description' => t('The internal Drupal path of the URL.'), + ); + $info['tokens']['url']['alias'] = array( + 'name' => t('Alias'), + 'description' => t('The alias of the URL.'), + ); + $info['tokens']['url']['relative'] = array( + 'name' => t('Relative URL'), + 'description' => t('The relative URL.'), + ); + $info['tokens']['url']['absolute'] = array( + 'name' => t('Absolute URL'), + 'description' => t('The absolute URL.'), + ); + + return $info; +} + +/** + * Implements hook_tokens(). + */ +function token_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + + $url_options = array('absolute' => TRUE); + if (isset($options['language'])) { + $url_options['language'] = $options['language']; + $language_code = $options['language']->language; + } + else { + $language_code = NULL; + } + + $sanitize = !empty($options['sanitize']); + + // Current date tokens. + // @todo Remove when http://drupal.org/node/943028 is fixed. + if ($type == 'current-date') { + $replacements += token_generate('date', $tokens, array('date' => REQUEST_TIME), $options); + } + + // Comment tokens. + if ($type == 'comment' && !empty($data['comment'])) { + $comment = $data['comment']; + + // Chained token relationships. + if (($url_tokens = token_find_with_prefix($tokens, 'url'))) { + $replacements += token_generate('url', $url_tokens, entity_uri('comment', $comment), $options); + } + } + + // Node tokens. + if ($type == 'node' && !empty($data['node'])) { + $node = $data['node']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'source': + if (!empty($node->tnid) && $source_node = node_load($node->tnid)) { + $title = $source_node->title; + $replacements[$original] = $sanitize ? filter_xss($title) : $title; + } + break; + case 'log': + $replacements[$original] = $sanitize ? filter_xss($node->log) : $node->log; + break; + case 'content-type': + $type_name = node_type_get_name($node); + $replacements[$original] = $sanitize ? check_plain($type_name) : $type_name; + break; + } + } + + // Chained token relationships. + if (!empty($node->tnid) && ($source_tokens = token_find_with_prefix($tokens, 'source')) && $source_node = node_load($node->tnid)) { + $replacements += token_generate('node', $source_tokens, array('node' => $source_node), $options); + } + if (($node_type_tokens = token_find_with_prefix($tokens, 'content-type')) && $node_type = node_type_load($node->type)) { + $replacements += token_generate('content-type', $node_type_tokens, array('node_type' => $node_type), $options); + } + if (($url_tokens = token_find_with_prefix($tokens, 'url'))) { + $replacements += token_generate('url', $url_tokens, entity_uri('node', $node), $options); + } + } + + // Content type tokens. + if ($type == 'content-type' && !empty($data['node_type'])) { + $node_type = $data['node_type']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'name': + $replacements[$original] = $sanitize ? check_plain($node_type->name) : $node_type->name; + break; + case 'machine-name': + // This is a machine name so does not ever need to be sanitized. + $replacements[$original] = $node_type->type; + break; + case 'description': + $replacements[$original] = $sanitize ? filter_xss($node_type->description) : $node_type->description; + break; + case 'node-count': + $query = db_select('node'); + $query->condition('type', $node_type->type); + $query->addTag('node_type_node_count'); + $count = $query->countQuery()->execute()->fetchField(); + $replacements[$original] = (int) $count; + break; + case 'edit-url': + $replacements[$original] = url("admin/structure/types/manage/{$node_type->type}", $url_options); + break; + } + } + } + + // Taxonomy term tokens. + if ($type == 'term' && !empty($data['term'])) { + $term = $data['term']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'edit-url': + $replacements[$original] = url("taxonomy/term/{$term->tid}/edit", $url_options); + break; + } + } + + // Chained token relationships. + if (($url_tokens = token_find_with_prefix($tokens, 'url'))) { + $replacements += token_generate('url', $url_tokens, entity_uri('taxonomy_term', $term), $options); + } + } + + // Vocabulary tokens. + if ($type == 'vocabulary' && !empty($data['vocabulary'])) { + $vocabulary = $data['vocabulary']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'machine-name': + // This is a machine name so does not ever need to be sanitized. + $replacements[$original] = $vocabulary->machine_name; + break; + case 'edit-url': + $replacements[$original] = url("admin/structure/taxonomy/{$vocabulary->machine_name}/edit", $url_options); + break; + } + } + } + + // File tokens. + if ($type == 'file' && !empty($data['file'])) { + $file = $data['file']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'extension': + $extension = pathinfo($file->filename, PATHINFO_EXTENSION); + $replacements[$original] = $sanitize ? check_plain($extension) : $extension; + break; + } + } + } + + // User tokens. + if ($type == 'user' && !empty($data['user'])) { + $account = $data['user']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'picture': + if (variable_get('user_pictures', 0)) { + $replacements[$original] = theme('user_picture', array('account' => $account)); + } + break; + } + } + + // Chained token relationships. + if (variable_get('user_pictures', 0) && !empty($account->picture) && ($picture_tokens = token_find_with_prefix($tokens, 'picture'))) { + // @todo Remove when core bug http://drupal.org/node/978028 is fixed. + $account->picture->description = ''; + $replacements += token_generate('file', $picture_tokens, array('file' => $account->picture), $options); + } + if (($url_tokens = token_find_with_prefix($tokens, 'url'))) { + $replacements += token_generate('url', $url_tokens, entity_uri('user', $account), $options); + } + } + + // Current user tokens. + if ($type == 'current-user') { + foreach ($tokens as $name => $original) { + switch ($name) { + case 'ip-address': + $ip = ip_address(); + $replacements[$original] = $sanitize ? check_plain($ip) : $ip; + break; + } + } + } + + // Menu link tokens. + if ($type == 'menu-link' && !empty($data['menu-link'])) { + $link = (array) $data['menu-link']; + + if (!isset($link['title'])) { + // Re-load the link if it was not loaded via token_menu_link_load(). + $link = token_menu_link_load($link['mlid']); + } + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'mlid': + $replacements[$original] = $link['mlid']; + break; + case 'title': + $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title']; + break; + case 'url': + $replacements[$original] = url($link['href'], $url_options); + break; + case 'parent': + if (!empty($link['plid']) && $parent = token_menu_link_load($link['plid'])) { + $replacements[$original] = $sanitize ? check_plain($parent['title']) : $parent['title']; + } + break; + case 'root'; + if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && $root = token_menu_link_load($link['p1'])) { + $replacements[$original] = $sanitize ? check_plain($root['title']) : $root['title']; + } + break; + } + } + + // Chained token relationships. + if (!empty($link['plid']) && ($source_tokens = token_find_with_prefix($tokens, 'parent')) && $parent = token_menu_link_load($link['plid'])) { + $replacements += token_generate('menu-link', $source_tokens, array('menu-link' => $parent), $options); + } + if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && ($root_tokens = token_find_with_prefix($tokens, 'root')) && $root = token_menu_link_load($link['p1'])) { + $replacements += token_generate('menu-link', $root_tokens, array('menu-link' => $root), $options); + } + if ($url_tokens = token_find_with_prefix($tokens, 'url')) { + $replacements += token_generate('url', $url_tokens, array('path' => $link['href']), $options); + } + } + + // Current page tokens. + if ($type == 'current-page') { + $current_path = current_path(); + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'title': + $replacements[$original] = drupal_get_title(); + break; + case 'url': + $replacements[$original] = url($current_path, $url_options); + break; + case 'page-number': + if ($page = filter_input(INPUT_GET, 'page')) { + // @see PagerDefault::execute() + $pager_page_array = explode(',', $page); + $page = $pager_page_array[0]; + } + $replacements[$original] = (int) $page + 1; + break; + } + } + + // Argument tokens. + if ($arg_tokens = token_find_with_prefix($tokens, 'arg')) { + foreach ($arg_tokens as $name => $original) { + if (is_numeric($name) && ($arg = arg($name)) && isset($arg)) { + $replacements[$original] = $sanitize ? check_plain($arg) : $arg; + } + } + } + + // Chained token relationships. + if ($url_tokens = token_find_with_prefix($tokens, 'url')) { + $replacements += token_generate('url', $url_tokens, array('path' => $current_path), $options); + } + } + + // URL tokens. + if ($type == 'url' && !empty($data['path'])) { + $path = $data['path']; + + if (isset($data['options'])) { + // Merge in the URL options if available. + $url_options = $data['options'] + $url_options; + } + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'path': + $replacements[$original] = $sanitize ? check_plain($path) : $path; + break; + case 'alias': + $alias = drupal_get_path_alias($path, $language_code); + $replacements[$original] = $sanitize ? check_plain($alias) : $alias; + break; + case 'absolute': + $replacements[$original] = url($path, $url_options); + break; + case 'relative': + $replacements[$original] = url($path, array('absolute' => FALSE) + $url_options); + break; + } + } + } + + // Entity tokens. + if (!empty($data[$type]) && $entity_type = token_get_entity_mapping('token', $type)) { + $entity = $data[$type]; + + // Sometimes taxonomy terms are not properly loaded. + // @see http://drupal.org/node/870528 + if ($entity_type == 'taxonomy_term' && !isset($entity->vocabulary_machine_name)) { + $entity->vocabulary_machine_name = db_query("SELECT machine_name FROM {taxonomy_vocabulary} WHERE vid = :vid", array(':vid' => $entity->vid))->fetchField(); + } + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'url': + if (_token_module($type, 'url') == 'token' && $uri = entity_uri($entity_type, $entity)) { + $replacements[$original] = url($uri['path'], $uri['options']); + } + break; + } + } + + // Chained token relationships. + if (($url_tokens = token_find_with_prefix($tokens, 'url')) && _token_module($type, 'url') == 'token') { + $replacements += token_generate('url', $url_tokens, entity_uri($entity_type, $entity), $options); + } + } + + // If $type is a token type, $data[$type] is empty but $data[$entity_type] is + // not, re-run token replacements. + if (empty($data[$type]) && ($entity_type = token_get_entity_mapping('token', $type)) && $entity_type != $type && !empty($data[$entity_type]) && empty($options['recursive'])) { + $data[$type] = $data[$entity_type]; + $options['recursive'] = TRUE; + $replacements += module_invoke_all('tokens', $type, $tokens, $data, $options); + } + + return $replacements; +} + +/** + * Implements hook_token_info() on behalf of book.module. + */ +function book_token_info() { + $info['tokens']['node']['book'] = array( + 'name' => t('Book'), + 'description' => t('The book page associated with the node.'), + 'type' => 'menu-link', + ); + return $info; +} + +/** + * Implements hook_tokens() on behalf of book.module. + */ +function book_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + $sanitize = !empty($options['sanitize']); + + // Node tokens. + if ($type == 'node' && !empty($data['node'])) { + $node = $data['node']; + + if (!empty($node->book['mlid'])) { + $link = token_book_link_load($node->book['mlid']); + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'book': + $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title']; + break; + } + } + + // Chained token relationships. + if ($book_tokens = token_find_with_prefix($tokens, 'book')) { + $replacements += token_generate('menu-link', $book_tokens, array('menu-link' => $link), $options); + } + } + } + + return $replacements; +} + +/** + * Implements hook_token_info() on behalf of menu.module. + */ +function menu_token_info() { + // Menu tokens. + $info['types']['menu'] = array( + 'name' => t('Menus'), + 'description' => t('Tokens related to menus.'), + 'needs-data' => 'menu', + ); + $info['tokens']['menu']['name'] = array( + 'name' => t('Name'), + 'description' => t("The name of the menu."), + ); + $info['tokens']['menu']['machine-name'] = array( + 'name' => t('Machine-readable name'), + 'description' => t("The unique machine-readable name of the menu."), + ); + $info['tokens']['menu']['description'] = array( + 'name' => t('Description'), + 'description' => t('The optional description of the menu.'), + ); + $info['tokens']['menu']['menu-link-count'] = array( + 'name' => t('Menu link count'), + 'description' => t('The number of menu links belonging to the menu.'), + ); + $info['tokens']['menu']['edit-url'] = array( + 'name' => t('Edit URL'), + 'description' => t("The URL of the menu's edit page."), + ); + + $info['tokens']['menu-link']['menu'] = array( + 'name' => t('Menu'), + 'description' => t('The menu of the menu link.'), + 'type' => 'menu', + ); + $info['tokens']['menu-link']['edit-url'] = array( + 'name' => t('Edit URL'), + 'description' => t("The URL of the menu link's edit page."), + ); + $info['tokens']['node']['menu-link'] = array( + 'name' => t('Menu link'), + 'description' => t("The menu link for this node."), + 'type' => 'menu-link', + ); + + return $info; +} + +/** + * Implements hook_tokens() on behalf of menu.module. + */ +function menu_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + + $url_options = array('absolute' => TRUE); + if (isset($options['language'])) { + $url_options['language'] = $options['language']; + $language_code = $options['language']->language; + } + else { + $language_code = NULL; + } + + $sanitize = !empty($options['sanitize']); + + // Node tokens. + if ($type == 'node' && !empty($data['node'])) { + $node = $data['node']; + + if (!isset($node->menu)) { + // Nodes do not have their menu links loaded via menu_node_load(). + menu_node_prepare($node); + } + + if (!empty($node->menu['mlid'])) { + $link = token_menu_link_load($node->menu['mlid']); + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'menu-link': + $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title']; + break; + } + } + + // Chained token relationships. + if ($menu_tokens = token_find_with_prefix($tokens, 'menu-link')) { + $replacements += token_generate('menu-link', $menu_tokens, array('menu-link' => $link), $options); + } + } + } + + // Menu link tokens. + if ($type == 'menu-link' && !empty($data['menu-link'])) { + $link = (array) $data['menu-link']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'menu': + if ($menu = menu_load($link['menu_name'])) { + $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title']; + } + break; + case 'edit-url': + $replacements[$original] = url("admin/structure/menu/item/{$link['mlid']}/edit", $url_options); + break; + } + } + + // Chained token relationships. + if (($menu_tokens = token_find_with_prefix($tokens, 'menu')) && $menu = menu_load($link['menu_name'])) { + $replacements += token_generate('menu', $menu_tokens, array('menu' => $menu), $options); + } + } + + // Menu tokens. + if ($type == 'menu' && !empty($data['menu'])) { + $menu = (array) $data['menu']; + + foreach ($tokens as $name => $original) { + switch ($name) { + case 'name': + $replacements[$original] = $sanitize ? check_plain($menu['title']) : $menu['title']; + break; + case 'machine-name': + // This is a machine name so does not ever need to be sanitized. + $replacements[$original] = $menu['menu_name']; + break; + case 'description': + $replacements[$original] = $sanitize ? filter_xss($menu['description']) : $menu['description']; + break; + case 'menu-link-count': + $query = db_select('menu_links'); + $query->condition('menu_name', $menu['menu_name']); + $query->addTag('menu_menu_link_count'); + $count = $query->countQuery()->execute()->fetchField(); + $replacements[$original] = (int) $count; + break; + case 'edit-url': + $replacements[$original] = url("admin/structure/menu/manage/" . $menu['menu_name'], $url_options); + break; + } + } + } + + return $replacements; +} + +/** + * Implements hook_token_info() on behalf of profile.module. + */ +function profile_token_info() { + $info = array(); + + foreach (_token_profile_fields() as $field) { + $info['tokens']['user'][$field->token_name] = array( + 'name' => check_plain($field->title), + 'description' => t('@category @type field.', array('@category' => drupal_ucfirst($field->category), '@type' => $field->type)), + ); + + switch ($field->type) { + case 'date': + $info['tokens']['user'][$field->token_name]['type'] = 'date'; + break; + } + } + + return $info; +} + +/** + * Implements hook_tokens() on behalf of profile.module. + */ +function profile_tokens($type, $tokens, array $data = array(), array $options = array()) { + $replacements = array(); + $sanitize = !empty($options['sanitize']); + $language_code = isset($options['language']) ? $options['language']->language : NULL; + + if ($type == 'user' && !empty($data['user'])) { + $account = $data['user']; + + // Load profile fields if this is the global user account. + // @see http://drupal.org/node/361471 + // @see http://drupal.org/node/967330 + if ($account->uid == $GLOBALS['user']->uid && isset($account->timestamp)) { + $profile_users = array($account->uid => $account); + profile_user_load($profile_users); + $account = $profile_users[$account->uid]; + } + + $profile_fields = _token_profile_fields(); + foreach ($tokens as $name => $original) { + if (isset($profile_fields[$name]) && !empty($account->{$profile_fields[$name]->name})) { + $value = $account->{$profile_fields[$name]->name}; + switch ($profile_fields[$name]->type) { + case 'textarea': + $replacements[$original] = $sanitize ? check_markup($value, filter_default_format($account), '', TRUE) : $value; + break; + case 'date': + $timestamp = gmmktime(0, 0, 0, $value['month'], $value['day'], $value['year']); + $replacements[$original] = format_date($timestamp, 'medium', '', NULL, $language_code); + break; + case 'url': + $replacements[$original] = $sanitize ? check_url($value) : $value; + break; + case 'checkbox': + // Checkbox field if checked should return the text. + $replacements[$original] = $sanitize ? check_plain($profile_fields[$name]->title) : $profile_fields[$name]->title; + break; + case 'list': + $value = preg_split("/[,\n\r]/", $value); + $value = array_map('trim', $value); + $value = implode(', ', $value); + // Intentionally fall through to the default condition. + default: + $replacements[$original] = $sanitize ? check_plain($value) : $value; + break; + } + } + } + + // Chained token relationships. + foreach ($profile_fields as $field) { + if ($field->type == 'date' && isset($account->{$field->name}) && $field_tokens = token_find_with_prefix($tokens, $field->token_name)) { + $date = $account->{$field->name}; + $replacements += token_generate('date', $field_tokens, array('date' => gmmktime(0, 0, 0, $date['month'], $date['day'], $date['year'])), $options); + } + } + } + + return $replacements; +} + +/** + * Fetch an array of profile field objects, keyed by token name. + */ +function _token_profile_fields() { + $fields = &drupal_static(__FUNCTION__); + + if (!isset($fields)) { + $results = db_query("SELECT name, title, category, type FROM {profile_field}"); + foreach ($results as $field) { + $field->token_name = strtr($field->name, '_', '-'); + $fields[$field->token_name] = $field; + } + } + + return $fields; +}