# HG changeset patch # User danieleb # Date 1379583524 -3600 # Node ID ce11bbd8f642386847a51b3ca86e37b9987250c8 # Parent b28be78d81609709abce265a91c97c771fcd9b29 added modules diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/DEVELOPER.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/DEVELOPER.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,26 @@ +--------------------------------------------------- +Some notes for developers working on the rules code +--------------------------------------------------- + +Terminology & Overview +----------------------- + * Rules plugins extend the "rules language". Thus conditions and actions are + implemented with a plugin, but also loops or ORs are plugins. Each plugin is + declared to be used in the condition or the action part - specified by the + interface. + * The action and condition plugin are a so called "AbstractPlugin" which means + they have to be implemented by modules to be actually usable. In fact the + callbacks provided by the action or condition implementation are + incorporated in the plugin object using faces. That way an action or + condition element behaves exactly like any other plugin instance. + * Any rule element is an instance of a RulesPlugin. + * A rules configuration consists of multiple rule elements while one is the + root element, which may be a 'rule' but also any other plugin. + * Each rules configuration may be persistently saved to the db using the + entity CRUD API. Using the API on contained rule elements is working too and + results in the whole configuration being updated. + * Rules provides per plugin UI components, what makes the UI parts re-usable + outside of the rule admin module too. In fact the rules admin module is + pretty small, as it just relies on the provided UI of the components. + * The UI is incorporated using the faces object extension mechanism, see + rules_rules_plugin_info() for an overview of the used UI extenders. \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,91 @@ + +-------------------------------------------------------------------------------- + Rules +-------------------------------------------------------------------------------- + +Maintainers: + * Wolfgang Ziegler (fago), nuppla@zites.net + +The Rules module allows site administrators to define conditionally executed +actions based on occurring events (ECA-rules). + +Project homepage: http://drupal.org/project/rules + + +Installation +------------ + +*Before* starting, make sure that you have read at least the introduction - so +you know at least the basic concepts. You can find it here: + + http://drupal.org/node/298480 + + * Rules depends on the Entity API module, download and install it from + http://drupal.org/project/entity + * Copy the whole rules directory to your modules directory + (e.g. DRUPAL_ROOT/sites/all/modules) and activate the Rules and Rules UI + modules. + * The administrative user interface can be found at admin/config/workflow/rules + + +Documentation +------------- +* Check out the general docs at http://drupal.org/node/298476 +* Check out the developer targeted docs at http://drupal.org/node/878718 + + +Rules Scheduler +--------------- + + * If you enable the Rules scheduler module, you get new actions that allow you + to schedule the execution of Rules components. + * Make sure that you have configured cron for your drupal installation as cron + is used for scheduling the Rules components. For help see + http://drupal.org/cron + * If the Views module (http://drupal.org/project/views) is installed, the module + displays the list of scheduled tasks in the UI. + + +Upgrade from Rules 6.x-1.x to Rules 7.x-2.x +-------------------------------------------- + + * In order to upgrade Rules from 6.x-1.x to 7.x-2.x just run "update.php". This + is going to make sure Rules 2.x is properly installed, but it will leave your + Rules 1.x configurations untouched. Thus, your rules won't be upgraded yet. + * To convert your Rules 1.x configurations to Rules 2.x go to + 'admin/config/workflow/rules/upgrade'. + * At this page, you may choose the Rules 1.x rules and rule sets to upgrade + and whether the converted configurations should be immediately saved to + your database or whether the configuration export should be generated. + * Note that for importing an export the export needs to pass the + configuration integrity check, what might be troublesome if the + conversion was not 100% successful. In that case, try choosing the + immediate saving method and correct the configuration after conversion. + * A rule configuration might require multiple modules to be in place and + upgraded to work properly. E.g. if you used an action provided + by a third party module, make sure the module is in place and upgraded + before you convert the rule. + * If all required modules are installed and have been upgraded but the rule + conversion still fails, the cause might be that a module has not yet + upgraded its Rules integration or does not implement the Rules conversion + functionality. In that case, file an issue for the module that provided + the action or condition causing the conversion to fail. + * Note that any rule configurations containing token replacements or PHP + input evaluations might need some manual corrections in order to stay + working. This is, as some used token replacements might not be available + in Drupal 7 any more and the PHP code might need to be updated in order + to be compatible with Drupal 7. + * Once the upgrade was successful, you may delete the left over Rules 1.x + configurations by going to 'admin/config/workflow/rules/upgrade/clear'. + * The Rules Scheduler module also comes with an upgrade routine that is + invoked as usual via "update.php". Its actions can be upgraded via the usual + Rules upgrade tool, see above. + However, there is currently no support for upgrading already scheduled + tasks. That means, all previously on Drupal 6 scheduled tasks won't apply + for Drupal 7. The Drupal 6 tasks are preserved in the database as long as + you do not clear your Rules 1.x configuration though. + * The Rules Forms module has not been updated to Drupal 7 and there are no + plans to do so, as unfortuntely the module's design does not allow for + automatic configuration updates. + Thus, a possible future Rules 2.x Forms module is likely to work + different, e.g. by working only for entity forms on the field level. diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/includes/faces.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/includes/faces.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,311 @@ +object = $object; + } + + /** + * Returns the extended object. + */ + public function getExtendable() { + return $this->object; + } + + /** + * Makes protected properties of the extendable accessible. + */ + protected function &property($name) { + $var =& $this->object->property($name); + return $var; + } + + /** + * Invokes any method on the extended object. May be used to invoke + * protected methods. + * + * @param $name + * The method name. + * @param $arguments + * An array of arguments to pass to the method. + */ + protected function call($name, array $args = array()) { + return $this->object->call($name, $args); + } + } +} + + +if (!class_exists('FacesExtendable', FALSE)) { + + /** + * An extendable base class. + */ + abstract class FacesExtendable { + + protected $facesMethods = array(); + protected $faces = array(); + protected $facesIncludes = array(); + protected $facesClassInstances = array(); + static protected $facesIncluded = array(); + + /** + * Wraps calls to module_load_include() to prevent multiple inclusions. + * + * @see module_load_include() + */ + protected static function load_include($args) { + $args += array('type' => 'inc', 'module' => '', 'name' => NULL); + $key = implode(':', $args); + if (!isset(self::$facesIncluded[$key])) { + self::$facesIncluded[$key] = TRUE; + module_load_include($args['type'], $args['module'], $args['name']); + } + } + + /** + * Magic method: Invoke the dynamically implemented methods. + */ + function __call($name, $arguments = array()) { + if (isset($this->facesMethods[$name])) { + $method = $this->facesMethods[$name]; + // Include code, if necessary. + if (isset($this->facesIncludes[$name])) { + self::load_include($this->facesIncludes[$name]); + $this->facesIncludes[$name] = NULL; + } + if (isset($method[0])) { + // We always pass the object reference and the name of the method. + $arguments[] = $this; + $arguments[] = $name; + return call_user_func_array($method[0], $arguments); + } + // Call the method on the extender object, but don't use extender() + // for performance reasons. + if (!isset($this->facesClassInstances[$method[1]])) { + $this->facesClassInstances[$method[1]] = new $method[1]($this); + } + return call_user_func_array(array($this->facesClassInstances[$method[1]], $name), $arguments); + } + $class = check_plain(get_class($this)); + throw new FacesExtendableException("There is no method $name for this instance of the class $class."); + } + + /** + * Returns the extender object for the given class. May be used to + * explicitly invoke a specific extender, e.g. a function overriding a + * method may use that to explicitly invoke the original extender. + */ + public function extender($class) { + if (!isset($this->facesClassInstances[$class])) { + $this->facesClassInstances[$class] = new $class($this); + } + return $this->facesClassInstances[$class]; + } + + /** + * Returns whether the object can face as the given interface, thus it + * returns TRUE if this oject has been extended by an appropriate + * implementation. + * + * @param $interface + * Optional. A interface to test for. If it's omitted, all interfaces that + * the object can be faced as are returned. + * @return + * Whether the object can face as the interface or an array of interface + * names. + */ + public function facesAs($interface = NULL) { + if (!isset($interface)) { + return array_values($this->faces); + } + return in_array($interface, $this->faces) || $this instanceof $interface; + } + + /** + * Extend the object by a class to implement the given interfaces. + * + * @param $interface + * The interface name or an array of interface names. + * @param $class + * The extender class, which has to implement the FacesExtenderInterface. + * @param $include + * An optional array describing the file to include before invoking the + * class. The array entries known are 'type', 'module', and 'name' + * matching the parameters of module_load_include(). Only 'module' is + * required as 'type' defaults to 'inc' and 'name' to NULL. + */ + public function extendByClass($interface, $className, array $includes = array()) { + $parents = class_implements($className); + if (!in_array('FacesExtenderInterface', $parents)) { + throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the FacesExtenderInterface."); + } + $interfaces = is_array($interface) ? $interface : array($interface); + + foreach ($interfaces as $interface) { + if (!in_array($interface, $parents)) { + throw new FacesExtendableException("The class " . check_plain($className) . " doesn't implement the interface " . check_plain($interface) . "."); + } + $this->faces[$interface] = $interface; + $this->faces += class_implements($interface); + $face_methods = get_class_methods($interface); + $this->addIncludes($face_methods, $includes); + foreach ($face_methods as $method) { + $this->facesMethods[$method] = array(1 => $className); + } + } + } + + /** + * Extend the object by the given functions to implement the given + * interface. There has to be an implementation function for each method of + * the interface. + * + * @param $interface + * The interface name or FALSE to extend the object without a given + * interface. + * @param $methods + * An array, where the keys are methods of the given interface and the + * values the callback functions to use. + * @param $includes + * An optional array to describe files to include before invoking the + * callbacks. You may pass a single array describing one include for all + * callbacks or an array of arrays, keyed by the method names. Look at the + * extendByClass() $include parameter for more details about how to + * describe a single file. + */ + public function extend($interface, array $callbacks = array(), array $includes = array()) { + $face_methods = $interface ? get_class_methods($interface) : array_keys($callbacks); + if ($interface) { + if (array_diff($face_methods, array_keys($callbacks))) { + throw new FacesExtendableException("Missing methods for implementing the interface " . check_plain($interface) . "."); + } + $this->faces[$interface] = $interface; + $this->faces += class_implements($interface); + } + $this->addIncludes($face_methods, $includes); + foreach ($face_methods as $method) { + $this->facesMethods[$method] = array(0 => $callbacks[$method]); + } + } + + /** + * Override the implementation of an extended method. + * + * @param $methods + * An array of methods of the interface, that should be overriden, where + * the keys are methods to override and the values the callback functions + * to use. + * @param $includes + * An optional array to describe files to include before invoking the + * callbacks. You may pass a single array describing one include for all + * callbacks or an array of arrays, keyed by the method names. Look at the + * extendByClass() $include parameter for more details about how to + * describe a single file. + */ + public function override(array $callbacks = array(), array $includes = array()) { + if (array_diff_key($callbacks, $this->facesMethods)) { + throw new FacesExtendableException("A not implemented method is to be overridden."); + } + $this->addIncludes(array_keys($callbacks), $includes); + foreach ($callbacks as $method => $callback) { + $this->facesMethods[$method] = array(0 => $callback); + } + } + + /** + * Adds in include files for the given methods while removing any old files. + * If a single include file is described, it's added for all methods. + */ + protected function addIncludes($methods, $includes) { + $includes = isset($includes['module']) && is_string($includes['module']) ? array_fill_keys($methods, $includes) : $includes; + $this->facesIncludes = $includes + array_diff_key($this->facesIncludes, array_flip($methods)); + } + + /** + * Only serialize what is really necessary. + */ + public function __sleep() { + return array('facesMethods', 'faces', 'facesIncludes'); + } + + /** + * Destroys all references to created instances so that PHP's garbage + * collection can do its work. This is needed as PHP's gc has troubles with + * circular references until PHP < 5.3. + */ + public function destroy() { + // Avoid circular references. + $this->facesClassInstances = array(); + } + + /** + * Makes protected properties accessible. + */ + public function &property($name) { + if (property_exists($this, $name)) { + return $this->$name; + } + } + + /** + * Invokes any method. + * + * This also allows to pass arguments by reference, so it may be used to + * pass arguments by reference to dynamically extended methods. + * + * @param $name + * The method name. + * @param $arguments + * An array of arguments to pass to the method. + */ + public function call($name, array $args = array()) { + if (method_exists($this, $name)) { + return call_user_func_array(array($this, $name), $args); + } + return $this->__call($name, $args); + } + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/includes/rules.core.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/includes/rules.core.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,2808 @@ + 'rules'); + return parent::create($values); + } + + /** + * Overridden. + * @see DrupalDefaultEntityController::attachLoad() + */ + protected function attachLoad(&$queried_entities, $revision_id = FALSE) { + // Retrieve stdClass records and turn them in rules objects stored in 'data' + $ids = array_keys($queried_entities); + $result = db_select('rules_tags') + ->fields('rules_tags', array('id', 'tag')) + ->condition('id', $ids, 'IN') + ->execute(); + foreach ($result as $row) { + $tags[$row->id][] = $row->tag; + } + $result = db_select('rules_dependencies') + ->fields('rules_dependencies', array('id', 'module')) + ->condition('id', $ids, 'IN') + ->execute(); + foreach ($result as $row) { + $modules[$row->id][] = $row->module; + } + + $entities = array(); + foreach ($queried_entities as $record) { + $entity = $record->data; + // Set the values of the other columns. + foreach ($this->entityInfo['schema_fields_sql']['base table'] as $field) { + $entity->$field = $record->$field; + } + unset($entity->data, $entity->plugin); + // Add any tags or dependencies. + $entity->dependencies = isset($modules[$entity->id]) ? $modules[$entity->id] : array(); + $entity->tags = isset($tags[$entity->id]) ? $tags[$entity->id] : array(); + $entities[$entity->id] = $entity; + } + $queried_entities = $entities; + parent::attachLoad($queried_entities, $revision_id); + } + + /** + * Override to support having events and tags as conditions. + * @see EntityAPIController::applyConditions($entities, $conditions) + * @see rules_query_rules_config_load_multiple_alter() + */ + protected function applyConditions($entities, $conditions = array()) { + if (isset($conditions['event']) || isset($conditions['plugin'])) { + foreach ($entities as $key => $entity) { + if (isset($conditions['event']) && (!($entity instanceof RulesTriggerableInterface) || !in_array($conditions['event'], $entity->events()))) { + unset($entities[$key]); + } + if (isset($conditions['plugin']) && !is_array($conditions['plugin'])) { + $conditions['plugin'] = array($conditions['plugin']); + } + if (isset($conditions['plugin']) && !in_array($entity->plugin(), $conditions['plugin'])) { + unset($entities[$key]); + } + } + unset($conditions['event'], $conditions['plugin']); + } + if (!empty($conditions['tags'])) { + foreach ($entities as $key => $entity) { + foreach ($conditions['tags'] as $tag) { + if (in_array($tag, $entity->tags)) { + continue 2; + } + } + unset($entities[$key]); + } + unset($conditions['tags']); + } + return parent::applyConditions($entities, $conditions); + } + + /** + * Overridden to work with Rules' custom export format. + * + * @param $export + * A serialized string in JSON format as produced by the + * RulesPlugin::export() method, or the PHP export as usual PHP array. + */ + public function import($export, &$error_msg = '') { + $export = is_array($export) ? $export : drupal_json_decode($export); + if (!is_array($export)) { + $error_msg = t('Unable to parse the pasted export.'); + return FALSE; + } + // The key ist the configuration name and the value the actual export. + list($name, $export) = each($export); + if (!isset($export['PLUGIN'])) { + $error_msg = t('Export misses plugin information.'); + return FALSE; + } + // Create an empty configuration, re-set basic keys and import. + $config = rules_plugin_factory($export['PLUGIN']); + $config->name = $name; + foreach (array('label', 'active', 'weight', 'tags', 'access_exposed') as $key) { + if (isset($export[strtoupper($key)])) { + $config->$key = $export[strtoupper($key)]; + } + } + if (!empty($export['REQUIRES'])) { + foreach ($export['REQUIRES'] as $module) { + if (!module_exists($module)) { + $error_msg = t('Missing the required module %module.', array('%module' => $module)); + return FALSE; + } + } + $config->dependencies = $export['REQUIRES']; + } + $config->import($export); + return $config; + } + + public function save($rules_config, DatabaseTransaction $transaction = NULL) { + $transaction = isset($transaction) ? $transaction : db_transaction(); + + // Load the stored entity, if any. + if (!isset($rules_config->original) && $rules_config->{$this->idKey}) { + $rules_config->original = entity_load_unchanged($this->entityType, $rules_config->{$this->idKey}); + } + $original = isset($rules_config->original) ? $rules_config->original : NULL; + + $return = parent::save($rules_config, $transaction); + $this->storeTags($rules_config); + if ($rules_config instanceof RulesTriggerableInterface) { + $this->storeEvents($rules_config); + } + $this->storeDependencies($rules_config); + + // See if there are any events that have been removed. + if ($original && $rules_config->plugin == 'reaction rule') { + foreach (array_diff($original->events(), $rules_config->events()) as $event_name) { + // Check if the event handler implements the event dispatcher interface. + $handler = rules_get_event_handler($event_name, $rules_config->getEventSettings($event_name)); + if (!$handler instanceof RulesEventDispatcherInterface) { + continue; + } + + // Only stop an event dispatcher if there are no rules for it left. + if (!rules_config_load_multiple(FALSE, array('event' => $event_name, 'plugin' => 'reaction rule', 'active' => TRUE)) && $handler->isWatching()) { + $handler->stopWatching(); + } + } + } + + return $return; + } + + /** + * Save tagging information to the rules_tags table. + */ + protected function storeTags($rules_config) { + db_delete('rules_tags') + ->condition('id', $rules_config->id) + ->execute(); + if (!empty($rules_config->tags)) { + foreach ($rules_config->tags as $tag) { + db_insert('rules_tags') + ->fields(array('id', 'tag'), array($rules_config->id, $tag)) + ->execute(); + } + } + } + + /** + * Save event information to the rules_trigger table. + */ + protected function storeEvents(RulesTriggerableInterface $rules_config) { + db_delete('rules_trigger') + ->condition('id', $rules_config->id) + ->execute(); + foreach ($rules_config->events() as $event) { + db_insert('rules_trigger') + ->fields(array( + 'id' => $rules_config->id, + 'event' => $event, + )) + ->execute(); + } + } + + protected function storeDependencies($rules_config) { + db_delete('rules_dependencies') + ->condition('id', $rules_config->id) + ->execute(); + if (!empty($rules_config->dependencies)) { + foreach ($rules_config->dependencies as $dependency) { + db_insert('rules_dependencies') + ->fields(array( + 'id' => $rules_config->id, + 'module' => $dependency, + )) + ->execute(); + } + } + } + + /** + * Overridden to support tags and events in $conditions. + * @see EntityAPIControllerExportable::buildQuery() + */ + protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { + $query = parent::buildQuery($ids, $conditions, $revision_id); + $query_conditions =& $query->conditions(); + foreach ($query_conditions as &$condition) { + // One entry in $query_conditions is a string with key '#conjunction'. + // @see QueryConditionInterface::conditions(). + if (is_array($condition)) { + // Support using 'tags' => array('tag1', 'tag2') as condition. + if ($condition['field'] == 'base.tags') { + $query->join('rules_tags', 'rt', 'base.id = rt.id'); + $condition['field'] = 'rt.tag'; + } + // Support using 'event' => $name as condition. + if ($condition['field'] == 'base.event') { + $query->join('rules_trigger', 'tr', "base.id = tr.id"); + $condition['field'] = 'tr.event'; + // Use like operator to support % wildcards also. + $condition['operator'] = 'LIKE'; + } + } + } + return $query; + } + + /** + * Overridden to also delete tags and events. + * @see EntityAPIControllerExportable::delete() + */ + public function delete($ids, DatabaseTransaction $transaction = NULL) { + $transaction = isset($transaction) ? $transaction : db_transaction(); + // Use entity-load as ids may be the names as well as the ids. + $configs = $ids ? entity_load('rules_config', $ids) : array(); + if ($configs) { + foreach ($configs as $config) { + db_delete('rules_trigger') + ->condition('id', $config->id) + ->execute(); + db_delete('rules_tags') + ->condition('id', $config->id) + ->execute(); + db_delete('rules_dependencies') + ->condition('id', $config->id) + ->execute(); + } + } + $return = parent::delete($ids, $transaction); + + // Stop event dispatchers when deleting the last rule of an event set. + $processed = array(); + foreach ($configs as $config) { + if ($config->getPluginName() != 'reaction rule') { + continue; + } + + foreach ($config->events() as $event_name) { + // Only process each event once. + if (!empty($processed[$event_name])) { + continue; + } + $processed[$event_name] = TRUE; + + // Check if the event handler implements the event dispatcher interface. + $handler = rules_get_event_handler($event_name, $config->getEventSettings($event_name)); + if (!$handler instanceof RulesEventDispatcherInterface) { + continue; + } + + // Only stop an event dispatcher if there are no rules for it left. + if ($handler->isWatching() && !rules_config_load_multiple(FALSE, array('event' => $event_name, 'plugin' => 'reaction rule', 'active' => TRUE))) { + $handler->stopWatching(); + } + } + } + + return $return; + } +} + +/** + * The RulesExtendable uses the rules cache to setup the defined extenders + * and overrides automatically. + * As soon faces is used the faces information is autoloaded using setUp(). + */ +abstract class RulesExtendable extends FacesExtendable { + + /** + * The name of the info definitions associated with info about this class. + * This would be defined abstract, if possible. Common rules hooks with class + * info are e.g. plugin_info and data_info. + */ + protected $hook; + + /** + * The name of the item this class represents in the info hook. + */ + protected $itemName; + + protected $cache, $itemInfo = array(); + + + public function __construct() { + $this->setUp(); + } + + protected function setUp() { + // Keep a reference on the cache, so elements created during cache + // rebuilding end up with a complete cache in the end too. + $this->cache = &rules_get_cache(); + if (isset($this->cache[$this->hook][$this->itemName])) { + $this->itemInfo = &$this->cache[$this->hook][$this->itemName]; + } + // Set up the Faces Extenders + if (!empty($this->itemInfo['faces_cache'])) { + list($this->facesMethods, $this->facesIncludes, $this->faces) = $this->itemInfo['faces_cache']; + } + } + + /** + * Force the object to be setUp, this executes setUp() if not done yet. + */ + public function forceSetUp() { + if (!isset($this->cache) || (!empty($this->itemInfo['faces_cache']) && !$this->faces)) { + $this->setUp(); + } + } + + /** + * Magic method: Invoke the dynamically implemented methods. + */ + public function __call($name, $arguments = array()) { + $this->forceSetUp(); + return parent::__call($name, $arguments); + } + + public function facesAs($interface = NULL) { + $this->forceSetUp(); + return parent::facesAs($interface); + } + + /** + * Allows items to add something to the rules cache. + */ + public function rebuildCache(&$itemInfo, &$cache) { + // Speed up setting up items by caching the faces methods. + if (!empty($itemInfo['extenders'])) { + // Apply extenders and overrides. + $itemInfo += array('overrides' => array()); + foreach ($itemInfo['extenders'] as $face => $data) { + $data += array('file' => array()); + if (isset($data['class'])) { + $this->extendByClass($face, $data['class'], $data['file']); + } + elseif (isset($data['methods'])) { + $this->extend($face, $data['methods'], $data['file']); + } + } + foreach ($itemInfo['overrides'] as $data) { + $data += array('file' => array()); + $this->override($data['methods'], $data['file']); + } + $itemInfo['faces_cache'] = array($this->facesMethods, $this->facesIncludes, $this->faces); + // We don't need that any more. + unset($itemInfo['extenders'], $itemInfo['overrides']); + } + } + + /** + * Returns whether the a RuleExtendable supports the given interface. + * + * @param $itemInfo + * The info about the item as specified in the hook. + * @param $interface + * The interface to check for. + * @return + * Whether it supports the given interface. + */ + public static function itemFacesAs(&$itemInfo, $interface) { + return in_array($interface, class_implements($itemInfo['class'])) || isset($itemInfo['faces_cache'][2][$interface]); + } +} + +/** + * Base class for rules plugins. + * + * We cannot inherit from EntityDB at the same time, so we implement our own + * entity related methods. Any CRUD related actions performed on contained + * plugins are applied and the root element representing the configuration is + * saved. + */ +abstract class RulesPlugin extends RulesExtendable { + + /** + * If this is a configuration saved to the db, the id of it. + */ + public $id = NULL; + public $weight = 0; + public $name = NULL; + + /** + * An array of settings for this element. + */ + public $settings = array(); + + /** + * Info about this element. Usage depends on the plugin. + */ + protected $info = array(); + + /** + * The parent element, if any. + * @var RulesContainerPlugin + */ + protected $parent = NULL; + + protected $cache = NULL, $hook = 'plugin_info'; + + /** + * Identifies an element inside a configuration. + */ + protected $elementId = NULL; + + /** + * Static cache for availableVariables(). + */ + protected $availableVariables; + + + /** + * Sets a new parent element. + */ + public function setParent(RulesContainerPlugin $parent) { + if ($this->parent == $parent) { + return; + } + if (isset($this->parent) && ($key = array_search($this, $this->parent->children)) !== FALSE) { + // Remove element from any previous parent. + unset($this->parent->children[$key]); + $this->parent->resetInternalCache(); + } + // Make sure the interface matches the type of the container. + if (($parent instanceof RulesActionContainer && $this instanceof RulesActionInterface) || + ($parent instanceof RulesConditionContainer && $this instanceof RulesConditionInterface)) { + + $this->parent = $parent; + $parent->children[] = $this; + $this->parent->resetInternalCache(); + } + else { + throw new RulesEvaluationException('The given container is incompatible with this element.', array(), $this, RulesLog::ERROR); + } + } + + /** + * Gets the root element of the configuration. + */ + public function root() { + $element = $this; + while (!$element->isRoot()) { + $element = $element->parent; + } + return $element; + } + + /** + * Returns whether the element is the root of the configuration. + */ + public function isRoot() { + return empty($this->parent) || isset($this->name); + } + + /** + * Returns the element's parent. + */ + public function parentElement() { + return $this->parent; + } + + /** + * Returns the element id, which identifies the element inside the config. + */ + public function elementId() { + if (!isset($this->elementId)) { + $this->elementMap()->index(); + } + return $this->elementId; + } + + /** + * Gets the element map helper object, which helps mapping elements to ids. + * + * @return RulesElementMap + */ + public function elementMap() { + $config = $this->root(); + if (empty($config->map)) { + $config->map = new RulesElementMap($config); + } + return $config->map; + } + + /** + * Iterate over all elements nested below the current element. + * + * This helper can be used to recursively iterate over all elements of a + * configuration. To iterate over the children only, just regulary iterate + * over the object. + * + * @param $mode + * (optional) The iteration mode used. See + * RecursiveIteratorIterator::construct(). Defaults to SELF_FIRST. + * + * @return RecursiveIteratorIterator + */ + public function elements($mode = RecursiveIteratorIterator::SELF_FIRST) { + return new RecursiveIteratorIterator($this, $mode); + } + + /** + * Do a deep clone. + */ + public function __clone() { + // Make sure the element map is cleared. + // @see self::elementMap() + unset($this->map); + } + + /** + * Returns the depth of this element in the configuration. + */ + public function depth() { + $element = $this; + $i = 0; + while (!empty($element->parent)) { + $element = $element->parent; + $i++; + } + return $i; + } + + /** + * Execute the configuration. + * + * @param ... + * Arguments to pass to the configuration. + */ + public function execute() { + return $this->executeByArgs(func_get_args()); + } + + /** + * Execute the configuration by passing arguments in a single array. + */ + abstract public function executeByArgs($args = array()); + + /** + * Evaluate the element on a given rules evaluation state. + */ + abstract function evaluate(RulesState $state); + + protected static function compare(RulesPlugin $a, RulesPlugin $b) { + if ($a->weight == $b->weight) { + return 0; + } + return ($a->weight < $b->weight) ? -1 : 1; + } + + /** + * Returns info about parameters needed by the plugin. + * + * Note that not necessarily all parameters are needed when executing the + * plugin, as values for the parameter might have been already configured via + * the element settings. + * + * @see self::parameterInfo() + */ + public function pluginParameterInfo() { + return isset($this->info['parameter']) ? $this->info['parameter'] : array(); + } + + /** + * Returns info about parameters needed for executing the configured plugin. + * + * @param $optional + * Whether optional parameters should be included. + * + * @see self::pluginParameterInfo() + */ + public function parameterInfo($optional = FALSE) { + // We have to filter out parameters that are already configured. + foreach ($this->pluginParameterInfo() as $name => $info) { + if (!isset($this->settings[$name . ':select']) && !isset($this->settings[$name]) && ($optional || (empty($info['optional']) && $info['type'] != 'hidden'))) { + $vars[$name] = $info; + } + } + return isset($vars) ? $vars : array(); + } + + /** + * Returns the about variables the plugin provides for later evaluated elements. + * + * Note that this method returns info about the provided variables as defined + * by the plugin. Thus this resembles the original info, which may be + * adapted via configuration. + * + * @see self::providesVariables() + */ + public function pluginProvidesVariables() { + return isset($this->info['provides']) ? $this->info['provides'] : array(); + } + + /** + * Returns info about all variables provided for later evaluated elements. + * + * @see self::pluginProvidesVariables() + */ + public function providesVariables() { + foreach ($this->pluginProvidesVariables() as $name => $info) { + $info['source name'] = $name; + $info['label'] = isset($this->settings[$name . ':label']) ? $this->settings[$name . ':label'] : $info['label']; + if (isset($this->settings[$name . ':var'])) { + $name = $this->settings[$name . ':var']; + } + $provides[$name] = $info; + } + return isset($provides) ? $provides : array(); + } + + /** + * Returns the info of the plugin. + */ + public function info() { + return $this->info; + } + + /** + * When converted to a string, just use the export format. + */ + public function __toString() { + return $this->isRoot() ? $this->export() : entity_var_json_export($this->export()); + } + + /** + * Gets variables to return once the configuration has been executed. + */ + protected function returnVariables(RulesState $state, $result = NULL) { + $var_info = $this->providesVariables(); + foreach ($var_info as $name => $info) { + try { + $vars[$name] = $this->getArgument($name, $info, $state); + } + catch (RulesEvaluationException $e) { + // Ignore not existing variables. + $vars[$name] = NULL; + } + $var_info[$name] += array('allow null' => TRUE); + } + return isset($vars) ? array_values(rules_unwrap_data($vars, $var_info)) : array(); + } + + /** + * Sets up the execution state for the given arguments. + */ + public function setUpState(array $args) { + $state = new RulesState(); + $vars = $this->setUpVariables(); + // Fix numerically indexed args to start with 0. + if (!isset($args[rules_array_key($vars)])) { + $args = array_values($args); + } + $offset = 0; + foreach (array_keys($vars) as $i => $name) { + $info = $vars[$name]; + if (!empty($info['handler']) || (isset($info['parameter']) && $info['parameter'] === FALSE)) { + $state->addVariable($name, NULL, $info); + // Count the variables that are not passed as parameters. + $offset++; + } + // Support numerically indexed arrays as well as named parameter style. + // The index is reduced to exclude non-parameter variables. + elseif (isset($args[$i - $offset])) { + $state->addVariable($name, $args[$i - $offset], $info); + } + elseif (isset($args[$name])) { + $state->addVariable($name, $args[$name], $info); + } + elseif (empty($info['optional']) && $info['type'] != 'hidden') { + throw new RulesEvaluationException('Argument %name is missing.', array('%name' => $name), $this, RulesLog::ERROR); + } + } + return $state; + } + + /** + * Returns info about all variables that have to be setup in the state. + */ + protected function setUpVariables() { + return $this->parameterInfo(TRUE); + } + + /** + * Returns info about variables available to be used as arguments for this element. + * + * As this is called very often, e.g. during integrity checks, we statically + * cache the results. + * + * @see RulesPlugin::resetInternalCache() + */ + public function availableVariables() { + if (!isset($this->availableVariables)) { + $this->availableVariables = !$this->isRoot() ? $this->parent->stateVariables($this) : RulesState::defaultVariables(); + } + return $this->availableVariables; + } + + /** + * Returns asserted additions to the available variable info. Any returned + * info is merged into the variable info, in case the execution flow passes + * the element. + * E.g. this is used to assert the content type of a node if the condition + * is met, such that the per node type properties are available. + */ + protected function variableInfoAssertions() { + return array(); + } + + /** + * Get the name of this plugin instance. The returned name should identify + * the code which drives this plugin. + */ + public function getPluginName() { + return $this->itemName; + } + + /** + * Calculates an array of required modules. + * + * You can use $this->dependencies to access dependencies for saved + * configurations. + */ + public function dependencies() { + $this->processSettings(); + $modules = isset($this->itemInfo['module']) && $this->itemInfo['module'] != 'rules' ? array($this->itemInfo['module'] => 1) : array(); + foreach ($this->pluginParameterInfo() as $name => $info) { + if (isset($this->settings[$name . ':process']) && $this->settings[$name . ':process'] instanceof RulesDataProcessor) { + $modules += array_flip($this->settings[$name . ':process']->dependencies()); + } + } + return array_keys($modules); + } + + /** + * Whether the currently logged in user has access to all configured elements. + * + * Note that this only checks whether the current user has permission to all + * configured elements, but not whether a user has access to configure Rule + * configurations in general. Use rules_config_access() for that. + * + * Use this to determine access permissions for configuring or triggering the + * execution of certain configurations independent of the Rules UI. + * + * @see rules_config_access() + */ + public function access() { + $this->processSettings(); + foreach ($this->pluginParameterInfo() as $name => $info) { + if (isset($this->settings[$name . ':select']) && $wrapper = $this->applyDataSelector($this->settings[$name . ':select'])) { + if ($wrapper->access('view') === FALSE) { + return FALSE; + } + } + // Incorporate access checks for data processors and input evaluators. + if (isset($this->settings[$name . ':process']) && $this->settings[$name . ':process'] instanceof RulesDataProcessor && !$this->settings[$name . ':process']->editAccess()) { + return FALSE; + } + } + return TRUE; + } + + /** + * Processes the settings e.g. to prepare input evaluators. + * + * Usually settings get processed automatically, however if $this->settings + * has been altered manually after element construction, it needs to be + * invoked explicitly with $force set to TRUE. + * + */ + public function processSettings($force = FALSE) { + // Process if not done yet. + if ($force || !empty($this->settings['#_needs_processing'])) { + $var_info = $this->availableVariables(); + foreach ($this->pluginParameterInfo() as $name => $info) { + // Prepare input evaluators. + if (isset($this->settings[$name])) { + $this->settings[$name . ':process'] = $this->settings[$name]; + RulesDataInputEvaluator::prepareSetting($this->settings[$name . ':process'], $info, $var_info); + } + // Prepare data processors. + elseif (isset($this->settings[$name . ':select']) && !empty($this->settings[$name . ':process'])) { + RulesDataProcessor::prepareSetting($this->settings[$name . ':process'], $info, $var_info); + } + // Clean up. + if (empty($this->settings[$name . ':process'])) { + unset($this->settings[$name . ':process']); + } + } + unset($this->settings['#_needs_processing']); + } + } + + /** + * Makes sure the plugin is configured right, e.g. all needed variables + * are available in the element's scope and dependent modules are enabled. + * + * @return RulesPlugin + * Returns $this to support chained usage. + * + * @throws RulesIntegrityException + * In case of a failed integraty check, a RulesIntegrityException exception + * is thrown. + */ + public function integrityCheck() { + // First process the settings if not done yet. + $this->processSettings(); + // Check dependencies using the pre-calculated dependencies stored in + // $this->dependencies. Fail back to calculation them on the fly, e.g. + // during creation. + $dependencies = empty($this->dependencies) ? $this->dependencies() : $this->dependencies; + foreach ($dependencies as $module) { + if (!module_exists($module)) { + throw new RulesDependencyException(t('Missing required module %name.', array('%name' => $module))); + } + } + // Check the parameter settings. + $this->checkParameterSettings(); + // Check variable names for provided variables to be valid. + foreach ($this->pluginProvidesVariables() as $name => $info) { + if (isset($this->settings[$name . ':var'])) { + $this->checkVarName($this->settings[$name . ':var']); + } + } + return $this; + } + + protected function checkVarName($name) { + if (!preg_match('/^[0-9a-zA-Z_]*$/', $name)) { + throw new RulesIntegrityException(t('%plugin: The variable name %name contains not allowed characters.', array('%plugin' => $this->getPluginName(), '%name' => $name)), $this); + } + } + + /** + * Checks whether parameters are correctly configured. + */ + protected function checkParameterSettings() { + foreach ($this->pluginParameterInfo() as $name => $info) { + if (isset($info['restriction']) && $info['restriction'] == 'selector' && isset($this->settings[$name])) { + throw new RulesIntegrityException(t("The parameter %name may only be configured using a selector.", array('%name' => $name)), array($this, 'parameter', $name)); + } + elseif (isset($info['restriction']) && $info['restriction'] == 'input' && isset($this->settings[$name . ':select'])) { + throw new RulesIntegrityException(t("The parameter %name may not be configured using a selector.", array('%name' => $name)), array($this, 'parameter', $name)); + } + elseif (!empty($this->settings[$name . ':select']) && !$this->applyDataSelector($this->settings[$name . ':select'])) { + throw new RulesIntegrityException(t("Data selector %selector for parameter %name is invalid.", array('%selector' => $this->settings[$name . ':select'], '%name' => $name)), array($this, 'parameter', $name)); + } + elseif ($arg_info = $this->getArgumentInfo($name)) { + // If we have enough metadata, check whether the types match. + if (!RulesData::typesMatch($arg_info, $info)) { + throw new RulesIntegrityException(t("The data type of the configured argument does not match the parameter's %name requirement.", array('%name' => $name)), array($this, 'parameter', $name)); + } + } + elseif (!$this->isRoot() && !isset($this->settings[$name]) && empty($info['optional']) && $info['type'] != 'hidden') { + throw new RulesIntegrityException(t('Missing configuration for parameter %name.', array('%name' => $name)), array($this, 'parameter', $name)); + } + //TODO: Make sure used values are allowed. (key/value pairs + allowed values) + } + } + + /** + * Returns the argument as configured in the element settings for the + * parameter $name described with $info. + * + * @param $name + * The name of the parameter for which to get the argument. + * @param $info + * Info about the parameter. + * @param RulesState $state + * The current evaluation state. + * @param $langcode + * (optional) The language code used to get the argument value if the + * argument value should be translated. By default (NULL) the current + * interface language will be used. + * + * @return + * The argument, possibly wrapped. + * + * @throws RulesEvaluationException + * In case the argument cannot be retrieved an exception is thrown. + */ + protected function getArgument($name, $info, RulesState $state, $langcode = NULL) { + // Only apply the langcode if the parameter has been marked translatable. + if (empty($info['translatable'])) { + $langcode = LANGUAGE_NONE; + } + elseif (!isset($langcode)) { + $langcode = $GLOBALS['language']->language; + } + + if (!empty($this->settings[$name . ':select'])) { + $arg = $state->applyDataSelector($this->settings[$name . ':select'], $langcode); + } + elseif (isset($this->settings[$name])) { + $arg = rules_wrap_data($this->settings[$name], $info); + // We don't sanitize directly specified values. + $skip_sanitize = TRUE; + } + elseif ($state->varinfo($name)) { + $arg = $state->get($name); + } + elseif (empty($info['optional']) && $info['type'] != 'hidden') { + throw new RulesEvaluationException('Required parameter %name is missing.', array('%name' => $name), $this, RulesLog::ERROR); + } + else { + $arg = isset($info['default value']) ? $info['default value'] : NULL; + $skip_sanitize = TRUE; + $info['allow null'] = TRUE; + } + // Make sure the given value is set if required (default). + if (!isset($arg) && empty($info['allow null'])) { + throw new RulesEvaluationException('The provided argument for parameter %name is empty.', array('%name' => $name), $this); + } + + // Support passing already sanitized values. + if ($info['type'] == 'text' && !isset($skip_sanitize) && !empty($info['sanitize']) && !($arg instanceof EntityMetadataWrapper)) { + $arg = check_plain((string) $arg); + } + + // Apply any configured data processors. + if (!empty($this->settings[$name . ':process'])) { + // For processing, make sure the data is unwrapped now. + $return = rules_unwrap_data(array($arg), array($info)); + // @todo for Drupal 8: Refactor to add the name and language code as + // separate parameter to process(). + $info['#name'] = $name; + $info['#langcode'] = $langcode; + return isset($return[0]) ? $this->settings[$name . ':process']->process($return[0], $info, $state, $this) : NULL; + } + return $arg; + } + + /** + * Gets the right arguments for executing the element. + * + * @throws RulesEvaluationException + * If case an argument cannot be retrieved an exception is thrown. + */ + protected function getExecutionArguments(RulesState $state) { + $parameters = $this->pluginParameterInfo(); + // If there is language parameter, get its value first so it can be used + // for getting other translatable values. + $langcode = NULL; + if (isset($parameters['language'])) { + $lang_arg = $this->getArgument('language', $parameters['language'], $state); + $langcode = $lang_arg instanceof EntityMetadataWrapper ? $lang_arg->value() : $lang_arg; + } + // Now get all arguments. + foreach ($parameters as $name => $info) { + $args[$name] = $name == 'language' ? $lang_arg : $this->getArgument($name, $info, $state, $langcode); + } + // Append the settings and the execution state. Faces will append $this. + $args['settings'] = $this->settings; + $args['state'] = $state; + // Make the wrapped variables for the arguments available in the state. + $state->currentArguments = $args; + return rules_unwrap_data($args, $parameters); + } + + /** + * Apply the given data selector by using the info about available variables. + * Thus it doesn't require an actual evaluation state. + * + * @param $selector + * The selector string, e.g. "node:author:mail". + * + * @return EntityMetadataWrapper + * An empty wrapper for the given selector or FALSE if the selector couldn't + * be applied. + */ + public function applyDataSelector($selector) { + $parts = explode(':', str_replace('-', '_', $selector), 2); + if (($vars = $this->availableVariables()) && isset($vars[$parts[0]]['type'])) { + $wrapper = rules_wrap_data(NULL, $vars[$parts[0]], TRUE); + if (count($parts) > 1 && $wrapper instanceof EntityMetadataWrapper) { + try { + foreach (explode(':', $parts[1]) as $name) { + if ($wrapper instanceof EntityListWrapper || $wrapper instanceof EntityStructureWrapper) { + $wrapper = $wrapper->get($name); + } + else { + return FALSE; + } + } + } + // In case of an exception or we were unable to get a wrapper, return FALSE. + catch (EntityMetadataWrapperException $e) { + return FALSE; + } + } + } + return isset($wrapper) ? $wrapper : FALSE; + } + + /** + * Returns info about the configured argument. + * + * @return + * The determined info. If it's not known NULL is returned. + */ + public function getArgumentInfo($name) { + $vars = $this->availableVariables(); + if (!empty($this->settings[$name . ':select']) && !empty($vars[$this->settings[$name . ':select']])) { + return $vars[$this->settings[$name . ':select']]; + } + elseif (!empty($this->settings[$name . ':select'])) { + if ($wrapper = $this->applyDataSelector($this->settings[$name . ':select'])) { + return $wrapper->info(); + } + return; + } + elseif (isset($this->settings[$name . ':type'])) { + return array('type' => $this->settings[$name . ':type']); + } + elseif (!isset($this->settings[$name]) && isset($vars[$name])) { + return $vars[$name]; + } + } + + /** + * Saves the configuration to the database, regardless whether this is invoked + * on the rules configuration or a contained rule element. + */ + public function save($name = NULL, $module = 'rules') { + if (isset($this->parent)) { + $this->parent->sortChildren(); + return $this->parent->save($name, $module); + } + else { + // Update the dirty flag before saving. + // However, this operation depends on a fully built Rules-cache, so skip + // it when entities in code are imported to the database. + // @see _rules_rebuild_cache() + if (empty($this->is_rebuild)) { + rules_config_update_dirty_flag($this, FALSE); + // In case the config is not dirty, pre-calculate the dependencies for + // later checking. Note that this also triggers processing settings if + // necessary. + // @see rules_modules_enabled() + if (empty($this->dirty)) { + $this->dependencies = $this->dependencies(); + } + } + + $this->plugin = $this->itemName; + $this->name = isset($name) ? $name : $this->name; + // Module stores the module via which the rule is configured and is used + // for generating machine names with the right prefix. However, for + // default configurations 'module' points to the module providing the + // default configuration, so the module via which the rules is configured + // is stored in the "owner" property. + // @todo: For Drupal 8 use "owner" for generating machine names also and + // module only for the modules providing default configurations. + $this->module = !isset($this->module) || $module != 'rules' ? $module : $this->module; + $this->owner = !isset($this->owner) || $module != 'rules' ? $module : $this->module; + $this->ensureNameExists(); + $this->data = $this; + $return = entity_get_controller('rules_config')->save($this); + unset($this->data); + + // Care about clearing necessary caches. + if (!empty($this->is_rebuild)) { + rules_clear_cache(); + } + else { + $plugin_info = $this->pluginInfo(); + if (!empty($plugin_info['component'])) { + // When component variables changes rebuild the complete cache so the + // changes to the provided action/condition take affect. + if (empty($this->original) || $this->componentVariables() != $this->original->componentVariables()) { + rules_clear_cache(); + } + // Clear components cached for evaluation. + cache_clear_all('comp_', 'cache_rules', TRUE); + } + elseif ($this->plugin == 'reaction rule') { + // Clear event sets cached for evaluation. + cache_clear_all('event_', 'cache_rules', TRUE); + variable_del('rules_event_whitelist'); + } + drupal_static_reset('rules_get_cache'); + drupal_static_reset('rules_config_update_dirty_flag'); + } + + return $return; + } + } + + /** + * Ensure the configuration has a name. If not, generate one. + */ + protected function ensureNameExists() { + if (!isset($this->module)) { + $this->module = 'rules'; + } + if (!isset($this->name)) { + // Find a unique name for this configuration. + $this->name = $this->module . '_'; + for ($i = 0; $i < 8; $i++) { + // Alphanumeric name generation. + $rnd = mt_rand(97, 122); + $this->name .= chr($rnd); + } + } + } + + public function __sleep() { + // Keep the id always as we need it for the recursion prevention. + $array = drupal_map_assoc(array('parent', 'id', 'elementId', 'weight', 'settings')); + // Keep properties related to configurations if they are there. + $info = entity_get_info('rules_config'); + $fields = array_merge($info['schema_fields_sql']['base table'], array('recursion', 'tags')); + foreach ($fields as $key) { + if (isset($this->$key)) { + $array[$key] = $key; + } + } + return $array; + } + + /** + * Optimizes a rule configuration in order to speed up evaluation. + * + * Additional optimization methods may be inserted by an extender + * implementing the RulesOptimizationInterface. By default, there is no + * optimization extender. + * + * An optimization method may rearrange the internal structure of a + * configuration in order to speed up the evaluation. As the configuration may + * change optimized configurations should not be saved permanently, except + * when saving it temporary, for later execution only. + * + * @see RulesOptimizationInterface + */ + public function optimize() { + // Make sure settings are processed before configs are cached. + $this->processSettings(); + if ($this->facesAs('RulesOptimizationInterface')) { + $this->__call('optimize'); + } + } + + /** + * If invoked on a rules configuration it is deleted from database. If + * invoked on a contained rule element, it's removed from the configuration. + */ + public function delete() { + if (isset($this->parent)) { + foreach ($this->parent->children as $key => $child) { + if ($child === $this) { + unset($this->parent->children[$key]); + break; + } + } + } + elseif (isset($this->id)) { + entity_get_controller('rules_config')->delete(array($this->name)); + rules_clear_cache(); + } + } + + public function internalIdentifier() { + return isset($this->id) ? $this->id : NULL; + } + + /** + * Returns the config name. + */ + public function identifier() { + return isset($this->name) ? $this->name : NULL; + } + + public function entityInfo() { + return entity_get_info('rules_config'); + } + + public function entityType() { + return 'rules_config'; + } + + /** + * Checks if the configuration has a certain exportable status. + * + * @param $status + * A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE, + * ENTITY_OVERRIDDEN or ENTITY_FIXED. + * + * @return + * TRUE if the configuration has the status, else FALSE. + * + * @see entity_has_status() + */ + public function hasStatus($status) { + return $this->isRoot() && isset($this->status) && ($this->status & $status) == $status; + } + + /** + * Remove circular object references so the PHP garbage collector does its + * work. + */ + public function destroy() { + parent::destroy(); + $this->parent = NULL; + } + + /** + * Seamlessy invokes the method implemented via faces without having to think + * about references. + */ + public function form(&$form, &$form_state, array $options = array()) { + $this->__call('form', array(&$form, &$form_state, $options)); + } + + public function form_validate($form, &$form_state) { + $this->__call('form_validate', array($form, &$form_state)); + } + + public function form_submit($form, &$form_state) { + $this->__call('form_submit', array($form, &$form_state)); + } + + /** + * Returns the label of the element. + */ + public function label() { + if (!empty($this->label) && $this->label != t('unlabeled')) { + return $this->label; + } + $info = $this->info(); + return isset($info['label']) ? $info['label'] : (!empty($this->name) ? $this->name : t('unlabeled')); + } + + /** + * Returns the name of the element's plugin. + */ + public function plugin() { + return $this->itemName; + } + + /** + * Returns info about the element's plugin. + */ + public function pluginInfo() { + $this->forceSetUp(); + return $this->itemInfo; + } + + /** + * Applies the given export. + */ + public function import(array $export) { + $this->importSettings($export[strtoupper($this->plugin())]); + } + + protected function importSettings($export) { + // Import parameter settings. + $export += array('USING' => array(), 'PROVIDE' => array()); + foreach ($export['USING'] as $name => $param_export) { + $this->importParameterSetting($name, $param_export); + } + foreach ($export['PROVIDE'] as $name => $var_export) { + // The key of $var_export is the variable name, the value the label. + $this->settings[$name . ':var'] = rules_array_key($var_export); + $this->settings[$name . ':label'] = reset($var_export); + } + } + + protected function importParameterSetting($name, $export) { + if (is_array($export) && isset($export['select'])) { + $this->settings[$name . ':select'] = $export['select']; + if (count($export) > 1) { + // Add in processor settings. + unset($export['select']); + $this->settings[$name . ':process'] = $export; + } + } + // Convert back the [selector] strings being an array with one entry. + elseif (is_array($export) && count($export) == 1 && isset($export[0])) { + $this->settings[$name . ':select'] = $export[0]; + } + elseif (is_array($export) && isset($export['value'])) { + $this->settings[$name] = $export['value']; + } + else { + $this->settings[$name] = $export; + } + } + + /** + * Exports a rule configuration. + * + * @param $prefix + * An optional prefix for each line. + * @param $php + * Optional. Set to TRUE to format the export using PHP arrays. By default + * JSON is used. + * @return + * The exported confiugration. + * + * @see rules_import() + */ + public function export($prefix = '', $php = FALSE) { + $export = $this->exportToArray(); + return $this->isRoot() ? $this->returnExport($export, $prefix, $php) : $export; + } + + protected function exportToArray() { + $export[strtoupper($this->plugin())] = $this->exportSettings(); + return $export; + } + + protected function exportSettings() { + $export = array(); + if (!$this->isRoot()) { + foreach ($this->pluginParameterInfo() as $name => $info) { + if (($return = $this->exportParameterSetting($name, $info)) !== NULL) { + $export['USING'][$name] = $return; + } + } + foreach ($this->providesVariables() as $name => $info) { + if (!empty($info['source name'])) { + $export['PROVIDE'][$info['source name']][$name] = $info['label']; + } + } + } + return $export; + } + + protected function exportParameterSetting($name, $info) { + if (isset($this->settings[$name]) && (empty($info['optional']) || !isset($info['default value']) || $this->settings[$name] != $info['default value'])) { + // In case of an array-value wrap the value into another array, such that + // the value cannot be confused with an exported data selector. + return is_array($this->settings[$name]) ? array('value' => $this->settings[$name]) : $this->settings[$name]; + } + elseif (isset($this->settings[$name . ':select'])) { + if (isset($this->settings[$name . ':process']) && $processor = $this->settings[$name . ':process']) { + $export['select'] = $this->settings[$name . ':select']; + $export += $processor instanceof RulesDataProcessor ? $processor->getChainSettings() : $processor; + return $export; + } + // If there is no processor use a simple array to abbreviate this usual + // case. In JSON this turns to a nice [selector] string. + return array($this->settings[$name . ':select']); + } + } + + /** + * Finalizies the configuration export by adding general attributes regarding + * the configuration and returns it in the right format. + */ + protected function returnExport($export, $prefix = '', $php = FALSE) { + $this->ensureNameExists(); + if (!empty($this->label) && $this->label != t('unlabeled')) { + $export_cfg[$this->name]['LABEL'] = $this->label; + } + $export_cfg[$this->name]['PLUGIN'] = $this->plugin(); + if (!empty($this->weight)) { + $export_cfg[$this->name]['WEIGHT'] = $this->weight; + } + if (isset($this->active) && !$this->active) { + $export_cfg[$this->name]['ACTIVE'] = FALSE; + } + if (!empty($this->tags)) { + $export_cfg[$this->name]['TAGS'] = $this->tags; + } + if ($modules = $this->dependencies()) { + $export_cfg[$this->name]['REQUIRES'] = $modules; + } + if (!empty($this->access_exposed)) { + $export_cfg[$this->name]['ACCESS_EXPOSED'] = $this->access_exposed; + }; + $export_cfg[$this->name] += $export; + return $php ? entity_var_export($export_cfg, $prefix) : entity_var_json_export($export_cfg, $prefix); + } + + /** + * Resets any internal static caches. + * + * This function does not reset regular caches as retrieved via + * rules_get_cache(). Usually, it's invoked automatically when a Rules + * configuration is modified. + * + * Static caches are reset for the element and any elements down the tree. To + * clear static caches of the whole configuration, invoke the function at the + * root. + * + * @see RulesPlugin::availableVariables() + */ + public function resetInternalCache() { + $this->availableVariables = NULL; + } +} + +/** + * Defines a common base class for so called "Abstract Plugins" like actions. + * Thus modules have to provide the concrete plugin implementation. + */ +abstract class RulesAbstractPlugin extends RulesPlugin { + + protected $elementName; + protected $info = array('parameter' => array(), 'provides' => array()); + protected $infoLoaded = FALSE; + + /** + * @param $name + * The plugin implementation's name. + * @param $info + * Further information provided about the plugin. Optional. + * @throws RulesException + * If validation of the passed settings fails RulesExceptions are thrown. + */ + function __construct($name = NULL, $settings = array()) { + $this->elementName = $name; + $this->settings = (array) $settings + array('#_needs_processing' => TRUE); + $this->setUp(); + } + + protected function setUp() { + parent::setUp(); + if (isset($this->cache[$this->itemName . '_info'][$this->elementName])) { + $this->info = $this->cache[$this->itemName . '_info'][$this->elementName]; + // Remember that the info has been correctly setup. + // @see self::forceSetup(). + $this->infoLoaded = TRUE; + + // Register the defined class, if any. + if (isset($this->info['class'])) { + $this->faces['RulesPluginImplInterface'] = 'RulesPluginImplInterface'; + $face_methods = get_class_methods('RulesPluginImplInterface'); + $class_info = array(1 => $this->info['class']); + foreach ($face_methods as $method) { + $this->facesMethods[$method] = $class_info; + } + } + // Add in per-plugin implementation callbacks if any. + if (!empty($this->info['faces_cache'])) { + foreach ($this->info['faces_cache'] as $face => $data) { + list($methods, $file_names) = $data; + foreach ($methods as $method => $callback) { + $this->facesMethods[$method] = $callback; + } + foreach ((array) $file_names as $method => $name) { + $this->facesIncludes[$method] = array('module' => $this->info['module'], 'name' => $name); + } + } + // Invoke the info_alter callback, but only if it has been implemented. + if ($this->facesMethods['info_alter'] != $this->itemInfo['faces_cache'][0]['info_alter']) { + $this->__call('info_alter', array(&$this->info)); + } + } + } + elseif (!empty($this->itemInfo['faces_cache']) && function_exists($this->elementName)) { + // We don't have any info, so just add the name as execution callback. + $this->override(array('execute' => $this->elementName)); + } + } + + public function forceSetUp() { + if (!isset($this->cache) || (!empty($this->itemInfo['faces_cache']) && !$this->faces)) { + $this->setUp(); + } + // In case we have element specific information, which is not loaded yet, + // do so now. This might happen if the element has been initially loaded + // with an incomplete cache, i.e. during cache rebuilding. + elseif (!$this->infoLoaded && isset($this->cache[$this->itemName . '_info'][$this->elementName])) { + $this->setUp(); + } + } + + /** + * Returns the label of the element. + */ + public function label() { + $info = $this->info(); + return isset($info['label']) ? $info['label'] : t('@plugin "@name"', array('@name' => $this->elementName, '@plugin' => $this->plugin())); + } + + public function access() { + $info = $this->info(); + $this->loadBasicInclude(); + if (!empty($info['access callback']) && !call_user_func($info['access callback'], $this->itemName, $this->getElementName())) { + return FALSE; + } + return parent::access() && $this->__call('access'); + } + + public function integrityCheck() { + // Do the usual integrity check first so the implementation's validation + // handler can rely on that already. + parent::integrityCheck(); + // Make sure the element is known. + $this->forceSetUp(); + if (!isset($this->cache[$this->itemName . '_info'][$this->elementName])) { + throw new RulesIntegrityException(t('Unknown @plugin %name.', array('@plugin' => $this->plugin(), '%name' => $this->elementName))); + } + $this->validate(); + return $this; + } + + public function processSettings($force = FALSE) { + // Process if not done yet. + if ($force || !empty($this->settings['#_needs_processing'])) { + $this->resetInternalCache(); + // In case the element implements the info alteration callback, (re-)run + // the alteration so that any settings depending info alterations are + // applied. + if ($this->facesMethods && $this->facesMethods['info_alter'] != $this->itemInfo['faces_cache'][0]['info_alter']) { + $this->__call('info_alter', array(&$this->info)); + } + // First let the plugin implementation do processing, so data types of the + // parameters are fixed when we process the settings. + $this->process(); + parent::processSettings($force); + } + } + + public function pluginParameterInfo() { + // Ensure the info alter callback has been executed. + $this->forceSetup(); + return parent::pluginParameterInfo(); + } + + public function pluginProvidesVariables() { + // Ensure the info alter callback has been executed. + $this->forceSetup(); + return parent::pluginProvidesVariables(); + } + + public function info() { + // Ensure the info alter callback has been executed. + $this->forceSetup(); + return $this->info; + } + + protected function variableInfoAssertions() { + // Get the implementation's assertions and map them to the variable names. + if ($assertions = $this->__call('assertions')) { + foreach ($assertions as $param_name => $data) { + $name = isset($this->settings[$param_name . ':select']) ? $this->settings[$param_name . ':select'] : $param_name; + $return[$name] = $data; + } + return $return; + } + } + + public function import(array $export) { + // The key is the element name and the value the actual export. + $this->elementName = rules_array_key($export); + $export = reset($export); + + // After setting the element name, setup the element again so the right + // element info is loaded. + $this->setUp(); + + if (!isset($export['USING']) && !isset($export['PROVIDES']) && !empty($export)) { + // The export has been abbreviated to skip "USING". + $export = array('USING' => $export); + } + $this->importSettings($export); + } + + protected function exportToArray() { + $export = $this->exportSettings(); + if (!$this->providesVariables()) { + // Abbreviate the export making "USING" implicit. + $export = isset($export['USING']) ? $export['USING'] : array(); + } + return array($this->elementName => $export); + } + + public function dependencies() { + $modules = array_flip(parent::dependencies()); + $modules += array_flip((array) $this->__call('dependencies')); + return array_keys($modules + (isset($this->info['module']) ? array($this->info['module'] => 1) : array())); + } + + public function executeByArgs($args = array()) { + $replacements = array('%label' => $this->label(), '@plugin' => $this->itemName); + rules_log('Executing @plugin %label.', $replacements, RulesLog::INFO, $this, TRUE); + $this->processSettings(); + // If there is no element info, just pass through the passed arguments. + // That way we support executing actions without any info at all. + if ($this->info()) { + $state = $this->setUpState($args); + module_invoke_all('rules_config_execute', $this); + + $result = $this->evaluate($state); + $return = $this->returnVariables($state, $result); + } + else { + rules_log('Unable to execute @plugin %label.', $replacements, RulesLog::ERROR, $this); + } + $state->cleanUp(); + rules_log('Finished executing of @plugin %label.', $replacements, RulesLog::INFO, $this, FALSE); + return $return; + } + + /** + * Execute the configured execution callback and log that. + */ + abstract protected function executeCallback(array $args, RulesState $state = NULL); + + + public function evaluate(RulesState $state) { + $this->processSettings(); + try { + // Get vars as needed for execute and call it. + return $this->executeCallback($this->getExecutionArguments($state), $state); + } + catch (RulesEvaluationException $e) { + rules_log($e->msg, $e->args, $e->severity); + rules_log('Unable to evaluate %name.', array('%name' => $this->getPluginName()), RulesLog::WARN, $this); + } + // Catch wrapper exceptions that might occur due to failures loading an + // entity or similar. + catch (EntityMetadataWrapperException $e) { + rules_log('Unable to get a data value. Error: !error', array('!error' => $e->getMessage()), RulesLog::WARN); + rules_log('Unable to evaluate %name.', array('%name' => $this->getPluginName()), RulesLog::WARN, $this); + } + } + + public function __sleep() { + return parent::__sleep() + array('elementName' => 'elementName'); + } + + public function getPluginName() { + return $this->itemName ." ". $this->elementName; + } + + /** + * Gets the name of the configured action or condition. + */ + public function getElementName() { + return $this->elementName; + } + + /** + * Add in the data provided by the info hooks to the cache. + */ + public function rebuildCache(&$itemInfo, &$cache) { + parent::rebuildCache($itemInfo, $cache); + + // Include all declared files so we can find all implementations. + self::includeFiles(); + + // Get the plugin's own info data. + $cache[$this->itemName .'_info'] = rules_fetch_data($this->itemName .'_info'); + foreach ($cache[$this->itemName .'_info'] as $name => &$info) { + $info += array( + 'parameter' => isset($info['arguments']) ? $info['arguments'] : array(), + 'provides' => isset($info['new variables']) ? $info['new variables'] : array(), + 'base' => $name, + 'callbacks' => array(), + ); + unset($info['arguments'], $info['new variables']); + + if (function_exists($info['base'])) { + $info['callbacks'] += array('execute' => $info['base']); + } + + // We do not need to build a faces cache for RulesPluginHandlerInterface, + // which gets added in automatically as its a parent of + // RulesPluginImplInterface. + unset($this->faces['RulesPluginHandlerInterface']); + + // Build up the per plugin implementation faces cache. + foreach ($this->faces as $interface) { + $methods = $file_names = array(); + $includes = self::getIncludeFiles($info['module']); + + foreach (get_class_methods($interface) as $method) { + if (isset($info['callbacks'][$method]) && ($function = $info['callbacks'][$method])) { + $methods[$method][0] = $function; + $file_names[$method] = $this->getFileName($function, $includes); + } + // Note that this skips RulesPluginImplInterface, which is not + // implemented by plugin handlers. + elseif (isset($info['class']) && is_subclass_of($info['class'], $interface)) { + $methods[$method][1] = $info['class']; + } + elseif (function_exists($function = $info['base'] . '_' . $method)) { + $methods[$method][0] = $function; + $file_names[$method] = $this->getFileName($function, $includes); + } + } + // Cache only the plugin implementation specific callbacks. + $info['faces_cache'][$interface] = array($methods, array_filter($file_names)); + } + // Filter out interfaces with no overriden methods. + $info['faces_cache'] = rules_filter_array($info['faces_cache'], 0, TRUE); + // We don't need that any more. + unset($info['callbacks'], $info['base']); + } + } + + /** + * Makes sure the providing modules' .rules.inc file is included as diverse + * callbacks may reside in that file. + */ + protected function loadBasicInclude() { + static $included = array(); + + if (isset($this->info['module']) && !isset($included[$this->info['module']])) { + $module = $this->info['module']; + module_load_include('inc', $module, $module . '.rules'); + $included[$module] = TRUE; + } + } + + /** + * Make sure all supported destinations are included. + */ + public static function includeFiles() { + static $included; + + if (!isset($included)) { + foreach (module_implements('rules_file_info') as $module) { + // rules.inc are already included thanks to the rules_hook_info() group. + foreach (self::getIncludeFiles($module, FALSE) as $name) { + module_load_include('inc', $module, $name); + } + } + $dirs = array(); + foreach (module_implements('rules_directory') as $module) { + // Include all files once, so the discovery can find them. + $result = module_invoke($module, 'rules_directory'); + if (!is_array($result)) { + $result = array($module => $result); + } + $dirs += $result; + } + foreach ($dirs as $module => $directory) { + foreach (glob(drupal_get_path('module', $module) . "/$directory/*.{inc,php}", GLOB_BRACE) as $filename) { + include_once $filename; + } + } + $included = TRUE; + } + } + + /** + * Returns all include files for a module. If $all is set to FALSE the + * $module.rules.inc file isn't added. + */ + protected static function getIncludeFiles($module, $all = TRUE) { + $files = (array)module_invoke($module, 'rules_file_info'); + // Automatically add "$module.rules_forms.inc" and "$module.rules.inc". + $files[] = $module . '.rules_forms'; + if ($all) { + $files[] = $module . '.rules'; + } + return $files; + } + + protected function getFileName($function, $includes) { + $reflector = new ReflectionFunction($function); + // On windows the path contains backslashes instead of slashes, fix that. + $file = str_replace('\\', '/', $reflector->getFileName()); + foreach ($includes as $include) { + $pos = strpos($file, $include . '.inc'); + // Test whether the file ends with the given filename.inc. + if ($pos !== FALSE && strlen($file) - $pos == strlen($include) + 4) { + return $include; + } + } + } +} + +/** + * Interface for objects that can be used as action. + */ +interface RulesActionInterface { + + /** + * @return As specified. + * + * @throws RulesEvaluationException + * Throws an exception if not all necessary arguments have been provided. + */ + public function execute(); +} + +/** + * Interface for objects that can be used as condition. + */ +interface RulesConditionInterface { + + /** + * @return Boolean. + * + * @throws RulesEvaluationException + * Throws an exception if not all necessary arguments have been provided. + */ + public function execute(); + + /** + * Negate the result. + */ + public function negate($negate = TRUE); + + /** + * Returns whether the element is configured to negate the result. + */ + public function isNegated(); +} + +interface RulesTriggerableInterface { + + /** + * Returns the array of (configured) event names associated with this object. + */ + public function events(); + + /** + * Removes an event from the rule configuration. + * + * @param $event + * The name of the (configured) event to remove. + * @return RulesTriggerableInterface + * The object instance itself, to allow chaining. + */ + public function removeEvent($event_name); + + /** + * Adds the specified event. + * + * @param string $event_name + * The base name of the event to add. + * @param array $settings + * (optional) The event settings. If there are no event settings, pass an + * empty array (default). + * + * @return RulesTriggerableInterface + */ + public function event($event_name, array $settings = array()); + + /** + * Gets the event settings associated with the given (configured) event. + * + * @param $event_name + * The (configured) event's name. + * + * @return array|null + * The array of event settings, or NULL if there are no settings. + */ + public function getEventSettings($event_name); + +} + +/** + * Provides the base interface for implementing abstract plugins via classes. + */ +interface RulesPluginHandlerInterface { + + /** + * Validates $settings independent from a form submission. + * + * @throws RulesIntegrityException + * In case of validation errors, RulesIntegrityExceptions are thrown. + */ + public function validate(); + + /** + * Processes settings independent from a form submission. + * + * Processing results may be stored and accessed on execution time in $settings. + */ + public function process(); + + /** + * Allows altering of the element's action/condition info. + * + * Note that this method is also invoked on evaluation time, thus any costly + * operations should be avoided. + * + * @param $element_info + * A reference on the element's info as returned by RulesPlugin::info(). + */ + public function info_alter(&$element_info); + + /** + * Checks whether the user has access to configure this element. + * + * Note that this only covers access for already created elements. In order to + * control access for creating or using elements specify an 'access callback' + * in the element's info array. + * + * @see hook_rules_action_info() + */ + public function access(); + + /** + * Returns an array of required modules. + */ + public function dependencies(); + + /** + * Alter the generated configuration form of the element. + * + * Validation and processing of the settings should be untied from the form + * and implemented in validate() and process() wherever it makes sense. + * For the remaining cases where form tied validation and processing is needed + * make use of the form API #element_validate and #value_callback properties. + */ + public function form_alter(&$form, $form_state, $options); + + + /** + * Optionally returns an array of info assertions for the specified + * parameters. This allows conditions to assert additional metadata, such as + * info about the fields of a bundle. + * + * @see RulesPlugin::variableInfoAssertions() + */ + public function assertions(); + +} + +/** + * Interface for implementing conditions via classes. + * + * In addition to the interface an execute() and a static getInfo() method must + * be implemented. The static getInfo() method has to return the info as + * returned by hook_rules_condition_info() but including an additional 'name' + * key, specifying the plugin name. + * The execute method is the equivalent to the usual execution callback and + * gets the parameters passed as specified in the info array. + * + * See RulesNodeConditionType for an example and rules_discover_plugins() + * for information about class discovery. + */ +interface RulesConditionHandlerInterface extends RulesPluginHandlerInterface {} + +/** + * Interface for implementing actions via classes. + * + * In addition to the interface an execute() and a static getInfo() method must + * be implemented. The static getInfo() method has to return the info as + * returned by hook_rules_action_info() but including an additional 'name' key, + * specifying the plugin name. + * The execute method is the equivalent to the usual execution callback and + * gets the parameters passed as specified in the info array. + * + * See RulesNodeConditionType for an example and rules_discover_plugins() + * for information about class discovery. + */ +interface RulesActionHandlerInterface extends RulesPluginHandlerInterface {} + +/** + * + * Provides the interface used for implementing an abstract plugin by using + * the Faces extension mechanism. + */ +interface RulesPluginImplInterface extends RulesPluginHandlerInterface { + + /** + * Execute the action or condition making use of the parameters as specified. + */ + public function execute(); +} + +/** + * Interface for optimizing evaluation. + * + * @see RulesContainerPlugin::optimize() + */ +interface RulesOptimizationInterface { + /** + * Optimizes a rule configuration in order to speed up evaluation. + */ + public function optimize(); +} + +/** + * Base class for implementing abstract plugins via classes. + */ +abstract class RulesPluginHandlerBase extends FacesExtender implements RulesPluginHandlerInterface { + + /** + * @var RulesAbstractPlugin + */ + protected $element; + + /** + * Overridden to provide $this->element to make the code more meaningful. + */ + public function __construct(FacesExtendable $object) { + $this->object = $object; + $this->element = $object; + } + + /** + * Implements RulesPluginImplInterface. + */ + public function access() { + return TRUE; + } + + public function validate() {} + public function process() {} + public function info_alter(&$element_info) {} + public function dependencies() {} + public function form_alter(&$form, $form_state, $options) {} + public function assertions() {} +} + +/** + * Base class for implementing conditions via classes. + */ +abstract class RulesConditionHandlerBase extends RulesPluginHandlerBase implements RulesConditionHandlerInterface {} + +/** + * Base class for implementing actions via classes. + */ +abstract class RulesActionHandlerBase extends RulesPluginHandlerBase implements RulesActionHandlerInterface {} + +/** + * Class providing default implementations of the methods of the RulesPluginImplInterface. + * + * If a plugin implementation does not provide a function for a method, the + * default method of this class will be invoked. + * + * @see RulesPluginImplInterface + * @see RulesAbstractPlugin + */ +class RulesAbstractPluginDefaults extends RulesPluginHandlerBase implements RulesPluginImplInterface { + + public function execute() { + throw new RulesEvaluationException($this->object->getPluginName() .": Execution implementation is missing.", array(), $this->object, RulesLog::ERROR); + } +} + +/** + * A RecursiveIterator for rule elements. + */ +class RulesRecursiveElementIterator extends ArrayIterator implements RecursiveIterator { + + public function getChildren() { + return $this->current()->getIterator(); + } + + public function hasChildren() { + return $this->current() instanceof IteratorAggregate; + } +} + +/** + * Base class for ContainerPlugins like Rules, Logical Operations or Loops. + */ +abstract class RulesContainerPlugin extends RulesPlugin implements IteratorAggregate { + + protected $children = array(); + + public function __construct($variables = array()) { + $this->setUp(); + if (!empty($variables) && $this->isRoot()) { + $this->info['variables'] = $variables; + } + } + + /** + * Returns the specified variables, in case the plugin is used as component. + */ + public function &componentVariables() { + if ($this->isRoot()) { + $this->info += array('variables' => array()); + return $this->info['variables']; + } + // We have to return a reference in any case. + $return = NULL; + return $return; + } + + /** + * Allow access to the children through the iterator. + * + * @return RulesRecursiveElementIterator + */ + public function getIterator() { + return new RulesRecursiveElementIterator($this->children); + } + + /** + * @return RulesContainerPlugin + */ + public function integrityCheck() { + if (!empty($this->info['variables']) && !$this->isRoot()) { + throw new RulesIntegrityException(t('%plugin: Specifying state variables is not possible for child elements.', array('%plugin' => $this->getPluginName())), $this); + } + parent::integrityCheck(); + foreach ($this->children as $child) { + $child->integrityCheck(); + } + return $this; + } + + public function dependencies() { + $modules = array_flip(parent::dependencies()); + foreach ($this->children as $child) { + $modules += array_flip($child->dependencies()); + } + return array_keys($modules); + } + + public function parameterInfo($optional = FALSE) { + $params = parent::parameterInfo($optional); + if (isset($this->info['variables'])) { + foreach ($this->info['variables'] as $name => $var_info) { + if (empty($var_info['handler']) && (!isset($var_info['parameter']) || $var_info['parameter'])) { + $params[$name] = $var_info; + // For lists allow empty variables by default. + if (entity_property_list_extract_type($var_info['type'])) { + $params[$name] += array('allow null' => TRUE); + } + } + } + } + return $params; + } + + public function availableVariables() { + if (!isset($this->availableVariables)) { + if ($this->isRoot()) { + $this->availableVariables = RulesState::defaultVariables(); + if (isset($this->info['variables'])) { + $this->availableVariables += $this->info['variables']; + } + } + else { + $this->availableVariables = $this->parent->stateVariables($this); + } + } + return $this->availableVariables; + } + + /** + * Returns info about variables available in the evaluation state for any + * children elements or if given for a special child element. + * + * @param $element + * The element for which the available state variables should be returned. + * If NULL is given, the variables available before any children are invoked + * are returned. If set to TRUE, the variables available after evaluating + * all children will be returned. + */ + protected function stateVariables($element = NULL) { + $vars = $this->availableVariables(); + if (isset($element)) { + // Add in variables provided by siblings executed before the element. + foreach ($this->children as $child) { + if ($child === $element) { + break; + } + $vars += $child->providesVariables(); + // Take variable info assertions into account. + if ($assertions = $child->variableInfoAssertions()) { + $vars = RulesData::addMetadataAssertions($vars, $assertions); + } + } + } + return $vars; + } + + protected function variableInfoAssertions() { + $assertions = array(); + foreach ($this->children as $child) { + if ($add = $child->variableInfoAssertions()) { + $assertions = rules_update_array($assertions, $add); + } + } + return $assertions; + } + + protected function setUpVariables() { + return isset($this->info['variables']) ? parent::parameterInfo(TRUE) + $this->info['variables'] : $this->parameterInfo(TRUE); + } + + /** + * Condition containers just return a boolean while action containers return + * the configured provided variables as an array of variables. + */ + public function executeByArgs($args = array()) { + $replacements = array('%label' => $this->label(), '@plugin' => $this->itemName); + rules_log('Executing @plugin %label.', $replacements, RulesLog::INFO, $this, TRUE); + $this->processSettings(); + $state = $this->setUpState($args); + + // Handle recursion prevention. + if ($state->isBlocked($this)) { + return rules_log('Not evaluating @plugin %label to prevent recursion.', array('%label' => $this->label(), '@plugin' => $this->plugin()), RulesLog::INFO); + } + // Block the config to prevent any future recursion. + $state->block($this); + + module_invoke_all('rules_config_execute', $this); + $result = $this->evaluate($state); + $return = $this->returnVariables($state, $result); + + $state->unblock($this); + $state->cleanUp(); + rules_log('Finished executing of @plugin %label.', $replacements, RulesLog::INFO, $this, FALSE); + return $return; + } + + public function access() { + foreach ($this->children as $key => $child) { + if (!$child->access()) { + return FALSE; + } + } + return TRUE; + } + + public function destroy() { + foreach ($this->children as $key => $child) { + $child->destroy(); + } + parent::destroy(); + } + + /** + * By default we do a deep clone. + */ + public function __clone() { + parent::__clone(); + foreach ($this->children as $key => $child) { + $this->children[$key] = clone $child; + $this->children[$key]->parent = $this; + } + } + + /** + * Override delete to keep the children alive, if possible. + */ + public function delete($keep_children = TRUE) { + if (isset($this->parent) && $keep_children) { + foreach ($this->children as $child) { + $child->setParent($this->parent); + } + } + parent::delete(); + } + + public function __sleep() { + return parent::__sleep() + array('children' => 'children', 'info' => 'info'); + } + + /** + * Sorts all child elements by their weight. + * + * @param $deep + * If enabled a deep sort is performed, thus the whole element tree below + * this element is sorted. + */ + public function sortChildren($deep = FALSE) { + // Make sure the array order is kept in case two children have the same + // weight by ensuring later childrens would have higher weights. + foreach (array_values($this->children) as $i => $child) { + $child->weight += $i / 1000; + } + usort($this->children, array('RulesPlugin', 'compare')); + + // Fix up the weights afterwards to be unique integers. + foreach (array_values($this->children) as $i => $child) { + $child->weight = $i; + } + + if ($deep) { + foreach (new ParentIterator($this->getIterator()) as $child) { + $child->sortChildren(TRUE); + } + } + $this->resetInternalCache(); + } + + protected function exportChildren($key = NULL) { + $key = isset($key) ? $key : strtoupper($this->plugin()); + $export[$key] = array(); + foreach ($this->children as $child) { + $export[$key][] = $child->export(); + } + return $export; + } + + /** + * Determines whether the element should be exported in flat style. Flat style + * means that the export keys are written directly into the export array, + * whereas else the export is written into a sub-array. + */ + protected function exportFlat() { + // By default we always use flat style for plugins without any parameters + // or provided variables, as then only children have to be exported. E.g. + // this applies to the OR and AND plugins. + return $this->isRoot() || (!$this->pluginParameterInfo() && !$this->providesVariables()); + } + + protected function exportToArray() { + $export = array(); + if (!empty($this->info['variables'])) { + $export['USES VARIABLES'] = $this->info['variables']; + } + if ($this->exportFlat()) { + $export += $this->exportSettings() + $this->exportChildren(); + } + else { + $export[strtoupper($this->plugin())] = $this->exportSettings() + $this->exportChildren(); + } + return $export; + } + + public function import(array $export) { + if (!empty($export['USES VARIABLES'])) { + $this->info['variables'] = $export['USES VARIABLES']; + } + // Care for exports having the export array nested in a sub-array. + if (!$this->exportFlat()) { + $export = reset($export); + } + $this->importSettings($export); + $this->importChildren($export); + } + + protected function importChildren($export, $key = NULL) { + $key = isset($key) ? $key : strtoupper($this->plugin()); + foreach ($export[$key] as $child_export) { + $plugin = _rules_import_get_plugin(rules_array_key($child_export), $this instanceof RulesActionInterface ? 'action' : 'condition'); + $child = rules_plugin_factory($plugin); + $child->setParent($this); + $child->import($child_export); + } + } + + public function resetInternalCache() { + $this->availableVariables = NULL; + foreach ($this->children as $child) { + $child->resetInternalCache(); + } + } + + /** + * Overrides optimize(). + */ + public function optimize() { + parent::optimize(); + // Now let the children optimize itself. + foreach ($this as $element) { + $element->optimize(); + } + } +} + +/** + * Base class for all action containers. + */ +abstract class RulesActionContainer extends RulesContainerPlugin implements RulesActionInterface { + + public function __construct($variables = array(), $providesVars = array()) { + parent::__construct($variables); + // The provided vars of a component are the names of variables, which should + // be provided to the caller. See rule(). + if ($providesVars) { + $this->info['provides'] = $providesVars; + } + } + + /** + * Add an action. Pass either an instance of the RulesActionInterface + * or the arguments as needed by rules_action(). + * + * @return RulesActionContainer + * Returns $this to support chained usage. + */ + public function action($name, $settings = array()) { + $action = (is_object($name) && $name instanceof RulesActionInterface) ? $name : rules_action($name, $settings); + $action->setParent($this); + return $this; + } + + /** + * Evaluate, whereas by default new vars are visible in the parent's scope. + */ + public function evaluate(RulesState $state) { + foreach ($this->children as $action) { + $action->evaluate($state); + } + } + + public function pluginProvidesVariables() { + return array(); + } + + public function providesVariables() { + $provides = parent::providesVariables(); + if (isset($this->info['provides']) && $vars = $this->componentVariables()) { + // Determine the full variable info for the provided variables. Note that + // we only support providing variables list in the component vars. + $provides += array_intersect_key($vars, array_flip($this->info['provides'])); + } + return $provides; + } + + /** + * Returns an array of variable names, which are provided by passing through + * the provided variables of the children. + */ + public function &componentProvidesVariables() { + $this->info += array('provides' => array()); + return $this->info['provides']; + } + + protected function exportToArray() { + $export = parent::exportToArray(); + if (!empty($this->info['provides'])) { + $export['PROVIDES VARIABLES'] = $this->info['provides']; + } + return $export; + } + + public function import(array $export) { + parent::import($export); + if (!empty($export['PROVIDES VARIABLES'])) { + $this->info['provides'] = $export['PROVIDES VARIABLES']; + } + } +} + +/** + * Base class for all condition containers. + */ +abstract class RulesConditionContainer extends RulesContainerPlugin implements RulesConditionInterface { + + protected $negate = FALSE; + + /** + * Add a condition. Pass either an instance of the RulesConditionInterface + * or the arguments as needed by rules_condition(). + * + * @return RulesConditionContainer + * Returns $this to support chained usage. + */ + public function condition($name, $settings = array()) { + $condition = (is_object($name) && $name instanceof RulesConditionInterface) ? $name : rules_condition($name, $settings); + $condition->setParent($this); + return $this; + } + + /** + * Negate this condition. + * + * @return RulesConditionContainer + */ + public function negate($negate = TRUE) { + $this->negate = (bool) $negate; + return $this; + } + + public function isNegated() { + return $this->negate; + } + + public function __sleep() { + return parent::__sleep() + array('negate' => 'negate'); + } + + /** + * Just return the condition container's result. + */ + protected function returnVariables(RulesState $state, $result = NULL) { + return $result; + } + + protected function exportChildren($key = NULL) { + $key = isset($key) ? $key : strtoupper($this->plugin()); + return parent::exportChildren($this->negate ? 'NOT ' . $key : $key); + } + + protected function importChildren($export, $key = NULL) { + $key = isset($key) ? $key : strtoupper($this->plugin()); + // Care for negated elements. + if (!isset($export[$key]) && isset($export['NOT ' . $key])) { + $this->negate = TRUE; + $key = 'NOT ' . $key; + } + parent::importChildren($export, $key); + } + + /** + * Overridden to exclude variable assertions of negated conditions. + */ + protected function stateVariables($element = NULL) { + $vars = $this->availableVariables(); + if (isset($element)) { + // Add in variables provided by siblings executed before the element. + foreach ($this->children as $child) { + if ($child === $element) { + break; + } + $vars += $child->providesVariables(); + // Take variable info assertions into account. + if (!$this->negate && !$child->isNegated() && ($assertions = $child->variableInfoAssertions())) { + $vars = RulesData::addMetadataAssertions($vars, $assertions); + } + } + } + return $vars; + } +} + +/** + * The rules default logging class. + */ +class RulesLog { + + const INFO = 1; + const WARN = 2; + const ERROR = 3; + + static protected $logger; + + /** + * @return RulesLog + * Returns the rules logger instance. + */ + static function logger() { + if (!isset(self::$logger)) { + $class = __CLASS__; + self::$logger = new $class(variable_get('rules_log_level', self::INFO)); + } + return self::$logger; + } + + protected $log = array(); + protected $logLevel, $line = 0; + + /** + * This is a singleton. + */ + protected function __construct($logLevel = self::WARN) { + $this->logLevel = $logLevel; + } + + public function __clone() { + throw new Exception("Cannot clone the logger."); + } + + /** + * Logs a log message. + * + * @see rules_log() + */ + public function log($msg, $args = array(), $logLevel = self::INFO, $scope = NULL, $path = NULL) { + if ($logLevel >= $this->logLevel) { + $this->log[] = array($msg, $args, $logLevel, microtime(TRUE), $scope, $path); + } + } + + /** + * Checks the log and throws an exception if there were any problems. + */ + function checkLog($logLevel = self::WARN) { + foreach ($this->log as $entry) { + if ($entry[2] >= $logLevel) { + throw new Exception($this->render()); + } + } + } + + /** + * Checks the log for (error) messages with a log level equal or higher than the given one. + * + * @return + * Whether the an error has been logged. + */ + public function hasErrors($logLevel = self::WARN) { + foreach ($this->log as $entry) { + if ($entry[2] >= $logLevel) { + return TRUE; + } + } + return FALSE; + } + + /** + * Gets an array of logged messages. + */ + public function get() { + return $this->log; + } + + /** + * Renders the whole log. + */ + public function render() { + $line = 0; + $output = array(); + while (isset($this->log[$line])) { + $vars['head'] = t($this->log[$line][0], $this->log[$line][1]); + $vars['log'] = $this->renderHelper($line); + $output[] = theme('rules_debug_element', $vars); + $line++; + } + return implode('', $output); + } + + /** + * Renders the log of one event invocation. + */ + protected function renderHelper(&$line = 0) { + $startTime = isset($this->log[$line][3]) ? $this->log[$line][3] : 0; + $output = array(); + while ($line < count($this->log)) { + if ($output && !empty($this->log[$line][4])) { + // The next entry stems from another evaluated set, add in its log + // messages here. + $vars['head'] = t($this->log[$line][0], $this->log[$line][1]); + if (isset($this->log[$line][5])) { + $vars['link'] = '[' . l('edit', $this->log[$line][5]) . ']'; + } + $vars['log'] = $this->renderHelper($line); + $output[] = theme('rules_debug_element', $vars); + } + else { + $formatted_diff = round(($this->log[$line][3] - $startTime) * 1000, 3) .' ms'; + $msg = $formatted_diff .' '. t($this->log[$line][0], $this->log[$line][1]); + if ($this->log[$line][2] >= RulesLog::WARN) { + $level = $this->log[$line][2] == RulesLog::WARN ? 'warn' : 'error'; + $msg = ''. $msg .''; + } + if (isset($this->log[$line][5]) && !isset($this->log[$line][4])) { + $msg .= ' [' . l('edit', $this->log[$line][5]) . ']'; + } + $output[] = $msg; + + if (isset($this->log[$line][4]) && !$this->log[$line][4]) { + // This was the last log entry of this set. + return theme('item_list', array('items' => $output)); + } + } + $line++; + } + return theme('item_list', array('items' => $output)); + } + + /** + * Clears the logged messages. + */ + public function clear() { + $this->log = array(); + } +} + +/** + * A common exception for Rules. + * + * This class can be used to catch all exceptions thrown by Rules. + */ +abstract class RulesException extends Exception {} + +/** + * An exception that is thrown during evaluation. + * + * Messages are prepared to be logged to the watchdog, thus not yet translated. + * + * @see watchdog() + */ +class RulesEvaluationException extends RulesException { + + public $msg, $args, $severity, $element, $keys = array(); + + /** + * @param $msg + * The exception message containing placeholder as t(). + * @param $args + * Replacement arguments such as for t(). + * @param $element + * The element of a configuration causing the exception or an array + * consisting of the element and keys specifying a setting value causing + * the exception. + * @param $severity + * The RulesLog severity. Defaults to RulesLog::WARN. + */ + function __construct($msg, array $args = array(), $element = NULL, $severity = RulesLog::WARN) { + $this->element = is_array($element) ? array_shift($element) : $element; + $this->keys = is_array($element) ? $element : array(); + $this->msg = $msg; + $this->args = $args; + $this->severity = $severity; + // If an error happened, run the integrity check on the rules configuration + // and mark it as dirty if it the check fails. + if ($severity == RulesLog::ERROR && isset($this->element)) { + $rules_config = $this->element->root(); + rules_config_update_dirty_flag($rules_config); + // If we discovered a broken configuration, exclude it in future. + if ($rules_config->dirty) { + rules_clear_cache(); + } + } + // @todo fix _drupal_decode_exception() to use __toString() and override it. + $this->message = t($this->msg, $this->args); + } +} + +/** + * An exception that is thrown for Rules configurations that fail the integrity check. + * + * @see RulesPlugin::integrityCheck() + */ +class RulesIntegrityException extends RulesException { + + public $msg, $element, $keys = array(); + + /** + * @param string $msg + * The exception message, already translated. + * @param $element + * The element of a configuration causing the exception or an array + * consisting of the element and keys specifying a parameter or provided + * variable causing the exception, e.g. + * @code array($element, 'parameter', 'node') @endcode. + */ + function __construct($msg, $element = NULL) { + $this->element = is_array($element) ? array_shift($element) : $element; + $this->keys = is_array($element) ? $element : array(); + parent::__construct($msg); + } +} + +/** + * An exception that is thrown for missing module dependencies. + */ +class RulesDependencyException extends RulesIntegrityException {} + +/** + * Determines the plugin to be used for importing a child element. + * + * @param $key + * The key to look for, e.g. 'OR' or 'DO'. + * @param $default + * The default to return if no special plugin can be found. + */ +function _rules_import_get_plugin($key, $default = 'action') { + $map = &drupal_static(__FUNCTION__); + if (!isset($map)) { + $cache = rules_get_cache(); + foreach ($cache['plugin_info'] as $name => $info) { + if (!empty($info['embeddable'])) { + $info += array('import keys' => array(strtoupper($name))); + foreach ($info['import keys'] as $k) { + $map[$k] = $name; + } + } + } + } + // Cut of any leading NOT from the key. + if (strpos($key, 'NOT ') === 0) { + $key = substr($key, 4); + } + if (isset($map[$key])) { + return $map[$key]; + } + return $default; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/includes/rules.event.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/includes/rules.event.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,411 @@ +type, $node, $view_mode); + * @endcode + * If the event settings are optional, both events have to be invoked whereas + * usually the more general event is invoked last. E.g.: + * @code + * rules_invoke_event('node_view--' . $node->type, $node, $view_mode); + * rules_invoke_event('node_view', $node, $view_mode); + * @endcode + * + * Rules event handlers have to be declared using the 'class' key in + * hook_rules_event_info(), or may be discovered automatically, see + * rules_discover_plugins() for details. + * + * @see RulesEventHandlerBase + * @see RulesEventDefaultHandler + */ +interface RulesEventHandlerInterface { + + /** + * Constructs the event handler. + * + * @param string $event_name + * The base event string. + * @param array $info + * The event info of the given event. + */ + public function __construct($event_name, $info); + + /** + * Sets the event settings. + * + * @param array $settings + * An array of settings to set. + * + * @return RulesEventHandlerInterface + * The handler itself for chaining. + */ + public function setSettings(array $settings); + + /** + * Gets the event settings. + * + * @return array + * The array of settings. + */ + public function getSettings(); + + /** + * Returns an array of default settings. + * + * @return array + */ + public function getDefaults(); + + /** + * Returns a user-facing summary of the settings. + * + * @return string + * The summary in HTML, i.e. properly escaped or filtered. + */ + public function summary(); + + /** + * Builds the event settings form. + * + * @param array $form_state + * An associative array containing the current state of the form. + * + * @return array + * The form structure. + */ + public function buildForm(array &$form_state); + + /** + * Validate the event settings independent from a form submission. + * + * @throws RulesIntegrityException + * In case of validation errors, RulesIntegrityExceptions are thrown. + */ + public function validate(); + + /** + * Extract the form values and update the event settings. + * + * @param array $form + * An associative array containing the structure of the form. + * @param array $form_state + * An associative array containing the current state of the form. + */ + public function extractFormValues(array &$form, array &$form_state); + + /** + * Returns the suffix to be added to the base event named based upon settings. + * + * If event settings are used, the event name Rules uses for the configured + * event is {EVENT_NAME}--{SUFFIX}. + * + * @return string + * The suffix string. Return an empty string for not appending a suffix. + */ + public function getEventNameSuffix(); + + /** + * Returns info about the variables provided by this event. + * + * @return array + * An array of provided variables, keyed by variable names and with the + * variable info array as value. + */ + public function availableVariables(); + + /** + * Returns the base name of the event the event handler belongs to. + * + * @return string + * The name of the event the event handler belongs to. + */ + public function getEventName(); + + /** + * Returns the info array of the event the event handler belongs to. + * + * @return string + * The info array of the event the event handler belongs to. + */ + public function getEventInfo(); +} + +/** + * Interface for event dispatchers. + */ +interface RulesEventDispatcherInterface extends RulesEventHandlerInterface { + + /** + * Starts the event watcher. + */ + public function startWatching(); + + /** + * Stops the event watcher. + */ + public function stopWatching(); + + /** + * Returns whether the event dispatcher is currently active. + * + * @return bool + * TRUE if the event dispatcher is currently active, FALSE otherwise. + */ + public function isWatching(); +} + +/** + * Base class for event handler. + */ +abstract class RulesEventHandlerBase implements RulesEventHandlerInterface { + + /** + * The event name. + * + * @var string + */ + protected $eventName; + + /** + * The event info. + * + * @var array + */ + protected $eventInfo; + + /** + * The event settings. + * + * @var array + */ + protected $settings = array(); + + /** + * Implements RulesEventHandlerInterface::__construct() + */ + public function __construct($event_name, $info) { + $this->eventName = $event_name; + $this->eventInfo = $info; + $this->settings = $this->getDefaults(); + } + + /** + * Implements RulesEventHandlerInterface::getSettings() + */ + public function getSettings() { + return $this->settings; + } + + /** + * Implements RulesEventHandlerInterface::setSettings() + */ + public function setSettings(array $settings) { + $this->settings = $settings + $this->getDefaults(); + return $this; + } + + /** + * Implements RulesEventHandlerInterface::validate() + */ + public function validate() { + // Nothing to check by default. + } + + /** + * Implements RulesEventHandlerInterface::extractFormValues() + */ + public function extractFormValues(array &$form, array &$form_state) { + foreach ($this->getDefaults() as $key => $setting) { + $this->settings[$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : $setting; + } + } + + /** + * Implements RulesEventHandlerInterface::availableVariables() + */ + public function availableVariables() { + return isset($this->eventInfo['variables']) ? $this->eventInfo['variables'] : array(); + } + + /** + * Implements RulesEventHandlerInterface::getEventName() + */ + public function getEventName() { + return $this->eventName; + } + + /** + * Implements RulesEventHandlerInterface::getEventInfo() + */ + public function getEventInfo() { + return $this->eventInfo; + } +} + +/** + * A handler for events having no settings. This is the default handler. + */ +class RulesEventDefaultHandler extends RulesEventHandlerBase { + + /** + * Implements RulesEventHandlerInterface::buildForm() + */ + public function buildForm(array &$form_state) { + return array(); + } + + /** + * Implements RulesEventHandlerInterface::getConfiguredEventName() + */ + public function getEventNameSuffix() { + return ''; + } + + /** + * Implements RulesEventHandlerInterface::summary() + */ + public function summary() { + return check_plain($this->eventInfo['label']); + } + + /** + * Implements RulesEventHandlerInterface::getDefaults() + */ + public function getDefaults() { + return array(); + } + + /** + * Implements RulesEventHandlerInterface::getSettings() + */ + public function getSettings() { + return NULL; + } +} + +/** + * Exposes the bundle of an entity as event setting. + */ +class RulesEventHandlerEntityBundle extends RulesEventHandlerBase { + + protected $entityType, $entityInfo, $bundleKey; + + /** + * Implements RulesEventHandlerInterface::__construct() + */ + public function __construct($event_name, $info) { + parent::__construct($event_name, $info); + // Cut off the suffix, e.g. remove 'view' from node_view. + $this->entityType = implode('_', explode('_', $event_name, -1)); + $this->entityInfo = entity_get_info($this->entityType); + if (!$this->entityInfo) { + throw new InvalidArgumentException('Unsupported event name passed.'); + } + } + + /** + * Implements RulesEventHandlerInterface::summary() + */ + public function summary() { + $bundle = &$this->settings['bundle']; + $bundle_label = isset($this->entityInfo['bundles'][$bundle]['label']) ? $this->entityInfo['bundles'][$bundle]['label'] : $bundle; + $suffix = isset($bundle) ? ' ' . t('of @bundle-key %name', array('@bundle-key' => $this->getBundlePropertyLabel(), '%name' => $bundle_label)) : ''; + return check_plain($this->eventInfo['label']) . $suffix; + } + + /** + * Implements RulesEventHandlerInterface::buildForm() + */ + public function buildForm(array &$form_state) { + $form['bundle'] = array( + '#type' => 'select', + '#title' => t('Restrict by @bundle', array('@bundle' => $this->getBundlePropertyLabel())), + '#description' => t('If you need to filter for multiple values, either add multiple events or use the "Entity is of bundle" condition instead.'), + '#default_value' => $this->settings['bundle'], + '#empty_value' => '', + ); + foreach ($this->entityInfo['bundles'] as $name => $bundle_info) { + $form['bundle']['#options'][$name] = $bundle_info['label']; + } + return $form; + } + + /** + * Returns the label to use for the bundle property. + * + * @return string + */ + protected function getBundlePropertyLabel() { + return $this->entityInfo['entity keys']['bundle']; + } + + /** + * Implements RulesEventHandlerInterface::extractFormValues() + */ + public function extractFormValues(array &$form, array &$form_state) { + $this->settings['bundle'] = !empty($form_state['values']['bundle']) ? $form_state['values']['bundle'] : NULL; + } + + /** + * Implements RulesEventHandlerInterface::validate() + */ + public function validate() { + if ($this->settings['bundle'] && empty($this->entityInfo['bundles'][$this->settings['bundle']])) { + throw new RulesIntegrityException(t('The @bundle %bundle of %entity_type is not known.', + array( + '%bundle' => $this->settings['bundle'], + '%entity_type' => $this->entityInfo['label'], + '@bundle' => $this->getBundlePropertyLabel(), + )), array(NULL, 'bundle')); + } + } + + /** + * Implements RulesEventHandlerInterface::getConfiguredEventName() + */ + public function getEventNameSuffix() { + return $this->settings['bundle']; + } + + /** + * Implements RulesEventHandlerInterface::getDefaults() + */ + public function getDefaults() { + return array( + 'bundle' => NULL, + ); + } + + /** + * Implements RulesEventHandlerInterface::availableVariables() + */ + public function availableVariables() { + $variables = $this->eventInfo['variables']; + if ($this->settings['bundle']) { + // Add the bundle to all variables of the entity type. + foreach ($variables as $name => $variable_info) { + if ($variable_info['type'] == $this->entityType) { + $variables[$name]['bundle'] = $this->settings['bundle']; + } + } + } + return $variables; + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/includes/rules.plugins.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/includes/rules.plugins.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,827 @@ + $this->elementName), RulesLog::INFO, $this); + $return = $this->__call('execute', empty($this->info['named parameter']) ? $args : array($args)); + // Get the (partially) wrapped arguments. + $args = $state->currentArguments; + + if (is_array($return)) { + foreach ($return as $name => $data) { + // Add provided variables. + if (isset($this->info['provides'][$name])) { + $var_name = isset($this->settings[$name . ':var']) ? $this->settings[$name . ':var'] : $name; + if (!$state->varInfo($var_name)) { + $state->addVariable($var_name, $data, $this->info['provides'][$name]); + rules_log('Added the provided variable %name of type %type', array('%name' => $var_name, '%type' => $this->info['provides'][$name]['type']), RulesLog::INFO, $this); + if (!empty($this->info['provides'][$name]['save']) && $state->variables[$var_name] instanceof EntityMetadataWrapper) { + $state->saveChanges($var_name, $state->variables[$var_name]); + } + } + } + // Support updating variables by returning the values. + elseif (!isset($this->info['provides'][$name])) { + // Update the data value using the wrapper. + if (isset($args[$name]) && $args[$name] instanceof EntityMetadataWrapper) { + try { + $args[$name]->set($data); + } + catch (EntityMetadataWrapperException $e) { + throw new RulesEvaluationException('Unable to update the argument for parameter %name: %error', array('%name' => $name, '%error' => $e->getMessage()), $this); + } + } + elseif (array_key_exists($name, $args)) { + // Map back to the source variable name and update it. + $var_name = !empty($this->settings[$name . ':select']) ? str_replace('-', '_', $this->settings[$name . ':select']) : $name; + $state->variables[$var_name] = $data; + } + } + } + } + // Save parameters as defined in the parameter info. + if ($return !== FALSE) { + foreach ($this->info['parameter'] as $name => $info) { + if (!empty($info['save']) && $args[$name] instanceof EntityMetadataWrapper) { + if (isset($this->settings[$name . ':select'])) { + $state->saveChanges($this->settings[$name . ':select'], $args[$name]); + } + else { + // Wrapper has been configured via direct input, so just save. + rules_log('Saved argument of type %type for parameter %name.', array('%name' => $name, '%type' => $args[$name]->type())); + $args[$name]->save(); + } + } + } + } + } +} + +/** + * Implements a rules condition. + */ +class RulesCondition extends RulesAbstractPlugin implements RulesConditionInterface { + + protected $itemName = 'condition'; + protected $negate = FALSE; + + public function providesVariables() { + return array(); + } + + public function negate($negate = TRUE) { + $this->negate = (bool) $negate; + return $this; + } + + public function isNegated() { + return $this->negate; + } + + protected function executeCallback(array $args, RulesState $state = NULL) { + $return = (bool) $this->__call('execute', empty($this->info['named parameter']) ? $args : array($args)); + rules_log('The condition %name evaluated to %bool', array('%name' => $this->elementName, '%bool' => $return ? 'TRUE' : 'FALSE'), RulesLog::INFO, $this); + return $this->negate ? !$return : $return; + } + + public function __sleep() { + return parent::__sleep() + array('negate' => 'negate'); + } + + /** + * Just return the boolean result. + */ + protected function returnVariables(RulesState $state, $result = NULL) { + return $result; + } + + protected function exportToArray() { + $not = $this->negate ? 'NOT ' : ''; + $export = $this->exportSettings(); + // Abbreviate the export making "USING" implicit. + return array($not . $this->elementName => isset($export['USING']) ? $export['USING'] : array()); + } + + public function import(array $export) { + $this->elementName = rules_array_key($export); + if (strpos($this->elementName, 'NOT ') === 0) { + $this->elementName = substr($this->elementName, 4); + $this->negate = TRUE; + } + // After setting the element name, setup the element again so the right + // element info is loaded. + $this->setUp(); + + // Re-add 'USING' which has been removed for abbreviation. + $this->importSettings(array('USING' => reset($export))); + } + + public function label() { + $label = parent::label(); + return $this->negate ? t('NOT @condition', array('@condition' => $label)) : $label; + } +} + +/** + * An actual rule. + * Note: A rule also implements the RulesActionInterface (inherited). + */ +class Rule extends RulesActionContainer { + + protected $conditions = NULL; + protected $itemName = 'rule'; + + public $label = 'unlabeled'; + + public function __construct($variables = array(), $providesVars = array()) { + parent::__construct($variables, $providesVars); + + // Initialize the conditions container. + if (!isset($this->conditions)) { + $this->conditions = rules_and(); + // Don't use setParent() to avoid having it added to the children. + $this->conditions->parent = $this; + } + } + + /** + * Get an iterator over all contained conditions. Note that this iterator also + * implements the ArrayAcces interface. + * + * @return RulesRecursiveElementIterator + */ + public function conditions() { + return $this->conditions->getIterator(); + } + + /** + * Returns the "And" condition container, which contains all conditions of + * this rule. + * + * @return RulesAnd + */ + public function conditionContainer() { + return $this->conditions; + } + + public function __sleep() { + return parent::__sleep() + drupal_map_assoc(array('conditions', 'label')); + } + + /** + * Get an iterator over all contained actions. Note that this iterator also + * implements the ArrayAccess interface. + * + * @return RulesRecursiveElementIterator + */ + public function actions() { + return parent::getIterator(); + } + + /** + * Add a condition. Pass either an instance of the RulesConditionInterface + * or the arguments as needed by rules_condition(). + * + * @return Rule + * Returns $this to support chained usage. + */ + public function condition($name, $settings = array()) { + $this->conditions->condition($name, $settings); + return $this; + } + + public function sortChildren($deep = FALSE) { + $this->conditions->sortChildren($deep); + parent::sortChildren($deep); + } + + public function evaluate(RulesState $state) { + rules_log('Evaluating conditions of rule %label.', array('%label' => $this->label), RulesLog::INFO, $this); + if ($this->conditions->evaluate($state)) { + rules_log('Rule %label fires.', array('%label' => $this->label), RulesLog::INFO, $this, TRUE); + parent::evaluate($state); + rules_log('Rule %label has fired.', array('%label' => $this->label), RulesLog::INFO, $this, FALSE); + } + } + + /** + * Fires the rule, i.e. evaluates the rule without checking its conditions. + * + * @see RulesPlugin::evaluate() + */ + public function fire(RulesState $state) { + rules_log('Firing rule %label.', array('%label' => $this->label), RulesLog::INFO, $this); + parent::evaluate($state); + } + + public function integrityCheck() { + parent::integrityCheck(); + $this->conditions->integrityCheck(); + return $this; + } + + public function access() { + return (!isset($this->conditions) || $this->conditions->access()) && parent::access(); + } + + public function dependencies() { + return array_keys(array_flip($this->conditions->dependencies()) + array_flip(parent::dependencies())); + } + + public function destroy() { + $this->conditions->destroy(); + parent::destroy(); + } + + /** + * @return RulesRecursiveElementIterator + */ + public function getIterator() { + $array = array_merge(array($this->conditions), $this->children); + return new RulesRecursiveElementIterator($array); + } + + protected function stateVariables($element = NULL) { + // Don't add in provided action variables for the conditions. + if (isset($element) && $element === $this->conditions) { + return $this->availableVariables(); + } + $vars = parent::stateVariables($element); + // Take variable info assertions of conditions into account. + if ($assertions = $this->conditions->variableInfoAssertions()) { + $vars = RulesData::addMetadataAssertions($vars, $assertions); + } + return $vars; + } + + protected function exportFlat() { + return $this->isRoot(); + } + + protected function exportToArray() { + $export = parent::exportToArray(); + if (!$this->isRoot()) { + $export[strtoupper($this->plugin())]['LABEL'] = $this->label; + } + return $export; + } + + protected function exportChildren($key = NULL) { + $export = array(); + if ($this->conditions->children) { + $export = $this->conditions->exportChildren('IF'); + } + return $export + parent::exportChildren('DO'); + } + + public function import(array $export) { + if (!$this->isRoot() && isset($export[strtoupper($this->plugin())]['LABEL'])) { + $this->label = $export[strtoupper($this->plugin())]['LABEL']; + } + parent::import($export); + } + + protected function importChildren($export, $key = NULL) { + if (!empty($export['IF'])) { + $this->conditions->importChildren($export, 'IF'); + } + parent::importChildren($export, 'DO'); + } + + public function __clone() { + parent::__clone(); + $this->conditions = clone $this->conditions; + $this->conditions->parent = $this; + } + + /** + * Rules may not provided any variable info assertions, as Rules are only + * conditionally executed. + */ + protected function variableInfoAssertions() { + return array(); + } + + /** + * Overridden to ensure the whole Rule is deleted at once. + */ + public function delete($keep_children = FALSE) { + parent::delete($keep_children); + } + + /** + * Overriden to expose the variables of all actions for embedded rules. + */ + public function providesVariables() { + $provides = parent::providesVariables(); + if (!$this->isRoot()) { + foreach ($this->actions() as $action) { + $provides += $action->providesVariables(); + } + } + return $provides; + } + + public function resetInternalCache() { + parent::resetInternalCache(); + $this->conditions->resetInternalCache(); + } +} + +/** + * Represents rules getting triggered by events. + */ +class RulesReactionRule extends Rule implements RulesTriggerableInterface { + + protected $itemName = 'reaction rule'; + protected $events = array(), $eventSettings = array(); + + /** + * Implements RulesTriggerableInterface::events(). + */ + public function events() { + return $this->events; + } + + /** + * Implements RulesTriggerableInterface::removeEvent(). + */ + public function removeEvent($event) { + if (($id = array_search($event, $this->events)) !== FALSE) { + unset($this->events[$id]); + } + return $this; + } + + /** + * Implements RulesTriggerableInterface::event(). + */ + public function event($event_name, array $settings = NULL) { + // Process any settings and determine the configured event's name. + if ($settings) { + $handler = rules_get_event_handler($event_name, $settings); + if ($suffix = $handler->getEventNameSuffix()) { + $event_name .= '--' . $suffix; + $this->eventSettings[$event_name] = $settings; + } + else { + // Do not store settings if there is no suffix. + unset($this->eventSettings[$event_name]); + } + } + if (array_search($event_name, $this->events) === FALSE) { + $this->events[] = $event_name; + } + return $this; + } + + /** + * Implements RulesTriggerableInterface::getEventSettings(). + */ + public function getEventSettings($event_name) { + if (isset($this->eventSettings[$event_name])) { + return $this->eventSettings[$event_name]; + } + } + + public function integrityCheck() { + parent::integrityCheck(); + // Check integrity of the configured events. + foreach ($this->events as $event_name) { + $handler = rules_get_event_handler($event_name, $this->getEventSettings($event_name)); + $handler->validate(); + } + return $this; + } + + /** + * Reaction rules can't add variables to the parent scope, so clone $state. + */ + public function evaluate(RulesState $state) { + // Implement recursion prevention for reaction rules. + if ($state->isBlocked($this)) { + return rules_log('Not evaluating @plugin %label to prevent recursion.', array('%label' => $this->label(), '@plugin' => $this->plugin()), RulesLog::INFO, $this); + } + $state->block($this); + $copy = clone $state; + parent::evaluate($copy); + $state->unblock($this); + } + + public function access() { + foreach ($this->events as $event_name) { + $event_info = rules_get_event_info($event_name); + if (!empty($event_info['access callback']) && !call_user_func($event_info['access callback'], 'event', $event_info['name'])) { + return FALSE; + } + } + return parent::access(); + } + + public function dependencies() { + $modules = array_flip(parent::dependencies()); + foreach ($this->events as $event_name) { + $event_info = rules_get_event_info($event_name); + if (isset($event_info['module'])) { + $modules[$event_info['module']] = TRUE; + } + } + return array_keys($modules); + } + + public function providesVariables() { + return array(); + } + + public function parameterInfo($optional = FALSE) { + // If executed directly, all variables as defined by the event need to + // be passed. + return rules_filter_array($this->availableVariables(), 'handler', FALSE); + } + + public function availableVariables() { + if (!isset($this->availableVariables)) { + if (isset($this->parent)) { + // Return the event variables provided by the event set, once cached. + $this->availableVariables = $this->parent->stateVariables(); + } + else { + // The intersection of the variables provided by the events are + // available. + foreach ($this->events as $event_name) { + $handler = rules_get_event_handler($event_name, $this->getEventSettings($event_name)); + + if (isset($this->availableVariables)) { + $event_vars = $handler->availableVariables(); + // Merge variable info by intersecting the variable-info keys also, + // so we have only metadata available that is valid for all of the + // provided variables. + foreach (array_intersect_key($this->availableVariables, $event_vars) as $name => $variable_info) { + $this->availableVariables[$name] = array_intersect_key($variable_info, $event_vars[$name]); + } + } + else { + $this->availableVariables = $handler->availableVariables(); + } + } + $this->availableVariables = isset($this->availableVariables) ? RulesState::defaultVariables() + $this->availableVariables : RulesState::defaultVariables(); + } + } + return $this->availableVariables; + } + + public function __sleep() { + return parent::__sleep() + drupal_map_assoc(array('events', 'eventSettings')); + } + + protected function exportChildren($key = 'ON') { + foreach ($this->events as $event_name) { + $export[$key][$event_name] = (array) $this->getEventSettings($event_name); + } + return $export + parent::exportChildren(); + } + + protected function importChildren($export, $key = 'ON') { + // Detect and support old-style exports: a numerically indexed array of + // event names. + if (is_string(reset($export[$key])) && is_numeric(key($export[$key]))) { + $this->events = $export[$key]; + } + else { + $this->events = array_keys($export[$key]); + $this->eventSettings = array_filter($export[$key]); + } + parent::importChildren($export); + } + + /** + * Overrides optimize(). + */ + public function optimize() { + parent::optimize(); + // No need to keep event settings for evaluation. + $this->eventSettings = array(); + } +} + +/** + * A logical AND. + */ +class RulesAnd extends RulesConditionContainer { + + protected $itemName = 'and'; + + public function evaluate(RulesState $state) { + foreach ($this->children as $condition) { + if (!$condition->evaluate($state)) { + rules_log('AND evaluated to FALSE.'); + return $this->negate; + } + } + rules_log('AND evaluated to TRUE.'); + return !$this->negate; + } + + public function label() { + return !empty($this->label) ? $this->label : ($this->negate ? t('NOT AND') : t('AND')); + } +} + +/** + * A logical OR. + */ +class RulesOr extends RulesConditionContainer { + + protected $itemName = 'or'; + + public function evaluate(RulesState $state) { + foreach ($this->children as $condition) { + if ($condition->evaluate($state)) { + rules_log('OR evaluated to TRUE.'); + return !$this->negate; + } + } + rules_log('OR evaluated to FALSE.'); + return $this->negate; + } + + public function label() { + return !empty($this->label) ? $this->label : ($this->negate ? t('NOT OR') : t('OR')); + } + + /** + * Overridden to exclude all variable assertions as in an OR we cannot assert + * the children are successfully evaluated. + */ + protected function stateVariables($element = NULL) { + $vars = $this->availableVariables(); + if (isset($element)) { + // Add in variables provided by siblings executed before the element. + foreach ($this->children as $child) { + if ($child === $element) { + break; + } + $vars += $child->providesVariables(); + } + } + return $vars; + } +} + +/** + * A loop element. + */ +class RulesLoop extends RulesActionContainer { + + protected $itemName = 'loop'; + protected $listItemInfo; + + public function __construct($settings = array(), $variables = NULL) { + $this->setUp(); + $this->settings = (array) $settings + array( + 'item:var' => 'list_item', + 'item:label' => t('Current list item'), + ); + if (!empty($variables)) { + $this->info['variables'] = $variables; + } + } + + public function pluginParameterInfo() { + $info['list'] = array( + 'type' => 'list', + 'restriction' => 'selector', + 'label' => t('List'), + 'description' => t('The list to loop over. The loop will step through each item in the list, allowing further actions on them. See the online handbook for more information on how to use loops.', + array('@url' => rules_external_help('loops'))), + ); + return $info; + } + + public function integrityCheck() { + parent::integrityCheck(); + $this->checkVarName($this->settings['item:var']); + } + + public function listItemInfo() { + if (!isset($this->listItemInfo)) { + if ($info = $this->getArgumentInfo('list')) { + // Pass through the variable info keys like property info. + $this->listItemInfo = array_intersect_key($info, array_flip(array('type', 'property info', 'bundle'))); + $this->listItemInfo['type'] = isset($info['type']) ? entity_property_list_extract_type($info['type']) : 'unknown'; + } + else { + $this->listItemInfo = array('type' => 'unknown'); + } + $this->listItemInfo['label'] = $this->settings['item:label']; + } + return $this->listItemInfo; + } + + public function evaluate(RulesState $state) { + try { + $param_info = $this->pluginParameterInfo(); + $list = $this->getArgument('list', $param_info['list'], $state); + $item_var_info = $this->listItemInfo(); + $item_var_name = $this->settings['item:var']; + + if (isset($this->settings['list:select'])) { + rules_log('Looping over the list items of %selector', array('%selector' => $this->settings['list:select']), RulesLog::INFO, $this); + } + + // Loop over the list and evaluate the children for each list item. + foreach ($list as $key => $item) { + // Use a separate state so variables are available in the loop only. + $state2 = clone $state; + $state2->addVariable($item_var_name, $list[$key], $item_var_info); + parent::evaluate($state2); + + // Update variables from parent scope. + foreach ($state->variables as $var_key => &$var_value) { + if (array_key_exists($var_key, $state2->variables)) { + $var_value = $state2->variables[$var_key]; + } + } + } + } + catch (RulesEvaluationException $e) { + rules_log($e->msg, $e->args, $e->severity); + rules_log('Unable to evaluate %name.', array('%name' => $this->getPluginName()), RulesLog::WARN, $this); + } + } + + protected function stateVariables($element = NULL) { + return array($this->settings['item:var'] => $this->listItemInfo()) + parent::stateVariables($element); + } + + public function label() { + return !empty($this->label) ? $this->label : t('Loop'); + } + + protected function exportChildren($key = 'DO') { + return parent::exportChildren($key); + } + + protected function importChildren($export, $key = 'DO') { + parent::importChildren($export, $key); + } + + protected function exportSettings() { + $export = parent::exportSettings(); + $export['ITEM'][$this->settings['item:var']] = $this->settings['item:label']; + return $export; + } + + protected function importSettings($export) { + parent::importSettings($export); + if (isset($export['ITEM'])) { + $this->settings['item:var'] = rules_array_key($export['ITEM']); + $this->settings['item:label'] = reset($export['ITEM']); + } + } +} + +/** + * An action set component. + */ +class RulesActionSet extends RulesActionContainer { + + protected $itemName = 'action set'; + +} + +/** + * A set of rules to execute upon defined variables. + */ +class RulesRuleSet extends RulesActionContainer { + + protected $itemName = 'rule set'; + + /** + * @return RulesRuleSet + */ + public function rule($rule) { + return $this->action($rule); + } + + protected function exportChildren($key = 'RULES') { + return parent::exportChildren($key); + } + + protected function importChildren($export, $key = 'RULES') { + parent::importChildren($export, $key); + } +} + +/** + * This class is used for caching the rules to be evaluated per event. + */ +class RulesEventSet extends RulesRuleSet { + + protected $itemName = 'event set'; + // Event sets may recurse as we block recursions on rule-level. + public $recursion = TRUE; + + public function __construct($info = array()) { + $this->setup(); + $this->info = $info; + } + + public function executeByArgs($args = array()) { + rules_log('Reacting on event %label.', array('%label' => $this->info['label']), RulesLog::INFO, NULL, TRUE); + $state = $this->setUpState($args); + module_invoke_all('rules_config_execute', $this); + $this->evaluate($state); + $state->cleanUp($this); + rules_log('Finished reacting on event %label.', array('%label' => $this->info['label']), RulesLog::INFO, NULL, FALSE); + } + + /** + * Cache event-sets per event to allow efficient usage via rules_invoke_event(). + * + * @see rules_get_cache() + * @see rules_invoke_event() + */ + public static function rebuildEventCache() { + // Set up the per-event cache. + $events = rules_fetch_data('event_info'); + $sets = array(); + // Add all rules associated with this event to an EventSet for caching. + $rules = rules_config_load_multiple(FALSE, array('plugin' => 'reaction rule', 'active' => TRUE)); + + foreach ($rules as $name => $rule) { + foreach ($rule->events() as $event_name) { + $event_base_name = rules_get_event_base_name($event_name); + // Skip not defined events. + if (empty($events[$event_base_name])) { + continue; + } + // Create an event set if not yet done. + if (!isset($sets[$event_name])) { + $handler = rules_get_event_handler($event_name, $rule->getEventSettings($event_name)); + + // Start the event dispatcher for this event, if any. + if ($handler instanceof RulesEventDispatcherInterface && !$handler->isWatching()) { + $handler->startWatching(); + } + + // Update the event info with the variables available based on the + // event settings. + $event_info = $events[$event_base_name]; + $event_info['variables'] = $handler->availableVariables(); + $sets[$event_name] = new RulesEventSet($event_info); + $sets[$event_name]->name = $event_name; + } + + // If a rule is marked as dirty, check if this still applies. + if ($rule->dirty) { + rules_config_update_dirty_flag($rule); + } + if (!$rule->dirty) { + // Clone the rule to avoid modules getting the changed version from + // the static cache. + $sets[$event_name]->rule(clone $rule); + } + } + } + + // Create cache items for all created sets. + foreach ($sets as $event_name => $set) { + $set->sortChildren(); + $set->optimize(); + // Allow modules to alter the cached event set. + drupal_alter('rules_event_set', $event_name, $set); + rules_set_cache('event_' . $event_name, $set); + } + // Cache a whitelist of configured events so we can use it to speed up later + // calls. See rules_invoke_event(). + variable_set('rules_event_whitelist', array_flip(array_keys($sets))); + } + + protected function stateVariables($element = NULL) { + return $this->availableVariables(); + } + + /** + * Do not save since this class is for caching purposes only. + * + * @see RulesPlugin::save() + */ + public function save($name = NULL, $module = 'rules') { + return FALSE; + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/includes/rules.processor.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/includes/rules.processor.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,364 @@ +setting = $setting; + $this->processor = $processor; + } + + /** + * Return $this or skip this processor by returning the next processor. + */ + protected function getPreparedValue() { + return isset($this->setting) && array_filter($this->setting) ? $this : $this->processor; + } + + /** + * Returns whether the current user has permission to edit this chain of data + * processors. + */ + public function editAccess() { + return $this->access() && (!isset($this->processor) || $this->processor->editAccess()); + } + + + /** + * Prepares the processor for parameters. + * + * It turns the settings into a suiting processor object, which gets invoked + * on evaluation time. + * + * @param $setting + * The processor settings which are to be prepared. + * @param $param_info + * The info about the parameter to prepare the processor for. + * @param $var_info + * An array of info about the available variables. + */ + public static function prepareSetting(&$setting, $param_info, $var_info = array()) { + $processor = NULL; + foreach (self::processors($param_info, FALSE) as $name => $info) { + if (!empty($setting[$name])) { + $object = new $info['class']($setting[$name], $param_info, $var_info, $processor); + $processor = $object->getPreparedValue(); + } + } + $setting = $processor; + } + + /** + * Attaches the form of applicable data processors. + */ + public static function attachForm(&$form, $settings, $param_info, $var_info, $access_check = TRUE) { + // If $settings is already prepared get the settings from the processors. + if ($settings instanceof RulesDataProcessor) { + $settings = $settings->getChainSettings(); + } + foreach (self::processors($param_info, $access_check) as $name => $info) { + $settings += array($name => array()); + $form[$name] = call_user_func(array($info['class'], 'form'), $settings[$name], $var_info); + $form[$name]['#weight'] = $info['weight']; + } + } + + /** + * Returns defined data processors applicable for the given parameter. + * Optionally also access to the processors is checked. + * + * @param $param_info + * If given, only processors valid for this parameter are returned. + */ + public static function processors($param_info = NULL, $access_check = TRUE, $hook = 'data_processor_info') { + static $items = array(); + + if (!isset($items[$hook]['all'])) { + $items[$hook]['all'] = rules_fetch_data($hook); + uasort($items[$hook]['all'], array(__CLASS__, '_item_sort')); + } + // Data processing isn't supported for multiple types. + if (isset($param_info) && is_array($param_info['type'])) { + return array(); + } + // Filter the items by type. + if (isset($param_info['type']) && !isset($items[$hook][$param_info['type']])) { + $items[$hook][$param_info['type']] = array(); + foreach ($items[$hook]['all'] as $name => $info) { + // Check whether the parameter type matches the supported types. + $info += array('type' => 'text'); + if (RulesData::typesMatch($param_info, $info, FALSE)) { + $items[$hook][$param_info['type']][$name] = $info; + } + } + } + // Apply the access check. + $return = isset($param_info['type']) ? $items[$hook][$param_info['type']] : $items[$hook]['all']; + if ($access_check) { + foreach ($return as $base => $info) { + if (!call_user_func(array($info['class'], 'access'))) { + unset($return[$base]); + } + } + } + return $return; + } + + public static function _item_sort($a, $b) { + return $a['weight'] < $b['weight'] ? -1 : ($a['weight'] > $b['weight'] ? 1 : 0); + } + + /** + * Gets the settings array for this and all contained chained processors. + */ + public function getChainSettings() { + foreach ($this->unchain() as $name => $processor) { + $settings[$name] = $processor->getSetting(); + } + return isset($settings) ? $settings : array(); + } + + /** + * Returns an array of modules which we depend on. + */ + public function dependencies() { + $used_processor_info = array_intersect_key($this->processors(), $this->unchain()); + $modules = array(); + foreach ($used_processor_info as $name => $info) { + $modules[] = $info['module']; + } + return array_filter($modules); + } + + /** + * @return + * An array of processors keyed by processor name. + */ + protected function unchain() { + $processor = $this; + while ($processor instanceof RulesDataProcessor) { + $processors[get_class($processor)] = $processor; + $processor = $processor->processor; + } + // Note: Don't use the static context to call processors() here as we need a + // late binding to invoke the input evaluators version, if needed. + $return = array(); + foreach ($this->processors() as $name => $info) { + if (isset($processors[$info['class']])) { + $return[$name] = $processors[$info['class']]; + } + } + return $return; + } + + /** + * Gets the settings of this processor. + */ + public function getSetting() { + return $this->setting; + } + + /** + * Processes the value. If $this->processor is set, invoke this processor + * first so chaining multiple processors is working. + * + * @param $value + * The value to process. + * @param $info + * Info about the parameter for which we process the value. + * @param $state RulesState + * The rules evaluation state. + * @param $element RulesPlugin + * The element for which we process the value. + * @return + * The processed value. + */ + abstract public function process($value, $info, RulesState $state, RulesPlugin $element); + + /** + * Return whether the current user has permission to use the processor. + */ + public static function access() { + return TRUE; + } + + /** + * Defines the processor form element. + * + * @param $settings + * The settings of the processor. + * @param $var_info + * An array of info about the available variables. + * + * @return + * A form element structure. + */ + protected static function form($settings, $var_info) { + return array(); + } +} + + +/** + * A base processor for use as input evaluators. Input evaluators are not listed + * in hook_rules_data_processor_info(). Instead they use + * hook_rules_evaluator_info() and get attached to input forms. + */ +abstract class RulesDataInputEvaluator extends RulesDataProcessor { + + /** + * Overridden to invoke prepare(). + */ + protected function __construct($setting, $param_info, $var_info = array(), $processor = NULL) { + $this->setting = TRUE; + $this->processor = $processor; + $this->prepare($setting, $var_info, $param_info); + } + + /** + * Overridden to generate evaluator $options and invoke evaluate(). + */ + public function process($value, $info, RulesState $state, RulesPlugin $element, $options = NULL) { + $options = isset($options) ? $options : $this->getEvaluatorOptions($info, $state, $element); + $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element, $options) : $value; + return $this->evaluate($value, $options, $state); + } + + /** + * Generates the evaluator $options. + */ + protected function getEvaluatorOptions($info, $state, $element) { + $cache = rules_get_cache(); + $languages = language_list(); + $info += array( + 'cleaning callback' => isset($cache['data info'][$info['type']]['cleaning callback']) ? $cache['data info'][$info['type']]['cleaning callback'] : FALSE, + 'sanitize' => FALSE, + ); + $options = array_filter(array( + 'language' => $info['#langcode'] != LANGUAGE_NONE && isset($languages[$info['#langcode']]) ? $languages[$info['#langcode']] : NULL, + 'callback' => $info['cleaning callback'], + 'sanitize' => $info['sanitize'], + )); + return $options; + } + + /** + * Overriden to prepare input evaluator processors. The setting is expected + * to be the input value to be evaluated later on and is replaced by the + * suiting processor. + */ + public static function prepareSetting(&$setting, $param_info, $var_info = array()) { + $processor = NULL; + foreach (self::evaluators($param_info, FALSE) as $name => $info) { + $object = new $info['class']($setting, $param_info, $var_info, $processor); + $processor = $object->getPreparedValue(); + } + $setting = $processor; + } + + protected function getPreparedValue() { + return isset($this->setting) ? $this : $this->processor; + } + + /** + * Overriden to just attach the help() of evaluators. + */ + public static function attachForm(&$form, $settings, $param_info, $var_info, $access_check = TRUE) { + foreach (self::evaluators($param_info, $access_check) as $name => $info) { + $form['help'][$name] = call_user_func(array($info['class'], 'help'), $var_info, $param_info); + $form['help'][$name]['#weight'] = $info['weight']; + } + } + + /** + * Returns all input evaluators that can be applied to the parameters needed + * type. + */ + public static function evaluators($param_info = NULL, $access_check = TRUE) { + return parent::processors($param_info, $access_check, 'evaluator_info'); + } + + /** + * Overridden to default to our hook, thus being equivalent to + * self::evaluators(). + */ + public static function processors($param_info = NULL, $access_check = TRUE, $hook = 'evaluator_info') { + return parent::processors($param_info, $access_check, $hook); + } + + /** + * Prepares the evalution, e.g. to determine whether the input evaluator has + * been used. If this evaluator should be skipped just unset $this->setting. + * + * @param $text + * The text to evaluate later on. + * @param $variables + * An array of info about available variables. + * @param $param_info + * (optional) An array of information about the handled parameter value. + * For backward compatibility, this parameter is not required. + */ + abstract public function prepare($text, $variables); + + /** + * Apply the input evaluator. + * + * @param $text + * The text to evaluate. + * @param $options + * A keyed array of settings and flags to control the processing. + * Supported options are: + * - language: A language object to be used when processing. + * - callback: A callback function that will be used to post-process + * replacements that might be incorporated, so they can be cleaned in a + * certain way. + * - sanitize: A boolean flag indicating whether incorporated replacements + * should be sanitized. + * @param RulesState + * The rules evaluation state. + * + * @return + * The evaluated text. + */ + abstract public function evaluate($text, $options, RulesState $state); + + /** + * Provide some usage help for the evaluator. + * + * @param $variables + * An array of info about available variables. + * @param $param_info + * (optional) An array of information about the handled parameter value. + * For backward compatibility, this parameter is not required. + * + * @return + * A renderable array. + */ + public static function help($variables) { + return array(); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/includes/rules.state.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/includes/rules.state.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,770 @@ +save = new ArrayObject(); + $this->addVariable('site', FALSE, self::defaultVariables('site')); + } + + /** + * Adds the given variable to the given execution state. + */ + public function addVariable($name, $data, $info) { + $this->info[$name] = $info + array( + 'skip save' => FALSE, + 'type' => 'unknown', + 'handler' => FALSE, + ); + if (empty($this->info[$name]['handler'])) { + $this->variables[$name] = rules_wrap_data($data, $this->info[$name]); + } + } + + /** + * Runs post-evaluation tasks, such as saving variables. + */ + public function cleanUp() { + // Make changes permanent. + foreach ($this->save->getArrayCopy() as $selector => $wrapper) { + $this->saveNow($selector); + } + unset($this->currentArguments); + } + + /** + * Block a rules configuration from execution. + */ + public function block($rules_config) { + if (empty($rules_config->recursion) && $rules_config->id) { + self::$blocked[$rules_config->id] = TRUE; + } + } + + /** + * Unblock a rules configuration from execution. + */ + public function unblock($rules_config) { + if (empty($rules_config->recursion) && $rules_config->id) { + unset(self::$blocked[$rules_config->id]); + } + } + + /** + * Returns whether a rules configuration should be blocked from execution. + */ + public function isBlocked($rule_config) { + return !empty($rule_config->id) && isset(self::$blocked[$rule_config->id]); + } + + /** + * Get the info about the state variables or a single variable. + */ + public function varInfo($name = NULL) { + if (isset($name)) { + return isset($this->info[$name]) ? $this->info[$name] : FALSE; + } + return $this->info; + } + + /** + * Returns whether the given wrapper is savable. + */ + public function isSavable($wrapper) { + return ($wrapper instanceof EntityDrupalWrapper && entity_type_supports($wrapper->type(), 'save')) || $wrapper instanceof RulesDataWrapperSavableInterface; + } + + /** + * Returns whether the variable with the given name is an entity. + */ + public function isEntity($name) { + $entity_info = entity_get_info(); + return isset($this->info[$name]['type']) && isset($entity_info[$this->info[$name]['type']]); + } + + /** + * Gets a variable. + * + * If necessary, the specified handler is invoked to fetch the variable. + * + * @param $name + * The name of the variable to return. + * + * @return + * The variable or a EntityMetadataWrapper containing the variable. + * + * @throws RulesEvaluationException + * Throws a RulesEvaluationException in case we have info about the + * requested variable, but it is not defined. + */ + public function &get($name) { + if (!array_key_exists($name, $this->variables)) { + // If there is handler to load the variable, do it now. + if (!empty($this->info[$name]['handler'])) { + $data = call_user_func($this->info[$name]['handler'], rules_unwrap_data($this->variables), $name, $this->info[$name]); + $this->variables[$name] = rules_wrap_data($data, $this->info[$name]); + $this->info[$name]['handler'] = FALSE; + if (!isset($data)) { + throw new RulesEvaluationException('Unable to load variable %name, aborting.', array('%name' => $name), NULL, RulesLog::INFO); + } + } + else { + throw new RulesEvaluationException('Unable to get variable %name, it is not defined.', array('%name' => $name), NULL, RulesLog::ERROR); + } + } + return $this->variables[$name]; + } + + /** + * Apply permanent changes provided the wrapper's data type is savable. + * + * @param $selector + * The data selector of the wrapper to save or just a variable name. + * @param $immediate + * Pass FALSE to postpone saving to later on. Else it's immediately saved. + */ + public function saveChanges($selector, $wrapper, $immediate = FALSE) { + $info = $wrapper->info(); + if (empty($info['skip save']) && $this->isSavable($wrapper)) { + $this->save($selector, $wrapper, $immediate); + } + // No entity, so try saving the parent. + elseif (empty($info['skip save']) && isset($info['parent']) && !($wrapper instanceof EntityDrupalWrapper)) { + // Cut of the last part of the selector. + $selector = implode(':', explode(':', $selector, -1)); + $this->saveChanges($selector, $info['parent'], $immediate); + } + return $this; + } + + /** + * Remembers to save the wrapper on cleanup or does it now. + */ + protected function save($selector, EntityMetadataWrapper $wrapper, $immediate) { + // Convert variable names and selectors to both use underscores. + $selector = strtr($selector, '-', '_'); + if (isset($this->save[$selector])) { + if ($this->save[$selector][0]->getIdentifier() == $wrapper->getIdentifier()) { + // The entity is already remembered. So do a combined save. + $this->save[$selector][1] += self::$blocked; + } + else { + // The wrapper is already in there, but wraps another entity. So first + // save the old one, then care about the new one. + $this->saveNow($selector); + } + } + if (!isset($this->save[$selector])) { + // In case of immediate saving don't clone the wrapper, so saving a new + // entity immediately makes the identifier available afterwards. + $this->save[$selector] = array($immediate ? $wrapper : clone $wrapper, self::$blocked); + } + if ($immediate) { + $this->saveNow($selector); + } + } + + /** + * Saves the wrapper for the given selector. + */ + protected function saveNow($selector) { + // Add the set of blocked elements for the recursion prevention. + $previously_blocked = self::$blocked; + self::$blocked += $this->save[$selector][1]; + + // Actually save! + $wrapper = $this->save[$selector][0]; + $entity = $wrapper->value(); + // When operating in hook_entity_insert() $entity->is_new might be still + // set. In that case remove the flag to avoid causing another insert instead + // of an update. + if (!empty($entity->is_new) && $wrapper->getIdentifier()) { + $entity->is_new = FALSE; + } + rules_log('Saved %selector of type %type.', array('%selector' => $selector, '%type' => $wrapper->type())); + $wrapper->save(); + + // Restore the state's set of blocked elements. + self::$blocked = $previously_blocked; + unset($this->save[$selector]); + } + + /** + * Merges the info about to be saved variables form the given state into the + * existing state. Therefor we can aggregate saves from invoked components. + * Merged in saves are removed from the given state, but not mergable saves + * remain there. + * + * @param $state + * The state for which to merge the to be saved variables in. + * @param $component + * The component which has been invoked, thus needs to be blocked for the + * merged in saves. + * @param $settings + * The settings of the element that invoked the component. Contains + * information about variable/selector mappings between the states. + */ + public function mergeSaveVariables(RulesState $state, RulesPlugin $component, $settings) { + // For any saves that we take over, also block the component. + $this->block($component); + + foreach ($state->save->getArrayCopy() as $selector => $data) { + $parts = explode(':', $selector, 2); + // Adapt the selector to fit for the parent state and move the wrapper. + if (isset($settings[$parts[0] . ':select'])) { + $parts[0] = $settings[$parts[0] . ':select']; + $this->save(implode(':', $parts), $data[0], FALSE); + unset($state->save[$selector]); + } + } + $this->unblock($component); + } + + /** + * Returns an entity metadata wrapper as specified in the selector. + * + * @param $selector + * The selector string, e.g. "node:author:mail". + * @param $langcode + * (optional) The language code used to get the argument value if the + * argument value should be translated. Defaults to LANGUAGE_NONE. + * + * @return EntityMetadataWrapper + * The wrapper for the given selector. + * + * @throws RulesEvaluationException + * Throws a RulesEvaluationException in case the selector cannot be applied. + */ + public function applyDataSelector($selector, $langcode = LANGUAGE_NONE) { + $parts = explode(':', str_replace('-', '_', $selector), 2); + $wrapper = $this->get($parts[0]); + if (count($parts) == 1) { + return $wrapper; + } + elseif (!$wrapper instanceof EntityMetadataWrapper) { + throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not wrapped correctly.', array('%selector' => $selector)); + } + try { + foreach (explode(':', $parts[1]) as $name) { + if ($wrapper instanceof EntityListWrapper || $wrapper instanceof EntityStructureWrapper) { + // Make sure we are usign the right language. Wrappers might be cached + // and have previous langcodes set, so always set the right language. + if ($wrapper instanceof EntityStructureWrapper) { + $wrapper->language($langcode); + } + $wrapper = $wrapper->get($name); + } + else { + throw new RulesEvaluationException('Unable to apply data selector %selector. The specified variable is not a list or a structure: %wrapper.', array('%selector' => $selector, '%wrapper' => $wrapper)); + } + } + } + catch (EntityMetadataWrapperException $e) { + // In case of an exception, re-throw it. + throw new RulesEvaluationException('Unable to apply data selector %selector: %error', array('%selector' => $selector, '%error' => $e->getMessage())); + } + return $wrapper; + } + + /** + * Magic method. Only serialize variables and their info. + * Additionally we remember currently blocked configs, so we can restore them + * upon deserialization using restoreBlocks(). + */ + public function __sleep () { + $this->currentlyBlocked = self::$blocked; + return array('info', 'variables', 'currentlyBlocked'); + } + + public function __wakeup() { + $this->save = new ArrayObject(); + } + + /** + * Restore the before serialization blocked configurations. + * + * Warning: This overwrites any possible currently blocked configs. Thus + * do not invoke this method, if there might be evaluations active. + */ + public function restoreBlocks() { + self::$blocked = $this->currentlyBlocked; + } + + /** + * Defines always available variables. + */ + public static function defaultVariables($key = NULL) { + // Add a variable for accessing site-wide data properties. + $vars['site'] = array( + 'type' => 'site', + 'label' => t('Site information'), + 'description' => t("Site-wide settings and other global information."), + // Add the property info via a callback making use of the cached info. + 'property info alter' => array('RulesData', 'addSiteMetadata'), + 'property info' => array(), + 'optional' => TRUE, + ); + return isset($key) ? $vars[$key] : $vars; + } +} + +/** + * A class holding static methods related to data. + */ +class RulesData { + + /** + * Returns whether the type match. They match if type1 is compatible to type2. + * + * @param $var_info + * The name of the type to check for whether it is compatible to type2. + * @param $param_info + * The type expression to check for. + * @param $ancestors + * Whether sub-type relationships for checking type compatibility should be + * taken into account. Defaults to TRUE. + * + * @return + * Whether the types match. + */ + public static function typesMatch($var_info, $param_info, $ancestors = TRUE) { + $var_type = $var_info['type']; + $param_type = $param_info['type']; + + if ($param_type == '*' || $param_type == 'unknown') { + return TRUE; + } + + if ($var_type == $param_type) { + // Make sure the bundle matches, if specified by the parameter. + return !isset($param_info['bundles']) || isset($var_info['bundle']) && in_array($var_info['bundle'], $param_info['bundles']); + } + + // Parameters may specify multiple types using an array. + $valid_types = is_array($param_type) ? $param_type : array($param_type); + if (in_array($var_type, $valid_types)) { + return TRUE; + } + + // Check for sub-type relationships. + if ($ancestors && !isset($param_info['bundles'])) { + $cache = &rules_get_cache(); + self::typeCalcAncestors($cache, $var_type); + // If one of the types is an ancestor return TRUE. + return (bool)array_intersect_key($cache['data_info'][$var_type]['ancestors'], array_flip($valid_types)); + } + return FALSE; + } + + protected static function typeCalcAncestors(&$cache, $type) { + if (!isset($cache['data_info'][$type]['ancestors'])) { + $cache['data_info'][$type]['ancestors'] = array(); + if (isset($cache['data_info'][$type]['parent']) && $parent = $cache['data_info'][$type]['parent']) { + $cache['data_info'][$type]['ancestors'][$parent] = TRUE; + self::typeCalcAncestors($cache, $parent); + // Add all parent ancestors to our own ancestors. + $cache['data_info'][$type]['ancestors'] += $cache['data_info'][$parent]['ancestors']; + } + // For special lists like list add in "list" as valid parent. + if (entity_property_list_extract_type($type)) { + $cache['data_info'][$type]['ancestors']['list'] = TRUE; + } + } + } + + /** + * Returns matching data variables or properties for the given info and the to + * be configured parameter. + * + * @param $source + * Either an array of info about available variables or a entity metadata + * wrapper. + * @param $param_info + * The information array about the to be configured parameter. + * @param $prefix + * An optional prefix for the data selectors. + * @param $recursions + * The number of recursions used to go down the tree. Defaults to 2. + * @param $suggestions + * Whether possibilities to recurse are suggested as soon as the deepest + * level of recursions is reached. Defaults to TRUE. + * + * @return + * An array of info about matching variables or properties that match, keyed + * with the data selector. + */ + public static function matchingDataSelector($source, $param_info, $prefix = '', $recursions = 2, $suggestions = TRUE) { + // If an array of info is given, get entity metadata wrappers first. + $data = NULL; + if (is_array($source)) { + foreach ($source as $name => $info) { + $source[$name] = rules_wrap_data($data, $info, TRUE); + } + } + + $matches = array(); + foreach ($source as $name => $wrapper) { + $info = $wrapper->info(); + $name = str_replace('_', '-', $name); + + if (self::typesMatch($info, $param_info)) { + $matches[$prefix . $name] = $info; + if (!is_array($source) && $source instanceof EntityListWrapper) { + // Add some more possible list items. + for ($i = 1; $i < 4; $i++) { + $matches[$prefix . $i] = $info; + } + } + } + // Recurse later on to get an improved ordering of the results. + if ($wrapper instanceof EntityStructureWrapper || $wrapper instanceof EntityListWrapper) { + $recurse[$prefix . $name] = $wrapper; + if ($recursions > 0) { + $matches += self::matchingDataSelector($wrapper, $param_info, $prefix . $name . ':', $recursions - 1, $suggestions); + } + elseif ($suggestions) { + // We may not recurse any more, but indicate the possibility to recurse. + $matches[$prefix . $name . ':'] = $wrapper->info(); + if (!is_array($source) && $source instanceof EntityListWrapper) { + // Add some more possible list items. + for ($i = 1; $i < 4; $i++) { + $matches[$prefix . $i . ':'] = $wrapper->info(); + } + } + } + } + } + return $matches; + } + + /** + * Adds asserted metadata to the variable info. In case there are already + * assertions for a variable, the assertions are merged such that both apply. + * + * @see RulesData::applyMetadataAssertions() + */ + public static function addMetadataAssertions($var_info, $assertions) { + foreach ($assertions as $selector => $assertion) { + // Convert the selector back to underscores, such it matches the varname. + $selector = str_replace('-', '_', $selector); + + $parts = explode(':', $selector); + if (isset($var_info[$parts[0]])) { + // Apply the selector to determine the right target array. We build an + // array like + // $var_info['rules assertion']['property1']['property2']['#info'] = .. + $target = &$var_info[$parts[0]]['rules assertion']; + foreach (array_slice($parts, 1) as $part) { + $target = &$target[$part]; + } + + // In case the assertion is directly for a variable, we have to modify + // the variable info directly. In case the asserted property is nested + // the info-has to be altered by RulesData::applyMetadataAssertions() + // before the child-wrapper is created. + if (count($parts) == 1) { + // Support asserting a type in case of generic entity references only. + if (isset($assertion['type']) && $var_info[$parts[0]]['type'] == 'entity') { + if (entity_get_info($assertion['type'])) { + $var_info[$parts[0]]['type'] = $assertion['type']; + } + unset($assertion['type']); + } + // Add any single bundle directly to the variable info, so the + // variable fits as argument for parameters requiring the bundle. + if (isset($assertion['bundle']) && count($bundles = (array) $assertion['bundle']) == 1) { + $var_info[$parts[0]]['bundle'] = reset($bundles); + } + } + + // Add the assertions, but merge them with any previously added + // assertions if necessary. + $target['#info'] = isset($target['#info']) ? rules_update_array($target['#info'], $assertion) : $assertion; + + // Add in a callback that the entity metadata wrapper pick up for + // altering the property info, such that we can add in the assertions. + $var_info[$parts[0]] += array('property info alter' => array('RulesData', 'applyMetadataAssertions')); + + // In case there is a VARNAME_unchanged variable as it is used in update + // hooks, assume the assertions are valid for the unchanged variable + // too. + if (isset($var_info[$parts[0] . '_unchanged'])) { + $name = $parts[0] . '_unchanged'; + $var_info[$name]['rules assertion'] = $var_info[$parts[0]]['rules assertion']; + $var_info[$name]['property info alter'] = array('RulesData', 'applyMetadataAssertions'); + + if (isset($var_info[$parts[0]]['bundle']) && !isset($var_info[$name]['bundle'])) { + $var_info[$name]['bundle'] = $var_info[$parts[0]]['bundle']; + } + } + } + } + return $var_info; + } + + /** + * Property info alter callback for the entity metadata wrapper for applying + * the rules metadata assertions. + * + * @see RulesData::addMetadataAssertions() + */ + public static function applyMetadataAssertions(EntityMetadataWrapper $wrapper, $property_info) { + $info = $wrapper->info(); + + if (!empty($info['rules assertion'])) { + $assertion = $info['rules assertion']; + + // In case there are list-wrappers pass through the assertions of the item + // but make sure we only apply the assertions for the list items for + // which the conditions are executed. + if (isset($info['parent']) && $info['parent'] instanceof EntityListWrapper) { + $assertion = isset($assertion[$info['name']]) ? $assertion[$info['name']] : array(); + } + + // Support specifying multiple bundles, whereas the added properties are + // the intersection of the bundle properties. + if (isset($assertion['#info']['bundle'])) { + $bundles = (array) $assertion['#info']['bundle']; + foreach ($bundles as $bundle) { + $properties[] = isset($property_info['bundles'][$bundle]['properties']) ? $property_info['bundles'][$bundle]['properties'] : array(); + } + // Add the intersection. + $property_info['properties'] += count($properties) > 1 ? call_user_func_array('array_intersect_key', $properties) : reset($properties); + } + // Support adding directly asserted property info. + if (isset($assertion['#info']['property info'])) { + $property_info['properties'] += $assertion['#info']['property info']; + } + + // Pass through any rules assertion of properties to their info, so any + // derived wrappers apply it. + foreach (element_children($assertion) as $key) { + $property_info['properties'][$key]['rules assertion'] = $assertion[$key]; + $property_info['properties'][$key]['property info alter'] = array('RulesData', 'applyMetadataAssertions'); + + // Apply any 'type' and 'bundle' assertion directly to the propertyinfo. + if (isset($assertion[$key]['#info']['type'])) { + $type = $assertion[$key]['#info']['type']; + // Support asserting a type in case of generic entity references only. + if ($property_info['properties'][$key]['type'] == 'entity' && entity_get_info($type)) { + $property_info['properties'][$key]['type'] = $type; + } + } + if (isset($assertion[$key]['#info']['bundle'])) { + $bundle = (array) $assertion[$key]['#info']['bundle']; + // Add any single bundle directly to the variable info, so the + // property fits as argument for parameters requiring the bundle. + if (count($bundle) == 1) { + $property_info['properties'][$key]['bundle'] = reset($bundle); + } + } + } + } + return $property_info; + } + + /** + * Property info alter callback for the entity metadata wrapper to inject + * metadata for the 'site' variable. In contrast to doing this via + * hook_rules_data_info() this callback makes use of the already existing + * property info cache for site information of entity metadata. + * + * @see RulesPlugin::availableVariables() + */ + public static function addSiteMetadata(EntityMetadataWrapper $wrapper, $property_info) { + $site_info = entity_get_property_info('site'); + $property_info['properties'] += $site_info['properties']; + // Also invoke the usual callback for altering metadata, in case actions + // have specified further metadata. + return RulesData::applyMetadataAssertions($wrapper, $property_info); + } +} + +/** + * A wrapper class similar to the EntityDrupalWrapper, but for non-entities. + * + * This class is intended to serve as base for a custom wrapper classes of + * identifiable data types, which are non-entities. By extending this class only + * the extractIdentifier() and load() methods have to be defined. + * In order to make the data type savable implement the + * RulesDataWrapperSavableInterface. + * + * That way it is possible for non-entity data types to be work with Rules, i.e. + * one can implement a 'ui class' with a direct input form returning the + * identifier of the data. However, instead of that it is suggested to implement + * an entity type, such that the same is achieved via general API functions like + * entity_load(). + */ +abstract class RulesIdentifiableDataWrapper extends EntityStructureWrapper { + + /** + * Contains the id. + */ + protected $id = FALSE; + + /** + * Construct a new wrapper object. + * + * @param $type + * The type of the passed data. + * @param $data + * Optional. The data to wrap or its identifier. + * @param $info + * Optional. Used internally to pass info about properties down the tree. + */ + public function __construct($type, $data = NULL, $info = array()) { + parent::__construct($type, $data, $info); + $this->setData($data); + } + + /** + * Sets the data internally accepting both the data id and object. + */ + protected function setData($data) { + if (isset($data) && $data !== FALSE && !is_object($data)) { + $this->id = $data; + $this->data = FALSE; + } + elseif (is_object($data)) { + // We got the data object passed. + $this->data = $data; + $id = $this->extractIdentifier($data); + $this->id = isset($id) ? $id : FALSE; + } + } + + /** + * Returns the identifier of the wrapped data. + */ + public function getIdentifier() { + return $this->dataAvailable() && $this->value() ? $this->id : NULL; + } + + /** + * Overridden. + */ + public function value(array $options = array()) { + $this->setData(parent::value()); + if (!$this->data && !empty($this->id)) { + // Lazy load the data if necessary. + $this->data = $this->load($this->id); + if (!$this->data) { + throw new EntityMetadataWrapperException('Unable to load the ' . check_plain($this->type) . ' with the id ' . check_plain($this->id) . '.'); + } + } + return $this->data; + } + + /** + * Overridden to support setting the data by either the object or the id. + */ + public function set($value) { + if (!$this->validate($value)) { + throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.'); + } + // As custom wrapper classes can only appear for Rules variables, but not + // as properties we don't have to care about updating the parent. + $this->clear(); + $this->setData($value); + return $this; + } + + /** + * Overridden. + */ + public function clear() { + $this->id = NULL; + parent::clear(); + } + + /** + * Prepare for serializiation. + */ + public function __sleep() { + $vars = parent::__sleep(); + // Don't serialize the loaded data, except for the case the data is not + // saved yet. + if (!empty($this->id)) { + unset($vars['data']); + } + return $vars; + } + + public function __wakeup() { + if ($this->id !== FALSE) { + // Make sure data is set, so the data will be loaded when needed. + $this->data = FALSE; + } + } + + /** + * Extract the identifier of the given data object. + * + * @return + * The extracted identifier. + */ + abstract protected function extractIdentifier($data); + + /** + * Load a data object given an identifier. + * + * @return + * The loaded data object, or FALSE if loading failed. + */ + abstract protected function load($id); +} + +/** + * Interface that allows custom wrapper classes to declare that they are savable. + */ +interface RulesDataWrapperSavableInterface { + + /** + * Save the currently wrapped data. + */ + public function save(); +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/includes/rules.upgrade.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/includes/rules.upgrade.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,664 @@ + $export) { + // Rules have been already converted and exported, so show the export. + $form['export'][$key] = array( + '#type' => 'textarea', + '#title' => t('Export %name', array('%name' => $key)), + '#description' => t('For importing copy the content of the text area and paste it into the import page of the Rules admin UI. In case the export does not pass the integrity check during import, try using the save to database method instead and manually fix your configuration after conversion.'), + '#rows' => 10, + '#default_value' => $export, + ); + } + return $form; + } + + $form['help'] = array( + '#prefix' => '

', + '#suffix' => '

', + '#markup' => t('This form allows you to convert rules or rule sets from Rules 1.x to Rules 2.x.') . ' ' . + t('In order to convert a rule or rule set make sure you have all dependend modules installed and upgraded, i.e. modules which provide Rules integration that has been used in your rules or rule sets. In addition those modules may need to implement some Rules specific update hooks for the conversion to properly work.') . ' ' . + t('After conversion, the old rules and rule sets will stay in the database until you manually delete them. That way you can make sure the conversion has gone right before you delete the old rules and rule sets.') + ); + + $option_rules = $option_sets = array(); + if (!db_table_exists('rules_rules')) { + drupal_set_message('There are no Rules 1.x rules or rule sets left to convert.', 'error'); + } + else { + foreach (_rules_upgrade_fetch_all_rules() as $name => $rule) { + if (!empty($rule['#set']) && strpos($rule['#set'], 'event_') === 0) { + $option_rules[$name] = $name . ': ' . $rule['#label']; + } + } + $query = db_select('rules_sets', 'r')->fields('r'); + foreach ($query->execute() as $row) { + $set = unserialize($row->data); + $option_sets[$row->name] = $row->name . ': ' . $set['label']; + } + + $form['clear'] = array( + '#prefix' => '

', + '#suffix' => '

', + '#markup' => t('Once you have successfully converted your configuration, you can clean up your database and delete all Rules 1.x configurations.', array('!url' => url('admin/config/workflow/rules/upgrade/clear'))) + ); + } + + $form['rules'] = array( + '#type' => 'select', + '#title' => t('Rules'), + '#options' => $option_rules, + '#multiple' => TRUE, + ); + + $form['sets'] = array( + '#type' => 'select', + '#title' => t('Rule sets'), + '#options' => $option_sets, + '#multiple' => TRUE, + ); + $form['method'] = array( + '#type' => 'radios', + '#title' => t('Method'), + '#options' => array( + 'export' => t('Convert configuration and export it.'), + 'save' => t('Convert configuration and save it.'), + ), + '#default_value' => 'export', + ); + + $form['actions']['convert'] = array( + '#type' => 'submit', + '#value' => t('Convert'), + '#disabled' => !db_table_exists('rules_rules') + ); + return $form; +} + +/** + * Submit handler for the form. + */ +function rules_upgrade_form_submit($form, &$form_state) { + // Load all rules includes and install files so modules may put there upgrade + // information in both locations. + module_load_all_includes('rules.inc'); + module_load_all_includes('install'); + + $configs = array(); + + try { + foreach ($form_state['values']['rules'] as $name) { + drupal_set_message(t('Converting %plugin %name...', array('%plugin' => t('rule'), '%name' => $name))); + $configs[$name] = rules_upgrade_convert_rule($name, _rules_upgrade_fetch_item($name, 'rules_rules')); + } + foreach ($form_state['values']['sets'] as $name) { + drupal_set_message(t('Converting %plugin %name...', array('%plugin' => t('rule set'), '%name' => $name))); + $configs[$name] = rules_upgrade_convert_rule_set($name, _rules_upgrade_fetch_item($name, 'rules_sets')); + } + drupal_set_message(t('Completed.')); + + if ($form_state['values']['method'] == 'save') { + foreach ($configs as $config) { + $config->save(); + } + drupal_set_message(t('Converted configurations have been saved to the database and will appear in the Rules administration interface.')); + } + elseif ($form_state['values']['method'] == 'export') { + $export = array(); + foreach ($configs as $name => $config) { + $export[$name] = $config->export(); + } + $form_state['export'] = $export; + $form_state['rebuild'] = TRUE; + } + } + catch (RulesException $e) { + drupal_set_message($e->getMessage(), 'error'); + } +} + +/** + * Confirm form for deleting data. + */ +function rules_upgrade_confirm_clear_form($form, $form_state) { + $confirm_question = t('Are you sure you want to drop the Rules 1.x tables from the database?'); + $confirm_question_long = t('Are you sure you want to drop the Rules 1.x tables from the database? All Rules 1.x configurations will be deleted regardless whether they have been already converted.') . ' ' . t('This action cannot be undone.'); + return confirm_form($form, $confirm_question, 'admin/config/workflow/rules/upgrade', $confirm_question_long, t('Delete data'), t('Cancel')); +} + +function rules_upgrade_confirm_clear_form_submit($form, &$form_state) { + db_drop_table('rules_rules'); + db_drop_table('rules_sets'); + db_drop_table('rules_scheduler_d6'); + drupal_set_message(t('Rules 1.x configurations have been deleted.')); + $form_state['redirect'] = 'admin'; +} + +/** + * Fetches a single item (rule | rule set). + */ +function _rules_upgrade_fetch_item($name, $table) { + $query = db_select($table, 'r')->fields('r')->condition('name', $name); + $row = $query->execute()->fetchAssoc(); + return unserialize($row['data']); +} + +/** + * Fetches all rules. + */ +function _rules_upgrade_fetch_all_rules() { + $static = drupal_static(__FUNCTION__); + + if (!isset($static)) { + $query = db_select('rules_rules', 'r')->fields('r'); + $static['rules'] = array(); + foreach ($query->execute() as $row) { + $static['rules'][$row->name] = unserialize($row->data); + } + } + return $static['rules']; +} + +/** + * Converts a single reaction rule. + */ +function rules_upgrade_convert_rule($name, $cfg_old) { + $config = rules_upgrade_plugin_factory($cfg_old); + $config->name = $name; + + if ($config instanceof RulesReactionRule) { + rules_upgrade_convert_element($cfg_old, $config); + } + return $config; +} + +/** + * Converts a single rule set, including all of its rules. + */ +function rules_upgrade_convert_rule_set($name, $cfg_old) { + $config = rules_plugin_factory('rule set'); + $config->name = $name; + foreach (array('label', 'weight') as $key) { + if (isset($cfg_old[$key])) { + $config->$key = $cfg_old[$key]; + } + } + if (isset($cfg_old['arguments'])) { + $vars = &$config->componentVariables(); + foreach ($cfg_old['arguments'] as $var_name => $info) { + // Map data types as required. + if ($info['type'] == 'string') { + $info['type'] = 'text'; + } + $vars[$var_name] = $info; + } + } + + // Add in all rules of the set. + foreach(_rules_upgrade_fetch_all_rules() as $rule_name => $rule) { + if ($rule['#set'] == $name) { + drupal_set_message(' >> ' . t('Converting %plugin %name...', array('%plugin' => t('rule'), '%name' => $rule_name . ': ' . $rule['#label']))); + $new_rule = rules_upgrade_plugin_factory($rule); + rules_upgrade_convert_element($rule, $new_rule); + $new_rule->setParent($config); + } + } + return $config; +} + +/** + * Convert a single element. + * + * @param $element + * The element to convert. + * @param $target + * The converted element to write to. + */ +function rules_upgrade_convert_element(array $element, RulesPlugin $target) { + foreach (array('active', 'label', 'weight') as $key) { + if (isset($element['#' . $key])) { + $target->$key = $element['#' . $key]; + } + } + // Go through the parameters and take over its configuration if possible. + foreach ($target->pluginParameterInfo() as $name => $info) { + rules_upgrade_element_parameter_settings($element, $target, $name); + } + // @todo: Care about php input evaluator for non-text parameters. + + // Take care of variable names and labels. + foreach ($target->pluginProvidesVariables() as $name => $info) { + rules_upgrade_element_variable_settings($element, $target, $name); + } + + if ($target instanceof RulesConditionInterface && !empty($element['#negate'])) { + $target->negate(TRUE); + } + if ($target instanceof RulesReactionRule) { + // Cut of the 'event_' prefix. + $target->event(substr($element['#set'], 6)); + } + if ($element['#type'] == 'rule') { + if (!empty($element['#conditions'])) { + foreach (element_children($element['#conditions']) as $key) { + $child = rules_upgrade_plugin_factory($element['#conditions'][$key]); + rules_upgrade_convert_element($element['#conditions'][$key], $child); + $target->condition($child); + } + } + if (!empty($element['#actions'])) { + foreach (element_children($element['#actions']) as $key) { + $child = rules_upgrade_plugin_factory($element['#actions'][$key]); + rules_upgrade_convert_element($element['#actions'][$key], $child); + $target->action($child); + } + } + } + + // Invoke action/condition specific hooks and a general one. + if (($element['#type'] == 'action' || $element['#type'] == 'condition')) { + if (function_exists($function = $element['#name'] .'_upgrade')) { + $element_name = $function($element, $target); + } + elseif (isset($element['#info']['base']) && function_exists($function = $element['#info']['base'] .'_upgrade')) { + $element_name = $function($element, $target); + } + } + + drupal_alter('rules_element_upgrade', $element, $target); + // Recurse down, if necessary. + foreach (element_children($element) as $key) { + $child = rules_upgrade_plugin_factory($element[$key]); + rules_upgrade_convert_element($element[$key], $child); + $child->setParent($target); + } + if ($target instanceof RulesContainerPlugin) { + $target->sortChildren(); + } +} + +/** + * Creates the right element. + */ +function rules_upgrade_plugin_factory($element) { + if ($element['#type'] == 'rule' && !empty($element['#set']) && strpos($element['#set'], 'event_') === 0) { + return rules_plugin_factory('reaction rule'); + } + + switch ($element['#type']) { + case 'OR': + return rules_plugin_factory('or'); + case 'AND': + return rules_plugin_factory('and'); + default: + return rules_plugin_factory($element['#type']); + + case 'action': + case 'condition': + if (isset($element['#name'])) { + // Try to come up with the right action/condition name ourself, then + // invoke a hook. + $cache = rules_get_cache(); + $items = $cache[$element['#type'] == 'action' ? 'action_info' : 'condition_info']; + + if (isset($items[$element['#name']])) { + $element_name = $element['#name']; + } + elseif (($name = str_replace('rules_', '', $element['#name'])) && isset($items[$name])) { + $element_name = $name; + } + elseif (($name = str_replace($element['#type'] . '_', '', $element['#name'])) && isset($items[$name])) { + $element_name = $name; + } + elseif (($name = str_replace('rules_' . $element['#type'] . '_', '', $element['#name'])) && isset($items[$name])) { + $element_name = $name; + } + elseif (isset($element['#info']['base']) && isset($items[$element['#info']['base']])) { + $element_name = $name; + } + + // Call the upgrade callback if one has been defined. + if (function_exists($function = $element['#name'] .'_upgrade_map_name') || (isset($element['#info']['base']) && function_exists($function = $element['#info']['base'] .'_upgrade_map_name'))) { + $element_name = $function($element); + } + if (!isset($element_name)) { + throw new RulesIntegrityException(t("Cannot find @plugin %name. Maybe a required is missing or the module has not implemented the upgrade functionality.", array('@plugin' => $element['#type'], '%name' => $element['#name']))); + } + return rules_plugin_factory($element['#type'], $element_name); + } + break; + } +} + +/** + * Converts the settings for a given parameter. + */ +function rules_upgrade_element_parameter_settings($element, $target, $name, $new_name = NULL) { + if (!isset($new_name)) { + $new_name = $name; + } + if (isset($element['#settings'][$name])) { + // In case a single token has been used, just convert it to a data + // selector. + if (is_string($element['#settings'][$name]) && preg_match("/\[(.*)\]$/", $element['#settings'][$name], $matches)) { + $target->settings[$new_name . ':select'] = $matches[1]; + } + else { + $target->settings[$new_name] = $element['#settings'][$name]; + } + } + elseif (isset($element['#settings']['#argument map'][$name])) { + $target->settings[$new_name . ':select'] = $element['#settings']['#argument map'][$name]; + } +} + +/** + * Converts the settings for a given variable. + */ +function rules_upgrade_element_variable_settings($element, $target, $name, $new_name = NULL) { + if (!isset($new_name)) { + $new_name = $name; + } + if (isset($element['#settings']['#argument map'][$name])) { + $target->settings[$new_name . ':var'] = $element['#settings']['#argument map'][$name]; + $target->settings[$new_name . ':label'] = $element['#info']['new variables'][$target->settings[$new_name . ':var']]['label']; + } +} + +/** + * Upgrade callbacks for upgrading the provided Rules 1.x integration. + */ + +// Comment.module integration. +function rules_action_load_comment_upgrade_map_name($element) { + return 'entity_fetch'; +} +function rules_action_load_comment_upgrade($element, $target) { + $target->settings['type'] = 'comment'; + rules_upgrade_element_parameter_settings($element, $target, 'cid', 'id'); + rules_upgrade_element_variable_settings($element, $target, 'comment_loaded', 'entity_fetched'); +} + +// Node.module integration. +function rules_condition_content_is_type_upgrade_map_name($element) { + return 'node_is_of_type'; +} +function rules_condition_content_is_published_upgrade_map_name($element) { + return 'node_is_published'; +} +function rules_condition_content_is_sticky_upgrade_map_name($element) { + return 'node_is_sticky'; +} +function rules_condition_content_is_promoted_upgrade_map_name($element) { + return 'node_is_promoted'; +} +function rules_condition_content_is_new_upgrade_map_name($element) { + return 'entity_is_new'; +} +function rules_condition_content_is_new_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'node', 'entity'); +} +function rules_action_node_set_author_upgrade_map_name($element) { + return 'data_set'; +} +function rules_action_node_set_author_upgrade($element, $target) { + $target->settings['data:select'] = $element['#settings']['#argument map']['node'] . ':author'; + $target->settings['value:select'] = $element['#settings']['#argument map']['author']; +} +function rules_action_node_load_author_upgrade_map_name($element) { + return 'entity_fetch'; +} +function rules_action_node_load_author_upgrade($element, $target) { + $target->settings['type'] = 'user'; + $target->settings['id'] = $element['#settings']['#argument map']['node'] . ':author:uid'; +} +function rules_action_set_node_title_upgrade_map_name($element) { + return 'data_set'; +} +function rules_action_set_node_title_upgrade($element, $target) { + $target->settings['data:select'] = $element['#settings']['#argument map']['node'] . ':title'; + $target->settings['value'] = $element['#settings']['title']; +} +function rules_action_add_node_upgrade_map_name($element) { + return 'entity_create'; +} +function rules_action_add_node_upgrade($element, $target) { + $target->settings['type'] = 'node'; + rules_upgrade_element_parameter_settings($element, $target, 'title', 'param_title'); + rules_upgrade_element_parameter_settings($element, $target, 'author', 'param_author'); + rules_upgrade_element_parameter_settings($element, $target, 'type', 'param_type'); + rules_upgrade_element_variable_settings($element, $target, 'node_added', 'entity_created'); + if (!empty($element['#settings']['node_access'])) { + drupal_set_message(t('Warning: The node-access check option for the node creation action is not supported any more.')); + } +} +function rules_action_load_node_upgrade_map_name($element) { + return 'entity_fetch'; +} +function rules_action_load_node_upgrade($element, $target) { + $target->settings['type'] = 'node'; + rules_upgrade_element_parameter_settings($element, $target, 'nid', 'id'); + rules_upgrade_element_parameter_settings($element, $target, 'vid', 'revision_id'); + rules_upgrade_element_variable_settings($element, $target, 'node_loaded', 'entity_fetched'); +} +function rules_action_delete_node_upgrade_map_name($element) { + return 'entity_delete'; +} +function rules_action_delete_node_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'node', 'entity'); +} +function rules_core_node_publish_action_upgrade_map_name($element) { + return 'node_publish'; +} +function rules_core_node_unpublish_action_upgrade_map_name($element) { + return 'node_unpublish'; +} +function rules_core_node_make_sticky_action_upgrade_map_name($element) { + return 'node_make_sticky_action'; +} +function rules_core_node_make_unsticky_action_upgrade_map_name($element) { + return 'node_make_unsticky_action'; +} +function rules_core_node_promote_action_upgrade_map_name($element) { + return 'node_promote_action'; +} +function rules_core_node_unpromote_action_upgrade_map_name($element) { + return 'node_unpromote_action'; +} + + +// Path.module integration. +function rules_condition_url_has_alias_upgrade_map_name($element) { + return 'path_has_alias'; +} +function rules_condition_url_has_alias_upgrade($element, $target) { + $target->settings['source'] = $element['#settings']['src']; + $target->settings['alias'] = $element['#settings']['dst']; +} +function rules_condition_alias_exists_upgrade_map_name($element) { + return 'path_alias_exists'; +} +function rules_condition_alias_exists_upgrade($element, $target) { + $target->settings['alias'] = $element['#settings']['dst']; +} +function rules_action_path_alias_upgrade($element, $target) { + $target->settings['source'] = $element['#settings']['src']; + $target->settings['alias'] = $element['#settings']['dst']; +} +function rules_action_node_path_alias_upgrade($element, $target) { + $target->settings['alias'] = $element['#settings']['dst']; +} + +// PHP.module integration. +function rules_condition_custom_php_upgrade_map_name($element) { + return 'php_eval'; +} +function rules_action_custom_php_upgrade_map_name($element) { + return 'php_eval'; +} + +// General Rules integration. +function rules_condition_text_compare_upgrade_map_name($element) { + // @todo: Support regex. + return 'data_is'; +} +function rules_condition_text_compare_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'text1', 'data'); + rules_upgrade_element_parameter_settings($element, $target, 'text2', 'value'); +} +function rules_condition_number_compare_upgrade_map_name($element) { + return 'data_is'; +} +function rules_condition_number_compare_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'number1', 'data'); + rules_upgrade_element_parameter_settings($element, $target, 'number2', 'value'); +} +function rules_condition_check_boolean_upgrade_map_name($element) { + return 'data_is'; +} +function rules_condition_check_boolean_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'boolean', 'data'); + $target->settings['value'] = TRUE; +} +function rules_action_invoke_set_upgrade_map_name($element) { + return 'component_' . $element['#info']['set']; +} +function rules_action_invoke_set_upgrade($element, $target) { + foreach ($element['#info']['arguments'] as $name => $info) { + rules_upgrade_element_parameter_settings($element, $target, $name); + } +} +function rules_action_save_variable_upgrade_map_name($element) { + return isset($element['#info']['new variables']) ? 'variable_add' : 'entity_save'; +} +function rules_action_save_variable_upgrade($element, $target) { + $type = $element['#info']['arguments']['var_name']['default value']; + if (isset($element['#info']['new variables'])) { + $target->settings['type'] = $type; + rules_upgrade_element_parameter_settings($element, $target, $type, 'value'); + rules_upgrade_element_variable_settings($element, $target, $type, 'variable_added'); + } + else { + rules_upgrade_element_parameter_settings($element, $target, $type, 'entity'); + } +} + + +// System.module integration. +function rules_action_set_breadcrumb_upgrade_map_name($element) { + return 'breadcumb_set'; +} +function rules_action_mail_to_user_upgrade_map_name($element) { + return 'mail'; +} +function rules_action_mail_to_user_upgrade($element, $target) { + $target->settings['to:select'] = $element['#settings']['#argument map']['user'] . ':mail'; +} +function rules_action_drupal_goto_upgrade_map_name($element) { + return 'redirect'; +} +function rules_action_drupal_goto_upgrade($element, $target) { + $settings = $element['#settings']; + $target->settings['url'] = $settings['path']; + $target->settings['url'] .= $settings['query'] ? '?' . $settings['query'] : ''; + $target->settings['url'] .= $settings['fragment'] ? '#' . $settings['fragment'] : ''; + if ($settings['immediate']) { + drupal_set_message(t("Warning: The 'immediate' option for the page redirect action has been dropped in Rules 2.x.")); + } +} + +function rules_action_watchdog_upgrade_map_name($element) { + // @todo: Support action in Rules 2.x! + return NULL; +} + +// Taxonomy.module integration. +// @todo: Finish. +function rules_action_taxonomy_load_term_upgrade_map_name($element) { + return 'entity_fetch'; +} +function rules_action_taxonomy_add_term_upgrade_map_name($element) { + return 'entity_create'; +} +function rules_action_taxonomy_delete_term_upgrade_map_name($element) { + return 'entity_delete'; +} +function rules_action_taxonomy_term_assign_to_content_upgrade_map_name($element) { + // @todo : list. + return NULL; +} +function rules_action_taxonomy_term_remove_from_content_upgrade_map_name($element) { + // @todo : list. + return NULL; +} +function rules_action_taxonomy_load_vocab_upgrade_map_name($element) { + return 'entity_fetch'; +} +function rules_action_taxonomy_add_vocab_upgrade_map_name($element) { + return 'data_set'; +} + +// User.module integration. +function rules_condition_user_hasrole_upgrade_map_name($element) { + return 'user_has_role'; +} +function rules_condition_user_hasrole_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'user', 'account'); +} +function rules_condition_user_comparison_upgrade_map_name($element) { + return 'data_is'; +} +function rules_condition_user_comparison_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'user1', 'data'); + rules_upgrade_element_parameter_settings($element, $target, 'user2', 'value'); +} +function rules_action_user_addrole_upgrade_map_name($element) { + return 'user_add_role'; +} +function rules_action_user_addrole_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'user', 'account'); +} +function rules_action_user_removerole_upgrade_map_name($element) { + return 'user_remove_role'; +} +function rules_action_user_removerole_upgrade($element, $target) { + rules_upgrade_element_parameter_settings($element, $target, 'user', 'account'); +} +function rules_action_load_user_upgrade_map_name($element) { + if (!empty($element['#settings']['username'])) { + drupal_set_message(t('Warning: Directly upgrading the load user by name action is not supported.')); + } + return 'entity_fetch'; +} +function rules_action_load_user_upgrade($element, $target) { + $target->settings['type'] = 'user'; + rules_upgrade_element_parameter_settings($element, $target, 'userid', 'id'); + rules_upgrade_element_variable_settings($element, $target, 'user_loaded', 'entity_fetched'); +} +function rules_action_user_create_upgrade_map_name($element) { + return 'entity_create'; +} +function rules_action_user_create_upgrade($element, $target) { + $target->settings['type'] = 'user'; + rules_upgrade_element_parameter_settings($element, $target, 'username', 'param_name'); + rules_upgrade_element_parameter_settings($element, $target, 'email', 'param_mail'); + rules_upgrade_element_variable_settings($element, $target, 'user_added', 'entity_created'); + +} +function rules_core_user_block_user_action_upgrade_map_name($element) { + return 'user_block'; +} +function rules_core_user_block_user_action_upgrade($element, $target) { + $target->settings['account:select'] = $element['#settings']['#argument map']['user']; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/comment.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/comment.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,83 @@ + t('comment'), + 'module' => 'comment', + 'access callback' => 'rules_comment_integration_access', + 'class' => 'RulesCommentEventHandler', + ); + return array( + 'comment_insert' => $defaults + array( + 'label' => t('After saving a new comment'), + 'variables' => array( + 'comment' => array('type' => 'comment', 'label' => t('created comment')), + ), + ), + 'comment_update' => $defaults + array( + 'label' => t('After updating an existing comment'), + 'variables' => array( + 'comment' => array('type' => 'comment', 'label' => t('updated comment')), + 'comment_unchanged' => array('type' => 'comment', 'label' => t('unchanged comment'), 'handler' => 'rules_events_entity_unchanged'), + ), + ), + 'comment_presave' => $defaults + array( + 'label' => t('Before saving a comment'), + 'variables' => array( + 'comment' => array('type' => 'comment', 'label' => t('saved comment'), 'skip save' => TRUE), + 'comment_unchanged' => array('type' => 'comment', 'label' => t('unchanged comment'), 'handler' => 'rules_events_entity_unchanged'), + ), + ), + 'comment_view' => $defaults + array( + 'label' => t('A comment is viewed'), + 'variables' => array( + 'comment' => array('type' => 'comment', 'label' => t('viewed comment')), + ), + 'help' => t("Note that if drupal's page cache is enabled, this event won't be generated for pages served from cache."), + ), + 'comment_delete' => $defaults + array( + 'label' => t('After deleting a comment'), + 'variables' => array( + 'comment' => array('type' => 'comment', 'label' => t('deleted comment')), + ), + ), + ); +} + +/** + * Comment integration access callback. + */ +function rules_comment_integration_access($type, $name) { + if ($type == 'event' || $type == 'condition') { + return entity_access('view', 'comment'); + } +} + +/** + * Event handler support comment bundle event settings. + */ +class RulesCommentEventHandler extends RulesEventHandlerEntityBundle { + + /** + * Returns the label to use for the bundle property. + * + * @return string + */ + protected function getBundlePropertyLabel() { + return t('type'); + } +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/data.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/data.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,404 @@ +set($value); + } + catch (EntityMetadataWrapperException $e) { + throw new RulesEvaluationException('Unable to modify data "@selector": ' . $e->getMessage(), array('@selector' => $settings['data:select'])); + } + // Save changes if a property of a variable has been changed. + if (strpos($element->settings['data:select'], ':') !== FALSE) { + $info = $wrapper->info(); + // We always have to save the changes in the parent entity. E.g. when the + // node author is changed, we don't want to save the author but the node. + $state->saveChanges(implode(':', explode(':', $settings['data:select'], -1)), $info['parent']); + } + } + else { + // A not wrapped variable (e.g. a number) is being updated. Just overwrite + // the variable with the new value. + return array('data' => $value); + } +} + +/** + * Info alter callback for the data_set action. + */ +function rules_action_data_set_info_alter(&$element_info, $element) { + $element->settings += array('data:select' => NULL); + if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) { + $info = $wrapper->info(); + $element_info['parameter']['value']['type'] = $wrapper->type(); + $element_info['parameter']['value']['options list'] = !empty($info['options list']) ? 'rules_data_selector_options_list' : FALSE; + } +} + +/** + * Action: Calculate a value. + */ +function rules_action_data_calc($input1, $op, $input2, $settings, $state, $element) { + $info = $element->pluginParameterInfo(); + // Make sure to apply date offsets intelligently. + if ($info['input_1']['type'] == 'date' && $info['input_2']['type'] == 'duration') { + $input2 = ($op == '-') ? $input2 * -1 : $input2; + return array('result' => (int) RulesDateOffsetProcessor::applyOffset($input1, $input2)); + } + + switch ($op) { + case '+': + $result = $input1 + $input2; + break; + case '-': + $result = $input1 - $input2; + break; + case '*': + $result = $input1 * $input2; + break; + case '/': + $result = $input1 / $input2; + break; + case 'min': + $result = min($input1, $input2); + break; + case 'max': + $result = max($input1, $input2); + break; + } + if (isset($result)) { + // Ensure results are valid integer values if necessary. + $variables = $element->providesVariables(); + $var_info = reset($variables); + if ($var_info['type'] == 'integer') { + $result = (int) $result; + } + return array('result' => $result); + } +} + +/** + * Info alter callback for the data_calc action. + */ +function rules_action_data_calc_info_alter(&$element_info, RulesPlugin $element) { + if ($info = $element->getArgumentInfo('input_1')) { + // Only allow durations as offset for date values. + if ($info['type'] == 'date') { + $element_info['parameter']['input_2']['type'] = 'duration'; + } + // Specify the data type of the result. + $element_info['provides']['result']['type'] = $info['type']; + + if ($info['type'] == 'integer' && ($info2 = $element->getArgumentInfo('input_2')) && $info2['type'] == 'decimal') { + $element_info['provides']['result']['type'] = 'decimal'; + } + // A division with two integers results in a decimal. + elseif (isset($element->settings['op']) && $element->settings['op'] == '/') { + $element_info['provides']['result']['type'] = 'decimal'; + } + } +} + +/** + * Action: Add a list item. + */ +function rules_action_data_list_add($list, $item, $unique = FALSE, $pos = 'end', $settings, $state) { + // Optionally, only add the list item if it is not yet contained. + if ($unique && rules_condition_data_list_contains($list, $item, $settings, $state)) { + return; + } + + switch ($pos) { + case 'start': + array_unshift($list, $item); + break; + + default: + $list[] = $item; + break; + } + return array('list' => $list); +} + +/** + * Info alteration callback for the "Add and Remove a list item" actions. + */ +function rules_data_list_info_alter(&$element_info, RulesAbstractPlugin $element) { + // Update the required type for the list item if it is known. + $element->settings += array('list:select' => NULL); + if ($wrapper = $element->applyDataSelector($element->settings['list:select'])) { + if ($type = entity_property_list_extract_type($wrapper->type())) { + $info = $wrapper->info(); + $element_info['parameter']['item']['type'] = $type; + $element_info['parameter']['item']['options list'] = !empty($info['options list']) ? 'rules_data_selector_options_list' : FALSE; + } + } +} + +/** + * Action: Remove a list item. + */ +function rules_action_data_list_remove($list, $item) { + foreach (array_keys($list, $item) as $key) { + unset($list[$key]); + } + return array('list' => $list); +} + +/** + * Action: Add variable. + */ +function rules_action_variable_add($args, $element) { + return array('variable_added' => $args['value']); +} + +/** + * Info alteration callback for variable add action. + */ +function rules_action_variable_add_info_alter(&$element_info, RulesAbstractPlugin $element) { + if (isset($element->settings['type']) && $type = $element->settings['type']) { + $cache = rules_get_cache(); + $type_info = $cache['data_info'][$type]; + $element_info['parameter']['value']['type'] = $type; + $element_info['provides']['variable_added']['type'] = $type; + + // For lists, we default to an empty list so subsequent actions can add + // items. + if (entity_property_list_extract_type($type)) { + $element_info['parameter']['value']['default value'] = array(); + } + } +} + +/** + * Action: Convert a value. + */ +function rules_action_data_convert($arguments, RulesPlugin $element, $state) { + + $value_info = $element->getArgumentInfo('value'); + $from_type = $value_info['type']; + $target_type = $arguments['type']; + + // First apply the rounding behavior if given. + if (isset($arguments['rounding_behavior'])) { + switch ($arguments['rounding_behavior']) { + case 'up': + $arguments['value'] = ceil($arguments['value']); + break; + case 'down': + $arguments['value'] = floor($arguments['value']); + break; + default: + case 'round': + $arguments['value'] = round($arguments['value']); + break; + } + } + + switch ($target_type) { + case 'decimal': + $result = floatval($arguments['value']); + break; + case 'integer': + $result = intval($arguments['value']); + break; + case 'text': + $result = strval($arguments['value']); + break; + } + + return array('conversion_result' => $result); +} + +/** + * Info alteration callback for variable add action. + */ +function rules_action_data_convert_info_alter(&$element_info, RulesAbstractPlugin $element) { + + if (isset($element->settings['type']) && $type = $element->settings['type']) { + $element_info['provides']['conversion_result']['type'] = $type; + + if ($type != 'integer') { + // Only support the rounding behavior option for integers. + unset($element_info['parameter']['rounding_behavior']); + } + + // Configure compatible source-types: + switch ($type) { + case 'integer': + $sources = array('decimal', 'text', 'token', 'uri', 'date', 'duration', 'boolean'); + break; + case 'decimal': + $sources = array('integer', 'text', 'token', 'uri', 'date', 'duration', 'boolean'); + break; + case 'text': + $sources = array('integer', 'decimal', 'token', 'uri', 'date', 'duration', 'boolean'); + break; + } + $element_info['parameter']['value']['type'] = $sources; + } +} + +/** + * Action: Create data. + */ +function rules_action_data_create($args, $element) { + $type = $args['type']; + $values = array(); + foreach ($element->pluginParameterInfo() as $name => $info) { + if ($name != 'type') { + // Remove the parameter name prefix 'param_'. + $values[substr($name, 6)] = $args[$name]; + } + } + $cache = rules_get_cache(); + $type_info = $cache['data_info'][$type]; + if (isset($type_info['creation callback'])) { + try { + $data = $type_info['creation callback']($values, $type); + return array('data_created' => $data); + } + catch (EntityMetadataWrapperException $e) { + throw new RulesEvaluationException('Unable to create @data": ' . $e->getMessage(), array('@data' => $type), $element); + } + } + else { + throw new RulesEvaluationException('Unable to create @data, no creation callback found.', array('@data' => $type), $element, RulesLog::ERROR); + } +} + +/** + * Info alteration callback for data create action. + */ +function rules_action_data_create_info_alter(&$element_info, RulesAbstractPlugin $element) { + if (!empty($element->settings['type'])) { + $type = $element->settings['type']; + $cache = rules_get_cache(); + $type_info = $cache['data_info'][$type]; + if (isset($type_info['property info'])) { + // Add the data type's properties as parameters. + foreach ($type_info['property info'] as $property => $property_info) { + // Prefix parameter names to avoid name clashes with existing parameters. + $element_info['parameter']['param_' . $property] = array_intersect_key($property_info, array_flip(array('type', 'label', 'allow null'))); + if (empty($property_info['required'])) { + $element_info['parameter']['param_' . $property]['optional'] = TRUE; + } + } + } + $element_info['provides']['data_created']['type'] = $type; + } +} + +/** + * Creation callback for array structured data. + */ +function rules_action_data_create_array($values = array(), $type) { + // $values is an array already, so we can just pass it to the wrapper. + return rules_wrap_data($values, array('type' => $type)); +} + +/** + * Condition: Compare data. + */ +function rules_condition_data_is($data, $op, $value) { + switch ($op) { + default: + case '==': + // In case both values evaluate to FALSE, further differentiate between + // NULL values and values evaluating to FALSE. + if (!$data && !$value) { + return (isset($data) && isset($value)) || (!isset($data) && !isset($value)); + } + return $data == $value; + case '<': + return $data < $value; + case '>': + return $data > $value; + // Note: This is deprecated by the text comparison condition and IN below. + case 'contains': + return is_string($data) && strpos($data, $value) !== FALSE || is_array($data) && in_array($value, $data); + case 'IN': + return is_array($value) && in_array($data, $value); + } +} + +/** + * Info alteration callback for the data_is condition. + * + * If we check the bundle property of a variable, add an assertion so that later + * evaluated elements can make use of this information. + */ +function rules_condition_data_is_info_alter(&$element_info, RulesAbstractPlugin $element) { + $element->settings += array('data:select' => NULL, 'op' => '=='); + if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) { + $info = $wrapper->info(); + $element_info['parameter']['value']['type'] = $element->settings['op'] == 'IN' ? 'list<' . $wrapper->type() . '>' : $wrapper->type(); + $element_info['parameter']['value']['options list'] = !empty($info['options list']) ? 'rules_data_selector_options_list' : FALSE; + } +} + +/** + * Condition: List contains. + */ +function rules_condition_data_list_contains($list, $item, $settings, $state) { + $wrapper = $state->currentArguments['item']; + if ($wrapper instanceof EntityStructureWrapper && $id = $wrapper->getIdentifier()) { + // Check for equal items using the identifier if there is one. + foreach ($state->currentArguments['list'] as $i) { + if ($i->getIdentifier() == $id) { + return TRUE; + } + } + return FALSE; + } + return in_array($item, $list); +} + +/** + * Condition: Data value is empty. + */ +function rules_condition_data_is_empty($data) { + // Note that some primitive variables might not be wrapped at all. + if ($data instanceof EntityMetadataWrapper) { + try { + // We cannot use the dataAvailable() method from the wrapper because it + // is protected, so we catch possible exceptions with the value() method. + $value = $data->value(); + return empty($value); + } + catch (EntityMetadataWrapperException $e) { + // An exception means that the wrapper is somehow broken and we treat + // that as empty. + return TRUE; + } + } + return empty($data); +} + +/** + * Condition: Textual comparison. + */ +function rules_data_text_comparison($text, $text2, $op = 'contains') { + switch ($op) { + case 'contains': + return strpos($text, $text2) !== FALSE; + case 'starts': + return strpos($text, $text2) === 0; + case 'ends': + return strrpos($text, $text2) === (strlen($text) - strlen($text2)); + case 'regex': + return (bool) preg_match('/'. str_replace('/', '\\/', $text2) .'/', $text); + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/data.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/data.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,724 @@ + array( + 'label' => t('Data'), + 'equals group' => t('Data'), + 'weight' => -50, + ), + ); +} + +/** + * Implements hook_rules_file_info() on behalf of the pseudo data module. + * @see rules_core_modules() + */ +function rules_data_file_info() { + return array('modules/data.eval'); +} + +/** + * Implements hook_rules_action_info() on behalf of the pseudo data module. + * @see rules_core_modules() + */ +function rules_data_action_info() { + $return['data_set'] = array( + 'label' => t('Set a data value'), + 'parameter' => array( + 'data' => array( + 'type' => '*', + 'label' => t('Data'), + 'description' => t('Specifies the data to be modified using a data selector, e.g. "node:author:name".'), + 'restriction' => 'selector', + 'wrapped' => TRUE, + 'allow null' => TRUE, + ), + 'value' => array( + 'type' => '*', + 'label' => t('Value'), + 'description' => t('The new value to set for the specified data.'), + 'allow null' => TRUE, + 'optional' => TRUE, + ), + ), + 'group' => t('Data'), + 'base' => 'rules_action_data_set', + ); + $return['data_calc'] = array( + 'label' => t('Calculate a value'), + 'parameter' => array( + 'input_1' => array( + 'type' => array('decimal', 'date'), + 'label' => t('Input value 1'), + 'description' => t('The first input value for the calculation.'), + ), + 'op' => array( + 'type' => 'text', + 'label' => t('Operator'), + 'description' => t('The calculation operator.'), + 'options list' => 'rules_action_data_calc_operator_options', + 'restriction' => 'input', + 'default value' => '+', + ), + 'input_2' => array( + 'type' => 'decimal', + 'label' => t('Input value 2'), + 'description' => t('The second input value.'), + ), + ), + 'group' => t('Data'), + 'base' => 'rules_action_data_calc', + 'provides' => array( + 'result' => array( + 'type' => 'unknown', + 'label' => t('Calculation result'), + ), + ), + ); + $return['list_add'] = array( + 'label' => t('Add an item to a list'), + 'parameter' => array( + 'list' => array( + 'type' => 'list', + 'label' => t('List', array(), array('context' => 'data_types')), + 'description' => t('The data list, to which an item is to be added.'), + 'restriction' => 'selector', + 'allow null' => TRUE, + 'save' => TRUE, + ), + 'item' => array( + 'type' => 'unknown', + 'label' => t('Item to add'), + ), + 'unique' => array( + 'type' => 'boolean', + 'label' => t('Enforce uniqueness'), + 'description' => t('Only add the item to the list if it is not yet contained.'), + 'optional' => TRUE, + 'default value' => FALSE, + ), + 'pos' => array( + 'type' => 'text', + 'label' => t('Insert position'), + 'optional' => TRUE, + 'default value' => 'end', + 'options list' => 'rules_action_data_list_add_positions', + ), + ), + 'group' => t('Data'), + 'base' => 'rules_action_data_list_add', + 'callbacks' => array( + 'info_alter' => 'rules_data_list_info_alter', + 'form_alter' => 'rules_data_list_form_alter', + ), + ); + $return['list_remove'] = array( + 'label' => t('Remove an item from a list'), + 'parameter' => array( + 'list' => array( + 'type' => 'list', + 'label' => t('List', array(), array('context' => 'data_types')), + 'description' => t('The data list for which an item is to be removed.'), + 'restriction' => 'selector', + 'save' => TRUE, + ), + 'item' => array( + 'type' => 'unknown', + 'label' => t('Item to remove'), + ), + ), + 'group' => t('Data'), + 'base' => 'rules_action_data_list_remove', + 'callbacks' => array( + 'info_alter' => 'rules_data_list_info_alter', + 'form_alter' => 'rules_data_list_form_alter', + ), + ); + $return['variable_add'] = array( + 'label' => t('Add a variable'), + 'named parameter' => TRUE, + 'parameter' => array( + 'type' => array( + 'type' => 'text', + 'label' => t('Type'), + 'options list' => 'rules_data_action_variable_add_options', + 'description' => t('Specifies the type of the variable that should be added.'), + 'restriction' => 'input', + ), + 'value' => array( + 'type' => 'unknown', + 'label' => t('Value'), + 'optional' => TRUE, + 'description' => t('Optionally, specify the initial value of the variable.') + ), + ), + 'provides' => array( + 'variable_added' => array( + 'type' => 'unknown', + 'label' => t('Added variable'), + ), + ), + 'group' => t('Data'), + 'base' => 'rules_action_variable_add', + 'callbacks' => array( + 'form_alter' => 'rules_action_type_form_alter', + 'validate' => 'rules_action_create_type_validate', + ), + ); + + if (rules_data_action_data_create_options()) { + $return['data_create'] = array( + 'label' => t('Create a data structure'), + 'named parameter' => TRUE, + 'parameter' => array( + 'type' => array( + 'type' => 'text', + 'label' => t('Type'), + 'options list' => 'rules_data_action_data_create_options', + 'description' => t('Specifies the type of the data structure that should be created.'), + 'restriction' => 'input', + ), + // Further needed parameters depend on the type. + ), + 'provides' => array( + 'data_created' => array( + 'type' => 'unknown', + 'label' => t('Created data'), + ), + ), + 'group' => t('Data'), + 'base' => 'rules_action_data_create', + 'callbacks' => array( + 'form_alter' => 'rules_action_type_form_alter', + 'validate' => 'rules_action_create_type_validate', + ), + ); + } + $return['data_convert'] = array( + 'label' => t('Convert data type'), + 'parameter' => array( + 'type' => array( + 'type' => 'token', + 'label' => t('Target type'), + 'description' => t('The data type to convert a value to.'), + 'options list' => 'rules_action_data_convert_types_options', + 'restriction' => 'input', + ), + 'value' => array( + 'type' => array('decimal', 'integer', 'text'), + 'label' => t('Value to convert'), + 'default mode' => 'selector', + ), + // For to-integer conversion only. + 'rounding_behavior' => array( + 'type' => 'token', + 'label' => t('Rounding behavior'), + 'description' => t('The rounding behavior the conversion should use.'), + 'options list' => 'rules_action_data_convert_rounding_behavior_options', + 'restriction' => 'input', + 'default value' => 'round', + 'optional' => TRUE, + ), + ), + 'provides' => array( + 'conversion_result' => array( + 'type' => 'unknown', + 'label' => t('Conversion result'), + ), + ), + 'group' => t('Data'), + 'base' => 'rules_action_data_convert', + 'named parameter' => TRUE, + 'callbacks' => array( + 'form_alter' => 'rules_action_type_form_alter', + ), + ); + return $return; +} + +/** + * Data conversation action: Options list callback for the target type. + */ +function rules_action_data_convert_types_options(RulesPlugin $element, $param_name) { + return array( + 'decimal' => t('Decimal'), + 'integer' => t('Integer'), + 'text' => t('Text'), + ); +} + +/** + * Data conversation action: Options list callback for rounding behavior. + */ +function rules_action_data_convert_rounding_behavior_options(RulesPlugin $element, $param_name) { + return array( + 'down' => t('Always down (9.5 -> 9)'), + 'round' => t('Round, half up (9.5 -> 10)'), + 'up' => t('Always up (9.5 -> 10)'), + ); +} + +/** + * Customize access check for data set action. + */ +function rules_action_data_set_access(RulesAbstractPlugin $element) { + if (isset($element->settings['data:select']) && $wrapper = $element->applyDataSelector($element->settings['data:select'])) { + return $wrapper instanceof EntityMetadataWrapper && $wrapper->access('edit'); + } +} + +/** + * Custom validation callback for the data set action. + */ +function rules_action_data_set_validate(RulesAbstractPlugin $element) { + $element->settings += array('data:select' => NULL); + $info = $element->applyDataSelector($element->settings['data:select'])->info(); + if (strpos($element->settings['data:select'], ':') !== FALSE && empty($info['setter callback'])) { + throw new RulesIntegrityException(t("The selected data property doesn't support writing."), array($element, 'parameter', 'data')); + } +} + +/** + * Form alter callback for the data_set action. + */ +function rules_action_data_set_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) { + if (!empty($options['init']) && !isset($form_state['rules_element_step'])) { + $form['negate']['#access'] = FALSE; + unset($form['parameter']['value']); + unset($form['parameter']['language']); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Continue'), + '#limit_validation_errors' => array(array('parameter', 'data')), + '#submit' => array('rules_form_submit_rebuild'), + ); + $form_state['rules_element_step'] = 'data_value'; + // Clear the parameter mode for the value parameter, so its gets the proper + // default value based upon the type of the the selected data on rebuild. + unset($form_state['parameter_mode']['value']); + } + else { + // Change the data parameter to be not editable. + $form['parameter']['data']['settings']['#access'] = FALSE; + // TODO: improve display + $form['parameter']['data']['info'] = array( + '#prefix' => '

', + '#markup' => t('Selected data: %selector', array('%selector' => $element->settings['data:select'])), + '#suffix' => '

', + ); + } +} + +/** + * Form alter callback for the data calculation action. + */ +function rules_action_data_calc_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) { + + $form['reload'] = array( + '#weight' => 5, + '#type' => 'submit', + '#name' => 'reload', + '#value' => t('Reload form'), + '#limit_validation_errors' => array(array('parameter', 'input_1')), + '#submit' => array('rules_form_submit_rebuild'), + '#ajax' => rules_ui_form_default_ajax(), + ); +} + +/** + * Custom validate callback for entity create, add variable and data create + * action. + */ +function rules_action_create_type_validate($element) { + if (!isset($element->settings['type'])) { + throw new RulesIntegrityException(t('Invalid type specified.'), array($element, 'parameter', 'type')); + } +} + +/** + * Form alter callback for the list add and remove actions. + * + * Use multiple steps to configure the action to update the item configuration + * form once we know the data type. + * + * @see rules_data_list_info_alter() + */ +function rules_data_list_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) { + if (!empty($options['init']) && !isset($form_state['rules_element_step'])) { + unset($form['parameter']['item'], $form['parameter']['pos']); + $form_state['rules_element_step'] = 1; + $form['negate']['#access'] = FALSE; + $form['parameter']['unique']['#access'] = FALSE; + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Continue'), + '#limit_validation_errors' => array(array('parameter', 'list')), + '#submit' => array('rules_form_submit_rebuild'), + ); + } + else { + // Change the list parameter to be not editable any more. + $form['parameter']['list']['settings']['#access'] = FALSE; + $form['parameter']['list']['info'] = array( + '#prefix' => '

', + '#markup' => t('Selected list: %selector', array('%selector' => $element->settings['list:select'])), + '#suffix' => '

', + ); + } +} + + +/** + * Form alter callback for actions relying on the entity type or the data type. + */ +function rules_action_type_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) { + $first_step = empty($element->settings['type']); + $form['reload'] = array( + '#weight' => 5, + '#type' => 'submit', + '#name' => 'reload', + '#value' => $first_step ? t('Continue') : t('Reload form'), + '#limit_validation_errors' => array(array('parameter', 'type')), + '#submit' => array('rules_action_type_form_submit_rebuild'), + '#ajax' => rules_ui_form_default_ajax(), + ); + // Use ajax and trigger as the reload button. + $form['parameter']['type']['settings']['type']['#ajax'] = $form['reload']['#ajax'] + array( + 'event' => 'change', + 'trigger_as' => array('name' => 'reload'), + ); + + if ($first_step) { + // In the first step show only the type select. + foreach (element_children($form['parameter']) as $key) { + if ($key != 'type') { + unset($form['parameter'][$key]); + } + } + unset($form['submit']); + unset($form['provides']); + // Disable #ajax for the first step as it has troubles with lazy-loaded JS. + // @todo: Re-enable once JS lazy-loading is fixed in core. + unset($form['parameter']['type']['settings']['type']['#ajax']); + unset($form['reload']['#ajax']); + } + else { + // Hide the reload button in case js is enabled and it's not the first step. + $form['reload']['#attributes'] = array('class' => array('rules-hide-js')); + } +} + +/** + * FAPI submit callback for reloading the type form for entities or data types. + */ +function rules_action_type_form_submit_rebuild($form, &$form_state) { + rules_form_submit_rebuild($form, $form_state); + // Clear the parameter modes for the parameters, so they get the proper + // default values based upon the data types on rebuild. + $form_state['parameter_mode'] = array(); +} + +/** + * Options list callback for possible insertion positions. + */ +function rules_action_data_list_add_positions() { + return array( + 'end' => t('Append the item to the end.'), + 'start' => t('Prepend the item to the front.'), + ); +} + +/** + * Options list callback for variable add action. + */ +function rules_data_action_variable_add_options() { + return RulesPluginUI::getOptions('data'); +} + +/** + * Options list callback for the data calculation action. + */ +function rules_action_data_calc_operator_options(RulesPlugin $element, $param_name) { + $options = array( + '+' => '( + )', + '-' => '( - )', + '*' => '( * )', + '/' => '( / )', + 'min' => 'min', + 'max' => 'max', + ); + // Only show +/- in case a date has been selected. + if (($info = $element->getArgumentInfo('input_1')) && $info['type'] == 'date') { + unset($options['*']); + unset($options['/']); + } + return $options; +} + +/** + * Options list callback for data create action. + */ +function rules_data_action_data_create_options() { + $cache = rules_get_cache(); + $data_info = $cache['data_info']; + $entity_info = entity_get_info(); + // Remove entities. + $data_info = array_diff_key($data_info, $entity_info); + $options = array(); + foreach ($data_info as $type => $properties) { + if (isset($properties['creation callback'])) { + // Add data types with creation callback only. + $options[$type] = $properties['label']; + } + } + natcasesort($options); + return $options; +} + +/** + * Implements hook_rules_condition_info() on behalf of the pseudo data module. + * @see rules_core_modules() + */ +function rules_data_condition_info() { + return array( + 'data_is' => array( + 'label' => t('Data comparison'), + 'parameter' => array( + 'data' => array( + 'type' => '*', + 'label' => t('Data to compare'), + 'description' => t('The data to be compared, specified by using a data selector, e.g. "node:author:name".'), + 'allow null' => TRUE, + ), + 'op' => array( + 'type' => 'text', + 'label' => t('Operator'), + 'description' => t('The comparison operator.'), + 'optional' => TRUE, + 'default value' => '==', + 'options list' => 'rules_condition_data_is_operator_options', + 'restriction' => 'input', + ), + 'value' => array( + 'type' => '*', + 'label' => t('Data value'), + 'description' => t('The value to compare the data with.'), + 'allow null' => TRUE, + ), + ), + 'group' => t('Data'), + 'base' => 'rules_condition_data_is', + ), + 'data_is_empty' => array( + 'label' => t('Data value is empty'), + 'parameter' => array( + 'data' => array( + 'type' => '*', + 'label' => t('Data to check'), + 'description' => t('The data to be checked to be empty, specified by using a data selector, e.g. "node:author:name".'), + 'allow null' => TRUE, + 'wrapped' => TRUE, + ), + ), + 'group' => t('Data'), + 'base' => 'rules_condition_data_is_empty', + ), + 'list_contains' => array( + 'label' => t('List contains item'), + 'parameter' => array( + 'list' => array( + 'type' => 'list', + 'label' => t('List', array(), array('context' => 'data_types')), + 'restriction' => 'selector', + ), + 'item' => array( + 'type' => 'unknown', + 'label' => t('Item'), + 'description' => t('The item to check for.'), + ), + ), + 'group' => t('Data'), + 'base' => 'rules_condition_data_list_contains', + 'callbacks' => array( + 'info_alter' => 'rules_data_list_info_alter', + 'form_alter' => 'rules_data_list_form_alter', + ), + ), + 'text_matches' => array( + 'label' => t('Text comparison'), + 'parameter' => array( + 'text' => array( + 'type' => 'text', + 'label' => t('Text'), + 'restriction' => 'selector', + ), + 'match' => array( + 'type' => 'text', + 'label' => t('Matching text'), + ), + 'operation' => array( + 'type' => 'text', + 'label' => t('Comparison operation'), + 'options list' => 'rules_data_text_comparison_operation_list', + 'restriction' => 'input', + 'default value' => 'contains', + 'optional' => TRUE, + 'description' => t('In case the comparison operation @regex is selected, the matching pattern will be interpreted as a regular expression. Tip: RegExr: Online Regular Expression Testing Tool is helpful for learning, writing, and testing Regular Expressions.', array('@regex-wikipedia' => 'http://en.wikipedia.org/wiki/Regular_expression', '@RegExr' => 'http://gskinner.com/RegExr/', '@regex' => t('regular expression'))), + ), + ), + 'group' => t('Data'), + 'base' => 'rules_data_text_comparison', + ), + ); +} + +/** + * If the bundle is compared, add the metadata assertion so other elements + * can make use of properties specific to the bundle. + */ +function rules_condition_data_is_assertions($element) { + // Assert the bundle of entities, if its compared. + if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) { + $info = $wrapper->info(); + if (isset($info['parent']) && $info['parent'] instanceof EntityDrupalWrapper) { + $entity_info = $info['parent']->entityInfo(); + if (isset($entity_info['entity keys']['bundle']) && $entity_info['entity keys']['bundle'] == $info['name']) { + // Assert that the entity is of bundle $value. + $value = is_array($element->settings['value']) ? $element->settings['value'] : array($element->settings['value']); + // Chop of the last part of the selector. + $parts = explode(':', $element->settings['data:select'], -1); + return array(implode(':', $parts) => array('bundle' => $value)); + } + } + } +} + +/** + * Form alter callback for the condition data_is. + * + * Use multiple steps to configure the condition as the needed type of the value + * depends on the selected data. + */ +function rules_condition_data_is_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) { + if (!empty($options['init']) && !isset($form_state['rules_element_step'])) { + unset($form['parameter']['op'], $form['parameter']['value']); + $form['negate']['#access'] = FALSE; + $form_state['rules_element_step'] = 'data_value'; + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Continue'), + '#limit_validation_errors' => array(array('parameter', 'data'), array('parameter', 'op')), + '#submit' => array('rules_form_submit_rebuild'), + ); + // Clear the parameter mode for the value parameter, so its gets the proper + // default value based upon the type of the the selected data on rebuild. + unset($form_state['parameter_mode']['value']); + } + else { + // Change the data parameter to be not editable. + $form['parameter']['data']['settings']['#access'] = FALSE; + // TODO: improve display + $form['parameter']['data']['info'] = array( + '#prefix' => '

', + '#markup' => t('Selected data: %selector', array('%selector' => $element->settings['data:select'])), + '#suffix' => '

', + ); + + // Limit the operations to what makes sense for the selected data type. + $info = $element->pluginParameterInfo(); + $data_info = $info['value']; + if ($element->settings['op'] == 'IN') { + $data_info['type'] = entity_property_list_extract_type($data_info['type']); + } + + if (!RulesData::typesMatch($data_info, array('type' => array('decimal', 'date')))) { + $options =& $form['parameter']['op']['settings']['op']['#options']; + unset($options['<'], $options['>']); + } + // Remove 'contains' if it is not selected, as it is deprecated by the + // text comparison condition. + if ($element->settings['op'] != 'contains') { + unset($form['parameter']['op']['settings']['op']['#options']['contains']); + } + + // Auto-refresh the form if the operation is changed, so the input form + // changes in case "is one of" requires a list value. + $form['parameter']['op']['settings']['op']['#ajax'] = rules_ui_form_default_ajax() + array( + 'trigger_as' => array('name' => 'reload'), + ); + // Provide a reload button for non-JS users. + $form['reload'] = array( + '#type' => 'submit', + '#value' => t('Reload form'), + '#limit_validation_errors' => array(array('parameter', 'data'), array('parameter', 'op')), + '#submit' => array('rules_form_submit_rebuild'), + '#ajax' => rules_ui_form_default_ajax(), + '#weight' => 5, + ); + // Hide the reload button in case JS is enabled. + $form['reload']['#attributes'] = array('class' => array('rules-hide-js')); + } +} + +/** + * Provides configuration help for the data_is condition. + */ +function rules_condition_data_is_help() { + return array('#markup' => t('Compare two data values of the same type with each other.')); +} + +/** + * Options list callback for condition data_is. + */ +function rules_condition_data_is_operator_options() { + return array( + '==' => t('equals'), + 'IN' => t('is one of'), + '<' => t('is lower than'), + '>' => t('is greater than'), + // Note: This is deprecated by the text comparison condition. + 'contains' => t('contains'), + ); +} + +/** + * Options list callback for condition text_matches. + */ +function rules_data_text_comparison_operation_list() { + return array( + 'contains' => t('contains'), + 'starts' => t('starts with'), + 'ends' => t('ends with'), + 'regex' => t('regular expression'), + ); +} + +/** + * Returns the options list as specified by the selected property of the first parameter. + * + * @see rules_data_list_info_alter() + * @see rules_action_data_set_info_alter() + * @see rules_condition_data_is_info_alter() + */ +function rules_data_selector_options_list(RulesAbstractPlugin $element) { + $name = rules_array_key($element->pluginParameterInfo()); + // If the selected data property has an option list, make use of it. + if (isset($element->settings[$name . ':select']) && $wrapper = $element->applyDataSelector($element->settings[$name . ':select'])) { + return $wrapper->optionsList($element instanceof RulesActionInterface ? 'edit' : 'view'); + } +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/entity.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/entity.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,174 @@ + $revision); + } + + $return = entity_load($type, array($id), isset($conditions) ? $conditions : array()); + $entity = reset($return); + if (!$entity) { + throw new RulesEvaluationException('Unable to load @entity with id "@id"', array('@id' => $id, '@entity' => $type)); + } + return array('entity_fetched' => $entity); +} + +/** + * Info alteration callback for the entity fetch action. + */ +function rules_action_entity_fetch_info_alter(&$element_info, RulesAbstractPlugin $element) { + $element->settings += array('type' => NULL); + $info = entity_get_info($element->settings['type']); + + // Fix the type of the identifier. + $element_info['parameter']['id']['type'] = isset($info['entity keys']['name']) ? 'text' : 'integer'; + + // Add an optional revision parameter, if supported. + if (!empty($info['entity keys']['revision'])) { + $element_info['parameter']['revision_id'] = array( + 'type' => 'integer', + 'label' => t('Revision identifier'), + 'optional' => TRUE, + ); + } + $element_info['provides']['entity_fetched']['type'] = $element->settings['type']; +} + +/** + * Action: Query entities. + */ +function rules_action_entity_query($type, $property, $value, $limit) { + $return = entity_property_query($type, $property, $value, $limit); + return array('entity_fetched' => array_values($return)); +} + +/** + * Info alteration callback for the entity query action. + */ +function rules_action_entity_query_info_alter(&$element_info, RulesAbstractPlugin $element) { + $element->settings += array('type' => NULL, 'property' => NULL); + if ($element->settings['type']) { + $element_info['parameter']['property']['options list'] = 'rules_action_entity_query_property_options_list'; + + if ($element->settings['property']) { + $wrapper = rules_get_entity_metadata_wrapper_all_properties($element); + if (isset($wrapper->{$element->settings['property']}) && $property = $wrapper->{$element->settings['property']}) { + $element_info['parameter']['value']['type'] = $property->type(); + $element_info['parameter']['value']['options list'] = $property->optionsList() ? 'rules_action_entity_query_value_options_list' : FALSE; + } + } + } + $element_info['provides']['entity_fetched']['type'] = 'list<' . $element->settings['type'] . '>'; +} + +/** + * Action: Create entities. + */ +function rules_action_entity_create($args, $element) { + $values = array(); + foreach ($element->pluginParameterInfo() as $name => $info) { + if ($name != 'type') { + // Remove the parameter name prefix 'param_'. + $values[substr($name, 6)] = $args[$name]; + } + } + try { + $data = entity_property_values_create_entity($args['type'], $values); + return array('entity_created' => $data); + } + catch (EntityMetadataWrapperException $e) { + throw new RulesEvaluationException('Unable to create entity @type": ' . $e->getMessage(), array('@type' => $args['type']), $element); + } +} + +/** + * Info alteration callback for the entity create action. + */ +function rules_action_entity_create_info_alter(&$element_info, RulesAbstractPlugin $element) { + if (!empty($element->settings['type']) && entity_get_info($element->settings['type'])) { + $wrapper = entity_metadata_wrapper($element->settings['type']); + // Add the data type's needed parameter for loading to the parameter info. + foreach ($wrapper as $name => $child) { + $info = $child->info(); + if (!empty($info['required'])) { + $info += array('type' => 'text'); + // Prefix parameter names to avoid name clashes with existing parameters. + $element_info['parameter']['param_' . $name] = array_intersect_key($info, array_flip(array('type', 'label', 'description'))); + $element_info['parameter']['param_' . $name]['options list'] = $child->optionsList() ? 'rules_action_entity_parameter_options_list' : FALSE; + } + } + $element_info['provides']['entity_created']['type'] = $element->settings['type']; + if (($bundleKey = $wrapper->entityKey('bundle')) && isset($element->settings['param_' . $bundleKey])) { + $element_info['provides']['entity_created']['bundle'] = $element->settings['param_' . $bundleKey]; + } + } +} + +/** + * Action: Save entities. + */ +function rules_action_entity_save($wrapper, $immediate = FALSE, $settings, $state, $element) { + $state->saveChanges($settings['data:select'], $wrapper, $immediate); +} + +/** + * Action: Delete entities. + */ +function rules_action_entity_delete($wrapper, $settings, $state, $element) { + try { + $wrapper->delete(); + } + catch (EntityMetadataWrapperException $e) { + throw new RulesEvaluationException($e->getMessage(), array(), $element); + } +} + +/** + * Condition: Entity is new. + */ +function rules_condition_entity_is_new($wrapper, $settings, $state, $element) { + return !$wrapper->getIdentifier() || !empty($wrapper->value()->is_new); +} + +/** + * Condition: Entity has field. + */ +function rules_condition_entity_has_field($wrapper, $field_name, $settings, $state) { + return isset($wrapper->$field_name) || isset($wrapper->value()->$field_name); +} + +/** + * Condition: Entity is of type. + */ +function rules_condition_entity_is_of_type($wrapper, $type) { + return $wrapper->type() == $type; +} + +/** + * Condition: Entity is of type and bundle. + */ +function rules_condition_entity_is_of_bundle($wrapper, $type, $bundles) { + return $wrapper->type() == $type && in_array($wrapper->getBundle(), $bundles); +} + +/** + * Condition: User has access to field. + */ +function rules_condition_entity_field_access(EntityDrupalWrapper $wrapper, $field_name, $op, $account = NULL) { + $field = field_info_field($field_name); + return !empty($field) && field_access($op, $field, $wrapper->type(), $wrapper->value(), $account = NULL); +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/entity.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/entity.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,579 @@ + array( + 'label' => t('Entities'), + 'equals group' => t('Entities'), + 'weight' => -50, + ), + ); +} + +/** + * Implements hook_rules_action_info() on behalf of the entity module. + * @see rules_core_modules() + */ +function rules_entity_action_info() { + if (rules_entity_action_type_options('entity_fetch')) { + $return['entity_fetch'] = array( + 'label' => t('Fetch entity by id'), + 'parameter' => array( + 'type' => array( + 'type' => 'text', + 'label' => t('Entity type'), + 'options list' => 'rules_entity_action_type_options', + 'description' => t('Specifies the type of entity that should be fetched.'), + 'restriction' => 'input', + ), + 'id' => array('type' => 'unknown', 'label' => t('Identifier')), + ), + 'provides' => array( + 'entity_fetched' => array('type' => 'unknown', 'label' => t('Fetched entity')), + ), + 'group' => t('Entities'), + 'access callback' => 'rules_entity_action_access', + 'base' => 'rules_action_entity_fetch', + 'callbacks' => array( + 'access' => 'rules_action_entity_createfetch_access', + 'form_alter' => 'rules_action_type_form_alter', + ), + ); + $return['entity_query'] = array( + 'label' => t('Fetch entity by property'), + 'parameter' => array( + 'type' => array( + 'type' => 'text', + 'label' => t('Entity type'), + 'options list' => 'rules_entity_action_type_options', + 'description' => t('Specifies the type of the entity that should be fetched.'), + 'restriction' => 'input', + ), + 'property' => array( + 'type' => 'text', + 'label' => t('Property'), + 'description' => t('The property by which the entity is to be selected.'), + 'restriction' => 'input', + ), + 'value' => array( + 'type' => 'unknown', + 'label' => t('Value'), + 'description' => t('The property value of the entity to be fetched.'), + ), + 'limit' => array( + 'type' => 'integer', + 'label' => t('Limit result count'), + 'description' => t('Limit the maximum number of fetched entities.'), + 'optional' => TRUE, + 'default value' => '10', + ), + ), + 'provides' => array( + 'entity_fetched' => array('type' => 'list', 'label' => t('Fetched entity')), + ), + 'group' => t('Entities'), + 'access callback' => 'rules_entity_action_access', + 'base' => 'rules_action_entity_query', + 'callbacks' => array( + 'form_alter' => 'rules_action_type_form_alter', + ), + ); + } + + if (rules_entity_action_type_options('entity_create')) { + $return['entity_create'] = array( + 'label' => t('Create a new entity'), + 'named parameter' => TRUE, + 'parameter' => array( + 'type' => array( + 'type' => 'text', + 'label' => t('Entity type'), + 'options list' => 'rules_entity_action_type_options', + 'description' => t('Specifies the type of the entity that should be created.'), + 'restriction' => 'input', + ), + // Further needed parameter depends on the type. + ), + 'provides' => array( + 'entity_created' => array( + 'type' => 'unknown', + 'label' => t('Created entity'), + 'save' => TRUE, + ), + ), + 'group' => t('Entities'), + 'access callback' => 'rules_entity_action_access', + 'base' => 'rules_action_entity_create', + 'callbacks' => array( + 'access' => 'rules_action_entity_createfetch_access', + 'form_alter' => 'rules_action_type_form_alter', + 'validate' => 'rules_action_create_type_validate', + ), + ); + } + + $return['entity_save'] = array( + 'label' => t('Save entity'), + 'parameter' => array( + 'data' => array( + 'type' => 'entity', + 'label' => t('Entity'), + 'description' => t('Specifies the entity, which should be saved permanently.'), + 'restriction' => 'selector', + 'wrapped' => TRUE, + ), + 'immediate' => array( + 'type' => 'boolean', + 'label' => t('Force saving immediately'), + 'description' => t('Usually saving is postponed till the end of the evaluation, so that multiple saves can be fold into one. If this set, saving is forced to happen immediately.'), + 'default value' => FALSE, + 'optional' => TRUE, + 'restriction' => 'input', + ), + ), + 'group' => t('Entities'), + 'access callback' => 'rules_entity_action_access', + 'base' => 'rules_action_entity_save', + 'callbacks' => array( + 'access' => 'rules_action_entity_savedelete_access', + ), + ); + + $return['entity_delete'] = array( + 'label' => t('Delete entity'), + 'parameter' => array( + 'data' => array( + 'type' => 'entity', + 'label' => t('Entity'), + 'description' => t('Specifies the entity, which should be deleted permanently.'), + 'restriction' => 'selector', + 'wrapped' => TRUE, + ), + ), + 'group' => t('Entities'), + 'access callback' => 'rules_entity_action_access', + 'base' => 'rules_action_entity_delete', + 'callbacks' => array( + 'access' => 'rules_action_entity_savedelete_access', + ), + ); + return $return; +} + +/** + * Custom access callback for data create and fetch action. + */ +function rules_action_entity_createfetch_access(RulesAbstractPlugin $element) { + $op = $element->getElementName() == 'entity_create' ? 'create' : 'view'; + return entity_access($op, $element->settings['type']); +} + +/** + * Custom access callback for the data query action. + */ +function rules_action_entity_query_access(RulesAbstractPlugin $element) { + if (!rules_action_entity_createfetch_access($element)) { + return FALSE; + } + $properties = entity_get_all_property_info($element->settings['type']); + if (isset($element->settings['property']) && isset($properties[$element->settings['property']]['access callback'])) { + return call_user_func($properties[$element->settings['property']]['access callback'], 'view', $element->settings['property'], $element->settings['type'], NULL, NULL); + } + return TRUE; +} + +/** + * Options list callback for a parameter of entity_create. + */ +function rules_action_entity_parameter_options_list(RulesPlugin $element, $param_name) { + // Remove the parameter name prefix 'param_'. + $property_name = substr($param_name, 6); + $wrapper = entity_metadata_wrapper($element->settings['type']); + // The possible values of the "value" parameter are those of the data param. + return $wrapper->$property_name->optionsList(); +} + +/** + * Custom access callback for data save and delete action. + */ +function rules_action_entity_savedelete_access(RulesAbstractPlugin $element) { + if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) { + $op = $element->getElementName() == 'entity_save' ? 'save' : 'delete'; + return $wrapper instanceof EntityDrupalWrapper && $wrapper->entityAccess($op); + } + return FALSE; +} + +/** + * Returns the options list for choosing a property of an entity type. + */ +function rules_action_entity_query_property_options_list(RulesAbstractPlugin $element) { + $element->settings += array('type' => NULL); + if ($element->settings['type']) { + $properties = entity_get_all_property_info($element->settings['type']); + return rules_extract_property($properties, 'label'); + } +} + +/** + * Returns the options list specified for the chosen property. + */ +function rules_action_entity_query_value_options_list(RulesAbstractPlugin $element) { + // Get the possible values for the selected property. + $element->settings += array('type' => NULL, 'property' => NULL); + if ($element->settings['type'] && $element->settings['property']) { + $wrapper = rules_get_entity_metadata_wrapper_all_properties($element); + + if (isset($wrapper->{$element->settings['property']}) && $property = $wrapper->{$element->settings['property']}) { + return $property->optionsList('view'); + } + } +} + +/** + * Options list callback for data actions. + * + * @param $element + * The element to return options for. + * @param $param + * The name of the parameter to return options for. + */ +function rules_entity_action_type_options($element, $name = NULL) { + // We allow calling this function with just the element name too. That way + // we ease manual re-use. + $name = is_object($element) ? $element->getElementName() : $element; + return ($name == 'entity_create') ? rules_entity_type_options('create') : rules_entity_type_options(); +} + +/** + * Returns options containing entity types having the given key set in the info. + * + * Additionally, we exclude all entity types that are marked as configuration. + */ +function rules_entity_type_options($key = NULL) { + $info = entity_get_info(); + $types = array(); + foreach ($info as $type => $entity_info) { + if (empty($entity_info['configuration']) && empty($entity_info['exportable'])) { + if (!isset($key) || entity_type_supports($type, $key)) { + $types[$type] = $entity_info['label']; + } + } + } + return $types; +} + +/** + * Options list callback for getting a list of possible entity bundles. + * + * @param $element + * The element to return options for. + */ +function rules_entity_bundle_options(RulesAbstractPlugin $element) { + $bundles = array(); + if (isset($element->settings['type'])) { + $entity_info = entity_get_info($element->settings['type']); + foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { + $bundles[$bundle_name] = $bundle_info['label']; + } + } + return $bundles; +} + +/** + * Entity actions access callback. + * + * Returns TRUE if at least one type is available for configuring the action. + */ +function rules_entity_action_access($type, $name) { + if ($name == 'entity_fetch' || $name == 'entity_create' || $name == 'entity_query') { + $types = array_keys(rules_entity_action_type_options($name)); + $op = $name == 'entity_create' ? 'create' : 'view'; + } + elseif ($name == 'entity_save' || $name == 'entity_delete') { + $types = array_keys(entity_get_info()); + $op = $name == 'entity_save' ? 'save' : 'delete'; + } + foreach ($types as $key => $type) { + if (!entity_access($op, $type)) { + unset($types[$key]); + } + } + return !empty($types); +} + +/** + * Implements hook_rules_condition_info() on behalf of the entity module. + * @see rules_core_modules() + */ +function rules_entity_condition_info() { + return array( + 'entity_is_new' => array( + 'label' => t('Entity is new'), + 'parameter' => array( + 'entity' => array( + 'type' => 'entity', + 'label' => t('Entity'), + 'description' => t('Specifies the entity for which to evaluate the condition.'), + 'restriction' => 'selector', + ), + ), + 'group' => t('Entities'), + 'base' => 'rules_condition_entity_is_new', + ), + 'entity_has_field' => array( + 'label' => t('Entity has field'), + 'parameter' => array( + 'entity' => array( + 'type' => 'entity', + 'label' => t('Entity'), + 'description' => t('Specifies the entity for which to evaluate the condition.'), + 'restriction' => 'selector', + ), + 'field' => array( + 'type' => 'text', + 'label' => t('Field'), + 'description' => t('The name of the field to check for.'), + 'options list' => 'rules_condition_entity_has_field_options', + 'restriction' => 'input', + ), + ), + 'group' => t('Entities'), + 'base' => 'rules_condition_entity_has_field', + ), + 'entity_is_of_type' => array( + 'label' => t('Entity is of type'), + 'parameter' => array( + 'entity' => array( + 'type' => 'entity', + 'label' => t('Entity'), + 'description' => t('Specifies the entity for which to evaluate the condition.'), + ), + 'type' => array( + 'type' => 'token', + 'label' => t('Entity type'), + 'description' => t('The entity type to check for.'), + 'options list' => 'rules_entity_action_type_options', + 'restriction' => 'input', + ), + ), + 'group' => t('Entities'), + 'base' => 'rules_condition_entity_is_of_type', + ), + 'entity_is_of_bundle' => array( + 'label' => t('Entity is of bundle'), + 'parameter' => array( + 'entity' => array( + 'type' => 'entity', + 'label' => t('Entity'), + 'description' => t('Specifies the entity for which to evaluate the condition.'), + ), + 'type' => array( + 'type' => 'token', + 'label' => t('Entity type'), + 'description' => t('The type of the checked entity.'), + 'options list' => 'rules_entity_action_type_options', + 'restriction' => 'input', + ), + 'bundle' => array( + 'type' => 'list', + 'label' => t('Entity bundle'), + 'description' => t('The condition is met if the entity is of one of the selected bundles.'), + 'options list' => 'rules_entity_bundle_options', + 'restriction' => 'input', + ), + ), + 'group' => t('Entities'), + 'base' => 'rules_condition_entity_is_of_bundle', + ), + 'entity_field_access' => array( + 'label' => t('User has field access'), + 'parameter' => array( + 'entity' => array( + 'type' => 'entity', + 'label' => t('Entity'), + 'description' => t('Specifies the entity for which to evaluate the condition.'), + 'restriction' => 'selector', + 'wrapped' => TRUE, + ), + 'field' => array( + 'type' => 'token', + 'label' => t('Field name'), + 'description' => t('The name of the field to check for.'), + 'options list' => 'rules_condition_entity_has_field_options', + 'restriction' => 'input', + ), + 'op' => array( + 'type' => 'text', + 'label' => t('Access operation'), + 'options list' => 'rules_condition_entity_field_access_op_options', + 'restriction' => 'input', + 'optional' => TRUE, + 'default value' => 'view', + ), + 'account' => array( + 'type' => 'user', + 'label' => t('User account'), + 'description' => t('Specifies the user account for which to check access. If left empty, the currently logged in user will be used.'), + 'restriction' => 'selector', + 'optional' => TRUE, + 'default value' => NULL, + ), + ), + 'group' => t('Entities'), + 'base' => 'rules_condition_entity_field_access', + ), + ); +} + +/** + * Help callback for condition entity_is_new. + */ +function rules_condition_entity_is_new_help() { + return t('This condition determines whether the specified entity has just been created and has not yet been saved to the database.'); +} + +/** + * Returns options for choosing a field for the selected entity. + */ +function rules_condition_entity_has_field_options(RulesAbstractPlugin $element) { + $options = array(); + foreach (field_info_fields() as $field_name => $field) { + $options[$field_name] = $field_name; + } + return $options; +} + +/** + * Returns options for choosing a field_access() operation. + */ +function rules_condition_entity_field_access_op_options(RulesAbstractPlugin $element) { + return array( + 'view' => t('View'), + 'edit' => t('Edit'), + ); +} + +/** + * Assert that the entity has the field, if there is metadata for the field. + */ +function rules_condition_entity_has_field_assertions($element) { + // Assert the field is there if the condition matches. + if ($wrapper = $element->applyDataSelector($element->settings['entity:select'])) { + $type = $wrapper->type(); + $field_property = $element->settings['field']; + // Get all possible properties and check whether we have one for the field. + $properties = entity_get_all_property_info($type == 'entity' ? NULL : $type); + + if (isset($properties[$field_property])) { + $assertion = array('property info' => array($field_property => $properties[$field_property])); + return array($element->settings['entity:select'] => $assertion); + } + } +} + +/** + * Assert the selected entity type. + */ +function rules_condition_entity_is_of_type_assertions($element) { + if ($type = $element->settings['type']) { + return array('entity' => array('type' => $type)); + } +} + +/** + * Assert the selected entity type and bundle. + */ +function rules_condition_entity_is_of_bundle_assertions($element) { + if ($bundle = $element->settings['bundle']) { + $assertions = array(); + $assertions['entity']['type'] = $element->settings['type']; + $assertions['entity']['bundle'] = $bundle; + return $assertions; + } +} + +/** + * Process callback for the condition entity_is_of_bundle. + */ +function rules_condition_entity_is_of_bundle_process(RulesAbstractPlugin $element) { + // If we know the entity type, auto-populate it. + if (($info = $element->getArgumentInfo('entity')) && $info['type'] != 'entity') { + $element->settings['type'] = $info['type']; + } +} + +/** + * Form alter callback for the condition entity_is_of_bundle. + * + * Use multiple steps to configure the condition as the needed bundle field list + * depends on the selected entity type. + */ +function rules_condition_entity_is_of_bundle_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) { + if (empty($element->settings['entity:select'])) { + $step = 1; + } + elseif (empty($element->settings['type'])) { + $step = 2; + } + else { + $step = 3; + } + + $form['reload'] = array( + '#weight' => $form['submit']['#weight'] + 1, + '#type' => 'submit', + '#name' => 'reload', + '#value' => $step != 3 ? t('Continue') : t('Reload form'), + '#limit_validation_errors' => array(array('parameter', 'entity'), array('parameter', 'type')), + '#submit' => array('rules_form_submit_rebuild'), + '#ajax' => rules_ui_form_default_ajax('fade'), + '#attributes' => array('class' => array('rules-hide-js')), + ); + // Use ajax and trigger as the reload button. + $form['parameter']['type']['settings']['type']['#ajax'] = $form['reload']['#ajax'] + array( + 'event' => 'change', + 'trigger_as' => array('name' => 'reload'), + ); + + switch ($step) { + case 1: + $form['reload']['#limit_validation_errors'] = array(array('parameter', 'entity')); + unset($form['parameter']['type']); + unset($form['reload']['#attributes']['class']); + // NO break; + case 2: + $form['negate']['#access'] = FALSE; + unset($form['parameter']['bundle']); + unset($form['submit']); + break; + case 3: + if (($info = $element->getArgumentInfo('entity')) && $info['type'] != 'entity') { + // Hide the entity type parameter if not needed. + unset($form['parameter']['type']); + } + break; + } +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/events.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/events.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,205 @@ +original)) { + return $entity->original; + } +} + +/** + * Generic entity events, used for core-entities for which we provide Rules + * integration only. + * We are implementing the generic-entity hooks instead of the entity-type + * specific hooks to ensure we come last. See http://drupal.org/node/1211946 + * for details. + */ + +/** + * Implements hook_entity_view(). + */ +function rules_entity_view($entity, $type, $view_mode, $langcode) { + switch ($type) { + case 'comment': + rules_invoke_event('comment_view--' . $entity->node_type, $entity, $view_mode); + rules_invoke_event('comment_view', $entity, $view_mode); + break; + case 'node': + rules_invoke_event('node_view--' . $entity->type, $entity, $view_mode); + rules_invoke_event('node_view', $entity, $view_mode); + break; + case 'user': + rules_invoke_event('user_view', $entity, $view_mode); + break; + } +} + +/** + * Implements hook_entity_presave(). + */ +function rules_entity_presave($entity, $type) { + switch ($type) { + case 'comment': + rules_invoke_event('comment_presave--' . $entity->node_type, $entity); + rules_invoke_event('comment_presave', $entity); + break; + case 'node': + rules_invoke_event('node_presave--' . $entity->type, $entity); + rules_invoke_event('node_presave', $entity); + break; + case 'taxonomy_term': + rules_invoke_event('taxonomy_term_presave--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_presave', $entity); + break; + case 'taxonomy_vocabulary': + case 'user': + rules_invoke_event($type . '_presave', $entity); + break; + } +} + +/** + * Implements hook_entity_update(). + */ +function rules_entity_update($entity, $type) { + switch ($type) { + case 'comment': + rules_invoke_event('comment_update--' . $entity->node_type, $entity); + rules_invoke_event('comment_update', $entity); + break; + case 'node': + rules_invoke_event('node_update--' . $entity->type, $entity); + rules_invoke_event('node_update', $entity); + break; + case 'taxonomy_term': + rules_invoke_event('taxonomy_term_update--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_update', $entity); + break; + case 'taxonomy_vocabulary': + case 'user': + rules_invoke_event($type . '_update', $entity); + break; + } +} + +/** + * Implements hook_entity_insert(). + */ +function rules_entity_insert($entity, $type) { + switch ($type) { + case 'comment': + rules_invoke_event('comment_insert--' . $entity->node_type, $entity); + rules_invoke_event('comment_insert', $entity); + break; + case 'node': + rules_invoke_event('node_insert--' . $entity->type, $entity); + rules_invoke_event('node_insert', $entity); + break; + case 'taxonomy_term': + rules_invoke_event('taxonomy_term_insert--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_insert', $entity); + break; + case 'taxonomy_vocabulary': + case 'user': + rules_invoke_event($type . '_insert', $entity); + break; + } +} + +/** + * Implements hook_entity_delete(). + */ +function rules_entity_delete($entity, $type) { + switch ($type) { + case 'comment': + rules_invoke_event('comment_delete--' . $entity->node_type, $entity); + rules_invoke_event('comment_delete', $entity); + break; + case 'node': + rules_invoke_event('node_delete--' . $entity->type, $entity); + rules_invoke_event('node_delete', $entity); + break; + case 'taxonomy_term': + rules_invoke_event('taxonomy_term_delete--' . $entity->vocabulary_machine_name, $entity); + rules_invoke_event('taxonomy_term_delete', $entity); + break; + case 'taxonomy_vocabulary': + case 'user': + rules_invoke_event($type . '_delete', $entity); + break; + } +} + +/** + * Implements hook_user_login(). + */ +function rules_user_login(&$edit, $account) { + rules_invoke_event('user_login', $account); +} + +/** + * Implements hook_user_logout(). + */ +function rules_user_logout($account) { + rules_invoke_event('user_logout', $account); +} + +/** + * System events. Note that rules_init() is the main module file is used to + * invoke the init event. + */ + +/** + * Implements hook_cron(). + */ +function rules_cron() { + rules_invoke_event('cron'); +} + +/** + * Implements hook_watchdog(). + */ +function rules_watchdog($log_entry) { + rules_invoke_event('watchdog', $log_entry); +} + +/** + * Getter callback for the log entry message property. + */ +function rules_system_log_get_message($log_entry) { + return t($log_entry['message'], (array)$log_entry['variables']); +} + +/** + * Gets all view modes of an entity for an entity_view event. + */ +function rules_get_entity_view_modes($name, $var_info) { + // Read the entity type from a special key out of the variable info. + $entity_type = $var_info['options list entity type']; + $info = entity_get_info($entity_type); + foreach ($info['view modes'] as $mode => $mode_info) { + $modes[$mode] = $mode_info['label']; + } + return $modes; +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/node.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/node.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,137 @@ + array( + 'node' => array('type' => 'node', 'label' => t('Content')), + ), + 'category' => 'node', + 'access callback' => 'rules_node_integration_access', + ); + } +} + +/** + * Condition: Check for selected content types + */ +class RulesNodeConditionType extends RulesNodeConditionBase { + + /** + * Defines the condition. + */ + public static function getInfo() { + $info = self::defaults() + array( + 'name' => 'node_is_of_type', + 'label' => t('Content is of type'), + 'help' => t('Evaluates to TRUE if the given content is of one of the selected content types.'), + ); + $info['parameter']['type'] = array( + 'type' => 'list', + 'label' => t('Content types'), + 'options list' => 'node_type_get_names', + 'description' => t('The content type(s) to check for.'), + 'restriction' => 'input', + ); + return $info; + } + + /** + * Executes the condition. + */ + public function execute($node, $types) { + return in_array($node->type, $types); + } + + /** + * Provides the content type of a node as asserted metadata. + */ + function assertions() { + return array('node' => array('bundle' => $this->element->settings['type'])); + } +} + +/** + * Condition: Check if the node is published. + */ +class RulesNodeConditionPublished extends RulesNodeConditionBase { + + /** + * Defines the condition. + */ + public static function getInfo() { + return self::defaults() + array( + 'name' => 'node_is_published', + 'label' => t('Content is published'), + ); + } + + /** + * Executes the condition. + */ + public function execute($node) { + return $node->status == 1; + } +} + +/** + * Condition: Check if the node is sticky. + */ +class RulesNodeConditionSticky extends RulesNodeConditionBase { + + /** + * Defines the condition. + */ + public static function getInfo() { + return self::defaults() + array( + 'name' => 'node_is_sticky', + 'label' => t('Content is sticky'), + ); + } + + /** + * Executes the condition. + */ + public function execute($node) { + return $node->sticky == 1; + } +} + +/** + * Condition: Check if the node is promoted to the frontpage + */ +class RulesNodeConditionPromoted extends RulesNodeConditionBase { + + /** + * Defines the condition. + */ + public static function getInfo() { + return self::defaults() + array( + 'name' => 'node_is_promoted', + 'label' => t('Content is promoted to frontpage'), + ); + } + + /** + * Executes the condition. + */ + public function execute($node) { + return $node->promote == 1; + } +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/node.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/node.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,161 @@ + array( + 'label' => t('Node'), + 'equals group' => t('Node'), + ), + ); +} + +/** + * Implements hook_rules_file_info() on behalf of the node module. + */ +function rules_node_file_info() { + return array('modules/node.eval'); +} + +/** + * Implements hook_rules_event_info() on behalf of the node module. + */ +function rules_node_event_info() { + $items = array( + 'node_insert' => array( + 'label' => t('After saving new content'), + 'category' => 'node', + 'variables' => rules_events_node_variables(t('created content')), + 'access callback' => 'rules_node_integration_access', + 'class' => 'RulesNodeEventHandler', + ), + 'node_update' => array( + 'label' => t('After updating existing content'), + 'category' => 'node', + 'variables' => rules_events_node_variables(t('updated content'), TRUE), + 'access callback' => 'rules_node_integration_access', + 'class' => 'RulesNodeEventHandler', + ), + 'node_presave' => array( + 'label' => t('Before saving content'), + 'category' => 'node', + 'variables' => rules_events_node_variables(t('saved content'), TRUE), + 'access callback' => 'rules_node_integration_access', + 'class' => 'RulesNodeEventHandler', + ), + 'node_view' => array( + 'label' => t('Content is viewed'), + 'category' => 'node', + 'help' => t("Note that if drupal's page cache is enabled, this event won't be generated for pages served from cache."), + 'variables' => rules_events_node_variables(t('viewed content')) + array( + 'view_mode' => array( + 'type' => 'text', + 'label' => t('view mode'), + 'options list' => 'rules_get_entity_view_modes', + // Add the entity-type for the options list callback. + 'options list entity type' => 'node', + ), + ), + 'access callback' => 'rules_node_integration_access', + 'class' => 'RulesNodeEventHandler', + ), + 'node_delete' => array( + 'label' => t('After deleting content'), + 'category' => 'node', + 'variables' => rules_events_node_variables(t('deleted content')), + 'access callback' => 'rules_node_integration_access', + 'class' => 'RulesNodeEventHandler', + ), + ); + // Specify that on presave the node is saved anyway. + $items['node_presave']['variables']['node']['skip save'] = TRUE; + return $items; +} + +/** + * Returns some parameter suitable for using it with a node + */ +function rules_events_node_variables($node_label, $update = FALSE) { + $args = array( + 'node' => array('type' => 'node', 'label' => $node_label), + ); + if ($update) { + $args += array( + 'node_unchanged' => array( + 'type' => 'node', + 'label' => t('unchanged content'), + 'handler' => 'rules_events_entity_unchanged', + ), + ); + } + return $args; +} + +/** + * Implements hook_rules_action_info() on behalf of the node module. + */ +function rules_node_action_info() { + $defaults = array( + 'parameter' => array( + 'node' => array('type' => 'node', 'label' => t('Content'), 'save' => TRUE), + ), + 'category' => 'node', + 'access callback' => 'rules_node_admin_access', + ); + // Add support for hand-picked core actions. + $core_actions = node_action_info(); + $actions = array('node_publish_action', 'node_unpublish_action', 'node_make_sticky_action', 'node_make_unsticky_action', 'node_promote_action', 'node_unpromote_action'); + foreach ($actions as $base) { + $action_name = str_replace('_action', '', $base); + $items[$action_name] = $defaults + array( + 'label' => $core_actions[$base]['label'], + 'base' => $base, + ); + } + return $items; +} + +/** + * Node integration access callback. + */ +function rules_node_integration_access($type, $name) { + if ($type == 'event' || $type == 'condition') { + return entity_access('view', 'node'); + } +} + +/** + * Node integration admin access callback. + */ +function rules_node_admin_access() { + return user_access('administer nodes'); +} + + +/** + * Event handler support node bundle event settings. + */ +class RulesNodeEventHandler extends RulesEventHandlerEntityBundle { + + /** + * Returns the label to use for the bundle property. + * + * @return string + */ + protected function getBundlePropertyLabel() { + return t('type'); + } +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/path.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/path.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,148 @@ + $source, 'language' => $langcode)); + } + elseif (!$source) { + path_delete(array('alias' => $alias, 'language' => $langcode)); + } + // Only set the alias if the alias is not taken yet. + elseif (!path_load(array('alias' => $alias, 'language' => $langcode))) { + // Update the existing path or create a new one. + if ($path = path_load(array('source' => $source, 'language' => $langcode))) { + $path['alias'] = $alias; + } + else { + $path = array('source' => $source, 'alias' => $alias, 'language' => $langcode); + } + path_save($path); + } + else { + rules_log('The configured alias %alias already exists. Aborting.', array('%alias' => $alias)); + } +} + +/** + * Action Implementation: Set the URL alias for a node. + */ +function rules_action_node_path_alias($node, $alias) { + $langcode = isset($node->language) ? $node->language : LANGUAGE_NONE; + // Only set the alias if the alias is not taken yet. + if (($path = path_load(array('alias' => $alias, 'language' => $langcode))) && (empty($node->path['pid']) || $node->path['pid'] != $path['pid'])) { + rules_log('The configured alias %alias already exists. Aborting.', array('%alias' => $alias)); + return FALSE; + } + $node->path['alias'] = $alias; +} + +/** + * Action Implementation: Set the URL alias for a node. + */ +function rules_action_taxonomy_term_path_alias($term, $alias) { + // Only set the alias if the alias is not taken yet. + if (($path = path_load(array('alias' => $alias, 'language' => LANGUAGE_NONE))) && (empty($term->path['pid']) || $term->path['pid'] != $path['pid'])) { + rules_log('The configured alias %alias already exists. Aborting.', array('%alias' => $alias)); + return FALSE; + } + $term->path['alias'] = $alias; +} + +/** + * Condition implementation: Check if the path has an alias. + */ +function rules_condition_path_has_alias($source, $langcode = LANGUAGE_NONE) { + return (bool) drupal_lookup_path('alias', $source, $langcode); +} + +/** + * Condition implementation: Check if the URL alias exists. + */ +function rules_condition_path_alias_exists($alias, $langcode = LANGUAGE_NONE) { + return (bool) drupal_lookup_path('source', $alias, $langcode); +} + +/** + * Cleans the given path by replacing non ASCII characters with the replacment character. + * + * Path cleaning may be adapted by overriding the configuration variables + * @code rules_clean_path @endcode, + * @code rules_path_replacement_char @endcode and + * @code rules_path_transliteration @endcode + * in the site's settings.php file. + */ +function rules_path_default_cleaning_method($path) { + $replace = variable_get('rules_path_replacement_char', '-'); + if ($replace) { + // If the transliteration module is enabled, transliterate the alias first. + if (module_exists('transliteration') && variable_get('rules_path_transliteration', TRUE)) { + $path = transliteration_get($path); + } + + $array = variable_get('rules_clean_path', array('/[^a-zA-Z0-9\-_]+/', $replace)); + $array[2] = $path; + // Replace it and remove trailing and leading replacement characters. + $output = trim(call_user_func_array('preg_replace', $array), $replace); + + if (variable_get('rules_path_lower_case', TRUE)) { + $output = drupal_strtolower($output); + } + return $output; + } + else { + return $path; + } +} + +/** + * Cleans the given string so it can be used as part of a URL path. + */ +function rules_clean_path($path) { + $function = variable_get('rules_path_cleaning_callback', 'rules_path_default_cleaning_method'); + if (!function_exists($function)) { + rules_log('An invalid URL path cleaning callback has been configured. Falling back to the default cleaning method.', array(), RulesLog::WARN); + $function = 'rules_path_default_cleaning_method'; + } + return $function($path); +} + +/** + * CTools path cleaning callback. + * + * @see rules_admin_settings() + */ +function rules_path_clean_ctools($path) { + // Make use of the CTools cleanstring implementation. + ctools_include('cleanstring'); + $settings = array( + 'separator' => variable_get('rules_path_replacement_char', '-'), + 'transliterate' => module_exists('transliteration') && variable_get('rules_path_transliteration', TRUE), + 'lower case' => variable_get('rules_path_lower_case', TRUE), + ); + return ctools_cleanstring($path, $settings); +} + +/** + * Pathauto path cleaning callback. + * + * @see rules_admin_settings() + */ +function rules_path_clean_pathauto($path) { + module_load_include('inc', 'pathauto'); + return pathauto_cleanstring($path); +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/path.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/path.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,171 @@ + array( + 'label' => t('Create or delete any URL alias'), + 'group' => t('Path'), + 'parameter' => array( + 'source' => array( + 'type' => 'text', + 'label' => t('Existing system path'), + 'description' => t('Specifies the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1+2.') .' '. t('Leave it empty to delete URL aliases pointing to the given path alias.'), + 'optional' => TRUE, + ), + 'alias' => array( + 'type' => 'text', + 'label' => t('URL alias'), + 'description' => t('Specify an alternative path by which this data can be accessed. For example, "about" for an about page. Use a relative path and do not add a trailing slash.') .' '. t('Leave it empty to delete URL aliases pointing to the given system path.'), + 'optional' => TRUE, + 'cleaning callback' => 'rules_path_clean_replacement_values', + ), + 'language' => array( + 'type' => 'token', + 'label' => t('Language'), + 'description' => t('If specified, the language for which the URL alias applies.'), + 'options list' => 'entity_metadata_language_list', + 'optional' => TRUE, + 'default value' => LANGUAGE_NONE, + ), + ), + 'base' => 'rules_action_path_alias', + 'callbacks' => array('dependencies' => 'rules_path_dependencies'), + 'access callback' => 'rules_path_integration_access', + ), + 'node_path_alias' => array( + 'label' => t("Create or delete a content's URL alias"), + 'group' => t('Path'), + 'parameter' => array( + 'node' => array( + 'type' => 'node', + 'label' => t('Content'), + 'save' => TRUE, + ), + 'alias' => array( + 'type' => 'text', + 'label' => t('URL alias'), + 'description' => t('Specify an alternative path by which the content can be accessed. For example, "about" for an about page. Use a relative path and do not add a trailing slash.') .' '. t('Leave it empty to delete the URL alias.'), + 'optional' => TRUE, + 'cleaning callback' => 'rules_path_clean_replacement_values', + ), + ), + 'base' => 'rules_action_node_path_alias', + 'callbacks' => array('dependencies' => 'rules_path_dependencies'), + 'access callback' => 'rules_path_integration_access', + ), + 'taxonomy_term_path_alias' => array( + 'label' => t("Create or delete a taxonomy term's URL alias"), + 'group' => t('Path'), + 'parameter' => array( + 'node' => array( + 'type' => 'taxonomy_term', + 'label' => t('Taxonomy term'), + 'save' => TRUE, + ), + 'alias' => array( + 'type' => 'text', + 'label' => t('URL alias'), + 'description' => t('Specify an alternative path by which the term can be accessed. For example, "content/drupal" for a Drupal term. Use a relative path and do not add a trailing slash.') .' '. t('Leave it empty to delete the URL alias.'), + 'optional' => TRUE, + 'cleaning callback' => 'rules_path_clean_replacement_values', + ), + ), + 'base' => 'rules_action_node_path_alias', + 'callbacks' => array('dependencies' => 'rules_path_dependencies'), + 'access callback' => 'rules_path_integration_access', + ), + ); +} + +/** + * Callback to specify the path module as dependency. + */ +function rules_path_dependencies() { + return array('path'); +} + +/** + * Path integration access callback. + */ +function rules_path_integration_access($type, $name) { + if ($type == 'action' && $name == 'path_alias') { + return user_access('administer url aliases'); + } + return user_access('create url aliases'); +} + +/** + * Implements hook_rules_condition_info() on behalf of the path module. + */ +function rules_path_condition_info() { + return array( + 'path_has_alias' => array( + 'label' => t('Path has URL alias'), + 'group' => t('Path'), + 'parameter' => array( + 'source' => array( + 'type' => 'text', + 'label' => t('Existing system path'), + 'description' => t('Specifies the existing path you wish to check for. For example: node/28, forum/1, taxonomy/term/1+2.'), + 'optional' => TRUE, + ), + 'language' => array( + 'type' => 'token', + 'label' => t('Language'), + 'description' => t('If specified, the language for which the URL alias applies.'), + 'options list' => 'entity_metadata_language_list', + 'optional' => TRUE, + 'default value' => LANGUAGE_NONE, + ), + ), + 'base' => 'rules_condition_path_has_alias', + 'callbacks' => array('dependencies' => 'rules_path_dependencies'), + 'access callback' => 'rules_path_integration_access', + ), + 'path_alias_exists' => array( + 'label' => t('URL alias exists'), + 'group' => t('Path'), + 'parameter' => array( + 'alias' => array( + 'type' => 'text', + 'label' => t('URL alias'), + 'description' => t('Specify the URL alias to check for. For example, "about" for an about page.'), + 'optional' => TRUE, + 'cleaning callback' => 'rules_path_clean_replacement_values', + ), + 'language' => array( + 'type' => 'token', + 'label' => t('Language'), + 'description' => t('If specified, the language for which the URL alias applies.'), + 'options list' => 'entity_metadata_language_list', + 'optional' => TRUE, + 'default value' => LANGUAGE_NONE, + ), + ), + 'base' => 'rules_condition_path_alias_exists', + 'callbacks' => array('dependencies' => 'rules_path_dependencies'), + 'access callback' => 'rules_path_integration_access', + ), + ); +} + +/** + * @} + */ \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/php.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/php.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,151 @@ + $info) { + if (strpos($text, '$' . $name) !== FALSE) { + $used_vars[] = $name; + } + } + return $used_vars; + } + } + + public function prepare($text, $var_info) { + // A returned NULL skips the evaluator. + $this->setting = self::getUsedVars($text, $var_info); + } + + /** + * Evaluates PHP code contained in $text. This doesn't apply $options, thus + * the PHP code is responsible for behaving appropriately. + */ + public function evaluate($text, $options, RulesState $state) { + $vars['eval_options'] = $options; + foreach ($this->setting as $key => $var_name) { + $vars[$var_name] = $state->get($var_name); + } + return rules_php_eval($text, rules_unwrap_data($vars)); + } + + public static function help($var_info) { + module_load_include('inc', 'rules', 'rules/modules/php.rules'); + + $render = array( + '#type' => 'fieldset', + '#title' => t('PHP Evaluation'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ) + rules_php_evaluator_help($var_info); + + return $render; + } +} + +/** + * A data processor using PHP. + */ +class RulesPHPDataProcessor extends RulesDataProcessor { + + protected static function form($settings, $var_info) { + $settings += array('code' => ''); + $form = array( + '#type' => 'fieldset', + '#title' => t('PHP evaluation'), + '#collapsible' => TRUE, + '#collapsed' => empty($settings['code']), + '#description' => t('Enter PHP code to process the selected argument value.'), + ); + $form['code'] = array( + '#type' => 'textarea', + '#title' => t('Code'), + '#description' => t('Enter PHP code without <?php ?> delimiters that returns the processed value. The selected value is available in the variable $value. Example: %code', array('%code' => 'return $value + 1;')), + '#default_value' => $settings['code'], + '#weight' => 5, + ); + return $form; + } + + public static function access() { + return user_access('use PHP for settings'); + } + + public function process($value, $info, RulesState $state, RulesPlugin $element) { + $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value; + return rules_php_eval_return($this->setting['code'], array('value' => $value)); + } +} + +/** + * Action and condition callback: Execute PHP code. + */ +function rules_execute_php_eval($code, $settings, $state, $element) { + $data = array(); + if (!empty($settings['used_vars'])) { + foreach ($settings['used_vars'] as $key => $var_name) { + $data[$var_name] = $state->get($var_name); + } + } + return rules_php_eval_return($code, rules_unwrap_data($data)); +} + +/** + * Evalutes the given PHP code, with the given variables defined. + * + * @param $code + * The PHP code to run, with + * @param $arguments + * Array containing variables to be extracted to the code. + * + * @return + * The output of the php code. + */ +function rules_php_eval($code, $arguments = array()) { + extract($arguments); + + ob_start(); + print eval('?>'. $code); + $output = ob_get_contents(); + ob_end_clean(); + + return $output; +} + +/** + * Evalutes the given PHP code, with the given variables defined. This is like + * rules_php_eval() but does return the returned data from the PHP code. + * + * @param $code + * The PHP code to run, without + * @param $arguments + * Array containing variables to be extracted to the code. + * + * @return + * The return value of the evaled code. + */ +function rules_php_eval_return($code, $arguments = array()) { + extract($arguments); + return eval($code); +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/php.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/php.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,158 @@ + array( + 'class' => 'RulesPHPEvaluator', + 'type' => array('text', 'uri'), + 'weight' => -10, + 'module' => 'php', + ), + ); +} + +/** + * Implements hook_rules_data_processor_info() on behalf of the php module. + */ +function rules_php_data_processor_info() { + return array( + 'php' => array( + 'class' => 'RulesPHPDataProcessor', + 'type' => array('text', 'token', 'decimal', 'integer', 'date', 'duration', 'boolean', 'uri'), + 'weight' => 10, + 'module' => 'php', + ), + ); +} + +/** + * Implements hook_rules_action_info() on behalf of the php module. + */ +function rules_php_action_info() { + return array( + 'php_eval' => array( + 'label' => t('Execute custom PHP code'), + 'group' => t('PHP'), + 'parameter' => array( + 'code' => array( + 'restriction' => 'input', + 'type' => 'text', + 'label' => t('PHP code'), + 'description' => t('Enter PHP code without <?php ?> delimiters.'), + ), + ), + 'base' => 'rules_execute_php_eval', + 'access callback' => 'rules_php_integration_access', + ), + ); +} + +/** + * Alter the form for improved UX. + */ +function rules_execute_php_eval_form_alter(&$form, &$form_state) { + // Remove the PHP evaluation help to avoid confusion whether settings['used_vars'] = RulesPHPEvaluator::getUsedVars('settings['code'], $element->availableVariables()); +} + +/** + * Specify the php module as dependency. + */ +function rules_execute_php_eval_dependencies() { + return array('php'); +} + +/** + * PHP integration access callback. + */ +function rules_php_integration_access() { + return user_access('use PHP for settings'); +} + +/** + * Implements hook_rules_condition_info() on behalf of the PHP module. + */ +function rules_php_condition_info() { + return array( + 'php_eval' => array( + 'label' => t('Execute custom PHP code'), + 'group' => t('PHP'), + 'parameter' => array( + 'code' => array( + 'restriction' => 'input', + 'type' => 'text', + 'label' => t('PHP code'), + 'description' => t('Enter PHP code without <?php ?> delimiters that returns a boolean value; e.g. @code.', array('@code' => "return arg(0) == 'node';")), + ), + ), + 'base' => 'rules_execute_php_eval', + 'access callback' => 'rules_php_integration_access', + ), + ); +} + +/** + * Generates help for the PHP actions, conditions and input evaluator. + */ +function rules_php_evaluator_help($var_info, $action_help = FALSE) { + $render['top'] = array( + '#prefix' => '

', + '#suffix' => '

', + '#markup' => t('PHP code inside of <?php ?> delimiters will be evaluated and replaced by its output. E.g. <? echo 1+1?> will be replaced by 2.') + . ' ' . t('Furthermore you can make use of the following variables:'), + ); + $render['vars'] = array( + '#theme' => 'table', + '#header' => array(t('Variable name'), t('Type'), t('Description')), + '#attributes' => array('class' => array('rules-php-help')), + ); + + $cache = rules_get_cache(); + foreach ($var_info as $name => $info) { + $row = array(); + $row[] = '$'. check_plain($name); + $label = isset($cache['data_info'][$info['type']]['label']) ? $cache['data_info'][$info['type']]['label'] : $info['type']; + $row[] = check_plain(drupal_ucfirst($label)); + $row[] = check_plain($info['label']); + $render['vars']['#rows'][] = $row; + } + + if ($action_help) { + $render['updated_help'] = array( + '#prefix' => '

', + '#suffix' => '

', + '#markup' => t("If you want to change a variable just return an array of new variable values, e.g.: !code", array('!code' => '
return array("node" => $node);
')), + ); + } + return $render; +} + +/** + * @} + */ \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/rules_core.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/rules_core.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,242 @@ +info(); + $state = $arguments['state']; + $wrapped_args = $state->currentArguments; + + if ($component = rules_get_cache('comp_' . $info['#config_name'])) { + $replacements = array('%label' => $component->label(), '@plugin' => $component->plugin()); + // Handle recursion prevention. + if ($state->isBlocked($component)) { + return rules_log('Not evaluating @plugin %label to prevent recursion.', $replacements, RulesLog::INFO, $component); + } + $state->block($component); + rules_log('Evaluating @plugin %label.', $replacements, RulesLog::INFO, $component, TRUE); + module_invoke_all('rules_config_execute', $component); + + // Manually create a new evaluation state and evaluate the component. + $args = array_intersect_key($wrapped_args, $component->parameterInfo()); + $new_state = $component->setUpState($wrapped_args); + $return = $component->evaluate($new_state); + + // Care for the right return value in case we have to provide vars. + if ($component instanceof RulesActionInterface && !empty($info['provides'])) { + $return = array(); + foreach ($info['provides'] as $var => $var_info) { + $return[$var] = $new_state->get($var); + } + } + + // Now merge the info about to be saved variables in the parent state. + $state->mergeSaveVariables($new_state, $component, $element->settings); + $state->unblock($component); + + // Cleanup the state, what saves not mergable variables now. + $new_state->cleanup(); + rules_log('Finished evaluation of @plugin %label.', $replacements, RulesLog::INFO, $component, FALSE); + return $return; + } + else { + throw new RulesEvaluationException('Unable to get the component %name', array('%name' => $info['#config_name']), $element, RulesLog::ERROR); + } +} + +/** + * A class implementing a rules input evaluator processing date input. This is + * needed to treat relative date inputs for strtotime right, consider "now". + */ +class RulesDateInputEvaluator extends RulesDataInputEvaluator { + + const DATE_REGEX_LOOSE = '/^(\d{4})-?(\d{2})-?(\d{2})([T\s]?(\d{2}):?(\d{2}):?(\d{2})?)?$/'; + + public function prepare($text, $var_info) { + if (is_numeric($text)) { + // Let rules skip this input evaluators in case it's already a timestamp. + $this->setting = NULL; + } + } + + public function evaluate($text, $options, RulesState $state) { + return self::gmstrtotime($text); + } + + /** + * Convert a time string to a GMT (UTC) unix timestamp. + */ + public static function gmstrtotime($date) { + // Pass the current timestamp in UTC to ensure the retrieved time is UTC. + return strtotime($date, time()); + } + + /** + * Determine whether the given date string specifies a fixed date. + */ + public static function isFixedDateString($date) { + return is_string($date) && preg_match(self::DATE_REGEX_LOOSE, $date); + } +} + +/** + * A class implementing a rules input evaluator processing URI inputs to make + * sure URIs are absolute and path aliases get applied. + */ +class RulesURIInputEvaluator extends RulesDataInputEvaluator { + + public function prepare($uri, $var_info) { + if (!isset($this->processor) && valid_url($uri, TRUE)) { + // Only process if another evaluator is used or the url is not absolute. + $this->setting = NULL; + } + } + + public function evaluate($uri, $options, RulesState $state) { + if (!url_is_external($uri)) { + // Extract the path and build the URL using the url() function, so URL + // aliases are applied and query parameters and fragments get handled. + $url = drupal_parse_url($uri); + $url_options = array('absolute' => TRUE); + $url_options['query'] = $url['query']; + $url_options['fragment'] = $url['fragment']; + return url($url['path'], $url_options); + } + elseif (valid_url($uri)) { + return $uri; + } + throw new RulesEvaluationException('Input evaluation generated an invalid URI.', array(), NULL, RulesLog::WARN); + } +} + +/** + * A data processor for applying date offsets. + */ +class RulesDateOffsetProcessor extends RulesDataProcessor { + + protected static function form($settings, $var_info) { + $settings += array('value' => ''); + $form = array( + '#type' => 'fieldset', + '#title' => t('Add offset'), + '#collapsible' => TRUE, + '#collapsed' => empty($settings['value']), + '#description' => t('Add an offset to the selected date.'), + ); + $form['value'] = array( + '#type' => 'rules_duration', + '#title' => t('Offset'), + '#description' => t('Note that you can also specify negative numbers.'), + '#default_value' => $settings['value'], + '#weight' => 5, + ); + return $form; + } + + public function process($value, $info, RulesState $state, RulesPlugin $element) { + $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value; + return RulesDateOffsetProcessor::applyOffset($value, $this->setting['value']); + } + + /** + * Intelligently applies the given date offset in seconds. + * + * Intelligently apply duration values > 1 day, i.e. convert the duration + * to its biggest possible unit (months, days) and apply it to the date with + * the given unit. That's necessary as the number of days in a month + * differs, as well as the number of hours for a day (on DST changes). + */ + public static function applyOffset($timestamp, $offset) { + if (abs($offset) >= 86400) { + + // Get the days out of the seconds. + $days = intval($offset / 86400); + $sec = $offset % 86400; + // Get the months out of the number of days. + $months = intval($days / 30); + $days = $days % 30; + + // Apply the offset using the DateTime::modify and convert it back to a + // timestamp. + $date = date_create("@$timestamp"); + $date->modify("$months months $days days $sec seconds"); + return $date->format('U'); + } + else { + return $timestamp + $offset; + } + } +} + +/** + * A data processor for applying numerical offsets. + */ +class RulesNumericOffsetProcessor extends RulesDataProcessor { + + protected static function form($settings, $var_info) { + $settings += array('value' => ''); + $form = array( + '#type' => 'fieldset', + '#title' => t('Add offset'), + '#collapsible' => TRUE, + '#collapsed' => empty($settings['value']), + '#description' => t('Add an offset to the selected number. E.g. an offset of "1" adds 1 to the number before it is passed on as argument.'), + ); + $form['value'] = array( + '#type' => 'textfield', + '#title' => t('Offset'), + '#description' => t('Note that you can also specify negative numbers.'), + '#default_value' => $settings['value'], + '#element_validate' => array('rules_ui_element_integer_validate'), + '#weight' => 5, + ); + return $form; + } + + public function process($value, $info, RulesState $state, RulesPlugin $element) { + $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element) : $value; + return $value + $this->setting['value']; + } +} + + +/** + * A custom wrapper class for vocabularies that is capable of loading vocabularies by machine name. + */ +class RulesTaxonomyVocabularyWrapper extends EntityDrupalWrapper { + + /** + * Overridden to support identifying vocabularies by machine names. + */ + protected function setEntity($data) { + if (isset($data) && $data !== FALSE && !is_object($data) && !is_numeric($data)) { + // The vocabulary name has been passed. + parent::setEntity(taxonomy_vocabulary_machine_name_load($data)); + } + else { + parent::setEntity($data); + } + } + + /** + * Overriden to permit machine names as values. + */ + public function validate($value) { + if (isset($value) && is_string($value)) { + return TRUE; + } + return parent::validate($value); + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/rules_core.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/rules_core.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,332 @@ + array( + 'label' => t('Components'), + 'equals group' => t('Components'), + 'weight' => 50, + ), + ); +} + +/** + * Implements hook_rules_file_info() on behalf of the pseudo rules_core module. + * + * @see rules_core_modules() + */ +function rules_rules_core_file_info() { + return array('modules/rules_core.eval'); +} + +/** + * Implements hook_rules_data_info() on behalf of the pseudo rules_core module. + * + * @see rules_core_modules() + */ +function rules_rules_core_data_info() { + $return = array( + 'text' => array( + 'label' => t('text'), + 'ui class' => 'RulesDataUIText', + 'token type' => 'rules_text', + ), + 'token' => array( + 'label' => t('text token'), + 'parent' => 'text', + 'ui class' => 'RulesDataUITextToken', + 'token type' => 'rules_token', + ), + // A formatted text as used by entity metadata. + 'text_formatted' => array( + 'label' => t('formatted text'), + 'ui class' => 'RulesDataUITextFormatted', + 'wrap' => TRUE, + 'property info' => entity_property_text_formatted_info(), + ), + 'decimal' => array( + 'label' => t('decimal number'), + 'parent' => 'text', + 'ui class' => 'RulesDataUIDecimal', + 'token type' => 'rules_decimal', + ), + 'integer' => array( + 'label' => t('integer'), + 'class' => 'RulesIntegerWrapper', + 'parent' => 'decimal', + 'ui class' => 'RulesDataUIInteger', + 'token type' => 'rules_integer', + ), + 'date' => array( + 'label' => t('date'), + 'ui class' => 'RulesDataUIDate', + 'token type' => 'rules_date', + ), + 'duration' => array( + 'label' => t('duration'), + 'parent' => 'integer', + 'ui class' => 'RulesDataUIDuration', + 'token type' => 'rules_duration', + ), + 'boolean' => array( + 'label' => t('truth value'), + 'ui class' => 'RulesDataUIBoolean', + 'token type' => 'rules_boolean', + ), + 'uri' => array( + 'label' => t('URI'), + 'parent' => 'text', + // Clean inserted tokens with "rawurlencode". + 'cleaning callback' => 'rawurlencode', + 'ui class' => 'RulesDataUIURI', + 'token type' => 'rules_uri', + ), + 'list' => array( + 'label' => t('list', array(), array('context' => 'data_types')), + 'wrap' => TRUE, + 'group' => t('List', array(), array('context' => 'data_types')), + ), + 'list' => array( + 'label' => t('list of text'), + 'ui class' => 'RulesDataUIListText', + 'wrap' => TRUE, + 'group' => t('List', array(), array('context' => 'data_types')), + ), + 'list' => array( + 'label' => t('list of integer'), + 'ui class' => 'RulesDataUIListInteger', + 'wrap' => TRUE, + 'group' => t('List', array(), array('context' => 'data_types')), + ), + 'list' => array( + 'label' => t('list of text tokens'), + 'ui class' => 'RulesDataUIListToken', + 'wrap' => TRUE, + 'group' => t('List', array(), array('context' => 'data_types')), + ), + 'entity' => array( + 'label' => t('any entity'), + 'group' => t('Entity'), + 'is wrapped' => TRUE, + ), + ); + foreach (entity_get_info() as $type => $info) { + if (!empty($info['label'])) { + $return[$type] = array( + 'label' => strtolower($info['label'][0]) . substr($info['label'], 1), + 'parent' => 'entity', + 'wrap' => TRUE, + 'group' => t('Entity'), + 'ui class' => empty($info['exportable']) ? 'RulesDataUIEntity' : 'RulesDataUIEntityExportable', + ); + // If this entity type serves as bundle for another one, provide an + // options list for selecting a bundle entity. + if (!empty($info['bundle of'])) { + $return[$type]['ui class'] = 'RulesDataUIBundleEntity'; + } + } + } + + if (module_exists('taxonomy')) { + // For exportability identify vocabularies by name. + $return['taxonomy_vocabulary']['wrapper class'] = 'RulesTaxonomyVocabularyWrapper'; + $return['taxonomy_vocabulary']['ui class'] = 'RulesDataUITaxonomyVocabulary'; + } + + return $return; +} + +/** + * Implements hook_rules_data_info_alter() on behalf of the pseudo rules_core module. + * + * Makes sure there is a list data type for each type registered. + * + * @see rules_rules_data_info_alter() + */ +function rules_rules_core_data_info_alter(&$data_info) { + foreach ($data_info as $type => $info) { + if (!entity_property_list_extract_type($type)) { + $list_type = "list<$type>"; + if (!isset($data_info[$list_type])) { + $data_info[$list_type] = array( + 'label' => t('list of @type_label items', array('@type_label' => $info['label'])), + 'wrap' => TRUE, + 'group' => t('List', array(), array('context' => 'data_types')), + ); + if (isset($info['parent']) && $info['parent'] == 'entity') { + $data_info[$list_type]['ui class'] = 'RulesDataUIListEntity'; + } + } + } + } +} + +/** + * Implements hook_rules_evaluator_info() on behalf of the pseudo rules_core + * module. + * + * @see rules_core_modules() + */ +function rules_rules_core_evaluator_info() { + return array( + // Process strtotime() inputs to timestamps. + 'date' => array( + 'class' => 'RulesDateInputEvaluator', + 'type' => 'date', + 'weight' => -10, + ), + // Post-process any input value to absolute URIs. + 'uri' => array( + 'class' => 'RulesURIInputEvaluator', + 'type' => 'uri', + 'weight' => 50, + ), + ); +} + +/** + * Implements hook_rules_data_processor_info() on behalf of the pseudo + * rules_core module. + * + * @see rules_core_modules() + */ +function rules_rules_core_data_processor_info() { + return array( + 'date_offset' => array( + 'class' => 'RulesDateOffsetProcessor', + 'type' => 'date', + 'weight' => -2, + ), + 'num_offset' => array( + 'class' => 'RulesNumericOffsetProcessor', + 'type' => array('integer', 'decimal'), + 'weight' => -2, + ), + ); +} + +/** + * Implements hook_rules_condition_info() on behalf of the pseudo rules_core + * module. + * + * @see rules_core_modules() + */ +function rules_rules_core_condition_info() { + $defaults = array( + 'group' => t('Components'), + 'base' => 'rules_element_invoke_component', + 'named parameter' => TRUE, + 'access callback' => 'rules_element_invoke_component_access_callback', + ); + $items = array(); + foreach (rules_get_components(FALSE, 'condition') as $name => $config) { + $items['component_' . $name] = $defaults + array( + 'label' => $config->plugin() . ': ' . drupal_ucfirst($config->label()), + 'parameter' => $config->parameterInfo(), + ); + $items['component_' . $name]['#config_name'] = $name; + } + return $items; +} + +/** + * Implements hook_rules_action_info() on behalf of the pseudo rules_core + * module. + * + * @see rules_core_modules() + */ +function rules_rules_core_action_info() { + $defaults = array( + 'group' => t('Components'), + 'base' => 'rules_element_invoke_component', + 'named parameter' => TRUE, + 'access callback' => 'rules_element_invoke_component_access_callback', + ); + $items = array(); + foreach (rules_get_components(FALSE, 'action') as $name => $config) { + $items['component_' . $name] = $defaults + array( + 'label' => $config->plugin() . ': ' . drupal_ucfirst($config->label()), + 'parameter' => $config->parameterInfo(), + 'provides' => $config->providesVariables(), + ); + $items['component_' . $name]['#config_name'] = $name; + } + return $items; +} + +/** + * Implements RulesPluginUIInterface::operations() for the action. + */ +function rules_element_invoke_component_operations(RulesPlugin $element) { + $defaults = $element->extender('RulesPluginUI')->operations(); + $info = $element->info(); + + // Add an operation for editing the component. + $defaults['#links']['component'] = array( + 'title' => t('edit component'), + 'href' => RulesPluginUI::path($info['#config_name']), + ); + return $defaults; +} + +/** + * Validate callback to make sure the invoked component exists and is not dirty. + * + * @see rules_scheduler_action_schedule_validate() + */ +function rules_element_invoke_component_validate(RulesPlugin $element) { + $info = $element->info(); + $component = rules_config_load($info['#config_name']); + // Check if a component exists. + if (!$component) { + throw new RulesIntegrityException(t('The component %config does not exist.', array('%config' => $info['#config_name'])), $element); + } + // Check if a component is marked as dirty. + rules_config_update_dirty_flag($component); + if (!empty($component->dirty)) { + throw new RulesIntegrityException(t('The utilized component %config fails the integrity check.', array('%config' => $info['#config_name'])), $element); + } +} + +/** + * Implements the features export callback of the RulesPluginFeaturesIntegrationInterace. + */ +function rules_element_invoke_component_features_export(&$export, &$pipe, $module_name = '', $element) { + // Add the used component to the pipe. + $info = $element->info(); + $pipe['rules_config'][] = $info['#config_name']; +} + +/** + * Access callback for the invoke component condition/action. + */ +function rules_element_invoke_component_access_callback($type, $name) { + // Cut of the leading 'component_' from the action name. + $component = rules_config_load(substr($name, 10)); + + if (!$component) { + // Missing component. + return FALSE; + } + // If access is not exposed for this component, default to component access. + if (empty($component->access_exposed)) { + return $component->access(); + } + // Apply the permissions. + return user_access('bypass rules access') || user_access("use Rules component $component->name"); +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/system.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/system.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,267 @@ + $title) { + // Skip empty titles. + if ($title = trim($title)) { + // Output plaintext instead of a link if there is a title + // without a path. + $path = trim($paths[$i]); + if (!empty($paths[$i]) && $paths[$i] != '') { + $trail[] = l($title, trim($paths[$i])); + } + else { + $trail[] = check_plain($title); + } + } + } + drupal_set_breadcrumb($trail); +} + +/** + * Action Implementation: Send mail. + */ +function rules_action_mail($to, $subject, $message, $from = NULL, $langcode, $settings, RulesState $state, RulesPlugin $element) { + $to = str_replace(array("\r", "\n"), '', $to); + $from = !empty($from) ? str_replace(array("\r", "\n"), '', $from) : NULL; + $params = array( + 'subject' => $subject, + 'message' => $message, + 'langcode' => $langcode, + ); + // Set a unique key for this mail. + $name = isset($element->root()->name) ? $element->root()->name : 'unnamed'; + $key = 'rules_action_mail_' . $name . '_' . $element->elementId(); + $languages = language_list(); + $language = isset($languages[$langcode]) ? $languages[$langcode] : language_default(); + + $message = drupal_mail('rules', $key, $to, $language, $params, $from); + if ($message['result']) { + watchdog('rules', 'Successfully sent email to %recipient', array('%recipient' => $to)); + } +} + +/** + * Action: Send mail to all users of a specific role group(s). + */ +function rules_action_mail_to_users_of_role($roles, $subject, $message, $from = NULL, $settings, RulesState $state, RulesPlugin $element) { + $from = !empty($from) ? str_replace(array("\r", "\n"), '', $from) : NULL; + + // All authenticated users, which is everybody. + if (in_array(DRUPAL_AUTHENTICATED_RID, $roles)) { + $result = db_query('SELECT mail FROM {users} WHERE uid > 0'); + } + else { + $rids = implode(',', $roles); + // Avoid sending emails to members of two or more target role groups. + $result = db_query('SELECT DISTINCT u.mail FROM {users} u INNER JOIN {users_roles} r ON u.uid = r.uid WHERE r.rid IN ('. $rids .')'); + } + + // Now, actually send the mails. + $params = array( + 'subject' => $subject, + 'message' => $message, + ); + // Set a unique key for this mail. + $name = isset($element->root()->name) ? $element->root()->name : 'unnamed'; + $key = 'rules_action_mail_to_users_of_role_' . $name . '_' . $element->elementId(); $languages = language_list(); + + $message = array('result' => TRUE); + foreach ($result as $row) { + $message = drupal_mail('rules', $key, $row->mail, language_default(), $params, $from); + if (!$message['result']) { + break; + } + } + if ($message['result']) { + $role_names = array_intersect_key(user_roles(TRUE), array_flip($roles)); + watchdog('rules', 'Successfully sent email to the role(s) %roles.', array('%roles' => implode(', ', $role_names))); + } +} + +/** + * Implements hook_mail(). + * + * Set's the message subject and body as configured. + */ +function rules_mail($key, &$message, $params) { + + $message['subject'] .= str_replace(array("\r", "\n"), '', $params['subject']); + $message['body'][] = $params['message']; +} + +/** + * A class implementing a rules input evaluator processing tokens. + */ +class RulesTokenEvaluator extends RulesDataInputEvaluator { + + public function prepare($text, $var_info) { + $text = is_array($text) ? implode('', $text) : $text; + // Skip this evaluator if there are no tokens. + $this->setting = token_scan($text) ? TRUE : NULL; + } + + /** + * We replace the tokens on our own as we cannot use token_replace(), because + * token usually assumes that $data['node'] is a of type node, which doesn't + * hold in general in our case. + * So we properly map variable names to variable data types and then run the + * replacement ourself. + */ + public function evaluate($text, $options, RulesState $state) { + $var_info = $state->varInfo(); + $options += array('sanitize' => FALSE); + + $replacements = array(); + $data = array(); + // We also support replacing tokens in a list of textual values. + $whole_text = is_array($text) ? implode('', $text) : $text; + foreach (token_scan($whole_text) as $var_name => $tokens) { + $var_name = str_replace('-', '_', $var_name); + if (isset($var_info[$var_name]) && ($token_type = _rules_system_token_map_type($var_info[$var_name]['type']))) { + // We have to key $data with the type token uses for the variable. + $data = rules_unwrap_data(array($token_type => $state->get($var_name)), array($token_type => $var_info[$var_name])); + $replacements += token_generate($token_type, $tokens, $data, $options); + } + else { + $replacements += token_generate($var_name, $tokens, array(), $options); + } + // Remove tokens if no replacement value is found. As token_replace() does + // if 'clear' is set. + $replacements += array_fill_keys($tokens, ''); + } + + // Optionally clean the list of replacement values. + if (!empty($options['callback']) && function_exists($options['callback'])) { + $function = $options['callback']; + $function($replacements, $data, $options); + } + + // Actually apply the replacements. + $tokens = array_keys($replacements); + $values = array_values($replacements); + if (is_array($text)) { + foreach ($text as $i => $text_item) { + $text[$i] = str_replace($tokens, $values, $text_item); + } + return $text; + } + return str_replace($tokens, $values, $text); + } + + /** + * Create documentation about the available replacement patterns. + * + * @param array $var_info + * Array with the available variables. + * + * @return array + * Renderable array with the replacement pattern documentation. + */ + public static function help($var_info) { + $render = array( + '#type' => 'fieldset', + '#title' => t('Replacement patterns'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => t('Note that token replacements containing chained objects – such as [node:author:uid] – are not listed here, but are still available. The data selection input mode may help you find more complex replacement patterns. See the online documentation for more information about complex replacement patterns.', + array('@url' => rules_external_help('chained-tokens'))), + ); + $token_info = token_info(); + foreach ($var_info as $name => $info) { + $token_types[$name] = _rules_system_token_map_type($info['type']); + } + + foreach ($token_types as $name => $token_type) { + if (isset($token_info['types'][$token_type])) { + $render[$name] = array( + '#theme' => 'table', + '#header' => array(t('Token'), t('Label'), t('Description')), + '#prefix' => '

' . t('Replacement patterns for %label', array('%label' => $var_info[$name]['label'])) . '

', + ); + foreach ($token_info['tokens'][$token_type] as $token => $info) { + $token = '[' . str_replace('_', '-', $name) . ':' . $token . ']'; + $render[$name]['#rows'][$token] = array( + check_plain($token), + check_plain($info['name']), + check_plain($info['description']), + ); + } + } + } + return $render; + } +} + +/** + * Looks for a token type mapping. Defaults to passing through the type. + */ +function _rules_system_token_map_type($type) { + $entity_info = entity_get_info(); + if (isset($entity_info[$type]['token type'])) { + return $entity_info[$type]['token type']; + } + $cache = rules_get_cache(); + if (isset($cache['data_info'][$type]['token type'])) { + return $cache['data_info'][$type]['token type']; + } + return $type; +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/system.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/system.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,288 @@ + array( + 'label' => t('Drupal is initializing'), + 'group' => t('System'), + 'help' => t("Be aware that some actions might initialize the theme system. After that, it's impossible for any module to change the used theme."), + 'access callback' => 'rules_system_integration_access', + ), + 'cron' => array( + 'label' => t('Cron maintenance tasks are performed'), + 'group' => t('System'), + 'access callback' => 'rules_system_integration_access', + ), + 'watchdog' => array( + 'label' => t('System log entry is created'), + 'variables' => array( + 'log_entry' => array( + 'type' => 'log_entry', + 'label' => t('Log entry'), + ), + ), + 'group' => t('System'), + 'access callback' => 'rules_system_integration_access', + ), + ); +} + +/** + * Implements hook_rules_data_info() on behalf of the system module. + * @see rules_core_modules() + */ +function rules_system_data_info() { + return array( + 'log_entry' => array( + 'label' => t('watchdog log entry'), + 'wrap' => TRUE, + 'property info' => _rules_system_watchdog_log_entry_info(), + ), + ); +} + +/** + * Defines property info for watchdog log entries, used by the log entry data + * type to provide an useful metadata wrapper. + */ +function _rules_system_watchdog_log_entry_info() { + return array( + 'type' => array( + 'type' => 'text', + 'label' => t('The category to which this message belongs'), + ), + 'message' => array( + 'type' => 'text', + 'label' => ('Log message'), + 'getter callback' => 'rules_system_log_get_message', + 'sanitized' => TRUE, + ), + 'severity' => array( + 'type' => 'integer', + 'label' => t('Severity'), + 'options list' => 'watchdog_severity_levels', + ), + 'request_uri' => array( + 'type' => 'uri', + 'label' => t('Request uri'), + ), + 'link' => array( + 'type' => 'text', + 'label' => t('An associated, HTML formatted link'), + ), + ); +} + +/** + * Implements hook_rules_action_info() on behalf of the system module. + */ +function rules_system_action_info() { + return array( + 'drupal_message' => array( + 'label' => t('Show a message on the site'), + 'group' => t('System'), + 'parameter' => array( + 'message' => array( + 'type' => 'text', + 'label' => t('Message'), + 'sanitize' => TRUE, + 'translatable' => TRUE, + ), + 'type' => array( + 'type' => 'token', + 'label' => t('Message type'), + 'options list' => 'rules_action_drupal_message_types', + 'default value' => 'status', + 'optional' => TRUE, + ), + 'repeat' => array( + 'type' => 'boolean', + 'label' => t('Repeat message'), + 'description' => t("If disabled and the message has been already shown, then the message won't be repeated."), + 'default value' => TRUE, + 'optional' => TRUE, + 'restriction' => 'input', + ), + ), + 'base' => 'rules_action_drupal_message', + 'access callback' => 'rules_system_integration_access', + ), + 'redirect' => array( + 'label' => t('Page redirect'), + 'group' => t('System'), + 'parameter' => array( + 'url' => array( + 'type' => 'uri', + 'label' => t('URL'), + 'description' => t('A Drupal path, path alias, or external URL to redirect to. Enter (optional) queries after "?" and (optional) anchor after "#".'), + ), + 'force' => array( + 'type' => 'boolean', + 'label' => t('Force redirect'), + 'restriction' => 'input', + 'description' => t("Force the redirect even if another destination parameter is present. Per default Drupal would redirect to the path given as destination parameter, in case it is set. Usually the destination parameter is set by appending it to the URL, e.g. !example_url", array('!example_url' => 'http://example.com/user/login?destination=node/2')), + 'optional' => TRUE, + 'default value' => TRUE, + ), + 'destination' => array( + 'type' => 'boolean', + 'label' => t('Append destination parameter'), + 'restriction' => 'input', + 'description' => t('Whether to append a destination parameter to the URL, so another redirect issued later on would lead back to the origin page.'), + 'optional' => TRUE, + 'default value' => FALSE, + ), + ), + 'base' => 'rules_action_drupal_goto', + 'access callback' => 'rules_system_integration_access', + ), + 'breadcrumb_set' => array( + 'label' => t('Set breadcrumb'), + 'group' => t('System'), + 'parameter' => array( + 'titles' => array( + 'type' => 'list', + 'label' => t('Titles'), + 'description' => t('A list of titles for the breadcrumb links.'), + 'translatable' => TRUE, + ), + 'paths' => array( + 'type' => 'list', + 'label' => t('Paths'), + 'description' => t('A list of Drupal paths for the breadcrumb links, matching the order of the titles.'), + ), + ), + 'base' => 'rules_action_breadcrumb_set', + 'access callback' => 'rules_system_integration_access', + ), + 'mail' => array( + 'label' => t('Send mail'), + 'group' => t('System'), + 'parameter' => array( + 'to' => array( + 'type' => 'text', + 'label' => t('To'), + 'description' => t('The e-mail address or addresses where the message will be sent to. The formatting of this string must comply with RFC 2822.'), + ), + 'subject' => array( + 'type' => 'text', + 'label' => t('Subject'), + 'description' => t("The mail's subject."), + 'translatable' => TRUE, + ), + 'message' => array( + 'type' => 'text', + 'label' => t('Message'), + 'description' => t("The mail's message body."), + 'translatable' => TRUE, + ), + 'from' => array( + 'type' => 'text', + 'label' => t('From'), + 'description' => t("The mail's from address. Leave it empty to use the site-wide configured address."), + 'optional' => TRUE, + ), + 'language' => array( + 'type' => 'token', + 'label' => t('Language'), + 'description' => t('If specified, the language used for getting the mail message and subject.'), + 'options list' => 'entity_metadata_language_list', + 'optional' => TRUE, + 'default value' => LANGUAGE_NONE, + 'default mode' => 'selector', + ), + ), + 'base' => 'rules_action_mail', + 'access callback' => 'rules_system_integration_access', + ), + 'mail_to_users_of_role' => array( + 'label' => t('Send mail to all users of a role'), + 'group' => t('System'), + 'parameter' => array( + 'roles' => array( + 'type' => 'list', + 'label' => t('Roles'), + 'options list' => 'entity_metadata_user_roles', + 'description' => t('Select the roles whose users should receive the mail.'), + ), + 'subject' => array( + 'type' => 'text', + 'label' => t('Subject'), + 'description' => t("The mail's subject."), + ), + 'message' => array( + 'type' => 'text', + 'label' => t('Message'), + 'description' => t("The mail's message body."), + ), + 'from' => array( + 'type' => 'text', + 'label' => t('From'), + 'description' => t("The mail's from address. Leave it empty to use the site-wide configured address."), + 'optional' => TRUE, + ), + ), + 'base' => 'rules_action_mail_to_users_of_role', + 'access callback' => 'rules_system_integration_access', + ), + ); +} + +/** + * Help callback for the "Send mail to users of a role" action. + */ +function rules_action_mail_to_users_of_role_help() { + return t('WARNING: This may cause problems if there are too many users of these roles on your site, as your server may not be able to handle all the mail requests all at once.'); +} + +/** + * System integration access callback. + */ +function rules_system_integration_access($type, $name) { + return user_access('administer site configuration'); +} + +/** + * Options list callback defining drupal_message types. + */ +function rules_action_drupal_message_types() { + return array( + 'status' => t('Status'), + 'warning' => t('Warning'), + 'error' => t('Error'), + ); +} + +/** + * Implements hook_rules_evaluator_info() on behalf of the system module. + */ +function rules_system_evaluator_info() { + return array( + 'token' => array( + 'class' => 'RulesTokenEvaluator', + 'type' => array('text', 'uri', 'list', 'list'), + 'weight' => 0, + ), + ); +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/taxonomy.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/taxonomy.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,116 @@ + t('Taxonomy'), + 'access callback' => 'rules_taxonomy_term_integration_access', + 'module' => 'taxonomy', + 'class' => 'RulesTaxonomyEventHandler', + ); + $defaults_vocab = array( + 'group' => t('Taxonomy'), + 'access callback' => 'rules_taxonomy_vocabulary_integration_access', + 'module' => 'taxonomy', + ); + return array( + 'taxonomy_term_insert' => $defaults_term + array( + 'label' => t('After saving a new term'), + 'variables' => array( + 'term' => array('type' => 'taxonomy_term', 'label' => t('created term')), + ), + ), + 'taxonomy_term_update' => $defaults_term + array( + 'label' => t('After updating an existing term'), + 'variables' => array( + 'term' => array('type' => 'taxonomy_term', 'label' => t('updated term')), + 'term_unchanged' => array('type' => 'taxonomy_term', 'label' => t('unchanged term'), 'handler' => 'rules_events_entity_unchanged'), + ), + ), + 'taxonomy_term_presave' => $defaults_term + array( + 'label' => t('Before saving a taxonomy term'), + 'variables' => array( + 'term' => array('type' => 'taxonomy_term', 'label' => t('saved term'), 'skip save' => TRUE), + 'term_unchanged' => array('type' => 'taxonomy_term', 'label' => t('unchanged term'), 'handler' => 'rules_events_entity_unchanged'), + ), + ), + 'taxonomy_term_delete' => $defaults_term + array( + 'label' => t('After deleting a term'), + 'variables' => array( + 'term' => array('type' => 'taxonomy_term', 'label' => t('deleted term')), + ), + ), + 'taxonomy_vocabulary_insert' => $defaults_vocab + array( + 'label' => t('After saving a new vocabulary'), + 'variables' => array( + 'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('created vocabulary')), + ), + ), + 'taxonomy_vocabulary_update' => $defaults_vocab + array( + 'label' => t('After updating an existing vocabulary'), + 'variables' => array( + 'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('updated vocabulary')), + 'vocabulary_unchanged' => array('type' => 'taxonomy_vocabulary', 'label' => t('unchanged vocabulary'), 'handler' => 'rules_events_entity_unchanged'), + ), + ), + 'taxonomy_vocabulary_presave' => $defaults_vocab + array( + 'label' => t('Before saving a vocabulary'), + 'variables' => array( + 'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('saved vocabulary'), 'skip save' => TRUE), + 'vocabulary_unchanged' => array('type' => 'taxonomy_vocabulary', 'label' => t('unchanged vocabulary'), 'handler' => 'rules_events_entity_unchanged'), + ), + ), + 'taxonomy_vocabulary_delete' => $defaults_vocab + array( + 'label' => t('After deleting a vocabulary'), + 'variables' => array( + 'vocabulary' => array('type' => 'taxonomy_vocabulary', 'label' => t('deleted vocabulary')), + ), + ), + ); +} + +/** + * Taxonomy term integration access callback. + */ +function rules_taxonomy_term_integration_access($type, $name) { + if ($type == 'event' || $type == 'condition') { + return entity_access('view', 'taxonomy_term'); + } +} + +/** + * Taxonomy vocabulary integration access callback. + */ +function rules_taxonomy_vocabulary_integration_access($type, $name) { + if ($type == 'event' || $type == 'condition') { + return entity_access('view', 'taxonomy_vocabulary'); + } +} + +/** + * Event handler support taxonomy bundle event settings. + */ +class RulesTaxonomyEventHandler extends RulesEventHandlerEntityBundle { + + /** + * Returns the label to use for the bundle property. + * + * @return string + */ + protected function getBundlePropertyLabel() { + return t('vocabulary'); + } +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/user.eval.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/user.eval.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,102 @@ +roles[$rid])) { + return TRUE; + } + } + return FALSE; + + case 'AND': + foreach ($roles as $rid) { + if (!isset($account->roles[$rid])) { + return FALSE; + } + } + return TRUE; + } +} + +/** + * Condition: User is blocked. + */ +function rules_condition_user_is_blocked($account) { + return $account->status == 0; +} + +/** + * Action: Adds roles to a particular user. + */ +function rules_action_user_add_role($account, $roles) { + if ($account->uid || !empty($account->is_new)) { + // Get role list (minus the anonymous) + $role_list = user_roles(TRUE); + + foreach ($roles as $rid) { + $account->roles[$rid] = $role_list[$rid]; + } + if (!empty($account->is_new) && $account->uid) { + // user_save() inserts roles after invoking hook_user_insert() anyway, so + // we skip saving to avoid errors due saving them twice. + return FALSE; + } + } + else { + return FALSE; + } +} + +/** + * Action: Remove roles from a given user. + */ +function rules_action_user_remove_role($account, $roles) { + if ($account->uid || !empty($account->is_new)) { + foreach ($roles as $rid) { + // If the user has this role, remove it. + if (isset($account->roles[$rid])) { + unset($account->roles[$rid]); + } + } + if (!empty($account->is_new) && $account->uid) { + // user_save() inserts roles after invoking hook_user_insert() anyway, so + // we skip saving to avoid errors due saving them twice. + return FALSE; + } + } + else { + return FALSE; + } +} + +/** + * Action: Block a user. + */ +function rules_action_user_block($account) { + $account->status = 0; + drupal_session_destroy_uid($account->uid); +} + +/** + * Action: Unblock a user. + */ +function rules_action_user_unblock($account) { + $account->status = 1; +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/modules/user.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/modules/user.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,236 @@ + array( + 'label' => t('After saving a new user account'), + 'group' => t('User'), + 'variables' => array( + 'account' => array('type' => 'user', 'label' => t('registered user')), + ), + 'access callback' => 'rules_user_integration_access', + ), + 'user_update' => array( + 'label' => t('After updating an existing user account'), + 'group' => t('User'), + 'variables' => array( + 'account' => array('type' => 'user', 'label' => t('updated user')), + 'account_unchanged' => array('type' => 'user', 'label' => t('unchanged user'), 'handler' => 'rules_events_entity_unchanged'), + ), + 'access callback' => 'rules_user_integration_access', + ), + 'user_presave' => array( + 'label' => t('Before saving a user account'), + 'group' => t('User'), + 'variables' => array( + 'account' => array('type' => 'user', 'label' => t('saved user'), 'skip save' => TRUE), + 'account_unchanged' => array('type' => 'user', 'label' => t('unchanged user'), 'handler' => 'rules_events_entity_unchanged'), + ), + 'access callback' => 'rules_user_integration_access', + ), + 'user_view' => array( + 'label' => t('User account page is viewed'), + 'group' => t('User'), + 'variables' => array( + 'account' => array('type' => 'user', 'label' => t('viewed user')), + 'view_mode' => array( + 'type' => 'text', + 'label' => t('view mode'), + 'options list' => 'rules_get_entity_view_modes', + ), + ), + 'access callback' => 'rules_user_integration_access', + 'help' => t("Note that if drupal's page cache is enabled, this event won't be generated for pages served from cache."), + ), + 'user_delete' => array( + 'label' => t('After a user account has been deleted'), + 'group' => t('User'), + 'variables' => array( + 'account' => array('type' => 'user', 'label' => t('deleted user')), + ), + 'access callback' => 'rules_user_integration_access', + ), + 'user_login' => array( + 'label' => t('User has logged in'), + 'group' => t('User'), + 'variables' => array( + 'account' => array('type' => 'user', 'label' => t('logged in user')), + ), + 'access callback' => 'rules_user_integration_access', + ), + 'user_logout' => array( + 'label' => t('User has logged out'), + 'group' => t('User'), + 'variables' => array( + 'account' => array('type' => 'user', 'label' => t('logged out user')), + ), + 'access callback' => 'rules_user_integration_access', + ), + ); +} + +/** + * Options list for user cancel methods. + * @todo: Use for providing a user_cancel action. + */ +function rules_user_cancel_methods() { + module_load_include('inc', 'user', 'user.pages'); + foreach (user_cancel_methods() as $method => $form) { + $methods[$method] = $form['#title']; + } + return $methods; +} + +/** + * User integration access callback. + */ +function rules_user_integration_access($type, $name) { + if ($type == 'event' || $type == 'condition') { + return entity_access('view', 'user'); + } + // Else return admin access. + return user_access('administer users'); +} + +/** + * Implements hook_rules_condition_info() on behalf of the user module. + */ +function rules_user_condition_info() { + return array( + 'user_has_role' => array( + 'label' => t('User has role(s)'), + 'parameter' => array( + 'account' => array('type' => 'user', 'label' => t('User')), + 'roles' => array( + 'type' => 'list', + 'label' => t('Roles'), + 'options list' => 'rules_user_roles_options_list', + ), + 'operation' => array( + 'type' => 'text', + 'label' => t('Match roles'), + 'options list' => 'rules_user_condition_operations', + 'restriction' => 'input', + 'optional' => TRUE, + 'default value' => 'AND', + 'description' => t('If matching against all selected roles, the user must have all the roles selected.'), + ), + ), + 'group' => t('User'), + 'access callback' => 'rules_user_integration_access', + 'base' => 'rules_condition_user_has_role', + ), + 'user_is_blocked' => array( + 'label' => t('User is blocked'), + 'parameter' => array( + 'account' => array('type' => 'user', 'label' => t('User')), + ), + 'group' => t('User'), + 'access callback' => 'rules_user_integration_access', + 'base' => 'rules_condition_user_is_blocked', + ), + ); +} + +/** + * User has role condition help callback. + */ +function rules_condition_user_has_role_help() { + return t('Whether the user has the selected role(s).'); +} + +/** + * Options list callback for the operation parameter of condition user has role. + */ +function rules_user_condition_operations() { + return array( + 'AND' => t('all'), + 'OR' => t('any'), + ); +} + +/** + * Implements hook_rules_action_info() on behalf of the user module. + */ +function rules_user_action_info() { + $defaults = array( + 'parameter' => array( + 'account' => array( + 'type' => 'user', + 'label' => t('User'), + 'description' => t('The user whose roles should be changed.'), + 'save' => TRUE, + ), + 'roles' => array( + 'type' => 'list', + 'label' => t('Roles'), + 'options list' => 'rules_user_roles_options_list', + ), + ), + 'group' => t('User'), + 'access callback' => 'rules_user_role_change_access', + ); + $items['user_add_role'] = $defaults + array( + 'label' => t('Add user role'), + 'base' => 'rules_action_user_add_role', + ); + $items['user_remove_role'] = $defaults + array( + 'label' => t('Remove user role'), + 'base' => 'rules_action_user_remove_role', + ); + $defaults = array( + 'parameter' => array( + 'account' => array( + 'type' => 'user', + 'label' => t('User'), + 'save' => TRUE, + ), + ), + 'group' => t('User'), + 'access callback' => 'rules_user_integration_access', + ); + $items['user_block'] = $defaults + array( + 'label' => t('Block a user'), + 'base' => 'rules_action_user_block', + ); + $items['user_unblock'] = $defaults + array( + 'label' => t('Unblock a user'), + 'base' => 'rules_action_user_unblock', + ); + return $items; +} + +/** + * User integration role actions access callback. + */ +function rules_user_role_change_access() { + return entity_metadata_user_roles() && user_access('administer permissions'); +} + +/** + * Options list callback for user roles. + */ +function rules_user_roles_options_list($element) { + return entity_metadata_user_roles('roles', array(), $element instanceof RulesConditionInterface ? 'view' : 'edit'); +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules.api.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1098 @@ + as introduced by + * the entity metadata module, see hook_entity_property_info(). The special + * keyword '*' can be used when all types should be allowed. Required. + * - bundles: (optional) An array of bundle names. When the specified type is + * set to a single entity type, this may be used to restrict the allowed + * bundles. + * - description: (optional) If necessary, a further description of the + * parameter. + * - options list: (optional) A callback that returns an array of possible + * values for this parameter. The callback has to return an array as used + * by hook_options_list(). For an example implementation see + * rules_data_action_type_options(). + * - save: (optional) If this is set to TRUE, the parameter will be saved by + * rules when the rules evaluation ends. This is only supported for savable + * data types. If the action returns FALSE, saving is skipped. + * - optional: (optional) May be set to TRUE, when the parameter isn't + * required. + * - 'default value': (optional) The value to pass to the action, in case the + * parameter is optional and there is no specified value. + * - 'allow null': (optional) Usually Rules will not pass any NULL values as + * argument, but abort the evaluation if a NULL value is present. If set to + * TRUE, Rules will not abort and pass the NULL value through. Defaults to + * FALSE. + * - restriction: (optional) Restrict how the argument for this parameter may + * be provided. Supported values are 'selector' and 'input'. + * - default mode: (optional) Customize the default mode for providing the + * argument value for a parameter. Supported values are 'selector' and + * 'input'. The default depends on the required data type. + * - sanitize: (optional) Allows parameters of type 'text' to demand an + * already sanitized argument. If enabled, any user specified value won't be + * sanitized itself, but replacements applied by input evaluators are as + * well as values retrieved from selected data sources. + * - translatable: (optional) If set to TRUE, the provided argument value + * of the parameter is translatable via i18n String translation. This is + * applicable for textual parameters only, i.e. parameters of type 'text', + * 'token', 'list' and 'list'. Defaults to FALSE. + * - ui class: (optional) Allows overriding the UI class, which is used to + * generate the configuration UI of a parameter. Defaults to the UI class of + * the specified data type. + * - cleaning callback: (optional) A callback that input evaluators may use + * to clean inserted replacements; e.g. this is used by the token evaluator. + * - wrapped: (optional) Set this to TRUE in case the data should be passed + * wrapped. This only applies to wrapped data types, e.g. entities. + * Each 'provides' array may contain the following properties: + * - label: The label of the variable. Start capitalized. Required. + * - type: The rules data type of the variable. All types declared in + * hook_rules_data_info() may be specified. Types may be parametrized e.g. + * the types node or list are valid. + * - save: (optional) If this is set to TRUE, the provided variable is saved + * by rules when the rules evaluation ends. Only possible for savable data + * types. Defaults to FALSE. + * + * The module has to provide an implementation for each action, being a + * function named as specified in the 'base' key or for the execution callback. + * All other possible callbacks are optional. + * Supported action callbacks by rules are defined and documented in the + * RulesPluginImplInterface. However any module may extend the action plugin + * based upon a defined interface using hook_rules_plugin_info(). All methods + * defined in those interfaces can be overridden by the action implementation. + * The callback implementations for those interfaces may reside in any file + * specified in hook_rules_file_info(). + * + * @see hook_rules_file_info() + * @see rules_action_execution_callback() + * @see hook_rules_plugin_info() + * @see RulesPluginImplInterface + */ +function hook_rules_action_info() { + return array( + 'mail_user' => array( + 'label' => t('Send a mail to a user'), + 'parameter' => array( + 'user' => array('type' => 'user', 'label' => t('Recipient')), + ), + 'group' => t('System'), + 'base' => 'rules_action_mail_user', + 'callbacks' => array( + 'validate' => 'rules_action_custom_validation', + 'help' => 'rules_mail_help', + ), + ), + ); +} + +/** + * Define categories for Rules items, e.g. actions, conditions or events. + * + * Categories are similar to the previously used 'group' key in e.g. + * hook_rules_action_info(), but have a machine name and some more optional + * keys like a weight, or an icon. + * + * For best compatibility, modules may keep using the 'group' key for referring + * to categories. However, if a 'group' key and a 'category' is given the group + * will be treated as grouping in the given category (e.g. group "paypal" in + * category "commerce payment"). + * + * @return + * An array of information about the module's provided categories. + * The array contains a sub-array for each category, with the category name as + * the key. Names may only contain lowercase alpha-numeric characters + * and underscores and should be prefixed with the providing module name. + * Possible attributes for each sub-array are: + * - label: The label of the category. Start capitalized. Required. + * - weight: (optional) A weight for sorting the category. Defaults to 0. + * - equals group: (optional) For BC, categories may be defined that equal + * a previsouly used 'group'. + * - icon: (optional) The file path of an icon to use, relative to the module + * or specified icon path. The icon should be a transparent SVG containing + * no colors (only #fff). See https://drupal.org/node/2090265 for + * instructions on how to create a suiting icon. + * Note that the icon is currently not used by Rules, however other UIs + * building upon Rules (like fluxkraft) do, and future releases of Rules + * might do as well. Consequently, the definition of an icon is optional. + * However, if both an icon font and icon is given, the icon is preferred. + * - icon path: (optional) The base path for the icon. Defaults to the + * providing module's directory. + * - icon font class: (optional) An icon font class referring to a suiting + * icon. Icon font class names should map to the ones as defined by Font + * Awesome, while themes might want to choose to provide another icon font. + * See http://fortawesome.github.io/Font-Awesome/cheatsheet/. + * - icon background color: (optional) The color used as icon background. + * Should have a high contrast to white. Defaults to #ddd. + */ +function hook_rules_category_info() { + return array( + 'rules_data' => array( + 'label' => t('Data'), + 'equals group' => t('Data'), + 'weight' => -50, + ), + 'fluxtwitter' => array( + 'label' => t('Twitter'), + 'icon font class' => 'icon-twitter', + 'icon font background color' => '#30a9fd', + ), + ); +} + +/** + * Specify files containing rules integration code. + * + * All files specified in that hook will be included when rules looks for + * existing callbacks for any plugin. Rules remembers which callback is found in + * which file and automatically includes the right file before it is executing + * a plugin method callback. The file yourmodule.rules.inc is added by default + * and need not be specified here. + * This allows you to add new include files only containing functions serving as + * plugin method callbacks in any file without having to care about file + * inclusion. + * + * @return + * An array of file names without the file ending which defaults to '.inc'. + */ +function hook_rules_file_info() { + return array('yourmodule.rules-eval'); +} + +/** + * Specifies directories for class-based plugin handler discovery. + * + * Implementing this hook is not a requirement, it is just one option to load + * the files containing the classes during discovery - see + * rules_discover_plugins(). + * + * @return string|array + * A directory relative to the module directory, which holds the files + * containing rules plugin handlers, or multiple directories keyed by the + * module the directory is contained in. + * All files in those directories having a 'php' or 'inc' file extension will + * be loaded during discovery. Optionally, wildcards ('*') may be used to + * match multiple directories. + * + * @see rules_discover_plugins() + */ +function hook_rules_directory() { + return 'lib/Drupal/fluxtwitter/Rules/*'; +} + +/** + * The execution callback for an action. + * + * It should be placed in any file included by your module or in a file + * specified using hook_rules_file_info(). + * + * @param + * The callback gets arguments passed as described as parameter in + * hook_rules_action_info() as well as an array containing the action's + * configuration settings. + * @return + * The action may return an array containg parameter or provided variables + * with their names as key. This is used update the value of a parameter or to + * provdide the value for a provided variable. + * Apart from that any parameters which have the key 'save' set to TRUE will + * be remembered to be saved by rules unless the action returns FALSE. + * Conditions have to return a boolean value in any case. + * + * @see hook_rules_action_info() + * @see hook_rules_file_info() + */ +function rules_action_execution_callback($node, $title, $settings) { + $node->title = $title; + return array('node' => $node); +} + +/** + * Define rules conditions. + * + * This hook is required in order to add a new rules condition. It should be + * placed into the file MODULENAME.rules.inc, which gets automatically included + * when the hook is invoked. + * + * However, as an alternative to implementing this hook, class based plugin + * handlers may be provided by implementing RulesConditionHandlerInterface. See + * the interface for details. + * + * Adding conditions works exactly the same way as adding actions, with the + * exception that conditions can't provide variables and cannot save parameters. + * Thus the 'provides' attribute is not supported. Furthermore the condition + * implementation callback has to return a boolean value. + * + * @see hook_rules_action_info() + */ +function hook_rules_condition_info() { + return array( + 'rules_condition_text_compare' => array( + 'label' => t('Textual comparison'), + 'parameter' => array( + 'text1' => array('label' => t('Text 1'), 'type' => 'text'), + 'text2' => array('label' => t('Text 2'), 'type' => 'text'), + ), + 'group' => t('Rules'), + ), + ); +} + +/** + * Define rules events. + * + * This hook is required in order to add a new rules event. It should be + * placed into the file MODULENAME.rules.inc, which gets automatically included + * when the hook is invoked. + * The module has to invoke the event when it occurs using rules_invoke_event(). + * This function call has to happen outside of MODULENAME.rules.inc, + * usually it's invoked directly from the providing module but wrapped by a + * module_exists('rules') check. + * + * However, as an alternative to implementing this hook, class based event + * handlers may be provided by implementing RulesEventHandlerInterface. See + * the interface for details. + * + * @return + * An array of information about the module's provided rules events. The array + * contains a sub-array for each event, with the event name as the key. The + * name may only contain lower case alpha-numeric characters and underscores + * and should be prefixed with the providing module name. Possible attributes + * for each sub-array are: + * - label: The label of the event. Start capitalized. Required. + * - group: A group for this element, used for grouping the events in the + * interface. Should start with a capital letter and be translated. + * Required. + * - class: (optional) An event handler class implementing the + * RulesEventHandlerInterface. If none is specified the + * RulesEventDefaultHandler class will be used. While the default event + * handler has no settings, custom event handlers may be implemented to + * to make an event configurable. See RulesEventHandlerInterface. + * - access callback: (optional) An callback, which has to return whether the + * currently logged in user is allowed to configure rules for this event. + * Access should be only granted, if the user at least may access all the + * variables provided by the event. + * - help: (optional) A help text for rules reaction on this event. + * - variables: (optional) An array describing all variables that are + * available for elements reacting on this event. Each variable has to be + * described by a sub-array with the possible attributes: + * - label: The label of the variable. Start capitalized. Required. + * - type: The rules data type of the variable. All types declared in + * hook_rules_data_info() or supported by hook_entity_property_info() may + * be specified. + * - bundle: (optional) If the type is an entity type, the bundle of the + * entity. + * - description: (optional) A description for the variable. + * - 'options list': (optional) A callback that returns an array of possible + * values for this variable as specified for entity properties at + * hook_entity_property_info(). + * - 'skip save': (optional) If the variable is saved after the event has + * occurred anyway, set this to TRUE. So rules won't save the variable a + * second time. Defaults to FALSE. + * - handler: (optional) A handler to load the actual variable value. This + * is useful for lazy loading variables. The handler gets all so far + * available variables passed in the order as defined. Also see + * http://drupal.org/node/884554. + * Note that for lazy-loading entities just the entity id may be passed + * as variable value, so a handler is not necessary in that case. + * + * @see rules_invoke_event() + */ +function hook_rules_event_info() { + $items = array( + 'node_insert' => array( + 'label' => t('After saving new content'), + 'group' => t('Node'), + 'variables' => rules_events_node_variables(t('created content')), + ), + 'node_update' => array( + 'label' => t('After updating existing content'), + 'group' => t('Node'), + 'variables' => rules_events_node_variables(t('updated content'), TRUE), + ), + 'node_presave' => array( + 'label' => t('Content is going to be saved'), + 'group' => t('Node'), + 'variables' => rules_events_node_variables(t('saved content'), TRUE), + ), + 'node_view' => array( + 'label' => t('Content is going to be viewed'), + 'group' => t('Node'), + 'variables' => rules_events_node_variables(t('viewed content')) + array( + 'view_mode' => array('type' => 'text', 'label' => t('view mode')), + ), + ), + 'node_delete' => array( + 'label' => t('After deleting content'), + 'group' => t('Node'), + 'variables' => rules_events_node_variables(t('deleted content')), + ), + ); + // Specify that on presave the node is saved anyway. + $items['node_presave']['variables']['node']['skip save'] = TRUE; + return $items; +} + +/** + * Define rules data types. + * + * This hook is required in order to add a new rules data type. It should be + * placed into the file MODULENAME.rules.inc, which gets automatically included + * when the hook is invoked. + * Rules builds upon the entity metadata module, thus to improve the support of + * your data in rules, make it an entity if possible and provide metadata about + * its properties and CRUD functions by integrating with the entity metadata + * module. + * For a list of data types defined by rules see rules_rules_core_data_info(). + * + * + * @return + * An array of information about the module's provided data types. The array + * contains a sub-array for each data type, with the data type name as the + * key. The name may only contain lower case alpha-numeric characters and + * underscores and should be prefixed with the providing module name. Possible + * attributes for each sub-array are: + * - label: The label of the data type. Start uncapitalized. Required. + * - parent: (optional) A parent type may be set to specify a sub-type + * relationship, which will be only used for checking compatible types. E.g. + * the 'entity' data type is parent of the 'node' data type, thus a node may + * be also used for any action needing an 'entity' parameter. Can be set to + * any known rules data type. + * - ui class: (optional) Specify a class that is used to generate the + * configuration UI to configure parameters of this type. The given class + * must extend RulesDataUI and may implement the + * RulesDataDirectInputFormInterface in order to allow the direct data input + * configuration mode. For supporting selecting values from options lists, + * the UI class may implement RulesDataInputOptionsListInterface also. + * Defaults to RulesDataUI. + * - wrap: (optional) If set to TRUE, the data is wrapped internally using + * wrappers provided by the entity API module. This is required for entities + * and data structures to support selecting a property via the data selector + * and for intelligent saving. + * - is wrapped: (optional) In case the data wrapper is already wrapped when + * passed to Rules and Rules should not unwrap it when passing the data as + * argument, e.g. to an action, set this to TRUE. The default FALSE is fine + * in most cases. + * - wrapper class: (optional) Allows the specification of a custom wrapper + * class, which has to inherit from 'EntityMetadataWrapper'. If given Rules + * makes use of the class for wrapping the data of the given type. However + * note that if data is already wrapped when it is passed to Rules, the + * existing wrappers will be kept. + * For modules implementing identifiable data types being non-entites the + * class RulesIdentifiableDataWrapper is provided, which can be used as base + * for a custom wrapper class. See RulesIdentifiableDataWrapper for details. + * - property info: (optional) May be used for non-entity data structures to + * provide info about the data properties, such that data selectors via an + * entity metadata wrapper are supported. Specify an array as expected by + * the $info parameter of entity_metadata_wrapper(). + * - creation callback: (optional) If 'property info' is given, an optional + * callback that makes use of the property info to create a new instance of + * this data type. Entities should use hook_entity_info() to specify the + * 'creation callback' instead, as utilized by the entity API module. See + * rules_action_data_create_array() for an example callback. + * - property defaults: (optional) May be used for non-entity data structures + * to to provide property info defaults for the data properties. Specify an + * array as expected by entity_metadata_wrapper(). + * - group: (optional) A group for this element, used for grouping the data + * types in the interface. Should start with a capital letter and be + * translated. + * - token type: (optional) The type name as used by the token module. + * Defaults to the type name as used by rules. Use FALSE to let token ignore + * this type. + * - cleaning callback: (optional) A callback that input evaluators may use + * to clean inserted replacements; e.g. this is used by the token evaluator. + * + * @see entity_metadata_wrapper() + * @see hook_rules_data_info_alter() + * @see rules_rules_core_data_info() + */ +function hook_rules_data_info() { + return array( + 'node' => array( + 'label' => t('content'), + 'parent' => 'entity', + 'group' => t('Node'), + ), + // Formatted text as used by in hook_entity_property_info() for text fields. + 'text_formatted' => array( + 'label' => t('formatted text'), + 'ui class' => 'RulesDataUITextFormatted', + 'wrap' => TRUE, + 'property info' => entity_property_text_formatted_info(), + ), + ); +} + +/** + * Defines rules plugins. + * + * A rules configuration may consist of elements being instances of any rules + * plugin. This hook can be used to define new or to extend rules plugins. + * + * @return + * An array of information about the module's provided rules plugins. The + * array contains a sub-array for each plugin, with the plugin name as the + * key. The name may only contain lower case alpha-numeric characters, + * underscores and spaces and should be prefixed with the providing module + * name. Possible attributes for + * each sub-array are: + * - label: A label for the plugin. Start capitalized. Required only for + * components (see below). + * - class: The implementation class. Has to extend the RulesPlugin class. + * - embeddable: A container class in which elements of those plugin may be + * embedded via the UI or FALSE to disallow embedding it via the UI. This + * has no implications on the API level though. Common classes that are + * used here are RulesConditionContainer and RulesActionContainer. + * - component: If set to TRUE, the rules admin UI will list elements of those + * plugin in the components UI and allows the creation of new components + * based upon this plugin. Optional. + * - extenders: This allows one to specify faces extenders, which may be used + * to dynamically implement interfaces. Optional. All extenders specified + * here are setup automatically by rules once the object is created. To + * specify set this to an array, where the keys are the implemented + * interfaces pointing to another array with the keys: + * - class: The class of the extender, implementing the FacesExtender + * and the specified interface. Either 'class' or 'methods' has to exist. + * - methods: An array of callbacks that implement the methods of the + * interface where the method names are the keys and the callback names + * the values. There has to be a callback for each defined method. + * - file: An optional array describing the file to include when a method + * of the interface is invoked. The array entries known are 'type', + * 'module', and 'name' matching the parameters of module_load_include(). + * Only 'module' is required as 'type' defaults to 'inc' and 'name' to + * NULL. + * - overrides: An optional array, which may be used to specify callbacks to + * override specific methods. For that the following keys are supported: + * - methods: As in the extenders array, but you may specify as many methods + * here as you like. + * - file: Optionally an array specifying a file to include for a method. + * For each method appearing in methods a file may be specified by using + * the method name as key and another array as value, which describes the + * file to include - looking like the file array supported by 'extenders'. + * - import keys: (optional) Embeddable plugins may specify an array of import + * keys, which the plugin make use for exporting. Defaults to the upper + * case plugin name, thus the key 'OR' in an export triggers the creation + * of the 'or' plugin. Note that only uppercase values are allowed, as + * lower case values are treated as action or condition exports. + * + * @see class RulesPlugin + * @see hook_rules_plugin_info_alter() + */ +function hook_rules_plugin_info() { + return array( + 'or' => array( + 'label' => t('Condition set (OR)'), + 'class' => 'RulesOr', + 'embeddable' => 'RulesConditionContainer', + 'component' => TRUE, + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesConditionContainerUI', + ), + ), + ), + 'rule' => array( + 'class' => 'Rule', + 'embeddable' => 'RulesRuleSet', + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesRuleUI', + ), + ), + 'import keys' => array('DO', 'IF'), + ), + ); +} + +/** + * Declare provided rules input evaluators. + * + * The hook implementation should be placed into the file MODULENAME.rules.inc, + * which gets automatically included when the hook is invoked. + * For implementing an input evaluator a class has to be provided which + * extends the abstract RulesDataInputEvaluator class. Therefore the abstract + * methods prepare() and evaluate() have to be implemented, as well as access() + * and help() could be overridden in order to control access permissions or to + * provide some usage help. + * + * @return + * An array of information about the module's provided input evaluators. The + * array contains a sub-array for each evaluator, with the evaluator name as + * the key. The name may only contain lower case alpha-numeric characters and + * underscores and should be prefixed with the providing module name. Possible + * attributes for each sub-array are: + * - class: The implementation class, which has to extend the + * RulesDataInputEvaluator class. Required. + * - weight: A weight for controlling the evaluation order of multiple + * evaluators. Required. + * - type: Optionally, the data types for which the input evaluator should be + * used. Defaults to 'text'. Multiple data types may be specified using an + * array. + * + * @see class RulesDataInputEvaluator + * @see hook_rules_evaluator_info_alter() + */ +function hook_rules_evaluator_info() { + return array( + 'token' => array( + 'class' => 'RulesTokenEvaluator', + 'type' => array('text', 'uri'), + 'weight' => 0, + ), + ); +} + +/** + * Declare provided rules data processors. + * + * The hook implementation should be placed into the file MODULENAME.rules.inc, + * which gets automatically included when the hook is invoked. + * For implementing a data processors a class has to be provided which + * extends the abstract RulesDataProcessor class. Therefore the abstract + * method process() has to be implemented, but also the methods form() and + * access() could be overridden in order to provide a configuration form or + * to control access permissions. + * + * @return + * An array of information about the module's provided data processors. The + * array contains a sub-array for each processor, with the processor name as + * the key. The name may only contain lower case alpha-numeric characters and + * underscores and should be prefixed with the providing module name, whereas + * 'select' is reserved as well. + * Possible attributes for each sub-array are: + * - class: The implementation class, which has to extend the + * RulesDataProcessor class. Required. + * - weight: A weight for controlling the processing order of multiple data + * processors. Required. + * - type: Optionally, the data types for which the data processor should be + * used. Defaults to 'text'. Multiple data types may be specified using an + * array. + * + * @see class RulesDataProcessor + * @see hook_rules_data_processor_info_alter() + */ +function hook_rules_data_processor_info() { + return array( + 'date_offset' => array( + 'class' => 'RulesDateOffsetProcessor', + 'type' => 'date', + 'weight' => -2, + ), + ); +} + +/** + * Alter rules compatible actions. + * + * The implementation should be placed into the file MODULENAME.rules.inc, which + * gets automatically included when the hook is invoked. + * + * @param $actions + * The items of all modules as returned from hook_rules_action_info(). + * + * @see hook_rules_action_info(). + */ +function hook_rules_action_info_alter(&$actions) { + // The rules action is more powerful, so hide the core action + unset($actions['rules_core_node_assign_owner_action']); + // We prefer handling saving by rules - not by the user. + unset($actions['rules_core_node_save_action']); +} + +/** + * Alter rules conditions. + * + * The implementation should be placed into the file MODULENAME.rules.inc, which + * gets automatically included when the hook is invoked. + * + * @param $conditions + * The items of all modules as returned from hook_rules_condition_info(). + * + * @see hook_rules_condition_info() + */ +function hook_rules_condition_info_alter(&$conditions) { + // Change conditions. +} + +/** + * Alter rules events. + * + * The implementation should be placed into the file MODULENAME.rules.inc, which + * gets automatically included when the hook is invoked. + * + * @param $events + * The items of all modules as returned from hook_rules_event_info(). + * + * @see hook_rules_event_info(). + */ +function hook_rules_event_info_alter(&$events) { + // Change events. +} + +/** + * Alter rules data types. + * + * The implementation should be placed into the file MODULENAME.rules.inc, which + * gets automatically included when the hook is invoked. + * + * @param $data_info + * The items of all modules as returned from hook_rules_data_info(). + * + * @see hook_rules_data_info() + */ +function hook_rules_data_info_alter(&$data_info) { + // Change data types. +} + +/** + * Alter rules plugin info. + * + * The implementation should be placed into the file MODULENAME.rules.inc, which + * gets automatically included when the hook is invoked. + * + * @param $plugin_info + * The items of all modules as returned from hook_rules_plugin_info(). + * + * @see hook_rules_plugin_info() + */ +function hook_rules_plugin_info_alter(&$plugin_info) { + // Change plugin info. +} + +/** + * Alter rules input evaluator info. + * + * The implementation should be placed into the file MODULENAME.rules.inc, which + * gets automatically included when the hook is invoked. + * + * @param $evaluator_info + * The items of all modules as returned from hook_rules_evaluator_info(). + * + * @see hook_rules_evaluator_info() + */ +function hook_rules_evaluator_info_alter(&$evaluator_info) { + // Change evaluator info. +} + +/** + * Alter rules data_processor info. + * + * The implementation should be placed into the file MODULENAME.rules.inc, which + * gets automatically included when the hook is invoked. + * + * @param $processor_info + * The items of all modules as returned from hook_rules_data_processor_info(). + * + * @see hook_rules_data_processor_info() + */ +function hook_rules_data_processor_info_alter(&$processor_info) { + // Change processor info. +} + +/** + * Act on rules configuration being loaded from the database. + * + * This hook is invoked during rules configuration loading, which is handled + * by entity_load(), via classes RulesEntityController and EntityCRUDController. + * + * @param $configs + * An array of rules configurations being loaded, keyed by id. + */ +function hook_rules_config_load($configs) { + $result = db_query('SELECT id, foo FROM {mytable} WHERE id IN(:ids)', array(':ids' => array_keys($configs))); + foreach ($result as $record) { + $configs[$record->id]->foo = $record->foo; + } +} + +/** + * Respond to creation of a new rules configuration. + * + * This hook is invoked after the rules configuration is inserted into the + * the database. + * + * @param RulesPlugin $config + * The rules configuration that is being created. + */ +function hook_rules_config_insert($config) { + db_insert('mytable') + ->fields(array( + 'nid' => $config->id, + 'plugin' => $config->plugin, + )) + ->execute(); +} + +/** + * Act on a rules configuration being inserted or updated. + * + * This hook is invoked before the rules configuration is saved to the + * database. + * + * @param RulesPlugin $config + * The rules configuration that is being inserted or updated. + */ +function hook_rules_config_presave($config) { + if ($config->id && $config->owner == 'your_module') { + // Add custom condition. + $config->conditon(/* Your condition */); + } +} + +/** + * Respond to updates to a rules configuration. + * + * This hook is invoked after the configuration has been updated in the + * database. + * + * @param RulesPlugin $config + * The rules configuration that is being updated. + */ +function hook_rules_config_update($config) { + db_update('mytable') + ->fields(array('plugin' => $config->plugin)) + ->condition('id', $config->id) + ->execute(); +} + +/** + * Respond to rules configuration deletion. + * + * This hook is invoked after the configuration has been removed from the + * database. + * + * @param RulesPlugin $config + * The rules configuration that is being deleted. + */ +function hook_rules_config_delete($config) { + db_delete('mytable') + ->condition('id', $config->id) + ->execute(); +} + +/** + * Respond to rules configuration execution. + * + * This hook is invoked right before the rules configuration is executed. + * + * @param RulesPlugin $config + * The rules configuration that is being executed. + */ +function hook_rules_config_execute($config) { + +} + +/** + * Define default rules configurations. + * + * This hook is invoked when rules configurations are loaded. The implementation + * should be placed into the file MODULENAME.rules_defaults.inc, which gets + * automatically included when the hook is invoked. + * + * @return + * An array of rules configurations with the configuration names as keys. + * + * @see hook_default_rules_configuration_alter() + * @see hook_rules_config_defaults_rebuild() + */ +function hook_default_rules_configuration() { + $rule = rules_reaction_rule(); + $rule->label = 'example default rule'; + $rule->active = FALSE; + $rule->event('node_update') + ->condition(rules_condition('data_is', array('data:select' => 'node:status', 'value' => TRUE))->negate()) + ->condition('data_is', array('data:select' => 'node:type', 'value' => 'page')) + ->action('drupal_message', array('message' => 'A node has been updated.')); + + $configs['rules_test_default_1'] = $rule; + return $configs; +} + +/** + * Alter default rules configurations. + * + * The implementation should be placed into the file + * MODULENAME.rules_defaults.inc, which gets automatically included when the + * hook is invoked. + * + * @param $configs + * The default configurations of all modules as returned from + * hook_default_rules_configuration(). + * + * @see hook_default_rules_configuration() + */ +function hook_default_rules_configuration_alter(&$configs) { + // Add custom condition. + $configs['foo']->condition('bar'); +} + +/** + * Act after rebuilding default configurations. + * + * This hook is invoked by the entity module after default rules configurations + * have been rebuilt; i.e. defaults have been saved to the database. + * + * @param $rules_configs + * The array of default rules configurations which have been inserted or + * updated, keyed by name. + * @param $originals + * An array of original rules configurations keyed by name; i.e. the rules + * configurations before the current defaults have been applied. For inserted + * rules configurations no original is available. + * + * @see hook_default_rules_configuration() + * @see entity_defaults_rebuild() + */ +function hook_rules_config_defaults_rebuild($rules_configs, $originals) { + // Once all defaults have been rebuilt, update all i18n strings at once. That + // way we build the rules cache once the rebuild is complete and avoid + // rebuilding caches for each updated rule. + foreach ($rules_configs as $name => $rule_config) { + if (empty($originals[$name])) { + rules_i18n_rules_config_insert($rule_config); + } + else { + rules_i18n_rules_config_update($rule_config, $originals[$name]); + } + } +} + +/** + * Alter rules components before execution. + * + * This hooks allows altering rules components before they are cached for later + * re-use. Use this hook only for altering the component in order to prepare + * re-use through rules_invoke_component() or the provided condition/action. + * Note that this hook is only invoked for any components cached for execution, + * but not for components that are programmatically created and executed on the + * fly (without saving them). + * + * @param $plugin + * The name of the component plugin. + * @param $component + * The component that is to be cached. + * + * @see rules_invoke_component() + */ +function hook_rules_component_alter($plugin, RulesPlugin $component) { + +} + +/** + * Alters event sets. + * + * This hooks allows altering rules event sets, which contain all rules that are + * triggered upon a specific event. Rules internally caches all rules associated + * to an event in an event set, which is cached for fast evaluation. This hook + * is invoked just before any event set is cached, thus it allows altering of + * the to be executed rules without the changes to appear in the UI, e.g. to add + * a further condition to some rules. + * + * @param $event_name + * The name of the event. + * @param $event_set + * The event set that is to be cached. + * + * @see rules_invoke_event() + */ +function hook_rules_event_set_alter($event_name, RulesEventSet $event_set) { + +} + +/** + * D6 to D7 upgrade procedure hook for mapping action or condition names. + * + * If for a module the action or condition name changed since Drupal 6, this + * "hook" can be implemented in order to map to the new name of the action or + * condition. + * + * This is no real hook, but a callback that is invoked for each Drupal 6 + * action or condition that is to be upgraded to Drupal 7. E.g. the function + * name called for the action "rules_action_set_node_title" would be + * "rules_action_set_node_title_upgrade_map_name". + * + * @param $element + * The element array of a configured condition or action which is to be + * upgraded. + * @return + * The name of the action or condition which should be used. + */ +function hook_rules_action_base_upgrade_map_name($element) { + return 'data_set'; +} + +/** + * D6 to D7 upgrade procedure hook for mapping action or condition configuration. + * + * During upgrading Drupal 6 rule configurations to Drupal 7 Rules is taking + * care of upgrading the configuration of all known parameters, which only works + * if the parameter name has not changed. + * If something changed, this callback can be used to properly apply the + * configruation of the Drupal 6 action ($element) to the Drupal 7 version + * ($target). + * + * This is no real hook, but a callback that is invoked for each Drupal 6 + * action or condition that is to be upgraded to Drupal 7. E.g. the function + * name called for the action "rules_action_set_node_title" would be + * "rules_action_set_node_title_upgrade". + * + * @param $element + * The element array of a configured condition or action which is to be + * upgraded. + * @param $target + * The Drupal 7 version of the configured element. + * + * @see hook_rules_element_upgrade_alter() + */ +function hook_rules_action_base_upgrade($element, RulesPlugin $target) { + $target->settings['data:select'] = $element['#settings']['#argument map']['node'] . ':title'; + $target->settings['value'] = $element['#settings']['title']; +} + +/** + * D6 to D7 upgrade procedure hook for mapping action or condition configuration. + * + * A alter hook that is called after the action/condition specific callback for + * each element of a configuration that is upgraded. + * + * @param $element + * The element array of a configured condition or action which is to be + * upgraded. + * @param $target + * The Drupal 7 version of the configured element. + * + * @see hook_rules_action_base_upgrade() + */ +function hook_rules_element_upgrade_alter($element, $target) { + +} + +/** + * Allows modules to alter or to extend the provided Rules UI. + * + * Use this hook over the regular hook_menu_alter() as the Rules UI is re-used + * and embedded by modules. See rules_ui(). + * + * @param $items + * The menu items to alter. + * @param $base_path + * The base path of the Rules UI. + * @param $base_count + * The count of the directories contained in the base path. + */ +function hook_rules_ui_menu_alter(&$items, $base_path, $base_count) { + $items[$base_path . '/manage/%rules_config/schedule'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Schedule !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_scheduler_schedule_form', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'file' => 'rules_scheduler.admin.inc', + 'file path' => drupal_get_path('module', 'rules_scheduler'), + ); +} + +/** + * Control access to Rules configurations. + * + * Modules may implement this hook if they want to have a say in whether or not + * a given user has access to perform a given operation on a Rules + * configuration. + * + * @param $op + * The operation being performed. One of 'view', 'create', 'update' or + * 'delete'. + * @param $rules_config + * (optional) A Rules configuration to check access for. If nothing is given, + * access for all Rules configurations is determined. + * @param $account + * (optional) The user to check for. If no account is passed, access is + * determined for the current user. + * @return boolean + * Return TRUE to grant access, FALSE to explicitly deny access. Return NULL + * or nothing to not affect the operation. + * Access is granted as soon as a module grants access and no one denies + * access. Thus if no module explicitly grants access, access will be denied. + * + * @see rules_config_access() + */ +function hook_rules_config_access($op, $rules_config = NULL, $account = NULL) { + // Instead of returning FALSE return nothing, so others still can grant + // access. + if (isset($rules_config) && $rules_config->owner == 'mymodule' && user_access('my modules permission')) { + return TRUE; + } +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules.features.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules.features.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,72 @@ +type, $data) as $name => $rules_config) { + // Add in the dependencies. + $export['dependencies'] += drupal_map_assoc($rules_config->dependencies()); + // Add in plugin / element specific additions. + $iterator = new RecursiveIteratorIterator($rules_config, RecursiveIteratorIterator::SELF_FIRST); + foreach ($iterator as $element) { + if ($element->facesAs('RulesPluginFeaturesIntegrationInterace')) { + // Directly use __call() so we cann pass $export by reference. + $element->__call('features_export', array(&$export, &$pipe, $module_name)); + } + } + } + return $pipe; + } +} + +/** + * Default extension callback used as default for the abstract plugin class. + * Actions / conditions may override this with their own implementation, which + * actually does something. + * + * @see RulesPluginFeaturesIntegrationInterace + */ +function rules_features_abstract_default_features_export(&$export, &$pipe, $module_name = '', $element) { + +} + +/** + * Interface that allows rules plugins or actions/conditions to customize the + * features export by implementing the interface using the faces extensions + * mechanism. + * + * @see hook_rules_plugin_info() + * @see hook_rules_action_info() + */ +interface RulesPluginFeaturesIntegrationInterace { + + /** + * Allows customizing the features export for a given rule element. + */ + function features_export(&$export, &$pipe, $module_name = ''); +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,30 @@ +name = Rules +description = React on events and conditionally evaluate actions. +package = Rules +core = 7.x +files[] = rules.features.inc +files[] = tests/rules.test +files[] = includes/faces.inc +files[] = includes/rules.core.inc +files[] = includes/rules.event.inc +files[] = includes/rules.processor.inc +files[] = includes/rules.plugins.inc +files[] = includes/rules.state.inc +files[] = includes/rules.dispatcher.inc +files[] = modules/node.eval.inc +files[] = modules/php.eval.inc +files[] = modules/rules_core.eval.inc +files[] = modules/system.eval.inc +files[] = ui/ui.controller.inc +files[] = ui/ui.core.inc +files[] = ui/ui.data.inc +files[] = ui/ui.plugins.inc +dependencies[] = entity_token +dependencies[] = entity + +; Information added by drupal.org packaging script on 2013-09-16 +version = "7.x-2.4" +core = "7.x" +project = "rules" +datestamp = "1379354606" + diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,470 @@ + array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The internal identifier for any configuration.', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'description' => 'The name of the configuration.', + ), + 'label' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'description' => 'The label of the configuration.', + 'default' => 'unlabeled', + ), + 'plugin' => array( + 'type' => 'varchar', + 'length' => 127, + 'not null' => TRUE, + 'description' => 'The name of the plugin of this configuration.', + ), + 'active' => array( + 'description' => 'Boolean indicating whether the configuration is active. Usage depends on how the using module makes use of it.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'Weight of the configuration. Usage depends on how the using module makes use of it.', + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + // Set the default to ENTITY_CUSTOM without using the constant as it is + // not safe to use it at this point. + 'default' => 0x01, + 'size' => 'tiny', + 'description' => 'The exportable status of the entity.', + ), + 'dirty' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'Dirty configurations fail the integrity check, e.g. due to missing dependencies.', + ), + 'module' => array( + 'description' => 'The name of the providing module if the entity has been defined in code.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + 'owner' => array( + 'description' => 'The name of the module via which the rule has been configured.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => 'rules', + ), + 'access_exposed' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'Whether to use a permission to control access for using components.', + ), + 'data' => array( + 'type' => 'blob', + 'size' => 'big', + 'not null' => FALSE, + 'serialize' => TRUE, + 'description' => 'Everything else, serialized.', + ), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'name' => array('name'), + ), + 'indexes' => array( + 'plugin' => array('plugin'), + ), + ); + $schema['rules_trigger'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier of the configuration.', + ), + 'event' => array( + 'type' => 'varchar', + 'length' => '127', + 'not null' => TRUE, + 'default' => '', + 'description' => 'The name of the event on which the configuration should be triggered.', + ), + ), + 'primary key' => array('id', 'event'), + 'foreign keys' => array( + 'table' => 'rules_config', + 'columns' => array('id' => 'id'), + ), + ); + $schema['rules_tags'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier of the configuration.', + ), + 'tag' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'description' => 'The tag string associated with this configuration', + ), + ), + 'primary key' => array('id', 'tag'), + 'foreign keys' => array( + 'table' => 'rules_config', + 'columns' => array('id' => 'id'), + ), + ); + $schema['rules_dependencies'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier of the configuration.', + ), + 'module' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'description' => 'The name of the module that is required for the configuration.', + ), + ), + 'primary key' => array('id', 'module'), + 'indexes' => array( + 'module' => array('module'), + ), + 'foreign keys' => array( + 'table' => 'rules_config', + 'columns' => array('id' => 'id'), + ), + ); + $schema['cache_rules'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_rules']['description'] = 'Cache table for the rules engine to store configured items.'; + return $schema; +} + +/** + * Upgrade from Rules 6.x-1.x to 7.x. + */ +function rules_update_7200() { + // Create the new db tables first. + $schema['rules_config'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The internal identifier for any configuration.', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'description' => 'The name of the configuration.', + ), + 'label' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'description' => 'The label of the configuration.', + 'default' => 'unlabeled', + ), + 'plugin' => array( + 'type' => 'varchar', + 'length' => 127, + 'not null' => TRUE, + 'description' => 'The name of the plugin of this configuration.', + ), + 'active' => array( + 'description' => 'Boolean indicating whether the configuration is active. Usage depends on how the using module makes use of it.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'Weight of the configuration. Usage depends on how the using module makes use of it.', + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + // Set the default to ENTITY_CUSTOM without using the constant as it is + // not safe to use it at this point. + 'default' => 0x01, + 'size' => 'tiny', + 'description' => 'The exportable status of the entity.', + ), + 'module' => array( + 'description' => 'The name of the providing module if the entity has been defined in code.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + 'data' => array( + 'type' => 'blob', + 'size' => 'big', + 'not null' => FALSE, + 'serialize' => TRUE, + 'description' => 'Everything else, serialized.', + ), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'name' => array('name'), + ), + ); + $schema['rules_trigger'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier of the configuration.', + ), + 'event' => array( + 'type' => 'varchar', + 'length' => '127', + 'not null' => TRUE, + 'default' => '', + 'description' => 'The name of the event on which the configuration should be triggered.', + ), + ), + 'primary key' => array('id', 'event'), + 'foreign keys' => array( + 'table' => 'rules_config', + 'columns' => array('id' => 'id'), + ), + ); + db_create_table('rules_config', $schema['rules_config']); + db_create_table('rules_trigger', $schema['rules_trigger']); + // The cache table already exists, but changed. So re-create it. + db_drop_table('cache_rules'); + $schema['cache_rules'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_rules']['description'] = 'Cache table for the rules engine to store configured items.'; + db_create_table('cache_rules', $schema['cache_rules']); + // Remove deprecated variables. + variable_del('rules_inactive_sets'); + variable_del('rules_show_fixed'); + variable_del('rules_hide_token_message'); + variable_del('rules_counter'); + + return t('The database tables for Rules 2.x have been created. The old tables from Rules 1.x are still available and contain your rules, which are not updated yet.'); +} + +/** + * Add in the exportable entity db columns as required by the entity API. + */ +function rules_update_7201() { + // Previously this was update 7200, so check whether we need to run it really. + // The update has been moved as 7200 needs to be the 6.x-7.x upgrade. + if (!db_field_exists('rules_config', 'status')) { + db_add_field('rules_config', 'status', array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => ENTITY_CUSTOM, + 'size' => 'tiny', + 'description' => 'The exportable status of the entity.', + )); + // The module column did already exist before. + } +} + +/** + * Add an index for the rules configuration plugin column. + */ +function rules_update_7202() { + db_add_index('rules_config', 'plugin', array('plugin')); +} + +/** + * Fix the length of the rules_config.name column. + */ +function rules_update_7203() { + db_drop_unique_key('rules_config', 'name'); + $keys = array( + 'unique keys' => array( + 'name' => array('name'), + ), + ); + db_change_field('rules_config', 'name', 'name', array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'description' => 'The name of the configuration.', + ), $keys); +} + +/** + * Add a table for rules-config tags. + */ +function rules_update_7204() { + if (!db_table_exists('rules_tags')) { + $schema['rules_tags'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier of the configuration.', + ), + 'tag' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'description' => 'The tag string associated with this configuration', + ), + ), + 'primary key' => array('id', 'tag'), + 'foreign keys' => array( + 'table' => 'rules_config', + 'columns' => array('id' => 'id'), + ), + ); + db_create_table('rules_tags', $schema['rules_tags']); + } +} + +/** + * Add the rules_dependencies table and the rules_config.dirty column. + */ +function rules_update_7205() { + if (!db_table_exists('rules_dependencies')) { + $schema['rules_dependencies'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'The primary identifier of the configuration.', + ), + 'module' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'description' => 'The name of the module that is required for the configuration.', + ), + ), + 'primary key' => array('id', 'module'), + 'indexes' => array( + 'module' => array('module'), + ), + 'foreign keys' => array( + 'table' => 'rules_config', + 'columns' => array('id' => 'id'), + ), + ); + db_create_table('rules_dependencies', $schema['rules_dependencies']); + } + if (!db_field_exists('rules_config', 'dirty')) { + db_add_field('rules_config', 'dirty', array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + )); + } +} + +/** + * Flush all caches. + */ +function rules_update_7206() { + // The update system is going to flush all caches anyway, so nothing to do. +} + +/** + * Flush all caches. + */ +function rules_update_7207() { + // The update system is going to flush all caches anyway, so nothing to do. +} + +/** + * Flush all caches to update the data_is_empty condition info. + */ +function rules_update_7208() { + // The update system is going to flush all caches anyway, so nothing to do. +} + +/** + * Creates a flag that enables a permission for using components. + */ +function rules_update_7209() { + // Create a access exposed flag column. + db_add_field('rules_config', 'access_exposed', array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'Whether to use a permission to control access for using components.', + )); +} + +/** + * Deletes the unused rules_empty_sets variable. + */ +function rules_update_7210() { + variable_del('rules_empty_sets'); +} + +/** + * Creates the "owner" column. + */ +function rules_update_7211() { + // Create a owner column. + db_add_field('rules_config', 'owner', array( + 'description' => 'The name of the module via which the rule has been configured.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => 'rules', + )); +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1636 @@ += $settings['rules_log_errors']) { + $link = NULL; + if (isset($element) && isset($element->root()->name)) { + $link = l(t('edit configuration'), RulesPluginUI::path($element->root()->name, 'edit', $element)); + } + watchdog('rules', $msg, $args, $priority == RulesLog::WARN ? WATCHDOG_WARNING : WATCHDOG_ERROR, $link); + } + // Do nothing in case debugging is totally disabled. + if (!$settings['rules_debug_log'] && !$settings['rules_debug']) { + return; + } + if (!isset($logger)) { + $logger = RulesLog::logger(); + } + $path = isset($element) && isset($element->root()->name) ? RulesPluginUI::path($element->root()->name, 'edit', $element) : NULL; + $logger->log($msg, $args, $priority, $scope, $path); +} + +/** + * Fetches module definitions for the given hook name. + * + * Used for collecting events, rules, actions and condtions from other modules. + * + * @param $hook + * The hook of the definitions to get from invoking hook_rules_{$hook}. + */ +function rules_fetch_data($hook) { + $data = &drupal_static(__FUNCTION__, array()); + static $discover = array( + 'action_info' => 'RulesActionHandlerInterface', + 'condition_info' => 'RulesConditionHandlerInterface', + 'event_info' => 'RulesEventHandlerInterface', + ); + + if (!isset($data[$hook])) { + foreach (module_implements('rules_' . $hook) as $module) { + $result = call_user_func($module . '_rules_' . $hook); + if (isset($result) && is_array($result)) { + foreach ($result as $name => $item) { + $item += array('module' => $module); + $data[$hook][$name] = $item; + } + } + } + // Support class discovery. + if (isset($discover[$hook])) { + $data[$hook] += rules_discover_plugins($discover[$hook]); + } + drupal_alter('rules_'. $hook, $data[$hook]); + } + return $data[$hook]; +} + +/** + * Discover plugin implementations. + * + * Class based plugin handlers must be loaded when rules caches are rebuilt, + * such that they get discovered properly. You have the following options: + * - Put it into a regular module file (discouraged) + * - Put it into your module.rules.inc file + * - Put it in any file and declare it using hook_rules_file_info() + * - Put it in any file and declare it using hook_rules_directory() + * + * In addition to that, the class must be loadable via regular class + * auto-loading, thus put the file holding the class in your info file or use + * another class-loader. + * + * @param string $class + * The class or interface the plugins must implement. For a plugin to be + * discovered it must have a static getInfo() method also. + * + * @return array + * An info-hook style array containing info about discovered plugins. + * + * @see RulesActionHandlerInterface + * @see RulesConditionHandlerInterface + * @see RulesEventHandlerInterface + */ +function rules_discover_plugins($class) { + // Make sure all files possibly holding plugins are included. + RulesAbstractPlugin::includeFiles(); + + $items = array(); + foreach (get_declared_classes() as $plugin_class) { + if (is_subclass_of($plugin_class, $class) && method_exists($plugin_class, 'getInfo')) { + $info = call_user_func(array($plugin_class, 'getInfo')); + $info['class'] = $plugin_class; + $info['module'] = _rules_discover_module($plugin_class); + $items[$info['name']] = $info; + } + } + return $items; +} + +/** + * Determines the module providing the given class. + */ +function _rules_discover_module($class) { + $paths = &drupal_static(__FUNCTION__); + + if (!isset($paths)) { + // Build up a map of modules keyed by their directory. + foreach (system_list('module_enabled') as $name => $module_info) { + $paths[dirname($module_info->filename)] = $name; + } + } + + // Retrieve the class file and convert its absolute path to a regular Drupal + // path relative to the installation root. + $reflection = new ReflectionClass($class); + $path = str_replace(realpath(DRUPAL_ROOT) . DIRECTORY_SEPARATOR, '', realpath(dirname($reflection->getFileName()))); + $path = DIRECTORY_SEPARATOR != '/' ? str_replace(DIRECTORY_SEPARATOR, '/', $path) : $path; + + // Go up the path until we match a module up. + $parts = explode('/', $path); + while (!isset($paths[$path]) && array_pop($parts)) { + $path = dirname($path); + } + return isset($paths[$path]) ? $paths[$path] : FALSE; +} + +/** + * Gets a rules cache entry. + */ +function &rules_get_cache($cid = 'data') { + // Make use of the fast, advanced drupal static pattern. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__, array()); + } + $cache = &$drupal_static_fast['cache']; + + if (!isset($cache[$cid])) { + // The main 'data' cache includes translated strings, so each language is + // cached separately. + $cid_suffix = $cid == 'data' ? ':' . $GLOBALS['language']->language : ''; + + if ($get = cache_get($cid . $cid_suffix, 'cache_rules')) { + $cache[$cid] = $get->data; + } + elseif ($cid === 'data') { + // There is no 'data' cache so we need to rebuild it. Make sure subsequent + // cache gets of the main 'data' cache during rebuild get the interim + // cache by passing in the reference of the static cache variable. + _rules_rebuild_cache($cache['data']); + } + elseif (strpos($cid, 'comp_') === 0) { + $cache[$cid] = FALSE; + _rules_rebuild_component_cache(); + } + elseif (strpos($cid, 'event_') === 0) { + $cache[$cid] = FALSE; + RulesEventSet::rebuildEventCache(); + } + else { + $cache[$cid] = FALSE; + } + } + return $cache[$cid]; +} + +/** + * Rebuilds the rules cache. + * + * This rebuilds the rules 'data' cache and invokes rebuildCache() methods on + * all plugin classes, which allows plugins to add their own data to the cache. + * The cache is rebuilt in the order the plugins are defined. + * + * Note that building the action/condition info cache triggers loading of all + * components, thus depends on entity-loading and so syncing entities in code + * to the database. + * + * @see rules_rules_plugin_info() + * @see entity_defaults_rebuild() + */ +function _rules_rebuild_cache(&$cache) { + foreach(array('data_info', 'plugin_info') as $hook) { + $cache[$hook] = rules_fetch_data($hook); + } + foreach ($cache['plugin_info'] as $name => &$info) { + // Let the items add something to the cache. + $item = new $info['class'](); + $item->rebuildCache($info, $cache); + } + $cid_suffix = ':' . $GLOBALS['language']->language; + cache_set('data' . $cid_suffix, $cache, 'cache_rules'); +} + +/** + * Cache components to allow efficient usage via rules_invoke_component(). + * + * @see rules_invoke_component() + * @see rules_get_cache() + */ +function _rules_rebuild_component_cache() { + $components = rules_get_components(); + + foreach ($components as $id => $component) { + // If a component is marked as dirty, check if this still applies. + if ($component->dirty) { + rules_config_update_dirty_flag($component); + } + if (!$component->dirty) { + // Clone the component to avoid modules getting the to be cached + // version from the static loading cache. + $component = clone $component; + $component->optimize(); + // Allow modules to alter the cached component. + drupal_alter('rules_component', $component->plugin, $component); + rules_set_cache('comp_' . $component->name, $component); + } + } +} + +/** + * Sets a rules cache item. + * + * In addition to calling cache_set(), this function makes sure the cache item + * is immediately available via rules_get_cache() by keeping all cache items + * in memory. That way we can guarantee rules_get_cache() is able to retrieve + * any cache item, even if all cache gets fail. + * + * @see rules_get_cache() + */ +function rules_set_cache($cid, $data) { + $cache = &drupal_static('rules_get_cache', array()); + $cache[$cid] = $data; + cache_set($cid, $data, 'cache_rules'); +} + +/** + * Implements hook_flush_caches(). + */ +function rules_flush_caches() { + return array('cache_rules'); +} + +/** + * Clears the rule set cache + */ +function rules_clear_cache() { + cache_clear_all('*', 'cache_rules', TRUE); + variable_del('rules_event_whitelist'); + drupal_static_reset('rules_get_cache'); + drupal_static_reset('rules_fetch_data'); + drupal_static_reset('rules_config_update_dirty_flag'); + entity_get_controller('rules_config')->resetCache(); +} + +/** + * Imports the given export and returns the imported configuration. + * + * @param $export + * A serialized string in JSON format as produced by the RulesPlugin::export() + * method, or the PHP export as usual PHP array. + * @return RulesPlugin + */ +function rules_import($export, &$error_msg = '') { + return entity_get_controller('rules_config')->import($export, $error_msg); +} + + +/** + * Wraps the given data. + * + * @param $data + * If available, the actual data, else NULL. + * @param $info + * An array of info about this data. + * @param $force + * Usually data is only wrapped if really needed. If set to TRUE, wrapping the + * data is forced, so primitive data types are also wrapped. + * @return EntityMetadataWrapper + * An EntityMetadataWrapper or the unwrapped data. + * + * @see hook_rules_data_info() + */ +function &rules_wrap_data($data = NULL, $info, $force = FALSE) { + // If the data is already wrapped, use the existing wrapper. + if ($data instanceof EntityMetadataWrapper) { + return $data; + } + $cache = rules_get_cache(); + // Define the keys to be passed through to the metadata wrapper. + $wrapper_keys = array_flip(array('property info', 'property defaults')); + if (isset($cache['data_info'][$info['type']])) { + $info += array_intersect_key($cache['data_info'][$info['type']], $wrapper_keys); + } + // If a list is given, also add in the info of the item type. + $list_item_type = entity_property_list_extract_type($info['type']); + if ($list_item_type && isset($cache['data_info'][$list_item_type])) { + $info += array_intersect_key($cache['data_info'][$list_item_type], $wrapper_keys); + } + // By default we do not wrap the data, except for completely unknown types. + if (!empty($cache['data_info'][$info['type']]['wrap']) || $list_item_type || $force || empty($cache['data_info'][$info['type']])) { + unset($info['handler']); + // Allow data types to define custom wrapper classes. + if (!empty($cache['data_info'][$info['type']]['wrapper class'])) { + $class = $cache['data_info'][$info['type']]['wrapper class']; + $wrapper = new $class($info['type'], $data, $info); + } + else { + $wrapper = entity_metadata_wrapper($info['type'], $data, $info); + } + return $wrapper; + } + return $data; +} + +/** + * Unwraps the given data, if it's wrapped. + * + * @param $data + * An array of wrapped data. + * @param $info + * Optionally an array of info about how to unwrap the data. Keyed as $data. + * @return + * An array containing unwrapped or passed through data. + */ +function rules_unwrap_data(array $data, $info = array()) { + $cache = rules_get_cache(); + foreach ($data as $key => $entry) { + // If it's a wrapper, unwrap unless specified otherwise. + if ($entry instanceof EntityMetadataWrapper) { + if (!isset($info[$key]['allow null'])) { + $info[$key]['allow null'] = FALSE; + } + if (!isset($info[$key]['wrapped'])) { + // By default, do not unwrap special data types that are always wrapped. + $info[$key]['wrapped'] = (isset($info[$key]['type']) && is_string($info[$key]['type']) && !empty($cache['data_info'][$info[$key]['type']]['is wrapped'])); + } + // Activate the decode option by default if 'sanitize' is not enabled, so + // any text is either sanitized or decoded. + // @see EntityMetadataWrapper::value() + $options = $info[$key] + array('decode' => empty($info[$key]['sanitize'])); + + try { + if (!($info[$key]['allow null'] && $info[$key]['wrapped'])) { + $value = $entry->value($options); + + if (!$info[$key]['wrapped']) { + $data[$key] = $value; + } + if (!$info[$key]['allow null'] && !isset($value)) { + throw new RulesEvaluationException('The variable or parameter %name is empty.', array('%name' => $key)); + } + } + } + catch (EntityMetadataWrapperException $e) { + throw new RulesEvaluationException('Unable to get the data value for the variable or parameter %name. Error: !error', array('%name' => $key, '!error' => $e->getMessage())); + } + } + } + return $data; +} + +/** + * Gets event info for a given event. + * + * @param string $event_name + * A (configured) event name. + * + * @return array + * An array of event info. If the event is unknown, a suiting info array is + * generated and returned + */ +function rules_get_event_info($event_name) { + $base_event_name = rules_get_event_base_name($event_name); + $events = rules_fetch_data('event_info'); + if (isset($events[$base_event_name])) { + return $events[$base_event_name] + array('name' => $base_event_name); + } + return array( + 'label' => t('Unknown event "!event_name"', array('!event_name' => $base_event_name)), + 'name' => $base_event_name, + ); +} + +/** + * Returns the base name of a configured event name. + * + * For a configured event name like node_view--article the base event name + * node_view is returned. + * + * @return string + * The event base name. + */ +function rules_get_event_base_name($event_name) { + // Cut off any suffix from a configured event name. + if (strpos($event_name, '--') !== FALSE) { + $parts = explode('--', $event_name, 2); + return $parts[0]; + } + return $event_name; +} + +/** + * Returns the rule event handler for the given event. + * + * Events having no settings are handled via the class RulesEventSettingsNone. + * + * @param string $event_name + * The event name (base or configured). + * @param array $settings + * (optional) An array of event settings to set on the handler. + * + * @return RulesEventHandlerInterface + * The event handler. + */ +function rules_get_event_handler($event_name, array $settings = NULL) { + $event_name = rules_get_event_base_name($event_name); + $event_info = rules_get_event_info($event_name); + $class = !empty($event_info['class']) ? $event_info['class'] : 'RulesEventDefaultHandler'; + $handler = new $class($event_name, $event_info); + return isset($settings) ? $handler->setSettings($settings) : $handler; +} + +/** + * Creates a new instance of a the given rules plugin. + * + * @return RulesPlugin + */ +function rules_plugin_factory($plugin_name, $arg1 = NULL, $arg2 = NULL) { + $cache = rules_get_cache(); + if (isset($cache['plugin_info'][$plugin_name]['class'])) { + return new $cache['plugin_info'][$plugin_name]['class']($arg1, $arg2); + } +} + +/** + * Implementation of hook_rules_plugin_info(). + * + * Note that the cache is rebuilt in the order of the plugins. Therefore the + * condition and action plugins must be at the top, so that any components + * re-building their cache can create configurations including properly setup-ed + * actions and conditions. + */ +function rules_rules_plugin_info() { + return array( + 'condition' => array( + 'class' => 'RulesCondition', + 'embeddable' => 'RulesConditionContainer', + 'extenders' => array ( + 'RulesPluginImplInterface' => array( + 'class' => 'RulesAbstractPluginDefaults', + ), + 'RulesPluginFeaturesIntegrationInterace' => array( + 'methods' => array( + 'features_export' => 'rules_features_abstract_default_features_export', + ), + ), + 'RulesPluginUIInterface' => array( + 'class' => 'RulesAbstractPluginUI', + ), + ), + ), + 'action' => array( + 'class' => 'RulesAction', + 'embeddable' => 'RulesActionContainer', + 'extenders' => array ( + 'RulesPluginImplInterface' => array( + 'class' => 'RulesAbstractPluginDefaults', + ), + 'RulesPluginFeaturesIntegrationInterace' => array( + 'methods' => array( + 'features_export' => 'rules_features_abstract_default_features_export', + ), + ), + 'RulesPluginUIInterface' => array( + 'class' => 'RulesAbstractPluginUI', + ), + ), + ), + 'or' => array( + 'label' => t('Condition set (OR)'), + 'class' => 'RulesOr', + 'embeddable' => 'RulesConditionContainer', + 'component' => TRUE, + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesConditionContainerUI', + ), + ), + ), + 'and' => array( + 'label' => t('Condition set (AND)'), + 'class' => 'RulesAnd', + 'embeddable' => 'RulesConditionContainer', + 'component' => TRUE, + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesConditionContainerUI', + ), + ), + ), + 'action set' => array( + 'label' => t('Action set'), + 'class' => 'RulesActionSet', + 'embeddable' => FALSE, + 'component' => TRUE, + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesActionContainerUI', + ), + ), + ), + 'rule' => array( + 'label' => t('Rule'), + 'class' => 'Rule', + 'embeddable' => 'RulesRuleSet', + 'component' => TRUE, + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesRuleUI', + ), + ), + ), + 'loop' => array( + 'class' => 'RulesLoop', + 'embeddable' => 'RulesActionContainer', + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesLoopUI', + ), + ), + ), + 'reaction rule' => array( + 'class' => 'RulesReactionRule', + 'embeddable' => FALSE, + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesReactionRuleUI', + ), + ), + ), + 'event set' => array( + 'class' => 'RulesEventSet', + 'embeddable' => FALSE, + ), + 'rule set' => array( + 'label' => t('Rule set'), + 'class' => 'RulesRuleSet', + 'component' => TRUE, + // Rule sets don't get embedded - we use a separate action to execute. + 'embeddable' => FALSE, + 'extenders' => array( + 'RulesPluginUIInterface' => array( + 'class' => 'RulesRuleSetUI', + ), + ), + ), + ); +} + +/** + * Implementation of hook_entity_info(). + */ +function rules_entity_info() { + return array( + 'rules_config' => array( + 'label' => t('Rules configuration'), + 'controller class' => 'RulesEntityController', + 'base table' => 'rules_config', + 'fieldable' => TRUE, + 'entity keys' => array( + 'id' => 'id', + 'name' => 'name', + 'label' => 'label', + ), + 'module' => 'rules', + 'static cache' => TRUE, + 'bundles' => array(), + 'configuration' => TRUE, + 'exportable' => TRUE, + 'export' => array( + 'default hook' => 'default_rules_configuration', + ), + 'access callback' => 'rules_config_access', + 'features controller class' => 'RulesFeaturesController', + ), + ); +} + +/** + * Implementation of hook_hook_info(). + */ +function rules_hook_info() { + foreach(array('plugin_info', 'rules_directory', 'data_info', 'condition_info', 'action_info', 'event_info', 'file_info', 'evaluator_info', 'data_processor_info') as $hook) { + $hooks['rules_' . $hook] = array( + 'group' => 'rules', + ); + $hooks['rules_' . $hook . '_alter'] = array( + 'group' => 'rules', + ); + } + $hooks['default_rules_configuration'] = array( + 'group' => 'rules_defaults', + ); + $hooks['default_rules_configuration_alter'] = array( + 'group' => 'rules_defaults', + ); + return $hooks; +} + +/** + * Load rule configurations from the database. + * + * This function should be used whenever you need to load more than one entity + * from the database. The entities are loaded into memory and will not require + * database access if loaded again during the same page request. + * + * @see hook_entity_info() + * @see RulesEntityController + * + * @param $names + * An array of rules configuration names or FALSE to load all. + * @param $conditions + * An array of conditions in the form 'field' => $value. + * + * @return + * An array of rule configurations indexed by their ids. + */ +function rules_config_load_multiple($names = array(), $conditions = array()) { + return entity_load_multiple_by_name('rules_config', $names, $conditions); +} + +/** + * Loads a single rule configuration from the database. + * + * @see rules_config_load_multiple() + * + * @return RulesPlugin + */ +function rules_config_load($name) { + return entity_load_single('rules_config', $name); +} + +/** + * Returns an array of configured components. + * + * For actually executing a component use rules_invoke_component(), as this + * retrieves the component from cache instead. + * + * @param $label + * Whether to return only the label or the whole component object. + * @param $type + * Optionally filter for 'action' or 'condition' components. + * @param $conditions + * An array of additional conditions as required by rules_config_load(). + * + * @return + * An array keyed by component name containing either the label or the config. + */ +function rules_get_components($label = FALSE, $type = NULL, $conditions = array()) { + $cache = rules_get_cache(); + $plugins = array_keys(rules_filter_array($cache['plugin_info'], 'component', TRUE)); + $conditions = $conditions + array('plugin' => $plugins); + $faces = array( + 'action' => 'RulesActionInterface', + 'condition' => 'RulesConditionInterface', + ); + $items = array(); + foreach (rules_config_load_multiple(FALSE, $conditions) as $name => $config) { + if (!isset($type) || $config instanceof $faces[$type]) { + $items[$name] = $label ? $config->label() : $config; + } + } + return $items; +} + +/** + * Delete rule configurations from database. + * + * @param $ids + * An array of entity IDs. + */ +function rules_config_delete(array $ids) { + return entity_get_controller('rules_config')->delete($ids); +} + +/** + * Ensures the configuration's 'dirty' flag is up to date by running an integrity check. + * + * @param $update + * (optional) Whether the dirty flag is also updated in the database if + * necessary. Defaults to TRUE. + */ +function rules_config_update_dirty_flag($rules_config, $update = TRUE) { + // Keep a log of already check configurations to avoid repetitive checks on + // oftent used components. + // @see rules_element_invoke_component_validate() + $checked = &drupal_static(__FUNCTION__, array()); + if (!empty($checked[$rules_config->name])) { + return; + } + $checked[$rules_config->name] = TRUE; + + $was_dirty = !empty($rules_config->dirty); + try { + // First set the rule to dirty, so any repetitive checks give green light + // for this configuration. + $rules_config->dirty = FALSE; + $rules_config->integrityCheck(); + if ($was_dirty) { + $variables = array('%label' => $rules_config->label(), '%name' => $rules_config->name, '@plugin' => $rules_config->plugin()); + watchdog('rules', 'The @plugin %label (%name) was marked dirty, but passes the integrity check now and is active again.', $variables, WATCHDOG_INFO); + } + } + catch (RulesIntegrityException $e) { + $rules_config->dirty = TRUE; + if (!$was_dirty) { + $variables = array('%label' => $rules_config->label(), '%name' => $rules_config->name, '!message' => $e->getMessage(), '@plugin' => $rules_config->plugin()); + watchdog('rules', 'The @plugin %label (%name) fails the integrity check and cannot be executed. Error: !message', $variables, WATCHDOG_ERROR); + } + } + // Save the updated dirty flag to the database. + if ($was_dirty != $rules_config->dirty) { + db_update('rules_config') + ->fields(array('dirty' => (int) $rules_config->dirty)) + ->condition('id', $rules_config->id) + ->execute(); + } +} + +/** + * Invokes a hook and the associated rules event. + * + * Calling this function does the same as calling module_invoke_all() and + * rules_invoke_event() separately, however merges both functions into one in + * order to ease usage and to work efficiently. + * + * @param $hook + * The name of the hook / event to invoke. + * @param ... + * Arguments to pass to the hook / event. + * + * @return + * An array of return values of the hook implementations. If modules return + * arrays from their implementations, those are merged into one array. + */ +function rules_invoke_all() { + // Copied code from module_invoke_all(). + $args = func_get_args(); + $hook = $args[0]; + unset($args[0]); + $return = array(); + foreach (module_implements($hook) as $module) { + $function = $module . '_' . $hook; + if (function_exists($function)) { + $result = call_user_func_array($function, $args); + if (isset($result) && is_array($result)) { + $return = array_merge_recursive($return, $result); + } + elseif (isset($result)) { + $return[] = $result; + } + } + } + // Invoke the event. + rules_invoke_event_by_args($hook, $args); + + return $return; +} + +/** + * Invokes configured rules for the given event. + * + * @param $event_name + * The event's name. + * @param ... + * Pass parameters for the variables provided by this event, as defined in + * hook_rules_event_info(). Example given: + * @code + * rules_invoke_event('node_view', $node, $view_mode); + * @endcode + * + * @see rules_invoke_event_by_args() + */ +function rules_invoke_event() { + global $conf; + + $args = func_get_args(); + $event_name = $args[0]; + unset($args[0]); + // We maintain a whitelist of configured events to reduces the number of cache + // reads. We access it directly via the global $conf as this is fast without + // having to introduce another static cache. Then, if the whitelist is unset, + // we ignore it so cache rebuilding is triggered. + if (!defined('MAINTENANCE_MODE') && (!isset($conf['rules_event_whitelist']) || isset($conf['rules_event_whitelist'][$event_name])) && $event = rules_get_cache('event_' . $event_name)) { + $event->executeByArgs($args); + } +} + +/** + * Invokes configured rules for the given event. + * + * @param $event_name + * The event's name. + * @param $args + * An array of parameters for the variables provided by the event, as defined + * in hook_rules_event_info(). Either pass an array keyed by the variable + * names or a numerically indexed array, in which case the ordering of the + * passed parameters has to match the order of the specified variables. + * Example given: + * @code + * rules_invoke_event_by_args('node_view', array('node' => $node, 'view_mode' => $view_mode)); + * @endcode + * + * @see rules_invoke_event() + */ +function rules_invoke_event_by_args($event_name, $args = array()) { + global $conf; + + // We maintain a whitelist of configured events to reduces the number of cache + // reads. We access it directly via the global $conf as this is fast without + // having to introduce another static cache. Then, if the whitelist is unset, + // we ignore it so cache rebuilding is triggered. + if (!defined('MAINTENANCE_MODE') && (!isset($conf['rules_event_whitelist']) || isset($conf['rules_event_whitelist'][$event_name])) && $event = rules_get_cache('event_' . $event_name)) { + $event->executeByArgs($args); + } +} + +/** + * Invokes a rule component, e.g. a rule set. + * + * @param $component_name + * The component's name. + * @param $args + * Pass further parameters as required for the invoked component. + * + * @return + * An array of variables as provided by the component, or FALSE in case the + * component could not be executed. + */ +function rules_invoke_component() { + $args = func_get_args(); + $name = array_shift($args); + if ($component = rules_get_cache('comp_' . $name)) { + return $component->executeByArgs($args); + } + return FALSE; +} + +/** + * Filters the given array of arrays by keeping only entries which have $key set + * to the value of $value. + * + * @param $array + * The array of arrays to filter. + * @param $key + * The key used for the comparison. + * @param $value + * The value to compare the array's entry to. + * + * @return array + * The filtered array. + */ +function rules_filter_array($array, $key, $value) { + $return = array(); + foreach ($array as $i => $entry) { + $entry += array($key => NULL); + if ($entry[$key] == $value) { + $return[$i] = $entry; + } + } + return $return; +} + +/** + * Merges the $update array into $array making sure no values of $array not + * appearing in $update are lost. + * + * @return + * The updated array. + */ +function rules_update_array(array $array, array $update) { + foreach ($update as $key => $data) { + if (isset($array[$key]) && is_array($array[$key]) && is_array($data)) { + $array[$key] = rules_update_array($array[$key], $data); + } + else { + $array[$key] = $data; + } + } + return $array; +} + +/** + * Extracts the property with the given name. + * + * @param $arrays + * An array of arrays from which a property is to be extracted. + * @param $key + * The name of the property to extract. + * + * @return An array of extracted properties, keyed as in $arrays- + */ +function rules_extract_property($arrays, $key) { + $data = array(); + foreach ($arrays as $name => $item) { + $data[$name] = $item[$key]; + } + return $data; +} + +/** + * Returns the first key of the array. + */ +function rules_array_key($array) { + reset($array); + return key($array); +} + +/** + * Clean replacements so they are URL friendly. + * + * Can be used as 'cleaning callback' for action or condition parameters. + * + * @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. + * + * @see rules_path_action_info() + */ +function rules_path_clean_replacement_values(&$replacements, $data = array(), $options = array()) { + // Include path.eval.inc which contains path cleaning functions. + module_load_include('inc', 'rules', 'modules/path.eval'); + foreach ($replacements as $token => $value) { + $replacements[$token] = rules_clean_path($value); + } +} + +/** + * Implements hook_theme(). + */ +function rules_theme() { + return array( + 'rules_elements' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_content_group' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_parameter_configuration' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_variable_view' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_data_selector_help' => array( + 'variables' => array('parameter' => NULL, 'variables' => NULL), + 'file' => 'ui/ui.theme.inc', + ), + 'rules_ui_variable_form' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_log' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_autocomplete' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_debug_element' => array( + 'render element' => 'element', + 'file' => 'ui/ui.theme.inc', + ), + 'rules_settings_help' => array( + 'variables' => array('text' => '', 'heading' => ''), + 'file' => 'ui/ui.theme.inc', + ), + ); +} + +/** + * Implements hook_permission(). + */ +function rules_permission() { + $perms = array( + 'administer rules' => array( + 'title' => t('Administer rule configurations'), + 'description' => t('Administer rule configurations including events, conditions and actions for which the user has sufficient access permissions.'), + ), + 'bypass rules access' => array( + 'title' => t('Bypass Rules access control'), + 'description' => t('Control all configurations regardless of permission restrictions of events, conditions or actions.'), + 'restrict access' => TRUE, + ), + 'access rules debug' => array( + 'title' => t('Access the Rules debug log'), + ), + ); + + // Fetch all components to generate the access keys. + $conditions['plugin'] = array_keys(rules_filter_array(rules_fetch_data('plugin_info'), 'component', TRUE)); + $conditions['access_exposed'] = 1; + $components = entity_load('rules_config', FALSE, $conditions); + $perms += rules_permissions_by_component($components); + + return $perms; +} + +/** + * Helper function to get all the permissions for components that have access exposed. + */ +function rules_permissions_by_component(array $components = array()) { + $perms = array(); + foreach ($components as $component) { + $perms += array( + "use Rules component $component->name" => array( + 'title' => t('Use Rules component %component', array('%component' => $component->label())), + 'description' => t('Controls access for using the component %component via the provided action or condition. Edit this component.', array('%component' => $component->label(), '@component-edit-url' => url(RulesPluginUI::path($component->name)))), + ), + ); + } + return $perms; +} + +/** + * Menu callback for loading rules configuration elements. + * @see RulesUIController::config_menu() + */ +function rules_element_load($element_id, $config_name) { + $config = rules_config_load($config_name); + return $config->elementMap()->lookup($element_id); +} + +/** + * Menu callback for getting the title as configured. + * @see RulesUIController::config_menu() + */ +function rules_get_title($text, $element) { + if ($element instanceof RulesPlugin) { + $cache = rules_get_cache(); + $plugin = $element->plugin(); + $plugin = isset($cache['plugin_info'][$plugin]['label']) ? $cache['plugin_info'][$plugin]['label'] : $plugin; + $plugin = drupal_strtolower(drupal_substr($plugin, 0, 1)) . drupal_substr($plugin, 1); + return t($text, array('!label' => $element->label(), '!plugin' => $plugin)); + } + // As fallback treat $element as simple string. + return t($text, array('!plugin' => $element)); +} + +/** + * Menu callback for getting the title for the add element page. + * + * Uses a work-a-round for accessing the plugin name. + * @see RulesUIController::config_menu() + */ +function rules_menu_add_element_title($array) { + $plugin_name = arg($array[0]); + $cache = rules_get_cache(); + if (isset($cache['plugin_info'][$plugin_name]['class'])) { + $info = $cache['plugin_info'][$plugin_name] + array('label' => $plugin_name); + $label = drupal_strtolower(drupal_substr($info['label'], 0, 1)) . drupal_substr($info['label'], 1); + return t('Add a new !plugin', array('!plugin' => $label)); + } +} + +/** + * Returns the current region for the debug log. + */ +function rules_debug_log_region() { + // If there is no setting for the current theme use the default theme setting. + global $theme_key; + $theme_default = variable_get('theme_default', 'bartik'); + return variable_get('rules_debug_region_' . $theme_key, variable_get('rules_debug_region_' . $theme_default, 'help')); +} + +/** + * Implements hook_page_build() to add the rules debug log to the page bottom. + */ +function rules_page_build(&$page) { + // Invoke a the page redirect, in case the action has been executed. + // @see rules_action_drupal_goto() + if (isset($GLOBALS['_rules_action_drupal_goto_do'])) { + list($url, $force) = $GLOBALS['_rules_action_drupal_goto_do']; + drupal_goto($url); + } + + if (isset($_SESSION['rules_debug'])) { + $region = rules_debug_log_region(); + foreach ($_SESSION['rules_debug'] as $log) { + $page[$region]['rules_debug'][] = array( + '#markup' => $log, + ); + $page[$region]['rules_debug']['#theme_wrappers'] = array('rules_log'); + } + unset($_SESSION['rules_debug']); + } + + if (rules_show_debug_output()) { + $region = rules_debug_log_region(); + $page[$region]['rules_debug']['#pre_render'] = array('rules_debug_log_pre_render'); + } +} + +/** + * Pre-render callback for the debug log, which renders and then clears it. + */ +function rules_debug_log_pre_render($elements) { + $logger = RulesLog::logger(); + if ($log = $logger->render()) { + $logger = RulesLog::logger(); + $logger->clear(); + $elements[] = array('#markup' => $log); + $elements['#theme_wrappers'] = array('rules_log'); + // Log the rules log to the system log if enabled. + if (variable_get('rules_debug_log', FALSE)) { + watchdog('rules', 'Rules debug information: !log', array('!log' => $log), WATCHDOG_NOTICE); + } + } + return $elements; +} + +/** + * Implements hook_drupal_goto_alter(). + * + * @see rules_action_drupal_goto() + */ +function rules_drupal_goto_alter(&$path, &$options, &$http_response_code) { + // Invoke a the page redirect, in case the action has been executed. + if (isset($GLOBALS['_rules_action_drupal_goto_do'])) { + list($url, $force) = $GLOBALS['_rules_action_drupal_goto_do']; + + if ($force || !isset($_GET['destination'])) { + $url = drupal_parse_url($url); + $path = $url['path']; + $options['query'] = $url['query']; + $options['fragment'] = $url['fragment']; + $http_response_code = 302; + } + } +} + +/** + * Returns whether the debug log should be shown. + */ +function rules_show_debug_output() { + if (variable_get('rules_debug', FALSE) == RulesLog::INFO && user_access('access rules debug')) { + return TRUE; + } + // For performance avoid unnecessary auto-loading of the RulesLog class. + return variable_get('rules_debug', FALSE) == RulesLog::WARN && user_access('access rules debug') && class_exists('RulesLog', FALSE) && RulesLog::logger()->hasErrors(); +} + +/** + * Implements hook_exit(). + */ +function rules_exit() { + // Bail out if this is cached request and modules are not loaded. + if (!module_exists('rules') || !module_exists('user')) { + return; + } + if (rules_show_debug_output()) { + if ($log = RulesLog::logger()->render()) { + // Keep the log in the session so we can show it on the next page. + $_SESSION['rules_debug'][] = $log; + } + } + // Log the rules log to the system log if enabled. + if (variable_get('rules_debug_log', FALSE) && $log = RulesLog::logger()->render()) { + watchdog('rules', 'Rules debug information: !log', array('!log' => $log), WATCHDOG_NOTICE); + } +} + +/** + * Implements hook_element_info(). + */ +function rules_element_info() { + // A duration form element for rules. Needs ui.forms.inc included. + $types['rules_duration'] = array( + '#input' => TRUE, + '#tree' => TRUE, + '#default_value' => 0, + '#value_callback' => 'rules_ui_element_duration_value', + '#process' => array('rules_ui_element_duration_process', 'ajax_process_form'), + '#after_build' => array('rules_ui_element_duration_after_build'), + '#pre_render' => array('form_pre_render_conditional_form_element'), + ); + $types['rules_data_selection'] = array( + '#input' => TRUE, + '#pre_render' => array('form_pre_render_conditional_form_element'), + '#process' => array('rules_data_selection_process', 'ajax_process_form'), + '#theme' => 'rules_autocomplete', + ); + return $types; +} + +/** + * Implements hook_modules_enabled(). + */ +function rules_modules_enabled($modules) { + // Re-enable Rules configurations that are dirty, because they require one of + // the enabled the modules. + $query = db_select('rules_dependencies', 'rd'); + $query->join('rules_config', 'rc', 'rd.id = rc.id'); + $query->fields('rd', array('id')) + ->condition('rd.module', $modules, 'IN') + ->condition('rc.dirty', 1); + $ids = $query->execute()->fetchCol(); + + // If there are some configurations that might work again, re-check all dirty + // configurations as others might work again too, e.g. consider a rule that is + // dirty because it requires a dirty component. + if ($ids) { + $rules_configs = rules_config_load_multiple(FALSE, array('dirty' => 1)); + foreach ($rules_configs as $rules_config) { + try { + $rules_config->integrityCheck(); + // If no exceptions were thrown we can set the configuration back to OK. + db_update('rules_config') + ->fields(array('dirty' => 0)) + ->condition('id', $rules_config->id) + ->execute(); + if ($rules_config->active) { + drupal_set_message(t('All dependencies for the Rules configuration %config are met again, so it has been re-activated.', array('%config' => $rules_config->label()))); + } + } + catch (RulesIntegrityException $e) { + // The rule is still dirty, so do nothing. + } + } + } + rules_clear_cache(); +} + +/** + * Implements hook_modules_disabled(). + */ +function rules_modules_disabled($modules) { + // Disable Rules configurations that depend on one of the disabled modules. + $query = db_select('rules_dependencies', 'rd'); + $query->join('rules_config', 'rc', 'rd.id = rc.id'); + $query->fields('rd', array('id')) + ->distinct() + ->condition('rd.module', $modules, 'IN') + ->condition('rc.dirty', 0); + $ids = $query->execute()->fetchCol(); + + if (!empty($ids)) { + db_update('rules_config') + ->fields(array('dirty' => 1)) + ->condition('id', $ids, 'IN') + ->execute(); + // Tell the user about enabled rules that have been marked as dirty. + $count = db_select('rules_config', 'r') + ->fields('r') + ->condition('id', $ids, 'IN') + ->condition('active', 1) + ->execute()->rowCount(); + if ($count > 0) { + $message = format_plural($count, + '1 Rules configuration requires some of the disabled modules to function and cannot be executed any more.', + '@count Rules configuration require some of the disabled modules to function and cannot be executed any more.' + ); + drupal_set_message($message, 'warning'); + } + } + rules_clear_cache(); +} + +/** + * Access callback for dealing with Rules configurations. + * + * @see entity_access() + */ +function rules_config_access($op, $rules_config = NULL, $account = NULL) { + if (user_access('bypass rules access', $account)) { + return TRUE; + } + // Allow modules to grant / deny access. + $access = module_invoke_all('rules_config_access', $op, $rules_config, $account); + + // Only grant access if at least one module granted access and no one denied + // access. + if (in_array(FALSE, $access, TRUE)) { + return FALSE; + } + elseif (in_array(TRUE, $access, TRUE)) { + return TRUE; + } + return FALSE; +} + +/** + * Implements hook_rules_config_access(). + */ +function rules_rules_config_access($op, $rules_config = NULL, $account = NULL) { + // Instead of returning FALSE return nothing, so others still can grant + // access. + if (!isset($rules_config) || (isset($account) && $account->uid != $GLOBALS['user']->uid)) { + return; + } + if (user_access('administer rules', $account) && ($op == 'view' || $rules_config->access())) { + return TRUE; + } +} + +/** + * Implements hook_menu(). + */ +function rules_menu() { + $items['admin/config/workflow/rules/upgrade'] = array( + 'title' => 'Upgrade', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_upgrade_form'), + 'access arguments' => array('administer rules'), + 'file' => 'includes/rules.upgrade.inc', + 'file path' => drupal_get_path('module', 'rules'), + 'type' => MENU_CALLBACK, + ); + $items['admin/config/workflow/rules/upgrade/clear'] = array( + 'title' => 'Clear', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_upgrade_confirm_clear_form'), + 'access arguments' => array('administer rules'), + 'file' => 'includes/rules.upgrade.inc', + 'file path' => drupal_get_path('module', 'rules'), + 'type' => MENU_CALLBACK, + ); + $items['admin/config/workflow/rules/autocomplete_tags'] = array( + 'title' => 'Rules tags autocomplete', + 'page callback' => 'rules_autocomplete_tags', + 'page arguments' => array(5), + 'access arguments' => array('administer rules'), + 'file' => 'ui/ui.forms.inc', + 'type' => MENU_CALLBACK, + ); + return $items; +} + +/** + * Helper function to keep track of external documentation pages for Rules. + * + * @param $topic + * The topic key for used for identifying help pages. + * + * @return + * Either a URL for the given page, or the full list of external help pages. + */ +function rules_external_help($topic = NULL) { + $help = array( + 'rules' => 'http://drupal.org/node/298480', + 'terminology' => 'http://drupal.org/node/1299990', + 'condition-components' => 'http://drupal.org/node/1300034', + 'data-selection' => 'http://drupal.org/node/1300042', + 'chained-tokens' => 'http://drupal.org/node/1300042', + 'loops' => 'http://drupal.org/node/1300058', + 'components' => 'http://drupal.org/node/1300024', + 'component-types' => 'http://drupal.org/node/1300024', + 'variables' => 'http://drupal.org/node/1300024', + 'scheduler' => 'http://drupal.org/node/1300068', + 'coding' => 'http://drupal.org/node/878720', + ); + + if (isset($topic)) { + return isset($help[$topic]) ? $help[$topic] : FALSE; + } + return $help; +} + +/** + * Implements hook_help(). + */ +function rules_help($path, $arg) { + // Only enable the help if the admin module is active. + if ($path == 'admin/help#rules' && module_exists('rules_admin')) { + + $output['header'] = array( + '#markup' => t('Rules documentation is kept online. Please use the links below for more information about Rules. Feel free to contribute to improving the online documentation!'), + ); + // Build a list of essential Rules help pages, formatted as a bullet list. + $link_list['rules'] = l(t('Rules introduction'), rules_external_help('rules')); + $link_list['terminology'] = l(t('Rules terminology'), rules_external_help('terminology')); + $link_list['scheduler'] = l(t('Rules Scheduler'), rules_external_help('scheduler')); + $link_list['coding'] = l(t('Coding for Rules'), rules_external_help('coding')); + + $output['topic-list'] = array( + '#markup' => theme('item_list', array('items' => $link_list)), + ); + return render($output); + } +} + +/** + * Implements hook_token_info(). + */ +function rules_token_info() { + $cache = rules_get_cache(); + $data_info = $cache['data_info']; + + $types = array('text', 'integer', 'uri', 'token', 'decimal', 'date', 'duration'); + + foreach ($types as $type) { + $token_type = $data_info[$type]['token type']; + + $token_info['types'][$token_type] = array( + 'name' => $data_info[$type]['label'], + 'description' => t('Tokens related to %label Rules variables.', array('%label' => $data_info[$type]['label'])), + 'needs-data' => $token_type, + ); + $token_info['tokens'][$token_type]['value'] = array( + 'name' => t("Value"), + 'description' => t('The value of the variable.'), + ); + } + return $token_info; +} + +/** + * Implements hook_tokens(). + */ +function rules_tokens($type, $tokens, $data, $options = array()) { + // Handle replacements of primitive variable types. + if (substr($type, 0, 6) == 'rules_' && !empty($data[$type])) { + // Leverage entity tokens token processor by passing on as struct. + $info['property info']['value'] = array( + 'type' => substr($type, 6), + 'label' => '', + ); + // Entity tokens uses metadata wrappers as values for 'struct' types. + $wrapper = entity_metadata_wrapper('struct', array('value' => $data[$type]), $info); + return entity_token_tokens('struct', $tokens, array('struct' => $wrapper), $options); + } +} + +/** + * Helper function that retrieves a metadata wrapper with all properties. + * + * Note that without this helper, bundle-specific properties aren't added. + */ +function rules_get_entity_metadata_wrapper_all_properties(RulesAbstractPlugin $element) { + return entity_metadata_wrapper($element->settings['type'], NULL, array( + 'property info alter' => 'rules_entity_metadata_wrapper_all_properties_callback', + )); +} + +/** + * Callback that returns a metadata wrapper with all properties. + */ +function rules_entity_metadata_wrapper_all_properties_callback(EntityMetadataWrapper $wrapper, $property_info) { + $info = $wrapper->info(); + $properties = entity_get_all_property_info($info['type']); + $property_info['properties'] += $properties; + return $property_info; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,116 @@ + 'reaction rule', 'active' => TRUE); + $collapsed = TRUE; + if (empty($_GET['tag'])) { + $tag = 0; + } + else { + $tag = $_GET['tag']; + $conditions['tags'] = array($tag); + $collapsed = FALSE; + } + if (empty($_GET['event'])) { + $event = 0; + } + else { + $event = $_GET['event']; + // Filter using a wildcard suffix so configured event names with suffixes + // are found also. + $conditions['event'] = $event . '%'; + $collapsed = FALSE; + } + $form['help'] = array( + '#type' => 'markup', + '#markup' => t('Reaction rules, listed below, react on selected events on the site. Each reaction rule may fire any number of actions, and may have any number of conditions that must be met for the actions to be executed. You can also set up components – stand-alone sets of Rules configuration that can be used in Rules and other parts of your site. See the online documentation for an introduction on how to use Rules.', + array('@url1' => url('admin/config/workflow/rules/components'), + '@url2' => rules_external_help('rules'))), + ); + + $form['filter'] = array( + '#type' => 'fieldset', + '#title' => t('Filter'), + '#collapsible' => TRUE, + ); + $form['filter']['#id'] = 'rules-filter-form'; + $form['filter']['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.css'; + $form['filter']['event'] = array( + '#type' => 'select', + '#title' => t('Filter by event'), + '#options' => array(0 => t('')) + RulesPluginUI::getOptions('event'), + '#default_value' => $event, + ); + $form['filter']['tag'] = array( + '#type' => 'select', + '#title' => t('Filter by tag'), + '#options' => array(0 => t('')) + RulesPluginUI::getTags(), + '#default_value' => $tag, + ); + $form['filter']['submit'] = array( + '#type' => 'submit', + '#value' => t('Filter'), + '#name' => '', // prevent from showing up in $_GET. + ); + + $options = array('show plugin' => FALSE, 'base path' => $base_path); + $form['active'] = rules_ui()->overviewTable($conditions, $options); + $form['active']['#caption'] = t('Active rules'); + $form['active']['#empty'] = t('There are no active rules. Add new rule.', array('!url' => url('admin/config/workflow/rules/reaction/add'))); + + $conditions['active'] = FALSE; + $form['inactive'] = rules_ui()->overviewTable($conditions, $options); + $form['inactive']['#caption'] = t('Inactive rules'); + $form['inactive']['#empty'] = t('There are no inactive rules.'); + + $form['filter']['#collapsed'] = $collapsed; + $form['#submit'][] = 'rules_form_submit_rebuild'; + $form['#method'] = 'get'; + return $form; +} + +/** + * Components overview. + */ +function rules_admin_components_overview($form, &$form_state, $base_path) { + RulesPluginUI::formDefaults($form, $form_state); + + $collapsed = TRUE; + if (empty($_GET['tag'])) { + $tag = 0; + } + else { + $tag = $_GET['tag']; + $conditions['tags'] = array($tag); + $collapsed = FALSE; + } + if (empty($_GET['plugin'])) { + // Get the plugin name usable as component. + $conditions['plugin'] = array_keys(rules_filter_array(rules_fetch_data('plugin_info'), 'component', TRUE)); + $plugin = 0; + } + else { + $plugin = $_GET['plugin']; + $conditions['plugin'] = $plugin; + $collapsed = FALSE; + } + $form['help'] = array( + '#type' => 'markup', + '#markup' => t('Components are stand-alone sets of Rules configuration that can be used by Rules and other modules on your site. Components are for example useful if you want to use the same conditions, actions or rules in multiple places, or call them from your custom module. You may also export each component separately. See the online documentation for more information about how to use components.', + array('@url' => rules_external_help('components'))), + ); + $form['filter'] = array( + '#type' => 'fieldset', + '#title' => t('Filter'), + '#collapsible' => TRUE, + ); + $form['filter']['#id'] = 'rules-filter-form'; + $form['filter']['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.css'; + $form['filter']['plugin'] = array( + '#type' => 'select', + '#title' => t('Filter by plugin'), + '#options' => array(0 => t('')) + rules_admin_component_options(), + '#default_value' => $plugin, + ); + $form['filter']['tag'] = array( + '#type' => 'select', + '#title' => t('Filter by tag'), + '#options' => array(0 => '') + RulesPluginUI::getTags(), + '#default_value' => $tag, + ); + $form['filter']['submit'] = array( + '#type' => 'submit', + '#value' => t('Filter'), + '#name' => '', // prevent from showing up in $_GET. + ); + + $form['table'] = RulesPluginUI::overviewTable($conditions, array('hide status op' => TRUE)); + $form['table']['#empty'] = t('There are no rule components.'); + + $form['filter']['#collapsed'] = $collapsed; + $form['#submit'][] = 'rules_form_submit_rebuild'; + $form['#method'] = 'get'; + return $form; +} + +/** + * Rules settings form. + */ +function rules_admin_settings($form, &$form_state) { + + if (module_exists('path')) { + // Present a list of available path cleaning callbacks. + // @see rules_clean_path() + $options = array( + 'rules_path_default_cleaning_method' => t('Rules (built in)'), + ); + if (module_exists('ctools')) { + $options['rules_path_clean_ctools'] = t('CTools'); + } + if (module_exists('pathauto')) { + $options['rules_path_clean_pathauto'] = t('Pathauto'); + $pathauto_help = t("Note that Pathauto's URL path cleaning method can be configured at admin/config/search/path/settings.", array('!url' => url('admin/config/search/path/settings'))); + } + else { + $pathauto_help = t('Install the Pathauto module in order to get a configurable URL path cleaning method.'); + } + + $form['path']['rules_path_cleaning_callback'] = array( + '#type' => 'select', + '#title' => t('URL path cleaning method'), + '#description' => t('Choose the path cleaning method to be applied when generating URL path aliases.') . ' ' . $pathauto_help, + '#default_value' => variable_get('rules_path_cleaning_callback', 'rules_path_default_cleaning_method'), + '#options' => $options, + ); + } + + $form['rules_log_errors'] = array( + '#type' => 'radios', + '#title' => t('Logging of Rules evaluation errors'), + '#options' => array( + RulesLog::WARN => t('Log all warnings and errors'), + RulesLog::ERROR => t('Log errors only'), + ), + '#default_value' => variable_get('rules_log_errors', RulesLog::WARN), + '#description' => t('Evaluations errors are logged to the system log.'), + ); + + $form['debug'] = array( + '#type' => 'fieldset', + '#title' => t('Debugging'), + ); + $form['debug']['rules_debug_log'] = array( + '#type' => 'checkbox', + '#title' => t('Log debug information to the system log'), + '#default_value' => variable_get('rules_debug_log', 0), + ); + $form['debug']['rules_debug'] = array( + '#type' => 'radios', + '#title' => t('Show debug information'), + '#default_value' => variable_get('rules_debug', 0), + '#options' => array( + 0 => t('Never'), + RulesLog::WARN => t('In case of errors'), + RulesLog::INFO => t('Always'), + ), + '#description' => t('Debug information is only shown when rules are evaluated and is visible for users having the permission %link.', array('%link' => t('Access the Rules debug log'), '!url' => url('admin/people/permissions', array('fragment' => 'module-rules')))), + ); + + $form['debug']['regions'] = array( + '#type' => 'container', + '#states' => array( + // Hide the regions settings when the debug log is disabled. + 'invisible' => array( + 'input[name="rules_debug"]' => array('value' => '0'), + ), + ), + ); + + $theme_default = variable_get('theme_default', 'bartik'); + $admin_theme = variable_get('admin_theme', 'seven'); + + $form['debug']['regions']['rules_debug_region_' . $theme_default] = array( + '#type' => 'select', + '#title' => t('Default theme region'), + '#description' => t("The region, where the debug log should be displayed on the default theme %theme. For other themes, Rules will try to display the log on the same region, or hide it in case it doesn't exist.", array('%theme' => $theme_default)), + '#options' => system_region_list($theme_default, REGIONS_VISIBLE), + '#default_value' => variable_get('rules_debug_region_' . $theme_default, 'help'), + ); + + $form['debug']['regions']['rules_debug_region_' . $admin_theme] = array( + '#type' => 'select', + '#title' => t('Admin theme region'), + '#description' => t('The region, where the debug log should be displayed on the admin theme %theme.', array('%theme' => $admin_theme)), + '#options' => system_region_list($admin_theme, REGIONS_VISIBLE), + '#default_value' => variable_get('rules_debug_region_' . $admin_theme, 'help'), + ); + if (db_table_exists('rules_rules')) { + drupal_set_message(t('There are left over rule configurations from a previous Rules 1.x installation. Proceed to the upgrade page to convert them and consult the README.txt for more details.', array('!url' => url('admin/config/workflow/rules/upgrade'))), 'warning'); + } + + return system_settings_form($form); +} + +/** + * Advanced settings form. + */ +function rules_admin_settings_advanced($form, &$form_state) { + + $form['integrity'] = array( + '#type' => 'fieldset', + '#title' => t('Integrity'), + '#description' => t('Rules checks the integrity of your configurations to discover and exclude broken configurations from evaluation.'), + ); + $form['integrity']['start_integrity_check'] = array( + '#type' => 'submit', + '#value' => t('Recheck integrity'), + '#submit' => array('rules_admin_settings_integrity_check_submit'), + ); + $form['cache'] = array( + '#type' => 'fieldset', + '#title' => t('Cache'), + '#description' => t('Rules caches information about available actions, conditions and data types. Additionally all components and reaction rules are cached for efficient evaluation.'), + ); + $form['cache']['rebuild_rules_cache'] = array( + '#type' => 'submit', + '#value' => t("Rebuild Rules' cache"), + '#weight' => 2, + '#submit' => array('rules_admin_settings_cache_rebuild_submit'), + ); + return $form; +} + +/** + * Form submit callback to check the integrity of all configurations. + */ +function rules_admin_settings_integrity_check_submit($form, &$form_state) { + $start = microtime(TRUE); + $count = 0; + $rules_configs = rules_config_load_multiple(FALSE); + foreach ($rules_configs as $rules_config) { + rules_config_update_dirty_flag($rules_config, TRUE, TRUE); + if ($rules_config->dirty) { + $count++; + $variables = array('%label' => $rules_config->label(), '%name' => $rules_config->name, '@plugin' => $rules_config->plugin(), '!uri'=> url(RulesPluginUI::path($rules_config->name))); + drupal_set_message(t('The @plugin %label (%name) fails the integrity check and cannot be executed.', $variables), 'error'); + } + + } + drupal_set_message(t('Integrity of %count configurations checked in %duration seconds. %count_failed broken configurations found.', array( + '%count' => count($rules_configs), + '%count_failed' => $count, + '%duration' => round(microtime(TRUE) - $start, 2), + ))); +} + +/** + * Form submit callback: Rebuild the Rules' cache. + */ +function rules_admin_settings_cache_rebuild_submit($form, &$form_state) { + $start = microtime(TRUE); + rules_clear_cache(); + // Manually trigger cache rebuilding of all caches. + rules_get_cache(); + _rules_rebuild_component_cache(); + RulesEventSet::rebuildEventCache(); + drupal_set_message(t('Rules cache rebuilt in %duration seconds.', array( + '%duration' => round(microtime(TRUE) - $start, 2), + ))); +} + +/** + * Add reaction rules form. + */ +function rules_admin_add_reaction_rule($form, &$form_state, $base_path) { + RulesPluginUI::formDefaults($form, $form_state); + + $rules_config = isset($form_state['rules_config']) ? $form_state['rules_config'] : rules_reaction_rule(); + $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE)); + + $form['settings']['#collapsible'] = FALSE; + $form['settings']['#type'] = 'container'; + $form['settings']['label']['#default_value'] = ''; + + // Hide the rule elements stuff for now. + foreach (array('elements', 'conditions', 'add', 'events') as $key) { + $form[$key]['#access'] = FALSE; + } + foreach (array('active', 'weight') as $key) { + $form['settings'][$key]['#access'] = FALSE; + } + // Incorporate the form to add the first event. + $form['settings'] += rules_ui_add_event(array(), $form_state, $rules_config, $base_path); + $form['settings']['event']['#tree'] = FALSE; + $form['settings']['event_settings']['#tree'] = FALSE; + unset($form['settings']['help']); + + unset($form['settings']['submit']); + $form['submit']['#value'] = t('Save'); + + $form_state += array('rules_config' => $rules_config); + $form['#validate'][] = 'rules_ui_add_reaction_rule_validate'; + $form['#validate'][] = 'rules_ui_edit_element_validate'; + $form['#submit'][] = 'rules_ui_add_reaction_rule_submit'; + return $form; +} + +/** + * Form validation callback. + */ +function rules_ui_add_reaction_rule_validate(&$form, &$form_state) { + rules_ui_add_event_validate($form['settings'], $form_state); +} + +/** + * Form submit callback. + */ +function rules_ui_add_reaction_rule_submit(&$form, &$form_state) { + rules_ui_add_event_apply($form['settings'], $form_state); + rules_ui_edit_element_submit($form, $form_state); +} + +/** + * Add component form. + */ +function rules_admin_add_component($form, &$form_state, $base_path) { + RulesPluginUI::$basePath = $base_path; + RulesPluginUI::formDefaults($form, $form_state); + + $form['plugin_name'] = array( + '#type' => 'select', + '#title' => t('Component plugin'), + '#options' => rules_admin_component_options(), + '#description' => t('Choose which kind of component to create. Each component type is described in the online documentation.', + array('@url' => rules_external_help('component-types'))), + '#weight' => -2, + '#default_value' => isset($form_state['values']['plugin_name']) ? $form_state['values']['plugin_name'] : '', + ); + + if (!isset($form_state['rules_config'])) { + $form['continue'] = array( + '#type' => 'submit', + '#name' => 'continue', + '#submit' => array('rules_admin_add_component_create_submit'), + '#value' => t('Continue'), + ); + } + else { + $form['plugin_name']['#disabled'] = TRUE; + $form_state['rules_config']->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE, 'init' => TRUE)); + $form['settings']['#collapsible'] = FALSE; + $form['settings']['#type'] = 'container'; + $form['settings']['label']['#default_value'] = ''; + $form['settings']['#weight'] = -1; + + // Hide the rule elements stuff for now. + foreach (array('elements', 'negate') as $key) { + $form[$key]['#access'] = FALSE; + } + foreach (array('active', 'weight') as $key) { + $form['settings'][$key]['#access'] = FALSE; + } + } + return $form; +} + +function rules_admin_component_options() { + $cache = rules_get_cache(); + return rules_extract_property(rules_filter_array($cache['plugin_info'], 'component', TRUE), 'label'); +} + +/** + * Submit callback that creates the new component object initially. + */ +function rules_admin_add_component_create_submit($form, &$form_state) { + $form_state['rules_config'] = rules_plugin_factory($form_state['values']['plugin_name']); + $form_state['rebuild'] = TRUE; +} + +/** + * Validation callback for adding a component. + */ +function rules_admin_add_component_validate($form, &$form_state) { + if (isset($form_state['rules_config'])) { + $form_state['rules_config']->form_validate($form, $form_state); + } +} + +/** + * Final submit callback for adding a component. + */ +function rules_admin_add_component_submit($form, &$form_state) { + $rules_config = $form_state['rules_config']; + $rules_config->form_submit($form, $form_state); + drupal_set_message(t('Your changes have been saved.')); + $form_state['redirect'] = RulesPluginUI::path($rules_config->name); +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_admin/rules_admin.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_admin/rules_admin.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = Rules UI +description = Administrative interface for managing rules. +package = Rules +core = 7.x +files[] = rules_admin.module +files[] = rules_admin.inc +dependencies[] = rules + +; Information added by drupal.org packaging script on 2013-09-16 +version = "7.x-2.4" +core = "7.x" +project = "rules" +datestamp = "1379354606" + diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_admin/rules_admin.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_admin/rules_admin.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,134 @@ +config_menu($reaction_path); + + $items[$reaction_path] = array( + 'title' => 'Rules', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -1, + ); + $items[$reaction_path . '/add'] = array( + 'title' => 'Add new rule', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_admin_add_reaction_rule', $reaction_path), + 'access arguments' => array('administer rules'), + 'type' => MENU_LOCAL_ACTION, + 'file' => 'rules_admin.inc', + 'weight' => 0, + ); + $items[$reaction_path . '/import'] = array( + 'title' => 'Import rule', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_import_form', $reaction_path), + 'access arguments' => array('administer rules'), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + 'type' => MENU_LOCAL_ACTION, + ); + + // Components UI menu entries. + $component_path = 'admin/config/workflow/rules/components'; + $items += rules_ui()->config_menu($component_path); + $items[$component_path] = array( + 'title' => 'Components', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_admin_components_overview', $component_path), + 'access arguments' => array('administer rules'), + 'type' => MENU_LOCAL_TASK, + 'file' => 'rules_admin.inc', + 'weight' => 0, + ); + $items[$component_path . '/add'] = array( + 'title' => 'Add new component', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_admin_add_component', $component_path), + 'access arguments' => array('administer rules'), + 'type' => MENU_LOCAL_ACTION, + 'file' => 'rules_admin.inc', + 'weight' => 0, + ); + $items[$component_path . '/import'] = array( + 'title' => 'Import component', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_import_form', $component_path), + 'access arguments' => array('administer rules'), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + 'type' => MENU_LOCAL_ACTION, + ); + + // Some general rules admin menu items. + $items['admin/config/workflow/rules'] = array( + 'title' => 'Rules', + 'position' => 'right', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_admin_reaction_overview', $reaction_path), + 'description' => 'Manage reaction rules and rule components.', + 'access arguments' => array('administer rules'), + 'file' => 'rules_admin.inc', + ); + $items['admin/config/workflow/rules/settings'] = array( + 'title' => 'Settings', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_admin_settings'), + 'access arguments' => array('administer rules'), + 'type' => MENU_LOCAL_TASK, + 'file' => 'rules_admin.inc', + 'weight' => 1, + ); + $items['admin/config/workflow/rules/settings/basic'] = array( + 'title' => 'Basic', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/config/workflow/rules/settings/advanced'] = array( + 'title' => 'Advanced', + 'type' => MENU_LOCAL_TASK, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_admin_settings_advanced'), + 'access arguments' => array('administer rules'), + 'file' => 'rules_admin.inc', + ); + return $items; +} + +/** + * Implements hook_form_alter(). + * + * Since the overview forms are GET forms, we don't want them to send a wide + * variety of information. We need to use hook_form_alter() because the + * properties are added after form creation. + */ +function rules_admin_form_alter(&$form, &$form_state, $form_id) { + if ($form_id == 'rules_admin_reaction_overview' || $form_id == 'rules_admin_components_overview') { + $form['form_build_id']['#access'] = FALSE; + $form['form_token']['#access'] = FALSE; + $form['form_id']['#access'] = FALSE; + } +} + +/** + * Implements hook_system_info_alter(). + * + * Adds configuration links for Rules and Rules Scheduler in the modules list. + * (This is done by the Rules UI module, without which there would be no + * configuration pages to visit.) + */ +function rules_admin_system_info_alter(&$info, $file, $type) { + if ($file->name == 'rules') { + $info['configure'] = 'admin/config/workflow/rules'; + } + if ($file->name == 'rules_scheduler') { + $info['configure'] = 'admin/config/workflow/rules/schedule'; + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_i18n/rules_i18n.i18n.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_i18n/rules_i18n.i18n.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,94 @@ + t('Configuration name'), + 'string' => $this->object->label, + ); + + $this->buildElementProperties($this->object, $properties); + + // Add in translations for all elements. + foreach ($this->object->elements() as $element) { + $this->buildElementProperties($element, $properties); + } + $strings[$this->get_textgroup()]['rules_config'][$this->object->name] = $properties; + return $strings; + } + + /** + * Adds in translatable properties of the given element. + */ + protected function buildElementProperties($element, &$properties) { + + foreach ($element->pluginParameterInfo() as $name => $info) { + // Add in all directly provided input variables. + if (!empty($info['translatable']) && isset($element->settings[$name])) { + // If its an array of textual values, translate each value on its own. + if (is_array($element->settings[$name])) { + foreach ($element->settings[$name] as $i => $value) { + $properties[$element->elementId() . ':' . $name . ':' . $i] = array( + 'title' => t('@plugin "@label" (id @id), @parameter, Value @delta', array('@plugin' => drupal_ucfirst($element->plugin()), '@label' => $element->label(), '@id' => $element->elementId(), '@parameter' => $info['label'], '@delta' => $i + 1)), + 'string' => $value, + ); + } + } + else { + $properties[$element->elementId() . ':' . $name] = array( + 'title' => t('@plugin "@label" (id @id), @parameter', array('@plugin' => drupal_ucfirst($element->plugin()), '@label' => $element->label(), '@id' => $element->elementId(), '@parameter' => $info['label'])), + 'string' => $element->settings[$name], + ); + } + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_i18n/rules_i18n.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_i18n/rules_i18n.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,15 @@ +name = Rules translation +description = Allows translating rules. +dependencies[] = rules +dependencies[] = i18n_string +package = Multilingual - Internationalization +core = 7.x +files[] = rules_i18n.i18n.inc +files[] = rules_i18n.rules.inc +files[] = rules_i18n.test +; Information added by drupal.org packaging script on 2013-09-16 +version = "7.x-2.4" +core = "7.x" +project = "rules" +datestamp = "1379354606" + diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_i18n/rules_i18n.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_i18n/rules_i18n.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,132 @@ + 'Edit', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -100, + ); + + // For reaction-rules i18n generates the menu items, for the others we provide + // further i18n menu items for all other base paths. + + if ($base_path != 'admin/config/workflow/rules/reaction') { + + $items[$base_path . '/manage/%rules_config/translate'] = array( + 'title' => 'Translate', + 'page callback' => 'i18n_page_translate_localize', + 'page arguments' => array('rules_config', $base_count + 1), + 'access callback' => 'i18n_object_translate_access', + 'access arguments' => array('rules_config', $base_count + 1), + 'type' => MENU_LOCAL_TASK, + 'file' => 'i18n.pages.inc', + 'file path' => drupal_get_path('module', 'i18n'), + 'weight' => 10, + ); + + $items[$base_path . '/manage/%rules_config/translate/%i18n_language'] = array( + 'title' => 'Translate', + 'page callback' => 'i18n_page_translate_localize', + 'page arguments' => array('rules_config', $base_count + 1, $base_count + 3), + 'access callback' => 'i18n_object_translate_access', + 'access arguments' => array('rules_config', $base_count), + 'type' => MENU_CALLBACK, + 'file' => 'i18n.pages.inc', + 'file path' => drupal_get_path('module', 'i18n'), + 'weight' => 10, + ); + } +} + +/** + * Implements hook_entity_info_alter(). + */ +function rules_i18n_entity_info_alter(&$info) { + // Enable i18n support via the entity API. + $info['rules_config']['i18n controller class'] = 'RulesI18nStringController'; +} + +/** + * Implements hook_rules_config_insert(). + */ +function rules_i18n_rules_config_insert($rules_config) { + // Do nothing when rebuilding defaults to avoid multiple cache rebuilds. + // @see rules_i18n_rules_config_defaults_rebuild() + if (!empty($rules_config->is_rebuild)) { + return; + } + + i18n_string_object_update('rules_config', $rules_config); +} + +/** + * Implements hook_rules_config_update(). + */ +function rules_i18n_rules_config_update($rules_config, $original = NULL) { + // Do nothing when rebuilding defaults to avoid multiple cache rebuilds. + // @see rules_i18n_rules_config_defaults_rebuild() + if (!empty($rules_config->is_rebuild)) { + return; + } + $original = $original ? $original : $rules_config->original; + + // Account for name changes. + if ($original->name != $rules_config->name) { + i18n_string_update_context("rules:rules_config:{$original->name}:*", "rules:rules_config:{$rules_config->name}:*"); + } + + // We need to remove the strings of any disappeared properties, i.e. strings + // from translatable parameters of deleted actions. + + // i18n_object() uses a static cache per config, so bypass it to wrap the + // original entity. + $object_key = i18n_object_key('rules_config', $original); + $old_i18n_object = new RulesI18nStringObjectWrapper('rules_config', $object_key, $original); + $old_strings = $old_i18n_object->get_strings(array('empty' => TRUE)); + + // Note: For the strings to have updated values, the updated entity needs to + // be handled last due to i18n's cache. + $strings = i18n_object('rules_config', $rules_config)->get_strings(array('empty' => TRUE)); + + foreach (array_diff_key($old_strings, $strings) as $name => $string) { + $string->remove(array('empty' => TRUE)); + } + // Now update the remaining strings. + foreach ($strings as $string) { + $string->update(array('empty' => TRUE, 'update' => TRUE)); + } +} + +/** + * Implements hook_rules_config_delete(). + */ +function rules_i18n_rules_config_delete($rules_config) { + i18n_string_object_remove('rules_config', $rules_config); +} + +/** + * Implements hook_rules_config_defaults_rebuild(). + */ +function rules_i18n_rules_config_defaults_rebuild($rules_configs, $originals) { + // Once all defaults have been rebuilt, update all i18n strings at once. That + // way we build the rules cache once the rebuild is complete and avoid + // rebuilding caches for each updated rule. + foreach ($rules_configs as $name => $rule_config) { + if (empty($originals[$name])) { + rules_i18n_rules_config_insert($rule_config); + } + else { + rules_i18n_rules_config_update($rule_config, $originals[$name]); + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_i18n/rules_i18n.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_i18n/rules_i18n.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,196 @@ + t('Translate a text'), + 'group' => t('Translation'), + 'parameter' => array( + 'text' => array( + 'type' => 'text', + 'label' => t('Text'), + 'description' => t('The text to translate.'), + 'translatable' => TRUE, + ), + 'language' => array( + 'type' => 'token', + 'label' => t('Language'), + 'description' => t('The language to translate the text into.'), + 'options list' => 'entity_metadata_language_list', + 'default mode' => 'select', + ), + ), + 'provides' => array( + 'text' => array('type' => 'text', 'label' => t('The translated text')), + ), + 'base' => 'rules_i18n_action_t', + 'access callback' => 'rules_i18n_rules_integration_access', + ); + $items['rules_i18n_select'] = array( + 'label' => t('Select a translated value'), + 'group' => t('Translation'), + 'parameter' => array( + 'data' => array( + 'type' => '*', + 'label' => t('Data'), + 'description' => t('Select a translated value, e.g. a translatable field. If the selected data is not translatable, the language neutral value will be selected.'), + 'translatable' => TRUE, + 'restrict' => 'select', + ), + 'language' => array( + 'type' => 'token', + 'label' => t('Language'), + 'description' => t('The language to translate the value into.'), + 'options list' => 'entity_metadata_language_list', + ), + ), + 'provides' => array( + 'data_translated' => array('type' => '*', 'label' => t('The translated value')), + ), + 'base' => 'rules_i18n_action_select', + 'access callback' => 'rules_i18n_rules_integration_access', + ); + return $items; +} + +/** + * Access callback for the rules i18n integration. + */ +function rules_i18n_rules_integration_access() { + return user_access('translate interface'); +} + +/** + * Action callback: Translate a text. + */ +function rules_i18n_action_t($text) { + // Nothing to do, as our input evaluator has already translated it. + // @see RulesI18nStringEvaluator + return array('text' => $text); +} + +/** + * Implements the form_alter callback for the "Translate a text" action to set a default selector. + */ +function rules_i18n_action_t_form_alter(&$form, &$form_state, $options, $element) { + if (isset($form['parameter']['language']['settings']['language:select']) && empty($element->settings['language:select'])) { + $form['parameter']['language']['settings']['language:select']['#default_value'] = 'site:current-page:language'; + } +} + +/** + * Action callback: Select a translated value. + */ +function rules_i18n_action_select($data) { + // Nothing to do, as Rules applies the language to the data selector for us. + return array('data_translated' => $data); +} + +/** + * Action "Select a translated value" info_alter callback. + */ +function rules_i18n_action_select_info_alter(&$element_info, $element) { + $element->settings += array('data:select' => NULL); + if ($wrapper = $element->applyDataSelector($element->settings['data:select'])) { + $info = $wrapper->info(); + // Pass through the data type of the selected data. + $element_info['provides']['data_translated']['type'] = $wrapper->type(); + } +} + +/** + * Implements hook_rules_evaluator_info(). + */ +function rules_i18n_rules_evaluator_info() { + return array( + 'i18n' => array( + 'class' => 'RulesI18nStringEvaluator', + 'type' => array('text', 'list', 'token', 'list'), + // Be sure to translate after doing PHP evaluation. + 'weight' => -8, + ), + ); +} + +/** + * A class implementing a rules input evaluator processing tokens. + */ +class RulesI18nStringEvaluator extends RulesDataInputEvaluator { + + public static function access() { + return user_access('translate admin strings'); + } + + public function prepare($text, $var_info, $param_info = NULL) { + if (!empty($param_info['translatable'])) { + $this->setting = TRUE; + } + else { + // Else, skip this evaluator. + $this->setting = NULL; + } + } + + /** + * Prepare the i18n-context string. + * + * We have to use process() here instead of evaluate() because we need more + * context than evaluate() provides. + */ + public function process($value, $info, RulesState $state, RulesPlugin $element, $options = NULL) { + $options = isset($options) ? $options : $this->getEvaluatorOptions($info, $state, $element); + $value = isset($this->processor) ? $this->processor->process($value, $info, $state, $element, $options) : $value; + if (isset($element->root()->name)) { + $config_name = $element->root()->name; + $id = $element->elementId(); + $name = $info['#name']; + $options['i18n context'] = "rules:rules_config:$config_name:$id:$name"; + return $this->evaluate($value, $options, $state); + } + return $value; + } + + /** + * Translate the value. + * + * If the element provides a language parameter, we are using this target + * language provided via $options['language']. Sanitizing is handled by Rules, + * so disable that for i18n. + */ + public function evaluate($value, $options, RulesState $state) { + $langcode = isset($options['language']) ? $options['language']->language : NULL; + if (is_array($value)) { + foreach ($value as $key => $text) { + $value[$key] = i18n_string($options['i18n context'] . ':' . $key, $text, array('langcode' => $langcode, 'sanitize' => FALSE)); + } + } + else { + $value = i18n_string($options['i18n context'], $value, array('langcode' => $langcode, 'sanitize' => FALSE)); + } + return $value; + } + + public static function help($var_info, $param_info = array()) { + if (!empty($param_info['translatable'])) { + if ($param_info['custom translation language']) { + $text = t('Translations can be provided at the %translate tab. The argument value is translated to the configured language.', array('%translate' => t('Translate'))); + } + else { + $text = t('Translations can be provided at the %translate tab. The argument value is translated to the current interface language.', array('%translate' => t('Translate'))); + } + $render = array( + '#theme' => 'rules_settings_help', + '#text' => $text, + '#heading' => t('Translation'), + ); + return $render; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_i18n/rules_i18n.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_i18n/rules_i18n.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,183 @@ + 'Rules I18n', + 'description' => 'Tests translating Rules configs.', + 'group' => 'Rules', + 'dependencies' => array('i18n_string'), + ); + } + + public function setUp() { + parent::setUp('rules_i18n'); + $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages')); + $this->drupalLogin($this->admin_user); + $this->addLanguage('de'); + } + + /** + * Copied from i18n module (class Drupali18nTestCase). + * + * We cannot extend from Drupali18nTestCase as else the test-bot would die. + */ + public function addLanguage($language_code) { + // Check to make sure that language has not already been installed. + $this->drupalGet('admin/config/regional/language'); + + if (strpos($this->drupalGetContent(), 'enabled[' . $language_code . ']') === FALSE) { + // Doesn't have language installed so add it. + $edit = array(); + $edit['langcode'] = $language_code; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Make sure we are not using a stale list. + drupal_static_reset('language_list'); + $languages = language_list('language'); + $this->assertTrue(array_key_exists($language_code, $languages), t('Language was installed successfully.')); + + if (array_key_exists($language_code, $languages)) { + $this->assertRaw(t('The language %language has been created and can now be used. More information is available on the help screen.', array('%language' => $languages[$language_code]->name, '@locale-help' => url('admin/help/locale'))), t('Language has been created.')); + } + } + elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $language_code . ']'))) { + // It's installed and enabled. No need to do anything. + $this->assertTrue(true, 'Language [' . $language_code . '] already installed and enabled.'); + } + else { + // It's installed but not enabled. Enable it. + $this->assertTrue(true, 'Language [' . $language_code . '] already installed.'); + $this->drupalPost(NULL, array('enabled[' . $language_code . ']' => TRUE), t('Save configuration')); + $this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.')); + } + } + + /** + * Tests translating rules configs. + */ + public function testRulesConfigTranslation() { + // Create a rule and translate it. + $rule = rule(); + $rule->label = 'label-en'; + $rule->action('drupal_message', array('message' => 'English message for [site:current-user].')); + $rule->save(); + + $actions = $rule->actions(); + $id = $actions[0]->elementId(); + + // Add a translation. + i18n_string_textgroup('rules')->update_translation("rules_config:{$rule->name}:label", 'de', 'label-de'); + i18n_string_textgroup('rules')->update_translation("rules_config:{$rule->name}:$id:message", 'de', 'German message für [site:current-user].'); + + // Execute the Rule and make sure the translated message has been output. + // To do so, set the global language to German. + $languages = language_list(); + $GLOBALS['language'] = $languages['de']; + + // Clear messages and execute the rule. + i18n_string_textgroup('rules')->cache_reset(); + drupal_get_messages(); + $rule->execute(); + + $messages = drupal_get_messages(); + $this->assertEqual($messages['status'][0], 'German message für ' . $GLOBALS['user']->name . '.', 'Translated message has been output.'); + + // Test re-naming the rule. + $rule->name = 'rules_i18n_name_2'; + $rule->save(); + $translation = entity_i18n_string("rules:rules_config:{$rule->name}:label", $rule->label, 'de'); + $this->assertEqual($translation, 'label-de', 'Translation survives a name change.'); + + // Test updating and make sure the translation stays. + $rule->label = 'Label new'; + $rule->save(); + $translation = entity_i18n_string("rules:rules_config:{$rule->name}:label", $rule->label, 'de'); + $this->assertEqual($translation, 'label-de', 'Translation survives an update.'); + + // Test deleting the action and make sure the string is deleted too. + $actions[0]->delete(); + $rule->save(); + $translation = entity_i18n_string("rules_config:{$rule->name}:$id:message", 'English message for [site:current-user].', 'de'); + $this->assertEqual($translation, 'English message for [site:current-user].', 'Translation of deleted action has been deleted.'); + + // Now delete the whole config and make sure all translations are deleted. + $rule->delete(); + $translation = entity_i18n_string("rules_config:{$rule->name}:label", 'label-en', 'de'); + $this->assertEqual($translation, 'label-en', 'Translation of deleted config has been deleted.'); + } + + /** + * Tests the "Translate a text" action. + */ + public function testI18nActionT() { + $set = rules_action_set(array()); + $set->action('rules_i18n_t', array( + 'text' => 'untranslated', + 'language' => 'de', + )); + $set->action('drupal_message', array('message:select' => 'text')); + $set->save('rules_i18n_test'); + + // Add a translation. + $actions = $set->getIterator(); + $id = $actions[0]->elementId(); + i18n_string_textgroup('rules')->update_translation("rules_config:{$set->name}:$id:text", 'de', 'text-de'); + + // Clear messages and execute it. + drupal_get_messages(); + $set->execute(); + $messages = drupal_get_messages(); + $this->assertEqual($messages['status'][0], 'text-de', 'Text has been successfully translated.'); + + // Enable the PHP module and make sure PHP in translations is not evaluted. + module_enable(array('php')); + i18n_string_textgroup('rules')->update_translation("rules_config:{$set->name}:$id:text", 'de', 'text '); + + // Clear messages and execute it. + drupal_get_messages(); + $set->execute(); + $messages = drupal_get_messages(); + $this->assertEqual($messages['status'][0], check_plain('text '), 'PHP in translated text is not executed.'); + } + + /** + * Tests the "Select a translated value" action. + */ + public function testI18nActionSelect() { + // Make the body field and the node type 'page' translatable. + $field = field_info_field('body'); + $field['translatable'] = TRUE; + field_update_field($field); + variable_set('language_content_type_page', 1); + + $set = rules_action_set(array('node' => array('type' => 'node'))); + $set->action('rules_i18n_select', array( + 'data:select' => 'node:body:value', + 'language' => 'de', + 'data_translated:var' => 'body', + )); + $set->action('drupal_message', array('message:select' => 'body')); + $set->save(); + + $body['en'][0] = array('value' => 'English body.'); + $body['de'][0] = array('value' => 'German body.'); + $node = $this->drupalCreateNode(array('language' => 'en', 'body' => $body)); + + // Clear messages and execute it. + drupal_get_messages(); + $set->execute($node); + + $messages = drupal_get_messages(); + $this->assertEqual($messages['status'][0], "German body.\n", 'Translated text has been selected.'); + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/includes/rules_scheduler.handler.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/includes/rules_scheduler.handler.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,99 @@ +task = $task; + } + + /** + * Implements RulesSchedulerTaskHandlerInterface::runTask(). + */ + public function runTask() { + if ($component = rules_get_cache('comp_' . $this->task['config'])) { + $replacements = array('%label' => $component->label(), '%plugin' => $component->plugin()); + $replacements['%identifier'] = $this->task['identifier'] ? $this->task['identifier'] : t('without identifier'); + rules_log('Scheduled evaluation of %plugin %label, task %identifier.', $replacements, RulesLog::INFO, $component, TRUE); + $state = unserialize($this->task['data']); + $state->restoreBlocks(); + // Block the config to prevent any future recursion. + $state->block($component); + // Finally evaluate the component with the given state. + $component->evaluate($state); + $state->unblock($component); + rules_log('Finished evaluation of %plugin %label, task %identifier.', $replacements, RulesLog::INFO, $component, FALSE); + $state->cleanUp(); + } + } + + /** + * Implements RulesSchedulerTaskHandlerInterface::afterTaskQueued(). + */ + public function afterTaskQueued() { + // Delete the task from the task list. + db_delete('rules_scheduler') + ->condition('tid', $this->task['tid']) + ->execute(); + } + + /** + * Implements RulesSchedulerTaskHandlerInterface::getTask(). + */ + public function getTask() { + return $this->task; + } + +} + +/** + * Interface for scheduled task handlers. + * + * Task handlers control the behavior of a task when it's queued or executed. + * Unless specified otherwise, the RulesSchedulerDefaultTaskHandler task handler + * is used. + * + * @see rules_scheduler_run_task() + * @see rules_scheduler_cron() + * @see RulesSchedulerDefaultTaskHandler + */ +interface RulesSchedulerTaskHandlerInterface { + + /** + * Processes a queue item. + * + * @throws RulesEvaluationException + * If there are any problems executing the task. + * + * @see rules_scheduler_run_task() + */ + public function runTask(); + + /** + * Processes a task after it has been queued. + * + * @see rules_scheduler_cron() + */ + public function afterTaskQueued(); + + /** + * Returns the task associated with the task handler. + * + * @return array + * The task (queue item) array. + */ + public function getTask(); + +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/includes/rules_scheduler.views.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/includes/rules_scheduler.views.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,83 @@ + array( + 'table' => array( + 'group' => 'Rules scheduler', + 'base' => array( + 'field' => 'tid', + 'title' => t('Scheduled Rules components'), + 'help' => t("Scheduled Rules components that are executed based on time and cron"), + 'weight' => -10, + ), + ), + 'tid' => array( + 'title' => t('Tid'), + 'help' => t('The internal ID of the scheduled component'), + 'field' => array( + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ), + 'config' => array( + 'title' => t('Component name'), + 'help' => t('The name of the component'), + 'field' => array( + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'rules_scheduler_views_filter', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ), + 'date' => array( + 'title' => t('Scheduled date'), + 'help' => t('Scheduled date and time stamp'), + 'field' => array( + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ), + 'identifier' => array( + 'title' => t('User provided identifier'), + 'help' => t('ID to recognize this specific scheduled task'), + 'field' => array( + 'click sortable' => TRUE, + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + ), + ), + ); + return $table; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/includes/rules_scheduler.views_default.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/includes/rules_scheduler.views_default.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,180 @@ +name = 'rules_scheduler'; + $view->description = 'Scheduled Rules components'; + $view->tag = ''; + $view->base_table = 'rules_scheduler'; + $view->api_version = '3.0-alpha1'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Defaults */ + $handler = $view->new_display('default', 'Defaults', 'default'); + $handler->display->display_options['access']['type'] = 'perm'; + $handler->display->display_options['access']['perm'] = 'administer rules'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['pager']['options']['items_per_page'] = '30'; + $handler->display->display_options['pager']['options']['offset'] = '0'; + $handler->display->display_options['pager']['options']['id'] = '0'; + $handler->display->display_options['style_plugin'] = 'table'; + $handler->display->display_options['style_options']['columns'] = array( + 'tid' => 'tid', + 'config' => 'config', + 'date' => 'date', + 'identifier' => 'identifier', + 'nothing' => 'nothing', + ); + $handler->display->display_options['style_options']['default'] = 'date'; + $handler->display->display_options['style_options']['info'] = array( + 'tid' => array( + 'sortable' => 0, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'config' => array( + 'sortable' => 1, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'date' => array( + 'sortable' => 1, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'identifier' => array( + 'sortable' => 1, + 'default_sort_order' => 'asc', + 'align' => '', + 'separator' => '', + ), + 'nothing' => array( + 'align' => '', + 'separator' => '', + ), + ); + $handler->display->display_options['style_options']['override'] = 1; + $handler->display->display_options['style_options']['sticky'] = 0; + /* Empty text: Global: Text area */ + $handler->display->display_options['empty']['area']['id'] = 'area'; + $handler->display->display_options['empty']['area']['table'] = 'views'; + $handler->display->display_options['empty']['area']['field'] = 'area'; + $handler->display->display_options['empty']['area']['empty'] = FALSE; + $handler->display->display_options['empty']['area']['content'] = 'No tasks have been scheduled.'; + $handler->display->display_options['empty']['area']['format'] = 'plain_text'; + /* Field: Rules scheduler: Tid */ + $handler->display->display_options['fields']['tid']['id'] = 'tid'; + $handler->display->display_options['fields']['tid']['table'] = 'rules_scheduler'; + $handler->display->display_options['fields']['tid']['field'] = 'tid'; + /* Field: Rules scheduler: Component name */ + $handler->display->display_options['fields']['config']['id'] = 'config'; + $handler->display->display_options['fields']['config']['table'] = 'rules_scheduler'; + $handler->display->display_options['fields']['config']['field'] = 'config'; + $handler->display->display_options['fields']['config']['alter']['alter_text'] = 0; + $handler->display->display_options['fields']['config']['alter']['make_link'] = 1; + $handler->display->display_options['fields']['config']['alter']['path'] = 'admin/config/workflow/rules/config/[config]'; + $handler->display->display_options['fields']['config']['alter']['absolute'] = 0; + $handler->display->display_options['fields']['config']['alter']['trim'] = 0; + $handler->display->display_options['fields']['config']['alter']['word_boundary'] = 1; + $handler->display->display_options['fields']['config']['alter']['ellipsis'] = 1; + $handler->display->display_options['fields']['config']['alter']['strip_tags'] = 0; + $handler->display->display_options['fields']['config']['alter']['html'] = 0; + $handler->display->display_options['fields']['config']['element_label_colon'] = 1; + $handler->display->display_options['fields']['config']['element_default_classes'] = 1; + $handler->display->display_options['fields']['config']['hide_empty'] = 0; + $handler->display->display_options['fields']['config']['empty_zero'] = 0; + /* Field: Rules scheduler: Scheduled date */ + $handler->display->display_options['fields']['date']['id'] = 'date'; + $handler->display->display_options['fields']['date']['table'] = 'rules_scheduler'; + $handler->display->display_options['fields']['date']['field'] = 'date'; + /* Field: Rules scheduler: User provided identifier */ + $handler->display->display_options['fields']['identifier']['id'] = 'identifier'; + $handler->display->display_options['fields']['identifier']['table'] = 'rules_scheduler'; + $handler->display->display_options['fields']['identifier']['field'] = 'identifier'; + /* Field: Global: Custom text */ + $handler->display->display_options['fields']['nothing']['id'] = 'nothing'; + $handler->display->display_options['fields']['nothing']['table'] = 'views'; + $handler->display->display_options['fields']['nothing']['field'] = 'nothing'; + $handler->display->display_options['fields']['nothing']['label'] = 'Operations'; + $handler->display->display_options['fields']['nothing']['alter']['text'] = 'delete'; + $handler->display->display_options['fields']['nothing']['alter']['make_link'] = 1; + $handler->display->display_options['fields']['nothing']['alter']['path'] = 'admin/config/workflow/rules/schedule/[tid]/delete'; + $handler->display->display_options['fields']['nothing']['alter']['absolute'] = 0; + $handler->display->display_options['fields']['nothing']['alter']['alt'] = 'Delete this scheduled task'; + $handler->display->display_options['fields']['nothing']['alter']['trim'] = 0; + $handler->display->display_options['fields']['nothing']['alter']['word_boundary'] = 1; + $handler->display->display_options['fields']['nothing']['alter']['ellipsis'] = 1; + $handler->display->display_options['fields']['nothing']['alter']['strip_tags'] = 0; + $handler->display->display_options['fields']['nothing']['alter']['html'] = 0; + $handler->display->display_options['fields']['nothing']['element_label_colon'] = 1; + $handler->display->display_options['fields']['nothing']['element_default_classes'] = 1; + $handler->display->display_options['fields']['nothing']['hide_empty'] = 0; + $handler->display->display_options['fields']['nothing']['empty_zero'] = 0; + /* Sort criterion: Rules scheduler: Scheduled date */ + $handler->display->display_options['sorts']['date']['id'] = 'date'; + $handler->display->display_options['sorts']['date']['table'] = 'rules_scheduler'; + $handler->display->display_options['sorts']['date']['field'] = 'date'; + /* Argument: Rules scheduler: Component name */ + $handler->display->display_options['arguments']['config']['id'] = 'config'; + $handler->display->display_options['arguments']['config']['table'] = 'rules_scheduler'; + $handler->display->display_options['arguments']['config']['field'] = 'config'; + $handler->display->display_options['arguments']['config']['style_plugin'] = 'default_summary'; + $handler->display->display_options['arguments']['config']['wildcard'] = '0'; + $handler->display->display_options['arguments']['config']['default_argument_type'] = 'fixed'; + $handler->display->display_options['arguments']['config']['glossary'] = 0; + $handler->display->display_options['arguments']['config']['limit'] = '0'; + $handler->display->display_options['arguments']['config']['transform_dash'] = 0; + /* Filter: Rules scheduler: Component name */ + $handler->display->display_options['filters']['config']['id'] = 'config'; + $handler->display->display_options['filters']['config']['table'] = 'rules_scheduler'; + $handler->display->display_options['filters']['config']['field'] = 'config'; + $handler->display->display_options['filters']['config']['exposed'] = TRUE; + $handler->display->display_options['filters']['config']['expose']['operator'] = 'config_op'; + $handler->display->display_options['filters']['config']['expose']['label'] = 'Component filter'; + $handler->display->display_options['filters']['config']['expose']['identifier'] = 'config'; + $handler->display->display_options['filters']['config']['expose']['remember'] = 1; + $handler->display->display_options['filters']['config']['expose']['use_operator'] = 0; + $handler->display->display_options['filters']['config']['expose']['reduce'] = 0; + $translatables['rules_scheduler'] = array( + t('Defaults'), + t('more'), + t('Apply'), + t('Reset'), + t('Sort By'), + t('Asc'), + t('Desc'), + t('Items per page'), + t('- All -'), + t('Offset'), + t('No tasks have been scheduled.'), + t('Tid'), + t('Component name'), + t('admin/config/workflow/rules/config/[config]'), + t('Scheduled date'), + t('User provided identifier'), + t('Operations'), + t('delete'), + t('admin/config/workflow/rules/schedule/[tid]/delete'), + t('Delete this scheduled task'), + t('All'), + t('Component filter'), + ); + + $views = array(); + $views[$view->name] = $view; + return $views; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/includes/rules_scheduler_views_filter.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/includes/rules_scheduler_views_filter.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,22 @@ +value_options)) { + $this->value_title = t('Component'); + $result = db_select('rules_scheduler', 'r') + ->fields('r', array('config')) + ->distinct() + ->execute(); + $config_names = array(); + foreach ($result as $record) { + $config_names[$record->config] = $record->config; + } + $this->value_options = $config_names; + } + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/rules_scheduler.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/rules_scheduler.admin.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,140 @@ +override_path = RULES_SCHEDULER_PATH; + $task_list = $view->preview(); + } + else { + $task_list = t('To display scheduled tasks you have to install the Views module.'); + } + $page['task_view'] = array( + '#markup' => $task_list, + ); + $form = drupal_get_form('rules_scheduler_form'); + $page['delete'] = array( + '#markup' => drupal_render($form), + ); + return $page; +} + +/** + * Form for deletion of tasks by component. + */ +function rules_scheduler_form($form, &$form_state) { + $result = db_select('rules_scheduler', 'r') + ->fields('r', array('config')) + ->distinct() + ->execute(); + $config_options = array_intersect_key(rules_get_components(TRUE), $result->fetchAllAssoc('config')); + + // Fieldset for canceling by component name. + $form['delete_by_config'] = array( + '#type' => 'fieldset', + '#title' => t('Delete tasks by component name'), + '#disabled' => empty($config_options) + ); + $form['delete_by_config']['config'] = array( + '#title' => t('Component'), + '#type' => 'select', + '#options' => $config_options, + '#description' => t('Select the component for which to delete all scheduled tasks.'), + '#required' => TRUE, + ); + $form['delete_by_config']['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete tasks'), + '#submit' => array('rules_scheduler_form_delete_by_config_submit'), + ); + return $form; +} + +/** + * Submit handler for deleting future scheduled tasks. + */ +function rules_scheduler_form_delete_by_config_submit($form, &$form_state) { + $config = rules_config_load($form_state['values']['config']); + rules_action('schedule_delete')->execute($config->name); + drupal_set_message(t('All scheduled tasks associated with %config have been deleted.', array('%config' => $config->label()))); + $form_state['redirect'] = RULES_SCHEDULER_PATH; +} + +/** + * Confirmation form for deleting single tasks. + */ +function rules_scheduler_delete_task($form, &$form_state, $task) { + $form_state['task'] = $task; + $config = rules_config_load($task['config']); + $path['path'] = isset($_GET['destination']) ? $_GET['destination'] : RULES_SCHEDULER_PATH; + + $title = t('Are you sure you want to delete the scheduled task %id?', array('%id' => $task['tid'])); + if (!empty($task['identifier'])) { + $msg = t('This task with the custom identifier %id executes component %label on %date. The action cannot be undone.', array( + '%label' => $config->label(), + '%id' => $task['identifier'], + '%date' => format_date($task['date']), + )); + } + else { + $msg = t('This task executes component %label and will be executed on %date. The action cannot be undone.', array( + '%label' => $config->label(), + '%id' => $task['identifier'], + '%date' => format_date($task['date']), + )); + } + return confirm_form($form, $title, $path, $msg, t('Delete'), t('Cancel')); +} + +/** + * Submit handler for deleting single tasks. + */ +function rules_scheduler_delete_task_submit($form, &$form_state) { + rules_scheduler_task_delete($form_state['task']['tid']); + drupal_set_message(t('Task %tid has been deleted.', array('%tid' => $form_state['task']['tid']))); + $form_state['redirect'] = RULES_SCHEDULER_PATH; +} + +/** + * Configuration form to manually schedule a rules component. + */ +function rules_scheduler_schedule_form($form, &$form_state, $rules_config, $base_path) { + // Only components can be scheduled. + if (!($rules_config instanceof RulesTriggerableInterface)) { + RulesPluginUI::$basePath = $base_path; + $form_state['component'] = $rules_config->name; + $action = rules_action('schedule', array('component' => $rules_config->name)); + $action->form($form, $form_state); + // The component should be fixed, so hide the paramter for it. + $form['parameter']['component']['#access'] = FALSE; + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Schedule'), + ); + $form['#validate'] = array('rules_ui_form_rules_config_validate'); + return $form; + } + drupal_not_found(); + exit; +} + +/** + * Submit callback to execute the scheduling action. + */ +function rules_scheduler_schedule_form_submit($form, &$form_state) { + $action = $form_state['rules_element']; + $action->execute(); + drupal_set_message(t('Component %label has been scheduled.', array('%label' => rules_config_load($form_state['component'])->label()))); + $form_state['redirect'] = RULES_SCHEDULER_PATH; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/rules_scheduler.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/rules_scheduler.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,21 @@ +name = Rules Scheduler +description = Schedule the execution of Rules components using actions. +dependencies[] = rules +package = Rules +core = 7.x +files[] = rules_scheduler.admin.inc +files[] = rules_scheduler.module +files[] = rules_scheduler.install +files[] = rules_scheduler.rules.inc +files[] = rules_scheduler.test +files[] = includes/rules_scheduler.handler.inc +files[] = includes/rules_scheduler.views_default.inc +files[] = includes/rules_scheduler.views.inc +files[] = includes/rules_scheduler_views_filter.inc + +; Information added by drupal.org packaging script on 2013-09-16 +version = "7.x-2.4" +core = "7.x" +project = "rules" +datestamp = "1379354606" + diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/rules_scheduler.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/rules_scheduler.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,189 @@ + 'Stores scheduled tasks.', + 'fields' => array( + 'tid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => "The scheduled task's id.", + ), + 'config' => array( + 'type' => 'varchar', + 'length' => '64', + 'default' => '', + 'not null' => TRUE, + 'description' => "The scheduled configuration's name.", + ), + 'date' => array( + 'description' => 'The Unix timestamp of when the task is to be scheduled.', + 'type' => 'int', + 'not null' => TRUE, + ), + 'data' => array( + 'type' => 'text', + 'not null' => FALSE, + 'serialize' => TRUE, + 'description' => 'The whole, serialized evaluation data.', + ), + 'identifier' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'not null' => FALSE, + 'description' => 'The user defined string identifying this task.', + ), + 'handler' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => FALSE, + 'description' => 'The fully qualified class name of a the queue item handler.', + ), + ), + 'primary key' => array('tid'), + 'indexes' => array( + 'date' => array('date'), + ), + 'unique key' => array( + 'id' => array('config', 'identifier'), + ), + ); + return $schema; +} + +/** + * Upgrade from Rules scheduler 6.x-1.x to 7.x. + */ +function rules_scheduler_update_7200() { + // Rename the old table so we can keep its content and start over with a + // fresh one. + db_rename_table('rules_scheduler', 'rules_scheduler_d6'); + // Create the d7 table. + $schema['rules_scheduler'] = array( + 'description' => 'Stores scheduled tasks.', + 'fields' => array( + 'tid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => "The scheduled task's id.", + ), + 'config' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'not null' => TRUE, + 'description' => "The scheduled configuration's name.", + ), + 'date' => array( + 'description' => 'The Unix timestamp of when the task is to be scheduled.', + 'type' => 'int', + 'not null' => TRUE, + ), + 'data' => array( + 'type' => 'text', + 'not null' => FALSE, + 'serialize' => TRUE, + 'description' => 'The whole, serialized evaluation data.', + ), + 'identifier' => array( + 'type' => 'varchar', + 'length' => '255', + 'default' => '', + 'not null' => FALSE, + 'description' => 'The user defined string identifying this task.', + ), + ), + 'primary key' => array('tid'), + 'indexes' => array('date' => array('date')), + ); + db_create_table('rules_scheduler', $schema['rules_scheduler']); +} + +/** + * Fix the length of the rules_scheduler.name column. + */ +function rules_scheduler_update_7202() { + // Note that update 7201 (add the 'id' unique key') has been removed as it is + // incorporated by 7202. For anyone that has already run the previous update + // 7201, we have to first drop the unique key. + db_drop_unique_key('rules_scheduler', 'id'); + db_change_field('rules_scheduler', 'config', 'config', array( + 'type' => 'varchar', + 'length' => '64', + 'default' => '', + 'not null' => TRUE, + 'description' => "The scheduled configuration's name.", + )); + db_add_unique_key('rules_scheduler', 'id', array('config', 'identifier')); +} + +/** + * Add a database column for specifying a queue item handler. + */ +function rules_scheduler_update_7203() { + db_add_field('rules_scheduler', 'handler', array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => FALSE, + 'description' => 'The fully qualified class name of a the queue item handler.', + )); +} + +/** + * Rename rules_scheduler.state into rules_scheduler.data. + */ +function rules_scheduler_update_7204() { + db_change_field('rules_scheduler', 'state', 'data', array( + 'type' => 'text', + 'not null' => FALSE, + 'serialize' => TRUE, + 'description' => 'The whole, serialized evaluation data.', + )); +} + +/** + * Rules upgrade callback for mapping the action name. + */ +function rules_scheduler_action_upgrade_map_name($element) { + return 'schedule'; +} + +/** + * Rules upgrade callback. + */ +function rules_scheduler_action_upgrade($element, $target) { + $target->settings['component'] = $element['#info']['set']; + $target->settings['date'] = $element['#settings']['task_date']; + $target->settings['identifier'] = $element['#settings']['task_identifier']; + + unset($element['#info']['arguments']['task_date'], $element['#info']['arguments']['task_identifier']); + foreach ($element['#info']['arguments'] as $name => $info) { + rules_upgrade_element_parameter_settings($element, $target, $name, $info, 'param_' . $name); + } +} + +/** + * Rules upgrade callback for mapping the action name. + */ +function rules_action_delete_scheduled_set_upgrade_map_name($element) { + return 'schedule_delete'; +} + +/** + * Rules upgrade callback. + */ +function rules_action_delete_scheduled_set_upgrade($element, $target) { + $target->settings['component'] = $element['#settings']['ruleset']; + $target->settings['task'] = $element['#settings']['task_identifier']; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/rules_scheduler.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/rules_scheduler.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,196 @@ + PDO::FETCH_ASSOC)) + ->fields('r') + ->condition('date', time(), '<=') + ->orderBy('date') + ->range(0, 1000) + ->execute(); + + $queue = DrupalQueue::get('rules_scheduler_tasks'); + foreach ($result as $task) { + // Add the task to the queue and remove the entry afterwards. + if ($queue->createItem($task)) { + $task_created = TRUE; + rules_scheduler_task_handler($task)->afterTaskQueued(); + } + } + + if (!empty($task_created)) { + // hook_exit() is not invoked for cron runs, so register it as shutdown + // callback for logging the rules log to the watchdog. + drupal_register_shutdown_function('rules_exit'); + // Clear the log before running tasks via the queue to avoid logging + // unrelated logs from previous cron-operations. + RulesLog::logger()->clear(); + } +} + +/** + * Implements hook_cron_queue_info(). + */ +function rules_scheduler_cron_queue_info() { + $queues['rules_scheduler_tasks'] = array( + 'worker callback' => 'rules_scheduler_run_task', + 'time' => 15, + ); + return $queues; +} + +/** + * Queue worker callback for running a single task. + * + * @param array $task + * The task to process. + */ +function rules_scheduler_run_task(array $task) { + try { + rules_scheduler_task_handler($task)->runTask(); + } + catch (RulesEvaluationException $e) { + rules_log($e->msg, $e->args, $e->severity); + rules_log('Unable to execute task with identifier %id scheduled on date %date.', array('%id' => $task['identifier'], '%date' => format_date($task['date'])), RulesLog::ERROR); + } +} + +/** + * Returns the task handler for a given task. + * + * @param array $task + * A task (queue item) array. + * + * @throws RulesEvaluationException + * If the task handler class is missing. + * + * @return RulesSchedulerTaskHandlerInterface + * The task handler. + */ +function rules_scheduler_task_handler(array $task) { + $class = !empty($task['handler']) ? $task['handler'] : 'RulesSchedulerDefaultTaskHandler'; + if (!class_exists($class)) { + throw new RulesEvaluationException('Missing task handler implementation %class.', array('%class' => $class), NULL, RulesLog::ERROR); + } + return new $class($task); +} + +/** + * Implements hook_rules_ui_menu_alter(). + * + * Adds a menu item for the 'schedule' operation. + */ +function rules_scheduler_rules_ui_menu_alter(&$items, $base_path, $base_count) { + $items[$base_path . '/manage/%rules_config/schedule'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Schedule !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_scheduler_schedule_form', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'file' => 'rules_scheduler.admin.inc', + 'file path' => drupal_get_path('module', 'rules_scheduler'), + ); +} + +/** + * Implements hook_menu(). + */ +function rules_scheduler_menu() { + $items = array(); + $items[RULES_SCHEDULER_PATH] = array( + 'title' => 'Schedule', + 'type' => MENU_LOCAL_TASK, + 'page callback' => 'rules_scheduler_schedule_page', + 'access arguments' => array('administer rules'), + 'file' => 'rules_scheduler.admin.inc', + ); + $items[RULES_SCHEDULER_PATH .'/%rules_scheduler_task/delete'] = array( + 'title' => 'Delete a scheduled task', + 'type' => MENU_CALLBACK, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_scheduler_delete_task', 5), + 'access arguments' => array('administer rules'), + 'file' => 'rules_scheduler.admin.inc', + ); + return $items; +} + +/** + * Load a task by a given task ID. + */ +function rules_scheduler_task_load($tid) { + $result = db_select('rules_scheduler', 'r') + ->fields('r') + ->condition('tid', (int) $tid) + ->execute(); + return $result->fetchAssoc(); +} + +/** + * Delete a task by a given task ID. + */ +function rules_scheduler_task_delete($tid) { + db_delete('rules_scheduler') + ->condition('tid', $tid) + ->execute(); +} + +/** + * Schedule a task to be executed later on. + * + * @param $task + * An array representing the task with the following keys: + * - config: The machine readable name of the to be scheduled component. + * - date: Timestamp when the component should be executed. + * - state: (deprecated) Rules evaluation state to use for scheduling. + * - data: Any additional data to store with the task. + * - handler: The name of the task handler class. + * - identifier: User provided string to identify the task per scheduled + * configuration. + */ +function rules_scheduler_schedule_task($task) { + // Map the deprecated 'state' property into 'data'. + if (isset($task['state'])) { + $task['data'] = $task['state']; + unset($task['state']); + } + if (!empty($task['identifier'])) { + // If there is a task with the same identifier and component, we replace it. + db_delete('rules_scheduler') + ->condition('config', $task['config']) + ->condition('identifier', $task['identifier']) + ->execute(); + } + drupal_write_record('rules_scheduler', $task); +} + +/** + * Implements hook_rules_config_delete(). + */ +function rules_scheduler_rules_config_delete($rules_config) { + // Delete all tasks scheduled for this config. + db_delete('rules_scheduler') + ->condition('config', $rules_config->name) + ->execute(); +} + +/** + * Implements hook_views_api(). + */ +function rules_scheduler_views_api() { + return array( + 'api' => '3.0-alpha1', + 'path' => drupal_get_path('module', 'rules_scheduler') .'/includes', + ); +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/rules_scheduler.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/rules_scheduler.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,214 @@ + t('Schedule component evaluation'), + 'group' => t('Rules scheduler'), + 'base' => 'rules_scheduler_action_schedule', + 'named parameter' => TRUE, + 'parameter' => array( + 'component' => array( + 'type' => 'text', + 'label' => t('Component'), + 'options list' => 'rules_scheduler_component_options_list', + 'restriction' => 'input', + 'description' => 'Select the component to schedule. Only components containing actions are available – no condition sets.', + ), + 'date' => array( + 'type' => 'date', + 'label' => t('Scheduled evaluation date'), + ), + 'identifier' => array( + 'type' => 'text', + 'label' => t('Identifier'), + 'description' => t('A string used for identifying this task. Any existing tasks for this component with the same identifier will be replaced.'), + 'optional' => TRUE, + ), + // Further needed parameter by the component are added during processing. + ), + ); + // Add action to delete scheduled tasks. + $items['schedule_delete'] = array( + 'label' => t('Delete scheduled tasks'), + 'group' => t('Rules scheduler'), + 'base' => 'rules_scheduler_action_delete', + 'parameter' => array( + 'component' => array( + 'type' => 'text', + 'label' => t('Component'), + 'options list' => 'rules_scheduler_component_options_list', + 'description' => t('The component for which scheduled tasks will be deleted.'), + 'optional' => TRUE, + ), + 'task' => array( + 'type' => 'text', + 'label' => t('Task identifier'), + 'description' => t('All tasks that are annotated with the given identifier will be deleted.'), + 'optional' => TRUE, + ), + ), + ); + return $items; +} + +/** + * Options list callback returning a list of action components. + */ +function rules_scheduler_component_options_list() { + return rules_get_components(TRUE, 'action'); +} + +/** + * Base action implementation for scheduling components. + */ +function rules_scheduler_action_schedule($args, $element) { + $state = $args['state']; + if ($component = rules_get_cache('comp_' . $args['component'])) { + // Manually create a new evaluation state for scheduling the evaluation. + $new_state = new RulesState(); + + // Register all parameters as variables. + foreach ($element->pluginParameterInfo() as $name => $info) { + if (strpos($name, 'param_') === 0) { + // Remove the parameter name prefix 'param_'. + $var_name = substr($name, 6); + $new_state->addVariable($var_name, $state->currentArguments[$name], $info); + } + } + rules_scheduler_schedule_task(array( + 'date' => $args['date'], + 'config' => $args['component'], + 'data' => $new_state, + 'identifier' => $args['identifier'], + )); + } + else { + throw new RulesEvaluationException('Unable to get the component %name', array('%name' => $args['component']), $element, RulesLog::ERROR); + } +} + +/** + * Info alteration callback for the schedule action. + */ +function rules_scheduler_action_schedule_info_alter(&$element_info, RulesPlugin $element) { + if (isset($element->settings['component'])) { + // If run during a cache rebuild the cache might not be instantiated yet, + // so fail back to loading the component from database. + if (($component = rules_get_cache('comp_' . $element->settings['component'])) || $component = rules_config_load($element->settings['component'])) { + // Add in the needed parameters. + foreach ($component->parameterInfo() as $name => $info) { + $element_info['parameter']['param_' . $name] = $info; + } + } + } +} + +/** + * Validate callback for the schedule action to make sure the component exists and is not dirty. + * + * @see rules_element_invoke_component_validate() + */ +function rules_scheduler_action_schedule_validate(RulesPlugin $element) { + $info = $element->info(); + $component = rules_config_load($element->settings['component']); + if (!$component) { + throw new RulesIntegrityException(t('The component %config does not exist.', array('%config' => $element->settings['component'])), $element); + } + // Check if the component is marked as dirty. + rules_config_update_dirty_flag($component); + if (!empty($component->dirty)) { + throw new RulesIntegrityException(t('The utilized component %config fails the integrity check.', array('%config' => $element->settings['component'])), $element); + } +} + +/** + * Help for the schedule action. + */ +function rules_scheduler_action_schedule_help() { + return t("Note that component evaluation is triggered by cron – make sure cron is configured correctly by checking your site's !status. The scheduling time accuracy depends on your configured cron interval. See the online documentation for more information on how to schedule evaluation of components.", + array('!status' => l('Status report', 'admin/reports/status'), + '@url' => rules_external_help('scheduler'))); +} + +/** + * Form alter callback for the schedule action. + */ +function rules_scheduler_action_schedule_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) { + $first_step = empty($element->settings['component']); + $form['reload'] = array( + '#weight' => 5, + '#type' => 'submit', + '#name' => 'reload', + '#value' => $first_step ? t('Continue') : t('Reload form'), + '#limit_validation_errors' => array(array('parameter', 'component')), + '#submit' => array('rules_action_type_form_submit_rebuild'), + '#ajax' => rules_ui_form_default_ajax(), + ); + // Use ajax and trigger as the reload button. + $form['parameter']['component']['settings']['type']['#ajax'] = $form['reload']['#ajax'] + array( + 'event' => 'change', + 'trigger_as' => array('name' => 'reload'), + ); + + if ($first_step) { + // In the first step show only the component select. + foreach (element_children($form['parameter']) as $key) { + if ($key != 'component') { + unset($form['parameter'][$key]); + } + } + unset($form['submit']); + unset($form['provides']); + } + else { + // Hide the reload button in case js is enabled and it's not the first step. + $form['reload']['#attributes'] = array('class' => array('rules-hide-js')); + } +} + +/** + * Action: Delete scheduled tasks. + */ +function rules_scheduler_action_delete($component_name = NULL, $task_identifier = NULL) { + $query = db_delete('rules_scheduler'); + if (!empty($component_name)) { + $query->condition('config', $component_name); + } + if (!empty($task_identifier)) { + $query->condition('identifier', $task_identifier); + } + $query->execute(); +} + +/** + * Cancel scheduled task action validation callback. + */ +function rules_scheduler_action_delete_validate($element) { + if (empty($element->settings['task']) && empty($element->settings['task:select']) && + empty($element->settings['component']) && empty($element->settings['component:select'])) { + + throw new RulesIntegrityException(t('You have to specify at least either a component or a task identifier.'), $element); + } +} + +/** + * Help for the cancel action. + */ +function rules_scheduler_action_delete_help() { + return t('This action allows you to delete scheduled tasks that are waiting for future execution.') .' '. t('They can be addressed by an identifier or by the component name, whereas if both are specified only tasks fulfilling both requirements will be deleted.'); +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/rules_scheduler.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/rules_scheduler.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,121 @@ + 'Rules Scheduler tests', + 'description' => 'Test scheduling components.', + 'group' => 'Rules', + ); + } + + function setUp() { + parent::setUp('rules_scheduler', 'rules_scheduler_test'); + RulesLog::logger()->clear(); + variable_set('rules_debug_log', 1); + } + + /** + * Tests scheduling components from the action. + * + * Note that this also makes sure Rules properly handles timezones, else this + * test could fail due to a wrong 'now' timestamp. + */ + function testComponentSchedule() { + $set = rules_rule_set(array( + 'node1' => array('type' => 'node', 'label' => 'node'), + )); + $set->rule(rule()->condition('node_is_published', array('node:select' => 'node1')) + ->action('node_unpublish', array('node:select' => 'node1')) + ); + $set->integrityCheck()->save('rules_test_set_2'); + + // Use different names for the variables to ensure they are properly mapped. + $rule = rule(array( + 'node2' => array('type' => 'node', 'label' => 'node'), + )); + $rule->action('schedule', array( + 'component' => 'rules_test_set_2', + 'identifier' => 'node_[node2:nid]', + 'date' => 'now', + 'param_node1:select' => 'node2', + )); + + $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1)); + $rule->execute($node); + + // Run cron to let the rules scheduler do its work. + drupal_cron_run(); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertFalse($node->status, 'The component has been properly scheduled.'); + RulesLog::logger()->checkLog(); + } + + /** + * Make sure recurion prevention is working fine for scheduled rule sets. + */ + function testRecursionPrevention() { + $set = rules_rule_set(array( + 'node1' => array('type' => 'node', 'label' => 'node'), + )); + $set->rule(rule()->condition('node_is_published', array('node:select' => 'node1')) + ->action('node_unpublish', array('node:select' => 'node1')) + ); + $set->integrityCheck()->save('rules_test_set_2'); + + // Add an reaction rule that is triggered upon a node save. The scheduled + // component changes the node, thus it would be scheduled again and run in + // an endless loop. + $rule = rules_reaction_rule(); + $rule->event('node_insert'); + $rule->event('node_update'); + $rule->action('schedule', array( + 'component' => 'rules_test_set_2', + 'identifier' => '', + 'date' => 'now', + 'param_node1:select' => 'node', + )); + $rule->save(); + + // Create a node, what triggers the rule. + $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1)); + // Run cron to let the rules scheduler do its work. + drupal_cron_run(); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertFalse($node->status, 'The component has been properly scheduled.'); + $text1 = RulesLog::logger()->render(); + $text2 = RulesTestCase::t('Not evaluating reaction rule %unlabeled to prevent recursion.', array('unlabeled' => $rule->name)); + $this->assertTrue((strpos($text1, $text2) !== FALSE), "Scheduled recursion prevented."); + RulesLog::logger()->checkLog(); + } + + /** + * Tests that custom task handlers are properly invoked. + */ + public function testCustomTaskHandler() { + // Set up a scheduled task that will simply write a variable when executed. + $variable = 'rules_schedule_task_handler_variable'; + rules_scheduler_schedule_task(array( + 'date' => REQUEST_TIME, + 'identifier' => '', + 'config' => '', + 'data' => array('variable' => $variable), + 'handler' => 'RulesTestTaskHandler', + )); + + // Run cron to let the rules scheduler do its work. + drupal_cron_run(); + + // The task handler should have set the variable to TRUE now. + $this->assertTrue(variable_get($variable)); + } +} + diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/tests/rules_scheduler_test.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/tests/rules_scheduler_test.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,24 @@ +getTask(); + $data = unserialize($task['data']); + + // Set the variable defined in the test to TRUE. + variable_set($data['variable'], TRUE); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/tests/rules_scheduler_test.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/tests/rules_scheduler_test.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = "Rules Scheduler Tests" +description = "Support module for the Rules Scheduler tests." +package = Testing +core = 7.x +files[] = rules_scheduler_test.inc +hidden = TRUE + +; Information added by drupal.org packaging script on 2013-09-16 +version = "7.x-2.4" +core = "7.x" +project = "rules" +datestamp = "1379354606" + diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/rules_scheduler/tests/rules_scheduler_test.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/rules_scheduler/tests/rules_scheduler_test.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,6 @@ + 'Rules Engine tests', + 'description' => 'Test using the rules API to create and evaluate rules.', + 'group' => 'Rules', + ); + } + + function setUp() { + parent::setUp('rules', 'rules_test'); + RulesLog::logger()->clear(); + variable_set('rules_debug_log', 1); + } + + /** + * Calculates the output of t() given an array of placeholders to replace. + */ + static function t($text, $strings) { + $placeholders = array(); + foreach ($strings as $key => $string) { + $key = !is_numeric($key) ? $key : $string; + $placeholders['%' . $key] = drupal_placeholder($string); + } + return strtr($text, $placeholders); + } + + protected function createTestRule() { + $rule = rule(); + $rule->condition('rules_test_condition_true') + ->condition('rules_test_condition_true') + ->condition(rules_or() + ->condition(rules_condition('rules_test_condition_true')->negate()) + ->condition('rules_test_condition_false') + ->condition(rules_and() + ->condition('rules_test_condition_false') + ->condition('rules_test_condition_true') + ->negate() + ) + ); + $rule->action('rules_test_action'); + return $rule; + } + + /** + * Tests creating a rule and iterating over the rule elements. + */ + function testRuleCreation() { + $rule = $this->createTestRule(); + $rule->integrityCheck(); + $rule->execute(); + $log = RulesLog::logger()->get(); + $last = array_pop($log); + $last = array_pop($log); + $last = array_pop($log); + $this->assertEqual($last[0], 'action called', 'Action called'); + RulesLog::logger()->checkLog(); + + // Make sure condition and action iterators are working. + $it = new RecursiveIteratorIterator($rule->conditions(), RecursiveIteratorIterator::SELF_FIRST); + $this->assertEqual(iterator_count($it), 8, 'Iterated over all conditions and condition containers'); + $it = new RecursiveIteratorIterator($rule->conditions()); + $this->assertEqual(iterator_count($it), 6, 'Iterated over all conditions'); + $this->assertEqual(iterator_count($rule->actions()), 1, 'Iterated over all actions'); + $this->assertEqual(iterator_count($rule->elements()), 10, 'Iterated over all rule elements.'); + + // Test getting dependencies and the integrity check. + $rule->integrityCheck(); + $this->assertTrue($rule->dependencies() === array('rules_test'), 'Dependencies correctly returned.'); + } + + /** + * Test handling dependencies. + */ + function testdependencies() { + $action = rules_action('rules_node_publish_action'); + $this->assertEqual($action->dependencies(), array('rules_test'), 'Providing module is returned as dependency.'); + + $container = new RulesTestContainer(); + $this->assertEqual($container->dependencies(), array('rules_test'), 'Providing module for container plugin is returned as dependency.'); + + // Test handling unmet dependencies. + $rule = rules_config_load('rules_export_test'); + $this->assertTrue(in_array('comment', $rule->dependencies) && !$rule->dirty, 'Dependencies have been imported.'); + + // Remove the required comment module and make sure the rule is dirty then. + module_disable(array('comment')); + rules_clear_cache(); + $rule = rules_config_load('rules_export_test'); + $this->assertTrue($rule->dirty, 'Rule has been marked as dirty'); + + // Now try re-enabling. + module_enable(array('comment')); + rules_clear_cache(); + $rule = rules_config_load('rules_export_test'); + $this->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.'); + + // Test it with components. + module_enable(array('path')); + $action_set = rules_action_set(array('node' => array('type' => 'node'))); + $action_set->action('node_path_alias'); + $action_set->save('rules_test_alias'); + + $rule = rule(array('node' => array('type' => 'node'))); + $rule->action('component_rules_test_alias'); + $rule->integrityCheck(); + $rule->save('rules_test_rule'); + + $rule = rules_config_load('rules_test_rule'); + $component = rules_config_load('rules_test_alias'); + $this->assertTrue(in_array('path', $component->dependencies) && !$rule->dirty && !$component->dirty, 'Component has path module dependency.'); + + // Now disable path module and make sure both configs are marked as dirty. + module_disable(array('path')); + rules_clear_cache(); + $rule = rules_config_load('rules_test_rule'); + $component = rules_config_load('rules_test_alias'); + + $this->assertTrue($component->dirty, 'Component has been marked as dirty'); + $node = $this->drupalCreateNode(); + $result = rules_invoke_component('rules_test_alias', $node); + $this->assertTrue($result === FALSE, 'Unable to execute a dirty component.'); + + // When the rule is evaluated, the broken component is detected and the + // rule should be marked as dirty too. + $rule->execute($node); + $this->assertTrue($rule->dirty, 'Rule has been marked as dirty'); + + module_enable(array('path')); + rules_clear_cache(); + + // Trigger rebuilding the cache, so configs are checked again. + rules_get_cache(); + + $rule = rules_config_load('rules_test_rule'); + $component = rules_config_load('rules_test_alias'); + $this->assertTrue(!$component->dirty, 'Component has been marked as not dirty again.'); + $this->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.'); + } + + /** + * Test setting up an action with some action_info and serializing and + * executing it. + */ + function testActionSetup() { + $action = rules_action('rules_node_publish_action'); + + $s = serialize($action); + $action2 = unserialize($s); + $node = (object) array('status' => 0, 'type' => 'page'); + $node->title = 'test'; + + $action2->execute($node); + $this->assertEqual($node->status, 1, 'Action executed correctly'); + + $this->assertTrue(in_array('node', array_keys($action2->parameterInfo())), 'Parameter info returned.'); + + $node->status = 0; + $action2->integrityCheck(); + $action2->executeByArgs(array('node' => $node)); + $this->assertEqual($node->status, 1, 'Action executed correctly'); + + // Test calling an extended + overriden method. + $this->assertEqual($action2->help(), 'custom', 'Using custom help callback.'); + + // Inspect the cache + //$this->pass(serialize(rules_get_cache())); + RulesLog::logger()->checkLog(); + } + + /** + * Test executing with wrong arguments. + */ + function testActionExecutionFails() { + $action = rules_action('rules_node_publish_action'); + try { + $action->execute(); + $this->fail("Execution hasn't created an exception."); + } + catch (RulesEvaluationException $e) { + $this->pass("RulesEvaluationException was thrown: ". $e); + } + } + + /** + * Test setting up a rule and mapping variables. + */ + function testVariableMapping() { + $rule = rule(array( + 'node' => array('type' => 'node'), + 'node_unchanged' => array('type' => 'node'), + )); + $rule->condition(rules_condition('rules_condition_content_is_published')->negate()) + ->condition('rules_condition_content_is_type', array('type' => array('page', 'story'))) + ->action('rules_node_publish_action', array('node:select' => 'node_unchanged')); + + $node1 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page')); + $node2 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page')); + $rule->integrityCheck(); + $rule->execute($node1, $node2); + $this->assertEqual($node2->status, 1, 'Action executed correctly on node2.'); + $this->assertEqual($node1->status, 0, 'Action not executed on node1.'); + + RulesLog::logger()->checkLog(); + } + + /** + * Tests making use of class based actions. + */ + function testClassBasedActions() { + $cache = rules_get_cache(); + $this->assertTrue(!empty($cache['action_info']['rules_test_class_action']), 'Action has been discovered.'); + $action = rules_action('rules_test_class_action'); + + $parameters = $action->parameterInfo(); + $this->assertTrue($parameters['node'], 'Action parameter needs a value.'); + + $node = $this->drupalCreateNode(); + $action->execute($node); + $log = RulesLog::logger()->get(); + $last = array_pop($log); + $last = array_pop($log); + $this->assertEqual($last[0], 'Action called with node ' . $node->nid, 'Action called'); + RulesLog::logger()->checkLog(); + } + + /** + * Tests CRUD functionality. + */ + function testRulesCRUD() { + $rule = $this->createTestRule(); + $rule->integrityCheck()->save('test'); + + $this->assertEqual(TRUE, $rule->active, 'Rule is active.'); + $this->assertEqual(0, $rule->weight, 'Rule weight is zero.'); + + $results = entity_load('rules_config', array('test')); + $rule2 = array_pop($results); + $this->assertEqual($rule->id, $rule2->id, 'Rule created and loaded'); + $this->assertEqual(get_class($rule2), get_class($rule), 'Class properly instantiated.'); + $rule2->execute(); + // Update. + $rule2->save(); + + // Make sure all rule elements are still here. + $it = new RecursiveIteratorIterator($rule2->conditions(), RecursiveIteratorIterator::SELF_FIRST); + $this->assertEqual(iterator_count($it), 8, 'Iterated over all conditions and condition containers'); + $it = new RecursiveIteratorIterator($rule2->conditions()); + $this->assertEqual(iterator_count($it), 6, 'Iterated over all conditions'); + $this->assertEqual(iterator_count($rule2->actions()), 1, 'Iterated over all actions'); + + // Delete. + $rule2->delete(); + $this->assertEqual(entity_load('rules_config', FALSE, array('id' => $rule->id)), array(), 'Deleted.'); + + // Tests CRUD for tags - making sure the tags are stored properly.. + $rule = $this->createTestRule(); + $tag = $this->randomString(); + $rule->tags = array($tag); + $rule->save(); + $result = db_select('rules_tags') + ->fields('rules_tags', array('tag')) + ->condition('id', $rule->id) + ->execute(); + $this->assertEqual($result->fetchField(), $tag, 'Associated tag has been saved.'); + // Try updating. + $rule->tags = array($this->randomName(), $this->randomName()); + $rule->integrityCheck()->save(); + $result = db_select('rules_tags') + ->fields('rules_tags', array('tag')) + ->condition('id', $rule->id) + ->execute() + ->fetchCol(); + $this->assertTrue(in_array($rule->tags[0], $result) && in_array($rule->tags[1], $result), 'Updated associated tags.'); + // Try loading multiple rules by tags. + $rule2 = $this->createTestRule(); + $rule2->tags = array($this->randomName()); + $rule2->save(); + $loaded = entity_load('rules_config', FALSE, array('tags' => array($rule->tags[0], $rule2->tags[0]))); + $this->assertTrue($loaded[$rule->id]->id == $rule->id && $loaded[$rule2->id]->id == $rule2->id, 'Loading configs by tags'); + // Try deleting. + $rule->delete(); + $result = db_select('rules_tags') + ->fields('rules_tags', array('tag')) + ->condition('id', $rule->id) + ->execute(); + $this->assertEqual($result->fetchField(), FALSE, 'Deleted associated tags.'); + } + + /** + * Test automatic saving of variables. + */ + function testActionSaving() { + // Test saving a parameter. + $action = rules_action('rules_node_publish_action_save'); + $node = $this->drupalCreateNode(array('status' => 0, 'type' => 'page')); + $action->executeByArgs(array('node' => $node)); + + $this->assertEqual($node->status, 1, 'Action executed correctly on node.'); + // Sync node_load cache with node_save + entity_get_controller('node')->resetCache(); + + $node = node_load($node->nid); + $this->assertEqual($node->status, 1, 'Node has been saved.'); + + // Now test saving a provided variable, which is renamed and modified before + // it is saved. + $title = $this->randomName(); + $rule = rule(); + $rule->action('entity_create', array( + 'type' => 'node', + 'param_type' => 'article', + 'param_author:select' => 'site:current-user', + 'param_title' => $title, + 'entity_created:var' => 'node', + )); + $rule->action('data_set', array( + 'data:select' => 'node:body', + 'value' => array('value' => 'body'), + )); + $rule->integrityCheck(); + $rule->execute(); + + $node = $this->drupalGetNodeByTitle($title); + $this->assertTrue(!empty($node) && $node->body[LANGUAGE_NONE][0]['value'] == 'body', 'Saved a provided variable'); + RulesLog::logger()->checkLog(); + } + + /** + * Test adding a variable and optional parameters. + */ + function testVariableAdding() { + $node = $this->drupalCreateNode(); + $rule = rule(array('nid' => array('type' => 'integer'))); + $rule->condition('rules_test_condition_true') + ->action('rules_action_load_node') + ->action('rules_action_delete_node', array('node:select' => 'node_loaded')) + ->execute($node->nid); + + $this->assertEqual(FALSE, node_load($node->nid), 'Variable added and skipped optional parameter.'); + RulesLog::logger()->checkLog(); + + $vars = $rule->conditions()->offsetGet(0)->availableVariables(); + $this->assertEqual(!isset($vars['node_loaded']), 'Loaded variable is not available to conditions.'); + + + // Test adding a variable with a custom variable name. + $node = $this->drupalCreateNode(); + $rule = rule(array('nid' => array('type' => 'integer'))); + $rule->action('rules_action_load_node', array('node_loaded:var' => 'node')) + ->action('rules_action_delete_node') + ->execute($node->nid); + + $this->assertEqual(FALSE, node_load($node->nid), 'Variable with custom name added.'); + RulesLog::logger()->checkLog(); + } + + /** + * Test custom access for using component actions/conditions. + */ + function testRuleComponentAccess() { + // Create a normal user. + $normal_user = $this->drupalCreateUser(); + // Create a role for granting access to the rule component. + $this->normal_role = $this->drupalCreateRole(array(), 'test_role'); + $normal_user->roles[$this->normal_role] = 'test_role'; + user_save($normal_user, array('roles' => $normal_user->roles)); + // Create an 'action set' rule component making use of a permission. + $action_set = rules_action_set(array('node' => array('type' => 'node'))); + $action_set->access_exposed = TRUE; + $action_set->save('rules_test_roles'); + + // Set the global user to be the current one as access is checked for the + // global user. + global $user; + $user = user_load($normal_user->uid); + $this->assertFalse(rules_action('component_rules_test_roles')->access(), 'Authenticated user without the correct role can\'t use the rule component.'); + + // Assign the role that will have permissions for the rule component. + user_role_change_permissions($this->normal_role, array('use Rules component rules_test_roles' => TRUE)); + $this->assertTrue(rules_action('component_rules_test_roles')->access(), 'Authenticated user with the correct role can use the rule component.'); + + // Reset global user to anonyous. + $user = user_load(0); + $this->assertFalse(rules_action('component_rules_test_roles')->access(), 'Anonymous user can\'t use the rule component.'); + } + + /** + * Test passing arguments by reference to an action. + */ + function testPassingByReference() { + // Keeping references of variables is unsupported, though the + // EntityMetadataArrayObject may be used to achieve that. + $array = array('foo' => 'bar'); + $data = new EntityMetadataArrayObject($array); + rules_action('rules_action_test_reference')->execute($data); + $this->assertTrue($data['changed'], 'Parameter has been passed by reference'); + } + + /** + * Test sorting rule elements. + */ + function testSorting() { + $rule = $this->createTestRule(); + $conditions = $rule->conditions(); + $conditions[0]->weight = 10; + $conditions[2]->weight = 10; + $id[0] = $conditions[0]->elementId(); + $id[1] = $conditions[1]->elementId(); + $id[2] = $conditions[2]->elementId(); + // For testing use a deep sort, even if not necessary here. + $rule->sortChildren(TRUE); + $conditions = $rule->conditions(); + $this->assertEqual($conditions[0]->elementId(), $id[1], 'Condition sorted correctly.'); + $this->assertEqual($conditions[1]->elementId(), $id[0], 'Condition sorted correctly.'); + $this->assertEqual($conditions[2]->elementId(), $id[2], 'Condition sorted correctly.'); + } + + /** + * Tests using data selectors. + */ + function testDataSelectors() { + $body[LANGUAGE_NONE][0] = array('value' => 'The body & nothing.'); + $node = $this->drupalCreateNode(array('body' => $body, 'type' => 'page', 'summary' => '')); + + $rule = rule(array('nid' => array('type' => 'integer'))); + $rule->action('rules_action_load_node') + ->action('drupal_message', array('message:select' => 'node_loaded:body:value')) + ->execute($node->nid); + + RulesLog::logger()->checkLog(); + $msg = drupal_get_messages('status'); + $last_msg = array_pop($msg['status']); + $wrapper = entity_metadata_wrapper('node', $node); + $this->assertEqual($last_msg, $wrapper->body->value->value(array('sanitize' => TRUE)), 'Data selector for getting parameter applied.'); + + // Get a "reference" on the same object as returned by node_load(). + $node = node_load($node->nid); + $rule = rule(array('nid' => array('type' => 'integer'))); + $rule->action('rules_action_load_node') + ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title')) + // Use two actions and make sure the node get saved only once. + ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title2')) + ->execute($node->nid); + + $wrapper = entity_metadata_wrapper('node', $node); + $this->assertEqual('Test title2', $wrapper->title->value(), 'Data has been modified and saved.'); + + RulesLog::logger()->checkLog(); + $text = RulesLog::logger()->render(); + $msg = RulesTestCase::t('Saved %node_loaded of type %node.', array('node_loaded', 'node')); + if ($pos1 = strpos($text, $msg)) { + $pos2 = strpos($text, $msg, $pos1 + 1); + } + $this->assertTrue($pos1 && $pos2 === FALSE, 'Data has been saved only once.'); + + // Test validation. + try { + rules_action('data_set', array('data' => 'no-selector', 'value' => ''))->integrityCheck(); + $this->fail("Validation hasn't created an exception."); + } + catch (RulesIntegrityException $e) { + $this->pass("Validation error correctly detected: ". $e); + } + + // Test auto creation of nested data structures, like the node body field. + // I.e. if $node->body is not set, it is automatically initialized to an + // empty array, so that the nested value can be set and the wrappers do not + // complain about missing parent data structures. + $rule = rule(); + $rule->action('entity_create', array( + 'type' => 'node', + 'param_type' => 'page', + 'param_title' => 'foo', + 'param_author' => $GLOBALS['user'], + )); + $rule->action('data_set', array('data:select' => 'entity_created:body:value', 'value' => 'test content')) + ->execute(); + try { + RulesLog::logger()->checkLog(); + $this->pass('Auto creation of nested data structures.'); + } + catch (Exception $e) { + $this->fail('Auto creation of nested data structures.'); + } + + // Make sure variables that are passed wrapped work. + $result = rules_condition('rules_test_condition_node_wrapped')->execute($node->nid); + $this->assertTrue($result, 'Condition receiving wrapped parameter.'); + + // Make sure wrapped parameters are checked for containing NULL values. + $rule = rule(array('node' => array('type' => 'node', 'optional' => TRUE))); + $rule->condition('rules_test_condition_node_wrapped', array('node:select' => 'node')); + $rule->execute(entity_metadata_wrapper('node')); + $text = RulesLog::logger()->render(); + $msg = RulesTestCase::t('The variable or parameter %node is empty.', array('node')); + $this->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.'); + } + + /** + * Tests making use of rule sets. + */ + function testRuleSets() { + $set = rules_rule_set(array( + 'node' => array('type' => 'node', 'label' => 'node'), + )); + $set->rule(rule()->action('drupal_message', array('message:select' => 'node:title'))) + ->rule(rule()->condition('rules_condition_content_is_published') + ->action('drupal_message', array('message' => 'Node is published.')) + ); + $set->integrityCheck()->save('rules_test_set_1'); + + $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1)); + // Execute. + rules_invoke_component('rules_test_set_1', $node); + + $msg = drupal_get_messages(); + $this->assertEqual($msg['status'][0], 'The title.', 'First rule evaluated.'); + $this->assertEqual($msg['status'][1], 'Node is published.', 'Second rule evaluated.'); + + // Test a condition set. + $set = rules_or(array( + 'node' => array('type' => 'node', 'label' => 'node'), + )); + $set->condition('data_is', array('data:select' => 'node:author:name', 'value' => 'notthename')) + ->condition('data_is', array('data:select' => 'node:nid', 'value' => $node->nid)) + ->integrityCheck() + ->save('test', 'rules_test'); + // Load and execute condition set. + $set = rules_config_load('test'); + $this->assertTrue($set->execute($node), 'Set has been correctly evaluated.'); + RulesLog::logger()->checkLog(); + } + + /** + * Tests invoking components from the action. + */ + function testComponentInvocations() { + $set = rules_rule_set(array( + 'node1' => array('type' => 'node', 'label' => 'node'), + )); + $set->rule(rule()->condition('node_is_published', array('node:select' => 'node1')) + ->action('node_unpublish', array('node:select' => 'node1')) + ); + $set->integrityCheck()->save('rules_test_set_2'); + + // Use different names for the variables to ensure they are properly mapped + // when taking over the variables to be saved. + $rule = rule(array( + 'node2' => array('type' => 'node', 'label' => 'node'), + )); + $rule->action('component_rules_test_set_2', array('node1:select' => 'node2')); + $rule->action('node_make_sticky', array('node:select' => 'node2')); + + $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1, 'sticky' => 0)); + $rule->execute($node); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertFalse($node->status, 'The component changes have been saved correctly.'); + $this->assertTrue($node->sticky, 'The action changes have been saved correctly.'); + + // Check that we have saved the changes only once. + $text = RulesLog::logger()->render(); + // Make sure both saves are handled in one save operation. + $this->assertEqual(substr_count($text, 'Saved'), 1, 'Changes have been saved in one save operation.'); + RulesLog::logger()->checkLog(); + + // Test recursion prevention on components by invoking the component from + // itself, what should be prevented. + $set->action('component_rules_test_set_2', array('node1:select' => 'node1')) + ->save(); + + $rule->execute($node); + $text1 = RulesLog::logger()->render(); + $text2 = RulesTestCase::t('Not evaluating rule set %rules_test_set_2 to prevent recursion.', array('rules_test_set_2')); + $this->assertTrue((strpos($text1, $text2) !== FALSE), "Recursion of component invocation prevented."); + + // Test executing the component provided in code via the action. This makes + // sure the component in code has been properly picked up. + $node->status = 0; + node_save($node); + rules_action('component_rules_test_action_set')->execute($node); + $this->assertTrue($node->status == 1, 'Component provided in code has been executed.'); + } + + + /** + * Test asserting metadata, customizing action info and make sure integrity + * is checked. + */ + function testMetadataAssertion() { + $action = rules_action('rules_node_make_sticky_action'); + + // Test failing integrity check. + try { + $rule = rule(array('node' => array('type' => 'entity'))); + $rule->action($action); + // Fails due to the 'node' variable not matching the node type. + $rule->integrityCheck(); + $this->fail('Integrity check has not thrown an exception.'); + } + catch (RulesIntegrityException $e) { + $this->pass('Integrity check has thrown exception: ' . $e->getMessage()); + } + + // Test asserting additional metadata. + $rule = rule(array('node' => array('type' => 'node'))); + // Customize action info using the settings. + $rule->condition('data_is', array('data:select' => 'node:type', 'value' => 'page')) + // Configure an condition using the body. As the body is a field, + // tis requires the bundle to be correctly asserted. + ->condition(rules_condition('data_is', array('data:select' => 'node:body:value', 'value' => 'foo'))->negate()) + // The action also requires the page bundle in order to work. + ->action($action); + // Make sure the integrity check doesn't throw an exception. + $rule->integrityCheck(); + // Test the rule. + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0)); + $rule->execute($node); + $this->assertTrue($node->sticky, 'Rule with asserted metadata executed.'); + + + // Test asserting metadata on a derived property, i.e. not a variable. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('entity_is_of_type', array('entity:select' => 'node:reference', 'type' => 'node')) + ->condition('data_is', array('data:select' => 'node:reference:type', 'value' => 'page')) + ->action('rules_node_page_make_sticky_action', array('node:select' => 'node:reference')); + $rule->integrityCheck(); + $rule->execute($node); + + // Test asserting an entity field. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('entity_has_field', array('entity:select' => 'node:reference', 'field' => 'field_tags')) + ->action('data_set', array('data:select' => 'node:reference:field-tags', 'value' => array())); + $rule->integrityCheck(); + $rule->execute($node); + + // Make sure an asserted bundle can be used as argument. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('entity_is_of_type', array('entity:select' => 'node:reference', 'type' => 'node')) + ->condition('node_is_of_type', array('node:select' => 'node:reference', 'type' => array('page'))) + ->action('rules_node_page_make_sticky_action', array('node:select' => 'node:reference')); + $rule->integrityCheck(); + $rule->execute($node); + + // Test asserting metadata on a derived property being a list item. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('node_is_of_type', array('node:select' => 'node:ref-nodes:0', 'type' => array('article'))) + ->action('data_set', array('data:select' => 'node:ref-nodes:0:field-tags', 'value' => array())); + $rule->integrityCheck(); + $rule->execute($node); + + // Give green lights if there were no exceptions and check rules-log errors. + $this->pass('Rules asserting metadata on a derived property pass integrity checks.'); + RulesLog::logger()->checkLog(); + + // Make sure assertions of a one list item are not valid for another item. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('node_is_of_type', array('node:select' => 'node:ref-nodes:0', 'type' => array('article'))) + ->action('data_set', array('data:select' => 'node:ref-nodes:1:field-tags', 'value' => array())); + try { + $rule->integrityCheck(); + $this->fail('Assertion of a list item is not valid for another item.'); + } + catch (RulesException $e) { + $this->pass('Assertion of a list item is not valid for another item.'); + } + } + + /** + * Test using loops. + */ + function testLoops() { + // Test passing the list parameter as argument to ensure that is working + // generally for plugin container too. + drupal_get_messages(NULL, TRUE); + $loop = rules_loop(); + $loop->action('drupal_message', array('message' => 'test')); + $arg_info = $loop->parameterInfo(); + $this->assert($arg_info['list']['type'] == 'list', 'Argument info contains list.'); + $loop->execute(array(1, 2)); + + // Ensure the action has been executed twice, once for each list item. + $msg = drupal_get_messages(); + $this->assert($msg['status'][0] == 'test' && $msg['status'][1], 'Loop has been properly executed'); + + // Now test looping over nodes. + $node1 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0)); + $node2 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0)); + $node3 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0)); + + $rule = rule(array( + 'list' => array( + 'type' => 'list', + 'label' => 'A list of nodes', + ) + )); + $loop = rules_loop(array('list:select' => 'list', 'item:var' => 'node')); + $loop->action('data_set', array('data:select' => 'node:sticky', 'value' => TRUE)); + $rule->action($loop); + // Test using a list with data selectors, just output the last nodes type. + $rule->action('drupal_message', array('message:select' => 'list:2:type')); + + $rule->execute(array($node1->nid, $node2->nid, $node3->nid)); + $text = RulesLog::logger()->render(); + $save_msg = RulesTestCase::t('Saved %node of type %node.', array('node', 'node')); + $this->assertTrue(substr_count($text, $save_msg) == 3, 'List item variables have been saved.'); + RulesLog::logger()->checkLog(); + } + + /** + * Test access checks. + */ + function testAccessCheck() { + $rule = rule(); + // Try to set a property which is provided by the test module and is not + // accessible, so the access check has to return FALSE. + $rule->action('data_set', array('data:select' => 'site:no-access-user', 'value' => 'foo')); + $this->assertTrue($rule->access() === FALSE, 'Access check is working.'); + } + + /** + * Test returning provided variables. + */ + function testReturningVariables() { + $node = $this->drupalCreateNode(); + $action = rules_action('entity_fetch', array('type' => 'node', 'id' => $node->nid)); + list($node2) = $action->execute(); + $this->assertTrue($node2->nid == $node->nid, 'Action returned a variable.'); + + // Create a simple set that just passed through the given node. + $set = rules_rule_set(array('node' => array('type' => 'node')), array('node')); + $set->integrityCheck()->save('rules_test_set_1'); + + $provides = $set->providesVariables(); + $this->assertTrue($provides['node']['type'] == 'node', 'Rule set correctly passed through the node.'); + + list($node2) = $set->execute($node); + $this->assertTrue($node2->nid == $node->nid, 'Rule set returned a variable.'); + + // Create an action set returning a variable that is no parameter. + $set = rules_action_set(array( + 'node' => array( + 'type' => 'node', + 'parameter' => FALSE, + )), array('node')); + $set->action('entity_fetch', array('type' => 'node', 'id' => $node->nid)) + ->action('data_set', array('data:select' => 'node', 'value:select' => 'entity_fetched')); + $set->integrityCheck(); + list($node3) = $set->execute(); + $this->assertTrue($node3->nid == $node->nid, 'Action set returned a variable that has not been passed as parameter.'); + + // Test the same again with a variable holding a not wrapped data type. + $set = rules_action_set(array( + 'number' => array( + 'type' => 'integer', + 'parameter' => FALSE, + )), array('number')); + $set->action('data_set', array('data:select' => 'number', 'value' => 3)); + $set->integrityCheck(); + list($number) = $set->execute(); + $this->assertTrue($number == 3, 'Actions set returned a number.'); + } + + /** + * Tests using input evaluators. + */ + function testInputEvaluators() { + $node = $this->drupalCreateNode(array('title' => 'The body & nothing.', 'type' => 'page')); + + $rule = rule(array('nid' => array('type' => 'integer'))); + $rule->action('rules_action_load_node') + ->action('drupal_message', array('message' => 'Title: [node_loaded:title]')) + ->execute($node->nid); + + RulesLog::logger()->checkLog(); + $msg = drupal_get_messages(); + $this->assertEqual(array_pop($msg['status']), 'Title: ' . check_plain('The body & nothing.'), 'Token input evaluator applied.'); + + // Test token replacements on a list of text values. + $component = rules_action_set(array('var' => array('type' => 'list', 'label' => 'var')), array('var')); + $component->save('rules_test_input'); + + $action = rules_action('component_rules_test_input', array('var' => array('uid: [site:current-user:uid]'))); + list($var) = $action->execute(); + $uid = $GLOBALS['user']->uid; + $this->assertEqual(array("uid: $uid"), $var, 'Token replacements on a list of values applied.'); + } + + /** + * Test importing and exporting a rule. + */ + function testRuleImportExport() { + $rule = rule(array('nid' => array('type' => 'integer'))); + $rule->name = "rules_export_test"; + $rule->action('rules_action_load_node') + ->action('drupal_message', array('message' => 'Title: [node_loaded:title]')); + + $export = +'{ "rules_export_test" : { + "PLUGIN" : "rule", + "REQUIRES" : [ "rules_test", "rules" ], + "USES VARIABLES" : { "nid" : { "type" : "integer" } }, + "DO" : [ + { "rules_action_load_node" : { "PROVIDE" : { "node_loaded" : { "node_loaded" : "Loaded content" } } } }, + { "drupal_message" : { "message" : "Title: [node_loaded:title]" } } + ] + } +}'; + $this->assertEqual($export, $rule->export(), 'Rule has been exported correctly.'); + + // Test importing a rule which makes use of almost all features. + $export = _rules_export_get_test_export(); + $rule = rules_import($export); + $this->assertTrue(!empty($rule) && $rule->integrityCheck(), 'Rule has been imported.'); + + // Test loading the same export provided as default rule. + $rule = rules_config_load('rules_export_test'); + $this->assertTrue(!empty($rule) && $rule->integrityCheck(), 'Export has been provided in code.'); + + // Export it and make sure the same export is generated again. + $this->assertEqual($export, $rule->export(), 'Export of imported rule equals original export.'); + + // Now try importing a rule set. + $export = +'{ "rules_test_set" : { + "LABEL" : "Test set", + "PLUGIN" : "rule set", + "REQUIRES" : [ "rules" ], + "USES VARIABLES" : { "node" : { "label" : "Test node", "type" : "node" } }, + "RULES" : [ + { "RULE" : { + "IF" : [ { "NOT data_is" : { "data" : [ "node:title" ], "value" : "test" } } ], + "DO" : [ { "data_set" : { "data" : [ "node:title" ], "value" : "test" } } ], + "LABEL" : "Test Rule" + } + }, + { "RULE" : { + "DO" : [ { "drupal_message" : { "message" : "hi" } } ], + "LABEL" : "Test Rule 2" + } + } + ] + } +}'; + $set = rules_import($export); + $this->assertTrue(!empty($set) && $set->integrityCheck(), 'Rule set has been imported.'); + // Export it and make sure the same export is generated again. + $this->assertEqual($export, $set->export(), 'Export of imported rule set equals original export.'); + + // Try executing the imported rule set. + $node = $this->drupalCreateNode(); + $set->execute($node); + $this->assertEqual($node->title, 'test', 'Imported rule set has been executed.'); + RulesLog::logger()->checkLog(); + + // Try import / export for a rule component providing a variable. + $rule = rule(array( + 'number' => array( + 'type' => 'integer', + 'label' => 'Number', + 'parameter' => FALSE, + )), array('number')); + $rule->action('data_set', array('data:select' => 'number', 'value' => 3)); + $rule->name = 'rules_test_provides'; + + $export = '{ "rules_test_provides" : { + "PLUGIN" : "rule", + "REQUIRES" : [ "rules" ], + "USES VARIABLES" : { "number" : { "type" : "integer", "label" : "Number", "parameter" : false } }, + "DO" : [ { "data_set" : { "data" : [ "number" ], "value" : 3 } } ], + "PROVIDES VARIABLES" : [ "number" ] + } +}'; + $this->assertEqual($export, $rule->export(), 'Rule 2 has been exported correctly.'); + $imported_rule = rules_import($rule->export()); + + $this->assertTrue(!empty($imported_rule) && $imported_rule->integrityCheck(), 'Rule 2 has been imported.'); + $this->assertEqual($export, $imported_rule->export(), 'Export of imported rule 2 equals original export.'); + + // Test importing a negated condition component. + $export = '{ "rules_negated_component" : { + "LABEL" : "negated_component", + "PLUGIN" : "or", + "REQUIRES" : [ "rules" ], + "NOT OR" : [ { "data_is_empty" : { "data" : [ "site:slogan" ] } } ] + } +}'; + $or = rules_import($export); + $this->assertTrue($or->integrityCheck() && $or->isNegated(), 'Negated condition component imported.'); + } + + /** + * Test the named parameter mode. + */ + function testNamedParameters() { + $rule = rule(array('node' => array('type' => 'node'))); + $rule->action('rules_action_node_set_title', array('title' => 'foo')); + $rule->integrityCheck(); + + // Test the rule. + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0)); + $rule->execute($node); + $this->assertTrue($node->title == 'foo', 'Action with named parameters has been correctly executed.'); + RulesLog::logger()->checkLog(); + } + + /** + * Make sure Rules aborts when NULL values are used. + */ + function testAbortOnNULLValues() { + $rule = rule(array('node' => array('type' => 'node'))); + $rule->action('drupal_message', array('message:select' => 'node:log')); + $rule->integrityCheck(); + + // Test the rule. + $node = $this->drupalCreateNode(); + $node->log = NULL; + $rule->execute($node); + + $text = RulesLog::logger()->render(); + $msg = RulesTestCase::t('The variable or parameter %message is empty.', array('message')); + $this->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.'); + } +} + +/** + * Test rules data wrappers. + */ +class RulesTestDataCase extends DrupalWebTestCase { + + static function getInfo() { + return array( + 'name' => 'Rules Data tests', + 'description' => 'Tests rules data saving and type matching.', + 'group' => 'Rules', + ); + } + + function setUp() { + parent::setUp('rules', 'rules_test'); + variable_set('rules_debug_log', 1); + // Make sure we don't ran over issues with the node_load static cache. + entity_get_controller('node')->resetCache(); + } + + /** + * Tests intelligently saving data. + */ + function testDataSaving() { + $node = $this->drupalCreateNode(); + $state = new RulesState(rule()); + $state->addVariable('node', $node, array('type' => 'node')); + $wrapper = $state->get('node'); + $node->title = 'test'; + $wrapper->set($node); + $state->saveChanges('node', $wrapper, FALSE); + + $this->assertFalse($this->drupalGetNodeByTitle('test'), 'Changes have not been saved.'); + $state->saveChanges('node', $wrapper, TRUE); + $this->assertTrue($this->drupalGetNodeByTitle('test'), 'Changes have been saved.'); + + // Test skipping saving. + $state->addVariable('node2', $node, array( + 'type' => 'node', + 'skip save' => TRUE, + )); + $wrapper = $state->get('node2'); + $node->title = 'test2'; + $wrapper->set($node); + $state->saveChanges('node2', $wrapper, TRUE); + $this->assertFalse($this->drupalGetNodeByTitle('test2'), 'Changes have not been saved.'); + + // Try saving a non-entity wrapper, which should result in saving the + // parent entity containing the property. + $wrapper = $state->get('node'); + $wrapper->title->set('test3'); + $state->saveChanges('node:title', $wrapper, TRUE); + $this->assertTrue($this->drupalGetNodeByTitle('test3'), 'Parent entity has been saved.'); + } + + /** + * Test type matching + */ + function testTypeMatching() { + $entity = array('type' => 'entity'); + $node = array('type' => 'node'); + $this->assertTrue(RulesData::typesMatch($node, $entity), 'Types match.'); + $this->assertFalse(RulesData::typesMatch($entity, $node), 'Types don\'t match.'); + + $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node), 'Types match.'); + $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $entity), 'Types match.'); + $this->assertTrue(RulesData::typesMatch(array('type' => 'list'), array('type' => 'list')), 'Types match.'); + $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node + array('bundles' => array('page', 'story'))), 'Types match.'); + $this->assertFalse(RulesData::typesMatch($node, $node + array('bundles' => array('page', 'story'))), 'Types don\'t match.'); + + // Test that a type matches its grand-parent type (text > decimal > integer) + $this->assertTrue(RulesData::typesMatch(array('type' => 'integer'), array('type' => 'text')), 'Types match.'); + $this->assertFalse(RulesData::typesMatch(array('type' => 'text'), array('type' => 'integer')), 'Types don\'t match.'); + } + + /** + * Tests making use of custom wrapper classes. + */ + function testCustomWrapperClasses() { + // Test loading a vocabulary by name, which is done by a custom wrapper. + $set = rules_action_set(array('vocab' => array('type' => 'taxonomy_vocabulary')), array('vocab')); + $set->action('drupal_message', array('message:select' => 'vocab:name')); + $set->integrityCheck(); + list($vocab) = $set->execute('tags'); + $this->assertTrue($vocab->machine_name == 'tags', 'Loaded vocabulary by name.'); + + // Now test wrapper creation for a direct input argument value. + $set = rules_action_set(array('term' => array('type' => 'taxonomy_term'))); + $set->action('data_set', array('data:select' => 'term:vocabulary', 'value' => 'tags')); + $set->integrityCheck(); + + $vocab = entity_create('taxonomy_vocabulary', array( + 'name' => 'foo', + 'machine_name' => 'foo', + )); + entity_save('taxonomy_vocabulary', $vocab); + $term_wrapped = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => $vocab, + ))->save(); + $set->execute($term_wrapped); + $this->assertEqual($term_wrapped->vocabulary->machine_name->value(), 'tags', 'Vocabulary name used as direct input value.'); + RulesLog::logger()->checkLog(); + } + + /** + * Makes sure the RulesIdentifiableDataWrapper is working correctly. + */ + function testRulesIdentifiableDataWrapper() { + $node = $this->drupalCreateNode(); + $wrapper = new RulesTestTypeWrapper('rules_test_type', $node); + $this->assertTrue($wrapper->value() == $node, 'Data correctly wrapped.'); + + // Test serializing and make sure only the id is stored. + $this->assertTrue(strpos(serialize($wrapper), $node->title) === FALSE, 'Data has been correctly serialized.'); + $this->assertEqual(unserialize(serialize($wrapper))->value()->title, $node->title, 'Serializing works right.'); + + $wrapper2 = unserialize(serialize($wrapper)); + // Test serializing the unloaded wrapper. + $this->assertEqual(unserialize(serialize($wrapper2))->value()->title, $node->title, 'Serializing works right.'); + + // Test loading a not more existing node. + $s = serialize($wrapper2); + node_delete($node->nid); + $this->assertFalse(node_load($node->nid), 'Node deleted.'); + try { + unserialize($s)->value(); + $this->fail("Loading hasn't created an exception."); + } + catch (EntityMetadataWrapperException $e) { + $this->pass("Exception was thrown: ". $e->getMessage()); + } + + // Test saving a savable custom, identifiable wrapper. + $action = rules_action('test_type_save'); + $node = $this->drupalCreateNode(array('status' => 0, 'type' => 'page')); + $node->status = 1; + $action->execute($node); + + // Load the node fresh from the db. + $node = node_load($node->nid, NULL, TRUE); + $this->assertEqual($node->status, 1, 'Savable non-entity has been saved.'); + } +} + +/** + * Test triggering rules. + */ +class RulesTriggerTestCase extends DrupalWebTestCase { + + static function getInfo() { + return array( + 'name' => 'Reaction Rules', + 'description' => 'Tests triggering reactive rules.', + 'group' => 'Rules', + ); + } + + function setUp() { + parent::setUp('rules', 'rules_test'); + RulesLog::logger()->clear(); + variable_set('rules_debug_log', 1); + } + + protected function createTestRule($action = TRUE, $event = 'node_presave') { + $rule = rules_reaction_rule(); + $rule->event($event) + ->condition(rules_condition('data_is', array('data:select' => 'node:status', 'value' => TRUE))->negate()) + ->condition('data_is', array('data:select' => 'node:type', 'value' => 'page')); + if ($action) { + $rule->action('rules_action_delete_node'); + } + return $rule; + } + + /** + * Tests CRUD for reaction rules - making sure the events are stored properly. + */ + function testReactiveRuleCreation() { + $rule = $this->createTestRule(); + $rule->save(); + $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id)); + $this->assertEqual($result->fetchField(), 'node_presave', 'Associated event has been saved.'); + // Try updating. + $rule->removeEvent('node_presave'); + $rule->event('node_insert'); + $rule->event('node_update'); + $rule->active = FALSE; + $rule->integrityCheck()->save(); + $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id)); + $this->assertEqual($result->fetchCol(), array_values($rule->events()), 'Updated associated events.'); + // Try deleting. + $rule->delete(); + $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id)); + $this->assertEqual($result->fetchField(), FALSE, 'Deleted associated events.'); + } + + /** + * Tests creating and triggering a basic reaction rule. + */ + function testBasicReactionRule() { + $node = $this->drupalCreateNode(array('type' => 'page')); + $rule = $this->createTestRule(); + $rule->integrityCheck()->save(); + // Test the basics of the event set work right. + $event = rules_get_cache('event_node_presave'); + $this->assertEqual(array_keys($event->parameterInfo()), array('node'), 'EventSet returns correct argument info.'); + + // Trigger the rule by updating the node. + $nid = $node->nid; + $node->status = 0; + node_save($node); + + RulesLog::logger()->checkLog(); + $this->assertFalse(node_load($nid), 'Rule successfully triggered and executed'); + //debug(RulesLog::logger()->render()); + } + + /** + * Test a rule using a handler to load a variable. + */ + function testVariableHandler() { + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0)); + $rule = $this->createTestRule(FALSE, 'node_update'); + $rule->action('rules_node_publish_action_save', array('node:select' => 'node_unchanged')); + // Test without recursion prevention to make sure recursive invocations + // work right too. This rule won't ran in an infinite loop anyway. + $rule->recursion = TRUE; + $rule->label = 'rule 1'; + $rule->integrityCheck()->save(); + + $node->status = 0; + $node->sticky = 1; + node_save($node); + + RulesLog::logger()->checkLog(); + entity_get_controller('node')->resetCache(); + $node = node_load($node->nid); + + $this->assertFalse($node->sticky, 'Parameter has been loaded and saved.'); + $this->assertTrue($node->status, 'Action has been executed.'); + + // Ensure the rule was evaluated a second time + $text = RulesLog::logger()->render(); + $msg = RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1')); + $pos = strpos($text, $msg); + $pos = ($pos !== FALSE) ? strpos($text, $msg, $pos) : FALSE; + $this->assertTrue($pos !== FALSE, "Recursion prevented."); + //debug(RulesLog::logger()->render()); + } + + /** + * Test aborting silently when handlers are not able to load. + */ + function testVariableHandlerFailing() { + $rule = $this->createTestRule(FALSE, 'node_presave'); + $rule->action('rules_node_publish_action_save', array('node:select' => 'node_unchanged')); + $rule->integrityCheck()->save(); + + // On insert it's not possible to get the unchanged node during presave. + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0)); + + //debug(RulesLog::logger()->render()); + $text = RulesTestCase::t('Unable to load variable %node_unchanged, aborting.', array('node_unchanged')); + $this->assertTrue(strpos(RulesLog::logger()->render(), $text) !== FALSE, "Aborted evaluation."); + } + + /** + * Tests preventing recursive rule invocations by creating a rule that reacts + * on node-update and generates a node update that would trigger it itself. + */ + function testRecursionPrevention() { + $rule = $this->createTestRule(FALSE, 'node_update'); + $rule->action('rules_node_make_sticky_action'); + $rule->integrityCheck()->save(); + + // Now trigger the rule. + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0)); + node_save($node); + + $text = RulesTestCase::t('Not evaluating reaction rule %label to prevent recursion.', array('label' => $rule->name)); + //debug(RulesLog::logger()->render()); + $this->assertTrue((strpos(RulesLog::logger()->render(), $text) !== FALSE), "Recursion prevented."); + //debug(RulesLog::logger()->render()); + } + + /** + * Ensure the recursion prevention still allows to let the rule trigger again + * during evaluation of the same event set, if the event isn't caused by the + * rule itself - thus we won't run in an infinte loop. + */ + function testRecursionOnDifferentArguments() { + // Create rule1 - which might recurse. + $rule = $this->createTestRule(FALSE, 'node_update'); + $rule->action('rules_node_make_sticky_action'); + $rule->label = 'rule 1'; + $rule->integrityCheck()->save(); + + // Create rule2 - which triggers rule1 on another node. + $node2 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0)); + $rule2 = $this->createTestRule(FALSE, 'node_update'); + $rule2->action('rules_action_load_node', array('nid' => $node2->nid)) + ->action('rules_node_make_sticky_action', array('node:select' => 'node_loaded')); + $rule2->label = 'rule 2'; + $rule2->save(); + + // Now trigger both rules by generating the event. + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0)); + node_save($node); + + //debug(RulesLog::logger()->render()); + $text = RulesLog::logger()->render(); + $pos = strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1'))); + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 2', array('rule 2')), $pos) : FALSE; + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %node_loaded of type %node.', array('node_loaded', 'node')), $pos) : FALSE; + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1')), $pos) : FALSE; + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Not evaluating reaction rule %rule 2 to prevent recursion', array('rule 2')), $pos) : FALSE; + $this->assertTrue($pos !== FALSE, 'Rule1 was triggered on the event caused by Rule2.'); + } + + /** + * Tests the provided default rule 'rules_test_default_1'. + */ + function testDefaultRule() { + $rule = rules_config_load('rules_test_default_1'); + $this->assertTrue($rule->status & ENTITY_IN_CODE && !($rule->status & ENTITY_IN_DB), 'Default rule can be loaded and has the right status.'); + // Enable. + $rule->active = TRUE; + $rule->save(); + + // Create a node that triggers the rule. + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0)); + // Clear messages. + drupal_get_messages(); + // Let event node_update occur. + node_save($node); + + $msg = drupal_get_messages(); + $this->assertEqual($msg['status'][0], 'A node has been updated.', 'Default rule has been triggered.'); + } + + /** + * Tests creating and triggering a reaction rule with event settings. + */ + function testEventSettings() { + $rule = rules_reaction_rule(); + $rule->event('node_presave', array('bundle' => 'article')) + ->condition('data_is_empty', array('data:select' => 'node:field-tags')) + ->action('node_publish', array('node:select' => 'node')); + $rule->integrityCheck()->save(); + + $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 0)); + $this->assertEqual($node->status, 0, 'Rule has not been triggered.'); + $node = $this->drupalCreateNode(array('type' => 'article', 'status' => 0)); + $this->assertEqual($node->status, 1, 'Rule has been triggered.'); + RulesLog::logger()->checkLog(); + + // Make sure an invalid bundle raises integrity problems. + $rule->event('node_presave', array('bundle' => 'invalid')); + try { + $rule->integrityCheck(); + $this->fail('Integrity check failed.'); + } + catch (RulesIntegrityException $e) { + $this->pass('Integrity check failed: ' . $e); + } + } +} + +/** + * Tests provided module integration. + */ +class RulesIntegrationTestCase extends DrupalWebTestCase { + + static function getInfo() { + return array( + 'name' => 'Rules Core Integration', + 'description' => 'Tests provided integration for drupal core.', + 'group' => 'Rules', + ); + } + + function setUp() { + parent::setUp('rules', 'rules_test', 'php', 'path'); + RulesLog::logger()->clear(); + variable_set('rules_debug_log', 1); + } + + /** + * Just make sure the access callback run without errors. + */ + function testAccessCallbacks() { + $cache = rules_get_cache(); + foreach (array('action', 'condition', 'event') as $type) { + foreach (rules_fetch_data($type . '_info') as $name => $info) { + if (isset($info['access callback'])) { + $info['access callback']($type, $name); + } + } + } + } + + /** + * Test data integration. + */ + function testDataIntegration() { + // Test data_create action. + $action = rules_action('data_create', array( + 'type' => 'log_entry', + 'param_type' => 'rules_test', + 'param_message' => 'Rules test log message', + 'param_severity' => WATCHDOG_WARNING, + 'param_request_uri' => 'http://example.com', + 'param_link' => '', + )); + $action->access(); + $action->execute(); + $text = RulesLog::logger()->render(); + $pos = strpos($text, RulesTestCase::t('Added the provided variable %data_created of type %log_entry', array('data_created', 'log_entry'))); + $this->assertTrue($pos !== FALSE, 'Data of type log entry has been created.'); + + + // Test variable_add action. + $action = rules_action('variable_add', array( + 'type' => 'text_formatted', + 'value' => array( + 'value' => 'test text', + 'format' => 1, + ) + )); + $action->access(); + $action->execute(); + $text = RulesLog::logger()->render(); + $pos = strpos($text, RulesTestCase::t('Added the provided variable %variable_added of type %text_formatted', array('variable_added', 'text_formatted'))); + $this->assertTrue($pos !== FALSE, 'Data of type text formatted has been created.'); + + + // Test using the list actions. + $rule = rule(array( + 'list' => array( + 'type' => 'list', + 'label' => 'A list of text', + ) + )); + $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar2')); + $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'pos' => 'start')); + $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'unique' => TRUE)); + $rule->action('list_remove', array('list:select' => 'list', 'item' => 'bar2')); + $list = entity_metadata_wrapper('list', array('foo', 'foo2')); + $rule->execute($list); + RulesLog::logger()->checkLog(); + $this->assertEqual($list->value(), array('bar', 'foo', 'foo2'), 'List items removed and added.'); + $this->assertFalse(rules_condition('list_contains')->execute($list, 'foo-bar'), 'Condition "List item contains" evaluates to FALSE'); + $this->assertTrue(rules_condition('list_contains')->execute($list, 'foo'), 'Condition "List item contains" evaluates to TRUE'); + //debug(RulesLog::logger()->render()); + + // Test data_is condition with IN operation. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('data_is', array('data:select' => 'node:title', 'op' => 'IN', 'value' => array('foo', 'bar'))); + $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar')); + $rule->integrityCheck(); + + $node = $this->drupalCreateNode(array('title' => 'foo')); + $rule->execute($node); + $this->assertEqual($node->title, 'bar', "Data comparision using IN operation evaluates to TRUE."); + + + // Test Condition: Data is empty. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('data_is_empty', array('data:select' => 'node:title')); + $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar')); + $rule->integrityCheck(); + + // Data is empty condition evaluates to TRUE + // for node with empty title, action sets title to 'bar'. + $node = $this->drupalCreateNode(array('title' => '', 'type' => 'article')); + $rule->execute($node); + $this->assertEqual($node->title, 'bar', "Data is empty condition evaluates to TRUE for node with empty title, action sets title to 'bar'."); + + // Data is empty condition evaluates to FALSE + // for node with title 'foo', action is not executed. + $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article')); + $rule->execute($node); + $this->assertEqual($node->title, 'foo', "Data is empty condition evaluates to FALSE for node with title 'foo', action is not executed."); + + // Data is empty condition evaluates to TRUE for the parent of a + // not existing term in the tags field of the node. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('node_is_of_type', array('type' => array('article'))); + $rule->condition('data_is_empty', array('data:select' => 'node:field-tags:0:parent')); + $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar')); + $rule->integrityCheck(); + $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article')); + $rule->execute($node); + $this->assertEqual($node->title, 'bar', "Data is empty condition evaluates to TRUE for not existing data structures"); + + // Test Action: Calculate a value. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->action('data_calc', array('input_1:select' => 'node:nid', 'op' => '*', 'input_2' => 2)); + $rule->action('data_set', array('data:select' => 'node:title', 'value:select' => 'result')); + $rule->integrityCheck(); + $rule->execute($node); + $this->assertEqual($node->title, $node->nid * 2, "Value has been calculated."); + + // Test moving a date. + $action_set = rules_action_set(array('date' => array('type' => 'date')), array('date')); + $action_set->action('data_calc', array('input_1:select' => 'date', 'op' => '+', 'input_2' => 3600)) + ->action('data_set', array('data:select' => 'date', 'value:select' => 'result')); + $action_set->integrityCheck(); + list($result) = $action_set->execute(REQUEST_TIME); + $this->assertEqual($result, REQUEST_TIME + 3600, 'Used data calculation action to move a date by an hour.'); + + // Test data type conversion action. + $set = rules_action_set(array('result' => array('type' => 'text', 'parameter' => FALSE)), array('result')); + $set->action('data_convert', array('type' => 'text', 'value:select' => 'site:login-url')); + $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result')); + list($result) = $set->execute(); + $set->integrityCheck(); + $this->assertEqual($result, url('user', array('absolute' => TRUE)), 'Converted URI to text.'); + + $set = rules_action_set(array( + 'result' => array('type' => 'integer', 'parameter' => FALSE), + 'source' => array('type' => 'text'), + ), array('result')); + $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source')); + $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result')); + list($result) = $set->execute('9.4'); + $this->assertEqual($result, 9, 'Converted decimal to integer using rounding.'); + + $set = rules_action_set(array( + 'result' => array('type' => 'integer', 'parameter' => FALSE), + 'source' => array('type' => 'text'), + ), array('result')); + $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'down')); + $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result')); + list($result) = $set->execute('9.6'); + $this->assertEqual($result, 9, 'Converted decimal to integer using roundin behavio down.'); + + $set = rules_action_set(array( + 'result' => array('type' => 'integer', 'parameter' => FALSE), + 'source' => array('type' => 'text'), + ), array('result')); + $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'up')); + $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result')); + list($result) = $set->execute('9.4'); + $this->assertEqual($result, 10, 'Converted decimal to integer using rounding behavior up.'); + + // Test text matching condition. + $result = rules_condition('text_matches')->execute('my-text', 'text', 'contains'); + $result2 = rules_condition('text_matches')->execute('my-text', 'tex2t', 'contains'); + $this->assertTrue($result && !$result2, 'Text matching condition using operation contain evaluated.'); + + $result = rules_condition('text_matches')->execute('my-text', 'my', 'starts'); + $result2 = rules_condition('text_matches')->execute('my-text', 'text', 'starts'); + $this->assertTrue($result && !$result2, 'Text matching condition using operation starts evaluated.'); + + $result = rules_condition('text_matches')->execute('my-text', 'text', 'ends'); + $result2 = rules_condition('text_matches')->execute('my-text', 'my', 'ends'); + $this->assertTrue($result && !$result2, 'Text matching condition using operation ends evaluated.'); + + $result = rules_condition('text_matches')->execute('my-text', 'me?y-texx?t', 'regex'); + $result2 = rules_condition('text_matches')->execute('my-text', 'me+y-texx?t', 'regex'); + $this->assertTrue($result && !$result2, 'Text matching condition using operation regex evaluated.'); + } + + /** + * Tests entity related integration. + */ + function testEntityIntegration() { + global $user; + + $page = $this->drupalCreateNode(array('type' => 'page')); + $article = $this->drupalCreateNode(array('type' => 'article')); + + $result = rules_condition('entity_field_access') + ->execute(entity_metadata_wrapper('node', $article), 'field_tags'); + $this->assertTrue($result); + + // Test entiy_is_of_bundle condition. + $result = rules_condition('entity_is_of_bundle', array( + 'type' => 'node', + 'bundle' => array('article'), + ))->execute(entity_metadata_wrapper('node', $page)); + $this->assertFalse($result, 'Entity is of bundle condition has not been met.'); + $result = rules_condition('entity_is_of_bundle', array( + 'type' => 'node', + 'bundle' => array('article'), + ))->execute(entity_metadata_wrapper('node', $article)); + $this->assertTrue($result, 'Entity is of bundle condition has been met.'); + + // Also test a full rule so the integrity check must work. + $term_wrapped = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->save(); + $rule = rule(array( + 'node' => array('type' => 'node'), + )); + $rule->condition('entity_is_of_bundle', array( + 'entity:select' => 'node', + 'bundle' => array('article'), + )); + $rule->action('data_set', array('data:select' => 'node:field_tags', 'value' => array($term_wrapped->getIdentifier()))); + $rule->integrityCheck(); + $rule->execute($article); + $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.'); + + // Test again using an entity variable. + $article = $this->drupalCreateNode(array('type' => 'article')); + $rule = rule(array( + 'entity' => array('type' => 'entity'), + )); + $rule->condition('entity_is_of_bundle', array( + 'entity:select' => 'entity', + 'type' => 'node', + 'bundle' => array('article'), + )); + $rule->action('data_set', array('data:select' => 'entity:field_tags', 'value' => array($term_wrapped->getIdentifier()))); + $rule->integrityCheck(); + $rule->execute(entity_metadata_wrapper('node', $article)); + $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.'); + + // Test CRUD actions. + $action = rules_action('entity_create', array( + 'type' => 'node', + 'param_type' => 'page', + 'param_title' => 'foo', + 'param_author' => $GLOBALS['user'], + )); + $action->access(); + $action->execute(); + $text = RulesLog::logger()->render(); + $pos = strpos($text, RulesTestCase::t('Added the provided variable %entity_created of type %node', array('entity_created', 'node'))); + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %entity_created of type %node.', array('entity_created', 'node')), $pos) : FALSE; + $this->assertTrue($pos !== FALSE, 'Data has been created and saved.'); + + $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0)); + $rule = rule(); + $rule->action('entity_fetch', array('type' => 'node', 'id' => $node->nid, 'entity_fetched:var' => 'node')); + $rule->action('entity_save', array('data:select' => 'node', 'immediate' => TRUE)); + $rule->action('entity_delete', array('data:select' => 'node')); + $rule->access(); + $rule->integrityCheck()->execute(); + + $text = RulesLog::logger()->render(); + $pos = strpos($text, RulesTestCase::t('Evaluating the action %entity_fetch.', array('entity_fetch'))); + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Added the provided variable %node of type %node', array('node')), $pos) : FALSE; + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %node of type %node.', array('node')), $pos) : FALSE; + $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating the action %entity_delete.', array('entity_delete')), $pos) : FALSE; + $this->assertTrue($pos !== FALSE, 'Data has been fetched, saved and deleted.'); + //debug(RulesLog::logger()->render()); + + + + $node = entity_property_values_create_entity('node', array( + 'type' => 'article', + 'author' => $user, + 'title' => 'foo', + ))->value(); + $term_wrapped = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->save(); + + // Test asserting the field and using it afterwards. + $rule = rule(array('node' => array('type' => 'node'))); + $rule->condition('entity_has_field', array('entity:select' => 'node', 'field' => 'field_tags')); + $rule->condition('entity_is_new', array('entity:select' => 'node')); + $rule->action('list_add', array('list:select' => 'node:field-tags', 'item' => $term_wrapped)); + $rule->integrityCheck(); + $rule->execute($node); + + $tid = $term_wrapped->getIdentifier(); + $this->assertEqual(array_values($node->field_tags[LANGUAGE_NONE]), array(0 => array('tid' => $tid)), 'Entity has field conditions evaluted.'); + + // Test loading a non-node entity. + $action = rules_action('entity_fetch', array('type' => 'taxonomy_term', 'id' => $tid)); + list($term) = $action->execute(); + $this->assertEqual($term->tid, $tid, 'Fetched a taxonomy term using "entity_fetch".'); + + // Test the entity is of type condition. + $rule = rule(array('entity' => array('type' => 'entity', 'label' => 'entity'))); + $rule->condition('entity_is_of_type', array('type' => 'node')); + $rule->action('data_set', array('data:select' => 'entity:title', 'value' => 'bar')); + $rule->integrityCheck(); + $rule->execute(entity_metadata_wrapper('node', $node)); + + $this->assertEqual(entity_metadata_wrapper('node', $node->nid)->title->value(), 'bar', 'Entity is of type condition correctly asserts the entity type.'); + + + // Test the entity_query action. + $node = $this->drupalCreateNode(array('type' => 'page', 'title' => 'foo2')); + $rule = rule(); + $rule->action('entity_query', array('type' => 'node', 'property' => 'title', 'value' => 'foo2')) + ->action('data_set', array('data:select' => 'entity_fetched:0:title', 'value' => 'bar')); + $rule->access(); + $rule->integrityCheck(); + $rule->execute(); + $node = node_load($node->nid); + $this->assertEqual('bar', $node->title, 'Fetched a node by title and modified it.'); + + RulesLog::logger()->checkLog(); + } + + /** + * Test integration for the taxonomy module. + */ + function testTaxonomyIntegration() { + $term = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->value(); + $term2 = clone $term; + taxonomy_term_save($term); + taxonomy_term_save($term2); + + $tags[LANGUAGE_NONE][0]['tid'] = $term->tid; + $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', 'field_tags' => $tags)); + + // Test assigning and remove a term from an article. + $rule = rule(array('node' => array('type' => 'node', 'bundle' => 'article'))); + $term_wrapped = rules_wrap_data($term->tid, array('type' => 'taxonomy_term')); + $term_wrapped2 = rules_wrap_data($term2->tid, array('type' => 'taxonomy_term')); + $rule->action('list_add', array('list:select' => 'node:field-tags', 'item' => $term_wrapped2)); + $rule->action('list_remove', array('list:select' => 'node:field-tags', 'item' => $term_wrapped)); + $rule->execute($node); + RulesLog::logger()->checkLog(); + $this->assertEqual(array_values($node->field_tags[LANGUAGE_NONE]), array(0 => array('tid' => $term2->tid)), 'Term removed and added from a node.'); + + // Test using the taxonomy term reference field on a term object. + $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = field_create_field(array( + 'field_name' => $field_name, + 'type' => 'taxonomy_term_reference', + // Set cardinality to unlimited for tagging. + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => 'tags', + 'parent' => 0, + ), + ), + ), + )); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'taxonomy_term', + 'bundle' => 'tags', // Machine name of vocabulary. + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'widget' => array( + 'type' => 'taxonomy_autocomplete', + 'weight' => -4, + ), + 'display' => array( + 'default' => array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + ), + ), + ); + field_create_instance($instance); + + $term1 = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->save(); + $term2 = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->save(); + + // Test asserting the term reference field and using it afterwards. + $rule = rule(array('taxonomy_term' => array('type' => 'taxonomy_term'))); + $rule->condition('entity_has_field', array('entity:select' => 'taxonomy-term', 'field' => $field_name)); + // Add $term2 to $term1 using the term reference field. + $selector = str_replace('_', '-', 'taxonomy_term:' . $field_name); + $rule->action('list_add', array('list:select' => $selector, 'item' => $term2)); + $rule->integrityCheck(); + $rule->execute($term1); + + RulesLog::logger()->checkLog(); + $this->assertEqual($term1->{$field_name}[0]->getIdentifier(), $term2->getIdentifier(), 'Rule appended a term to the term reference field on a term.'); + + // Test an action set for merging term parents, which is provided as default + // config. + $term = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + 'parent' => array($term1->value()), + ))->save(); + + $action = rules_action('component_rules_retrieve_term_parents'); + list($parents) = $action->execute(array($term->getIdentifier())); + $this->assertTrue($parents[0]->tid == $term1->getIdentifier(), 'Invoked component to retrieve term parents.'); + RulesLog::logger()->checkLog(); + } + + /** + * Test integration for the node module. + */ + function testNodeIntegration() { + $tests = array( + array('node_unpublish', 'node_is_published', 'node_publish', 'status'), + array('node_make_unsticky', 'node_is_sticky', 'node_make_sticky', 'sticky'), + array('node_unpromote', 'node_is_promoted', 'node_promote', 'promote'), + ); + $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 1, 'sticky' => 1, 'promote' => 1)); + + foreach ($tests as $info) { + list($action1, $condition, $action2, $property) = $info; + rules_action($action1)->execute($node); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertFalse($node->$property, 'Action has permanently disabled node '. $property); + $return = rules_condition($condition)->execute($node); + $this->assertFalse($return, 'Condition determines node '. $property . ' is disabled.'); + + rules_action($action2)->execute($node); + $node = node_load($node->nid, NULL, TRUE); + $this->assertTrue($node->$property, 'Action has permanently enabled node '. $property); + $return = rules_condition($condition)->execute($node); + $this->assertTrue($return, 'Condition determines node '. $property . ' is enabled.'); + } + + $return = rules_condition('node_is_of_type', array('type' => array('page', 'article')))->execute($node); + $this->assertTrue($return, 'Condition determines node is of type page.'); + $return = rules_condition('node_is_of_type', array('type' => array('article')))->execute($node); + $this->assertFalse($return, 'Condition determines node is not of type article.'); + + + // Test auto saving of a new node after it has been inserted into the DB. + $rule = rules_reaction_rule(); + $rand = $this->randomName(); + $rule->event('node_insert') + ->action('data_set', array('data:select' => 'node:title', 'value' => $rand)); + $rule->save('test'); + $node = $this->drupalCreateNode(); + $node = node_load($node->nid); + $this->assertEqual($node->title, $rand, 'Node title is correct.'); + RulesLog::logger()->checkLog(); + } + + /** + * Test integration for the user module. + */ + function testUserIntegration() { + $rid = $this->drupalCreateRole(array('administer nodes'), 'foo'); + $user = $this->drupalCreateUser(); + + // Test assigning a role with the list_add action. + $rule = rule(array('user' => array('type' => 'user'))); + $rule->action('list_add', array('list:select' => 'user:roles', 'item' => $rid)); + $rule->execute($user); + $this->assertTrue(isset($user->roles[$rid]), 'Role assigned to user.'); + + // Test removing a role with the list_remove action. + $rule = rule(array('user' => array('type' => 'user'))); + $rule->action('list_remove', array('list:select' => 'user:roles', 'item' => $rid)); + $rule->execute($user); + $this->assertTrue(!isset($user->roles[$rid]), 'Role removed from user.'); + + // Test assigning a role with user_add_role action. + $rule = rule(array('user' => array('type' => 'user'))); + $rule->action('user_add_role', array('account:select' => 'user', 'roles' => array($rid))); + $rule->execute($user); + + $user = user_load($user->uid, TRUE); + $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user); + $this->assertTrue($result, 'Role assigned to user.'); + + // Test removing a role with the user_remove_role action. + $rule = rule(array('user' => array('type' => 'user'))); + $rule->action('user_remove_role', array('account:select' => 'user', 'roles' => array($rid))); + $rule->execute($user); + + $user = user_load($user->uid, TRUE); + $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user); + $this->assertFalse($result, 'Role removed from user.'); + + // Test user blocking. + rules_action('user_block')->execute($user); + $user = user_load($user->uid, TRUE); + $this->assertTrue(rules_condition('user_is_blocked')->execute($user), 'User has been blocked.'); + + rules_action('user_unblock')->execute($user); + $user = user_load($user->uid, TRUE); + $this->assertFalse(rules_condition('user_is_blocked')->execute($user), 'User has been unblocked.'); + + RulesLog::logger()->checkLog(); + } + + /** + * Test integration for the php module. + */ + function testPHPIntegration() { + $node = $this->drupalCreateNode(array('title' => 'foo')); + $rule = rule(array('var_name' => array('type' => 'node'))); + $rule->condition('php_eval', array('code' => 'return TRUE;')) + ->action('php_eval', array('code' => 'drupal_set_message("Executed-" . $var_name->title);')) + ->action('drupal_message', array('message' => 'Title: title; ?> Token: [var_name:title]')); + + $rule->execute($node); + $rule->access(); + RulesLog::logger()->checkLog(); + $msg = drupal_get_messages(); + $this->assertEqual(array_pop($msg['status']), "Title: foo Token: foo", 'PHP input evaluation has been applied.'); + $this->assertEqual(array_pop($msg['status']), "Executed-foo", 'PHP code condition and action have been evaluated.'); + + // Test PHP data processor + $rule = rule(array('var_name' => array('type' => 'node'))); + $rule->action('drupal_message', array( + 'message:select' => 'var_name:title', + 'message:process' => array( + 'php' => array('code' => 'return "Title: $value";') + ), + )); + $rule->execute($node); + $rule->access(); + RulesLog::logger()->checkLog(); + $msg = drupal_get_messages(); + $this->assertEqual(array_pop($msg['status']), "Title: foo", 'PHP data processor has been applied.'); + } + + /** + * Test the "rules_core" integration. + */ + function testRulesCoreIntegration() { + // Make sure the date input evaluator evaluates properly using strtotime(). + $node = $this->drupalCreateNode(array('title' => 'foo')); + $rule = rule(array('node' => array('type' => 'node'))); + $rule->action('data_set', array('data:select' => 'node:created', 'value' => '+1 day')); + + $rule->execute($node); + RulesLog::logger()->checkLog(); + $node = node_load($node->nid, NULL, TRUE); + $now = RulesDateInputEvaluator::gmstrtotime('now'); + // Tolerate a difference of a second. + $this->assertTrue(abs($node->created - $now - 86400) <= 1, 'Date input has been evaluated.'); + + // Test using a numeric offset. + $rule = rule(array('number' => array('type' => 'decimal')), array('number')); + $rule->action('data_set', array( + 'data:select' => 'number', + 'value:select' => 'number', + 'value:process' => array( + 'num_offset' => array('value' => 1), + ), + )); + $rule->integrityCheck(); + list($result) = $rule->execute(10); + $this->assertTrue($result == 11, 'Numeric offset has been applied'); + + // Test using a date offset. + $set = rules_action_set(array('date' => array('type' => 'date')), array('date')); + $set->action('data_set', array( + 'data:select' => 'date', + 'value:select' => 'date', + 'value:process' => array( + 'date_offset' => array('value' => 1000), + ), + )); + $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U'); + list($result) = $set->execute($date); + $this->assertEqual($result, $date + 1000, 'Date offset in seconds has been added.'); + + // Test using a negative offset of 2 months. + $set = rules_action_set(array('date' => array('type' => 'date')), array('date')); + $set->action('data_set', array( + 'data:select' => 'date', + 'value:select' => 'date', + 'value:process' => array( + 'date_offset' => array('value' => - 86400 * 30 * 2), + ), + )); + $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U'); + list($result) = $set->execute($date); + $this->assertEqual($result, date_create("14 Jan 1984 10:19:23 +01:00")->format('U'), 'Date offset of -2 months has been added.'); + + // Test using a positive offset of 1 year 6 months and 30 minutes. + $set = rules_action_set(array('date' => array('type' => 'date')), array('date')); + $set->action('data_set', array( + 'data:select' => 'date', + 'value:select' => 'date', + 'value:process' => array( + 'date_offset' => array('value' => 86400 * 30 * 18 + 30 * 60), + ), + )); + $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U'); + list($result) = $set->execute($date); + $this->assertEqual($result, date_create("14 Sep 1985 10:49:23 +01:00")->format('U'), 'Date offset of 1 year 6 months and 30 minutes has been added.'); + + RulesLog::logger()->checkLog(); + } + + /** + * Test site/system integration. + */ + function testSystemIntegration() { + // Test using the 'site' variable. + $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => $GLOBALS['user']->name)); + $this->assertTrue($condition->execute(), 'Retrieved the current user\'s name.'); + // Another test using a token replacement. + $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => '[site:current-user:name]')); + $this->assertTrue($condition->execute(), 'Replaced the token for the current user\'s name.'); + + // Test breadcrumbs and drupal set message. + $rule = rules_reaction_rule(); + $rule->event('init') + ->action('breadcrumb_set', array('titles' => array('foo'), 'paths' => array('bar'))) + ->action('drupal_message', array('message' => 'A message.')); + $rule->save('test'); + + $this->drupalGet('node'); + $this->assertLink('foo', 0, 'Breadcrumb has been set.'); + $this->assertText('A message.', 'Drupal message has been shown.'); + + // Test the page redirect. + $node = $this->drupalCreateNode(); + $rule = rules_reaction_rule(); + $rule->event('node_view') + ->action('redirect', array('url' => 'user')); + $rule->save('test2'); + + $this->drupalGet('node/' . $node->nid); + $this->assertEqual($this->getUrl(), url('user', array('absolute' => TRUE)), 'Redirect has been issued.'); + + // Also test using a url including a fragment. + $actions = $rule->actions(); + $actions[0]->settings['url'] = 'user#fragment'; + $rule->save(); + + $this->drupalGet('node/' . $node->nid); + $this->assertEqual($this->getUrl(), url('user', array('absolute' => TRUE, 'fragment' => 'fragment')), 'Redirect has been issued.'); + + + // Test sending mail. + $settings = array('to' => 'mail@example.com', 'subject' => 'subject', 'message' => 'hello.'); + rules_action('mail', $settings)->execute(); + $this->assertMail('to', 'mail@example.com', 'Mail has been sent.'); + $this->assertMail('from', variable_get('site_mail', ini_get('sendmail_from')), 'Default from address has been used'); + + rules_action('mail', $settings + array('from' => 'sender@example.com'))->execute(); + $this->assertMail('from', 'sender@example.com', 'Specified from address has been used'); + + // Test sending mail to all users of a role. First make sure there is a + // custom role and a user for it. + $user = $this->drupalCreateUser(array('administer nodes')); + $roles = $user->roles; + // Remove the authenticate role so we only use the new role created by + // drupalCreateUser(). + unset($roles[DRUPAL_AUTHENTICATED_RID]); + rules_action('mail_to_users_of_role', $settings + array('roles' => array_keys($roles)))->execute(); + $this->assertMail('to', $user->mail, 'Mail to users of a role has been sent.'); + + // Test reacting on new log entries and make sure the log entry is usable. + $rule = rules_reaction_rule(); + $rule->event('watchdog'); + $rule->action('drupal_message', array('message:select' => 'log_entry:message')); + $rule->integrityCheck()->save('test_watchdog'); + + watchdog('php', 'test %message', array('%message' => 'message')); + $msg = drupal_get_messages(); + $this->assertEqual(array_pop($msg['status']), t('test %message', array('%message' => 'message')), 'Watchdog event occurred and log entry properties can be used.'); + } + + /** + * Tests the path module integration. + */ + function testPathIntegration() { + rules_action('path_alias')->execute('foo', 'bar'); + $path = path_load('foo'); + $this->assertTrue($path['alias'] == 'bar', 'URL alias has been created.'); + + $alias_exists = rules_condition('path_alias_exists', array('alias' => 'bar'))->execute(); + $this->assertTrue($alias_exists, 'Created URL alias exists.'); + + $has_alias = rules_condition('path_has_alias', array('source' => 'foo'))->execute(); + $this->assertTrue($has_alias, 'System path has an alias.'); + + // Test node alias action. + $node = $this->drupalCreateNode(); + rules_action('node_path_alias')->execute($node, 'test'); + $path = path_load("node/$node->nid"); + $this->assertTrue($path['alias'] == 'test', 'Node URL alias has been created.'); + + // Test term alias action. + $term = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->value(); + rules_action('taxonomy_term_path_alias')->execute($term, 'term-test'); + $path = path_load("taxonomy/term/$term->tid"); + $this->assertTrue($path['alias'] == 'term-test', 'Term URL alias has been created.'); + + RulesLog::logger()->checkLog(); + } +} + +/** + * Test event dispatcher functionality. + */ +class RulesEventDispatcherTestCase extends DrupalWebTestCase { + + static function getInfo() { + return array( + 'name' => 'Rules event dispatchers', + 'description' => 'Tests event dispatcher functionality.', + 'group' => 'Rules', + ); + } + + function setUp() { + parent::setUp('rules', 'rules_test'); + } + + /** + * Tests start and stop functionality. + */ + public function testStartAndStop() { + $handler = rules_get_event_handler('rules_test_event'); + $rule = rules_reaction_rule(); + $rule->event('rules_test_event'); + + // The handler should not yet be watching. + $this->assertFalse($handler->isWatching()); + + // Once saved, the event cache rebuild should start the watcher. + $rule->save(); + RulesEventSet::rebuildEventCache(); + $this->assertTrue($handler->isWatching()); + + // Deleting should stop the watcher. + $rule->delete(); + $this->assertFalse($handler->isWatching()); + } + + /** + * Tests start and stop functionality when used with multiple events. + */ + public function testStartAndStopMultiple() { + $handler = rules_get_event_handler('rules_test_event'); + + // Initially, the task handler should not be watching. + $this->assertFalse($handler->isWatching()); + + // Set up five rules that all use the same event. + $rules = array(); + foreach (array(1, 2, 3, 4, 5) as $key) { + $rules[$key] = rules_reaction_rule(); + $rules[$key]->event('rules_test_event'); + $rules[$key]->save(); + } + + // Once saved, the event cache rebuild should start the watcher. + RulesEventSet::rebuildEventCache(); + $this->assertTrue($handler->isWatching()); + + // It should continue watching until all events are deleted. + foreach ($rules as $key => $rule) { + $rule->delete(); + $this->assertEqual($key !== 5, $handler->isWatching()); + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/tests/rules_test.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/tests/rules_test.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = "Rules Tests" +description = "Support module for the Rules tests." +package = Testing +core = 7.x +files[] = rules_test.rules.inc +files[] = rules_test.rules_defaults.inc +hidden = TRUE + +; Information added by drupal.org packaging script on 2013-09-16 +version = "7.x-2.4" +core = "7.x" +project = "rules" +datestamp = "1379354606" + diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/tests/rules_test.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/tests/rules_test.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,54 @@ + t("Logged in user"), + 'description' => t("The currently logged in user."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'access callback' => 'rules_test_no_access', + 'type' => 'user', + ); + + $properties =& $info['node']['properties']; + + $properties['reference'] = array( + 'label' => t("Referenced entity"), + 'getter callback' => 'rules_test_get_referenced_entity', + 'type' => 'entity', + ); + $properties['ref_nodes'] = array( + 'label' => t("Referenced nodes"), + 'getter callback' => 'rules_test_get_referenced_node', + 'type' => 'list', + ); +} + +/** + * Getter callback to get the referenced-entity property. + */ +function rules_test_get_referenced_entity($node) { + // For testing purposes we just return the node itself as property value. + return entity_metadata_wrapper('node', $node); +} + +/** + * Getter callback to get the referenced-node list-property. + */ +function rules_test_get_referenced_node($node) { + // For testing purposes we just return the node itself as property value. + return array($node->nid); +} + +function rules_test_no_access($op) { + return $op == 'edit' ? FALSE : TRUE; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/tests/rules_test.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/tests/rules_test.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,345 @@ + array( + 'label' => t('Test event'), + 'class' => 'RulesTestEventHandler', + )); +} + +/** + * Implements hook_rules_file_info(). + */ +function rules_test_rules_file_info() { + return array('rules_test.test'); +} + +/** + * Implements hook_rules_condition_info(). + */ +function rules_test_rules_condition_info() { + $items = array(); + $defaults = array( + 'parameter' => array( + 'node' => array('type' => 'node', 'label' => t('Content')), + ), + 'group' => t('Node'), + ); + $items['rules_condition_content_is_type'] = array( + 'label' => t('Content has type'), + 'parameter' => array( + 'node' => array('type' => 'node', 'label' => t('Content')), + 'type' => array('type' => 'list', 'label' => t('Content types')), + ), + 'help' => t('Evaluates to TRUE, if the given content has one of the selected content types.'), + ) + $defaults; + $items['rules_condition_content_is_published'] = $defaults + array( + 'label' => t('Content is published'), + ); + $items['rules_test_condition_true'] = array( + 'label' => t('Test condition returning true'), + 'group' => t('Rules test'), + ); + $items['rules_test_condition_false'] = array( + 'label' => t('Test condition returning false'), + 'group' => t('Rules test'), + ); + // A condition for testing passing entities wrapped. + $items['rules_test_condition_node_wrapped'] = array( + 'label' => t('Content is published'), + 'parameter' => array( + 'node' => array( + 'type' => 'node', + 'label' => t('Content'), + 'wrapped' => TRUE, + ), + ), + 'group' => t('Node'), + ); + return $items; +} + +/** + * Condition implementation returning true. + */ +function rules_test_condition_true($settings, $state, $element) { + if (!$element instanceof RulesCondition) { + throw new Exception('Rules element has not been passed to condition.'); + } + rules_log('condition true called'); + return TRUE; +} + +/** + * Condition implementation returning false. + */ +function rules_test_condition_false() { + rules_log('condition false called'); + return FALSE; +} + +/** + * Condition implementation receiving the node wrapped. + */ +function rules_test_condition_node_wrapped($wrapper) { + return $wrapper instanceof EntityMetadataWrapper; +} + +/** + * Implements hook_rules_action_info(). + */ +function rules_test_rules_action_info() { + $items['rules_test_action'] = array( + 'label' => t('Test action'), + 'group' => t('Rules test'), + ); + return $items + array( + 'rules_node_publish_action' => array( + 'label' => t('Publish content, but do not save'), + 'parameter' => array( + 'node' => array('type' => 'node', 'label' => t('Content')), + ), + 'callbacks' => array( + 'help' => 'rules_test_custom_help', + ), + 'base' => 'node_publish_action', + ), + 'rules_node_publish_action_save' => array( + 'label' => t('Publish content'), + 'parameter' => array( + 'node' => array( + 'type' => 'node', + 'label' => t('Content'), + 'save' => TRUE, + ), + ), + 'base' => 'node_publish_action', + ), + 'rules_node_make_sticky_action' => array( + 'label' => t('Make content sticky'), + 'parameter' => array( + 'node' => array( + 'type' => 'node', + 'label' => t('Content'), + 'save' => TRUE, + ), + ), + 'base' => 'node_make_sticky_action', + ), + // The same action again requiring a 'page' node. + 'rules_node_page_make_sticky_action' => array( + 'label' => t('Mage page content sticky'), + 'parameter' => array( + 'node' => array( + 'type' => 'node', + 'label' => t('Content'), + 'save' => TRUE, + 'bundles' => array('page'), + ), + ), + 'base' => 'node_make_sticky_action', + ), + 'rules_action_test_reference' => array( + 'label' => t('Change argument passed by reference'), + 'parameter' => array( + // For references working right, we need a data type with a wrapper. + 'arg' => array('type' => 'test'), + ), + ), + 'rules_action_load_node' => array( + 'label' => t('Fetch content by id'), + 'parameter' => array( + 'nid' => array('type' => 'integer', 'label' => t('Content ID')), + 'vid' => array( + 'type' => 'integer', + 'label' => t('Content Revision ID'), + 'description' => t("If you want to fetch a specific revision, specify it's revision id. Else leave it empty to fetch the currently active revision."), + 'optional' => TRUE, + ), + ), + 'provides' => array( + 'node_loaded' => array( + 'type' => 'node', + 'label' => t('Loaded content'), + 'label callback' => 'rules_action_load_node_variable_label', + ), + ), + 'group' => t('Node'), + 'access callback' => 'rules_node_integration_access', + ), + 'rules_action_delete_node' => array( + 'label' => t('Delete content'), + 'parameter' => array( + 'node' => array('type' => 'node', 'label' => t('Content')), + ), + 'group' => t('Node'), + 'access callback' => 'rules_node_integration_access', + ), + // An action for testing named parameters. + 'rules_action_node_set_title' => array( + 'label' => t('Content'), + 'parameter' => array( + 'node' => array('type' => 'node', 'label' => t('Content')), + 'title' => array('type' => 'text', 'label' => t('Text')), + ), + 'named parameter' => TRUE, + 'group' => t('Node'), + 'access callback' => 'rules_node_integration_access', + ), + // Tests automatic saving with a non-entity data type. + 'test_type_save' => array( + 'base' => 'rules_test_type_save', + 'label' => t('Save test type'), + 'parameter' => array( + 'node' => array('type' => 'rules_test_type', 'label' => t('Test content'), 'save' => TRUE), + ), + 'group' => t('Node'), + ), + ); +} + +/** + * Test action doing nothing exception logging it has been called. + */ +function rules_test_action() { + rules_log('action called'); +} + +/** + * Action for testing writing class-based actions. + */ +class RulesTestClassAction extends RulesActionHandlerBase { + + /** + * Defines the action. + */ + public static function getInfo() { + return array( + 'name' => 'rules_test_class_action', + 'label' => t('Test class based action'), + 'group' => t('Node'), + 'parameter' => array( + 'node' => array( + 'type' => 'node', + 'label' => t('Node'), + ), + ), + ); + } + + /** + * Executes the action. + */ + public function execute($node) { + rules_log('Action called with node ' . $node->nid); + } +} + +/** + * Implements hook_rules_data_info(). + */ +function rules_test_rules_data_info() { + return array( + 'rules_test_type' => array( + 'label' => t('test type'), + 'wrap' => TRUE, + 'wrapper class' => 'RulesTestTypeWrapper', + ), + ); +} + +/** + * Implements hook_rules_data_info_alter(). + */ +function rules_test_rules_data_info_alter(&$data_info) { + $data_info['log_entry']['creation callback'] = 'rules_action_data_create_array'; +} + +/** + * The custom wrapper class for the rules test type. + * + * For testing we internally just make use of nodes. + */ +class RulesTestTypeWrapper extends RulesIdentifiableDataWrapper implements RulesDataWrapperSavableInterface { + + protected function extractIdentifier($data) { + return $data->nid; + } + + protected function load($id) { + return node_load($id); + } + + public function save() { + node_save($this->value()); + } +} + +/** + * Implements hook_rules_plugin_info(). + */ +function rules_test_rules_plugin_info() { + return array( + 'rules test container' => array( + 'label' => t('Test container'), + 'class' => 'RulesTestContainer', + 'embeddable' => 'RulesActionContainer', + ), + ); +} + +/** + * Test container plugin. + */ +class RulesTestContainer extends RulesContainerPlugin { + protected $itemName = 'rules test container'; + + /** + * Evaluate the element on a given rules evaluation state. + */ + public function evaluate(RulesState $state) { + // Do nothing. + } +} + +/** + * Test event handler class. + */ +class RulesTestEventHandler extends RulesEventDefaultHandler implements RulesEventDispatcherInterface { + + /** + * Name of the variable in which to store the state of the event handler. + * + * @var string + */ + protected $variableName = 'rules_test_event_handler_watch'; + + /** + * Implements RulesEventDispatcherInterface::startWatching(). + */ + public function startWatching() { + variable_set($this->variableName, TRUE); + } + + /** + * Implements RulesEventDispatcherInterface::stopWatching(). + */ + public function stopWatching() { + variable_set($this->variableName, FALSE); + } + + /** + * Implements RulesEventDispatcherInterface::isWatching(). + */ + public function isWatching() { + return (bool) variable_get($this->variableName); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/tests/rules_test.rules_defaults.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/tests/rules_test.rules_defaults.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,121 @@ +label = 'example default rule'; + $rule->active = FALSE; + $rule->event('node_update') + ->condition(rules_condition('data_is', array('data:select' => 'node:status', 'value' => TRUE))->negate()) + ->condition('data_is', array('data:select' => 'node:type', 'value' => 'page')) + ->action('drupal_message', array('message' => 'A node has been updated.')); + + $configs['rules_test_default_1'] = $rule; + + $action_set = rules_action_set(array('node' => array('type' => 'node', 'label' => 'Content'))); + $action_set->action('node_publish'); + $configs['rules_test_action_set'] = $action_set; + + // Test providing a rule using an export. + $configs['rules_export_test'] = rules_import(_rules_export_get_test_export()); + + // An action set used to test merging term parents. + $configs['rules_retrieve_term_parents'] = rules_import('{ "rules_retrieve_term_parents" : { + "LABEL" : "Retrieve term parents", + "PLUGIN" : "action set", + "REQUIRES" : [ "rules" ], + "USES VARIABLES" : { + "terms" : { "label" : "Terms", "type" : "list\u003ctaxonomy_term\u003e" }, + "term_parents" : { + "label" : "Term parents", + "type" : "list\u003ctaxonomy_term\u003e", + "parameter" : false + } + }, + "ACTION SET" : [ + { "LOOP" : { + "USING" : { "list" : [ "terms" ] }, + "ITEM" : { "current_term" : "Current term" }, + "DO" : [ + { "LOOP" : { + "USING" : { "list" : [ "current-term:parent" ] }, + "ITEM" : { "current_parent" : "Current parent" }, + "DO" : [ + { "list_add" : { + "list" : [ "term-parents" ], + "item" : [ "current-parent" ], + "unique" : 1 + } + } + ] + } + } + ] + } + } + ], + "PROVIDES VARIABLES" : [ "term_parents" ] + } +}'); + + return $configs; +} + +/** + * Defines the export of rule for testing import/export. + */ +function _rules_export_get_test_export() { + return '{ "rules_export_test" : { + "LABEL" : "Test import rule2", + "PLUGIN" : "reaction rule", + "WEIGHT" : "-1", + "ACTIVE" : false, + "TAGS" : [ "bar", "baz", "foo" ], + "REQUIRES" : [ "rules", "comment" ], + "ON" : { "comment_insert" : [] }, + "IF" : [ + { "OR" : [ + { "NOT node_is_sticky" : { "node" : [ "comment:node" ] } }, + { "node_is_of_type" : { + "node" : [ "comment:node" ], + "type" : { "value" : { "page" : "page" } } + } + }, + { "NOT AND" : [ { "OR" : [] } ] } + ] + } + ], + "DO" : [ + { "data_set" : { + "data" : [ "comment:node:created" ], + "value" : { "select" : "site:current-date", "date_offset" : { "value" : -604800 } } + } + }, + { "node_make_sticky" : { "node" : [ "comment:node" ] } }, + { "variable_add" : { + "USING" : { "type" : "token", "value" : "error" }, + "PROVIDE" : { "variable_added" : { "level" : "Error level" } } + } + }, + { "drupal_message" : { + "message" : "fein, [comment:node:title] has been made sticky!", + "type" : [ "level" ] + } + }, + { "LOOP" : { + "USING" : { "list" : [ "site:current-user:roles" ] }, + "ITEM" : { "current_role" : "Current role" }, + "DO" : [ { "drupal_message" : { "message" : [ "current-role" ] } } ] + } + } + ] + } +}'; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/tests/rules_test.test.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/tests/rules_test.test.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,75 @@ + $data); +} + +/** + * Condition: Check for selected content types + */ +function rules_condition_content_is_type($node, $type) { + return in_array($node->type, $type); +} + +/** + * Condition: Check if the node is published + */ +function rules_condition_content_is_published($node, $settings) { + return $node->status == 1; +} + +/** + * Loads a node + */ +function rules_action_load_node($nid, $vid = NULL) { + return array('node_loaded' => node_load($nid, $vid ? $vid : NULL)); +} + +/** + * Action "Delete a node". + */ +function rules_action_delete_node($node) { + node_delete($node->nid); +} + +/** + * An action making use of named parameters. + */ +function rules_action_node_set_title($arguments) { + // Make sure the data is unwrapped. + if ($arguments['node'] instanceof EntityMetadataWrapper) { + throw new Exception('Argument has not been correctly unwrapped.'); + } + $arguments['node']->title = $arguments['title']; + return $arguments; +} + +/** + * Action: Test saving - nothing to do here. + */ +function rules_test_type_save($node) { + +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/ui/rules.autocomplete.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/ui/rules.autocomplete.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,195 @@ + +// Registers the rules namespace. +Drupal.rules = Drupal.rules || {}; + +(function($) { + Drupal.behaviors.rules_autocomplete = { + attach: function(context) { + var autocomplete_settings = Drupal.settings.rules_autocomplete; + + $('input.rules-autocomplete').once(function() { + var input = this; + new Drupal.rules.autocomplete(input, autocomplete_settings[$(input).attr('id')]); + }); + } + }; + + /** + * Rules autocomplete object. + */ + Drupal.rules.autocomplete = function(input, settings) { + this.id = settings.inputId; + this.uri = settings.source; + this.jqObject = $('#' + this.id); + this.cache = new Array(); + this.jqObject.addClass('ui-corner-left'); + + this.opendByFocus = false; + this.focusOpens = true; + this.groupSelected = false; + + this.button = $(' '); + this.button.attr( { + 'tabIndex': -1, + 'title': 'Show all items' + }); + this.button.insertAfter(this.jqObject); + + this.button.button( { + icons: { + primary: 'ui-icon-triangle-1-s' + }, + text: false + }); + + // Don't round the left corners. + this.button.removeClass('ui-corner-all'); + this.button.addClass('ui-corner-right ui-button-icon rules-autocomplete-button'); + + this.jqObject.autocomplete(); + this.jqObject.autocomplete("option", "minLength", 0); + // Add a custom class, so we can style the autocomplete box without + // interfering with other jquery autocomplete widgets. + this.jqObject.autocomplete("widget").addClass('rules-autocomplete'); + + // Save the current rules_autocomplete object, so it can be used in + // handlers. + var instance = this; + + // Event handlers + this.jqObject.focus(function() { + if (instance.focusOpens) { + instance.toggle(); + instance.opendByFocus = true; + } + else { + instance.focusOpens = true; + } + }); + + // Needed when the window is closed but the textfield has the focus. + this.jqObject.click(function() { + // Since the focus event happens earlier then the focus event, we need to + // check here, if the window should be opened. + if (!instance.opendByFocus) { + instance.toggle(); + } + else { + instance.opendByFocus = false; + } + }); + + this.jqObject.bind("autocompleteselect", function(event, ui) { + // If a group was selected then set the groupSelected to true for the + // overriden close function from jquery autocomplete. + if (ui.item.value.substring(ui.item.value.length - 1, ui.item.value.length) == ":") { + instance.groupSelected = true; + } + instance.focusOpens = false; + instance.opendByFocus = false; + }); + + this.jqObject.autocomplete("option", "source", function(request, response) { + if (request.term in instance.cache) { + response(instance.cache[request.term]); + return; + } + $.ajax( { + url: instance.uri + '/' + request.term, + dataType: "json", + success: function(data) { + instance.success(data, request, response); + } + }); + }); + + // Since jquery autocomplete by default strips html text by using .text() + // we need our own _renderItem function to display html content. + this.jqObject.data("autocomplete")._renderItem = function(ul, item) { + return $("
  • ").data("item.autocomplete", item).append("" + item.label + "").appendTo(ul); + }; + + // Override close function + this.jqObject.data("autocomplete").close = function (event) { + var value = this.element.val(); + // If the selector is not a group, then trigger the close event an and + // hide the menu. + if (value === undefined || instance.groupSelected === false) { + clearTimeout(this.closing); + if (this.menu.element.is(":visible")) { + this._trigger("close", event); + this.menu.element.hide(); + this.menu.deactivate(); + } + } + else { + // Else keep all open and trigger a search for the group. + instance.jqObject.autocomplete("search", instance.jqObject.val()); + // After the suggestion box was opened again, we want to be able to + // close it. + instance.groupSelected = false; + } + }; + + this.button.click(function() { + instance.toggle(); + }); + + }; + + /** + * Success function for Rules autocomplete object. + */ + Drupal.rules.autocomplete.prototype.success = function(data, request, response) { + var list = new Array(); + jQuery.each(data, function(index, value) { + list.push( { + label: value, + value: index + }); + }); + + this.cache[request.term] = list; + response(list); + }; + + /** + * Open the autocomplete window. + * @param searchFor The term for will be searched for. If undefined then the + * entered input text will be used. + */ + Drupal.rules.autocomplete.prototype.open = function(searchFor) { + // If searchFor is undefined, we want to search for the passed argument. + this.jqObject.autocomplete("search", ((searchFor === undefined) ? this.jqObject.val() : searchFor)); + this.button.addClass("ui-state-focus"); + }; + + /** + * Close the autocomplete window. + */ + Drupal.rules.autocomplete.prototype.close = function() { + this.jqObject.autocomplete("close"); + this.button.removeClass("ui-state-focus"); + }; + + /** + * Toogle the autcomplete window. + */ + Drupal.rules.autocomplete.prototype.toggle = function() { + if (this.jqObject.autocomplete("widget").is(":visible")) { + this.close(); + this.focusOpens = true; + } + else { + var groups = this.jqObject.val().split(":"); + var selector = ""; + for (var i=0; i 'rules_get_title', + 'title arguments' => array('Editing !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_form_edit_rules_config', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'type' => MENU_VISIBLE_IN_BREADCRUMB, + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/add/%rules_element'] = array( + // Adding another part to the path would hit the menu path-part-limit + // for base paths like admin/config/workflow/rules. Therefor we have to + // use this fugly way for setting the title. + 'title callback' => 'rules_menu_add_element_title', + // Wrap the integer in an array, so it is passed as is. + 'title arguments' => array(array($base_count + 4)), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_add_element', $base_count + 1, $base_count + 4, $base_count + 3, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'load arguments' => array($base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/add/event'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Adding event to !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_add_event_page', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'load arguments' => array($base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/delete/event'] = array( + //@todo: improve title. + 'title' => 'Remove event', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_remove_event', $base_count + 1, $base_count + 4, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'description' => 'Remove an event from a reaction rule.', + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/edit/%rules_element'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Editing !plugin "!label"', $base_count + 3), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_edit_element', $base_count + 1, $base_count + 3, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'load arguments' => array($base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/autocomplete'] = array( + 'page callback' => 'rules_ui_form_data_selection_auto_completion', + 'page arguments' => array($base_count + 3, $base_count + 4, $base_count + 5), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'type' => MENU_CALLBACK, + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/delete/%rules_element'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Editing !plugin "!label"', $base_count + 3), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_delete_element', $base_count + 1, $base_count + 3, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'load arguments' => array($base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/%'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_form_rules_config_confirm_op', $base_count + 1, $base_count + 2, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/clone'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Cloning !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_form_clone_rules_config', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/export'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Export of !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_form_export_rules_config', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('view', $base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + $items[$base_path . '/manage/%rules_config/execute'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Executing !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_ui_form_execute_rules_config', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'file' => 'ui/ui.forms.inc', + 'file path' => drupal_get_path('module', 'rules'), + ); + drupal_alter('rules_ui_menu', $items, $base_path, $base_count); + + if (module_exists('rules_scheduler')) { + $items[$base_path . '/manage/%rules_config/schedule'] = array( + 'title callback' => 'rules_get_title', + 'title arguments' => array('Schedule !plugin "!label"', $base_count + 1), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('rules_scheduler_schedule_form', $base_count + 1, $base_path), + 'access callback' => 'rules_config_access', + 'access arguments' => array('update', $base_count + 1), + 'file' => 'rules_scheduler.admin.inc', + 'file path' => drupal_get_path('module', 'rules_scheduler'), + ); + } + return $items; + } + + /** + * Generates the render array for a overview configuration table for arbitrary + * rule configs that match the given conditions. + * + * Note: The generated overview table contains multiple links for editing the + * rule configurations. For the links to properly work use + * RulesUIController::config_menu($base_path) to generate appropriate menu + * items for the path at which the overview table is displayed. + * + * @param $conditions + * An array of conditions as needed by rules_config_load_multiple(). + * @param $options + * An array with optional options. Known keys are: + * - 'hide status op': If set to TRUE, enable/disable links are not added. + * Defaults to FALSE. + * - 'show plugin': If set to FALSE, the plugin is not shown. Defaults to + * TRUE. + * - 'show events': If set to TRUE, the event column is shown. Defaults to + * TRUE if only reaction rules are listed. + * - 'show execution op': If set to TRUE an operation for execution a + * component is shown for components, as well as a link to schedule a + * component if the rules scheduler module is enabled. + * - 'base path': Optionally, a different base path to use instead of the + * currently set RulesPluginUI::$basePath. If no base path has been set + * yet, the current path is used by default. + * + * @return Array + * A renderable array. + */ + public function overviewTable($conditions = array(), $options = array()) { + $options += array( + 'hide status op' => FALSE, + 'show plugin' => TRUE, + 'show events' => isset($conditions['plugin']) && $conditions['plugin'] == 'reaction rule', + 'show execution op' => !(isset($conditions['plugin']) && $conditions['plugin'] == 'reaction rule'), + ); + // By default show only configurations owned by rules. + $conditions += array( + 'owner' => 'rules', + ); + if (!empty($options['base path'])) { + RulesPluginUI::$basePath = $options['base path']; + } + else if (!isset(RulesPluginUI::$basePath)) { + // Default to the current path, only if no path has been set yet. + RulesPluginUI::$basePath = current_path(); + } + + $entities = entity_load('rules_config', FALSE, $conditions); + ksort($entities); + + // Prepare some variables used by overviewTableRow(). + $this->event_info = rules_fetch_data('event_info'); + $this->cache = rules_get_cache(); + + $rows = array(); + foreach ($entities as $id => $entity) { + if (user_access('bypass rules access') || $entity->access()) { + $rows[] = $this->overviewTableRow($conditions, $id, $entity, $options); + } + } + // Assemble the right table header. + $header = array(t('Name'), t('Event'), t('Plugin'), t('Status'), array('data' => t('Operations'))); + if (!$options['show events']) { + // Remove the event heading as there is no such column. + unset($header[1]); + } + if (!$options['show plugin']) { + unset($header[2]); + } + // Fix the header operation column colspan. + $num_cols = isset($rows[0]) ? count($rows[0]) : 0; + if (($addition = $num_cols - count($header)) > 0) { + $header[4]['colspan'] = $addition + 1; + } + + $table = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('None.'), + ); + $table['#attributes']['class'][] = 'rules-overview-table'; + $table['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.css'; + + // TODO: hide configs where access() is FALSE. + return $table; + } + + /** + * Generates the row for a single rules config. + * + * @param $additional_cols + * Additional columns to be added after the entity label column. + */ + protected function overviewTableRow($conditions, $name, $config, $options) { + // Build content includes the label, as well as a short overview including + // the machine name. + $row[] = array('data' => $config->buildContent()); + + // Add events if the configs are assigned to events. + if ($options['show events']) { + $events = array(); + if ($config instanceof RulesTriggerableInterface) { + foreach ($config->events() as $event_name) { + $event_handler = rules_get_event_handler($event_name, $config->getEventSettings($event_name)); + $events[] = $event_handler->summary(); + } + } + $row[] = implode(", ", $events); + } + if ($options['show plugin']) { + $plugin = $config->plugin(); + $row[] = isset($this->cache['plugin_info'][$plugin]['label']) ? $this->cache['plugin_info'][$plugin]['label'] : $plugin; + } + + $row[] = array('data' => array( + '#theme' => 'entity_status', + '#status' => $config->status, + )); + + // Add operations depending on the options and the exportable status. + if (!$config->hasStatus(ENTITY_FIXED)) { + $row[] = l(t('edit'), RulesPluginUI::path($name), array('attributes' => array('class' => array('edit', 'action')))); + if (module_exists('rules_i18n')) { + $row[] = l(t('translate'), RulesPluginUI::path($name, 'translate'), array('attributes' => array('class' => array('translate', 'action')))); + } + } + else { + $row[] = ''; + if (module_exists('rules_i18n')) { + $row[] = ''; + } + } + + if (!$options['hide status op']) { + // Add either an enable or disable link. + $text = $config->active ? t('disable') : t('enable'); + $active_class = $config->active ? 'disable' : 'enable'; + $link_path = RulesPluginUI::path($name, $active_class); + $row[] = $config->hasStatus(ENTITY_FIXED) ? '' : l($text, $link_path, array('attributes' => array('class' => array($active_class, 'action')), 'query' => drupal_get_destination())); + } + $row[] = l(t('clone'), RulesPluginUI::path($name, 'clone'), array('attributes' => array('class' => array('clone', 'action')))); + + // Add execute link for for components. + if ($options['show execution op']) { + $row[] = ($config instanceof RulesTriggerableInterface) ? '' : l(t('execute'), RulesPluginUI::path($name, 'execute'), array('attributes' => array('class' => array('execute', 'action')), 'query' => drupal_get_destination())); + if (module_exists('rules_scheduler')) { + // Add schedule link for action components only. + $row[] = $config instanceof RulesActionInterface ? l(t('schedule'), RulesPluginUI::path($name, 'schedule'), array('attributes' => array('class' => array('schedule', 'action')), 'query' => drupal_get_destination())) : ''; + } + } + + if (!$config->hasStatus(ENTITY_IN_CODE) && !$config->hasStatus(ENTITY_FIXED)) { + $row[] = l(t('delete'), RulesPluginUI::path($name, 'delete'), array('attributes' => array('class' => array('delete', 'action')), 'query' => drupal_get_destination())); + } + elseif ($config->hasStatus(ENTITY_OVERRIDDEN) && !$config->hasStatus(ENTITY_FIXED)) { + $row[] = l(t('revert'), RulesPluginUI::path($name, 'revert'), array('attributes' => array('class' => array('revert', 'action')), 'query' => drupal_get_destination())); + } + else { + $row[] = ''; + } + $row[] = l(t('export'), RulesPluginUI::path($name, 'export'), array('attributes' => array('class' => array('export', 'action')))); + return $row; + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/ui/ui.core.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/ui/ui.core.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1275 @@ +save() afterwards. + * + * @param $form + * @param $form_state + */ + public function form_submit($form, &$form_state); + + /** + * Returns a structured array for rendering this element in overviews. + */ + public function buildContent(); + + /** + * Returns the help text for editing this plugin. + */ + public function help(); + + /** + * Returns ui operations for this element. + */ + public function operations(); + +} + +/** + * Helper object for mapping elements to ids. + */ +class RulesElementMap { + + /** + * @var RulesPlugin + */ + protected $configuration; + protected $index = array(); + protected $counter = 0; + + public function __construct(RulesPlugin $config) { + $this->configuration = $config->root(); + } + + /** + * Makes sure each element has an assigned id. + */ + public function index() { + foreach ($this->getUnIndexedElements($this->configuration) as $element) { + $id = &$element->property('elementId'); + $id = ++$this->counter; + $this->index[$id] = $element; + } + } + + protected function getUnIndexedElements($element, &$unindexed = array()) { + // Remember unindexed elements. + $id = $element->property('elementId'); + if (!isset($id)) { + $unindexed[] = $element; + } + else { + // Make sure $this->counter refers to the highest id. + if ($id > $this->counter) { + $this->counter = $id; + } + $this->index[$id] = $element; + } + // Recurse down the tree. + if ($element instanceof RulesContainerPlugin) { + foreach ($element as $child) { + $this->getUnIndexedElements($child, $unindexed); + } + } + return $unindexed; + } + + /** + * Looks up the element with the given id. + */ + public function lookup($id) { + if (!$this->index) { + $this->index(); + } + return isset($this->index[$id]) ? $this->index[$id] : FALSE; + } +} + +/** + * Faces UI extender for all kind of Rules plugins. Provides various useful + * methods for any rules UI. + */ +class RulesPluginUI extends FacesExtender implements RulesPluginUIInterface { + + /** + * @var RulesPlugin + */ + protected $element; + + /** + * The base path determines where a Rules overview UI lives. All forms that + * want to display Rules (overview) forms need to set this variable. This is + * necessary in order to get correct operation links, paths, redirects, bread + * crumbs etc. for the form() and overviewTable() methods. + * + * @see RulesUIController + * @see rules_admin_reaction_overview() + * @see rules_admin_components_overview() + */ + public static $basePath = NULL; + + /** + * Provide $this->element to make the code more meaningful. + */ + public function __construct(FacesExtendable $object) { + parent::__construct($object); + $this->element = $object; + } + + /** + * Returns the form values for the given form, possible being only a part of the whole form. + * + * In case the form is embedded somewhere, this function figures out the + * location of its form values and returns them for further use. + * + * @param $form + * A form array, or an array of form elements to get the value for. + * @param $form_state + * The form state as usual. + */ + public static function &getFormStateValues($form, &$form_state) { + $values = NULL; + if (isset($form_state['values'])) { + // Assume the top level if parents are not yet set. + $form += array('#parents' => array()); + $values = &$form_state['values']; + foreach ($form['#parents'] as $parent) { + $values = &$values[$parent]; + } + } + return $values; + } + + /** + * Implements RulesPluginUIInterface. + * Generates the element edit form. + * + * Note: Make sure that you set RulesPluginUI::$basePath before using this + * method, otherwise paths, links, redirects etc. won't be correct. + */ + public function form(&$form, &$form_state, $options = array()) { + self::formDefaults($form, $form_state); + $form_state += array('rules_element' => $this->element); + + // Add the help to the top of the form. + $help = $this->element->help(); + $form['help'] = is_array($help) ? $help : array('#markup' => $help); + + // We use $form_state['element_settings'] to store the settings of both + // parameter modes. That way one can switch between the parameter modes + // without losing the settings of those. + $form_state += array('element_settings' => $this->element->settings); + $settings = $this->element->settings + $form_state['element_settings']; + + $form['parameter'] = array( + '#tree' => TRUE, + ); + + foreach ($this->element->pluginParameterInfo() as $name => $parameter) { + if ($parameter['type'] == 'hidden') { + continue; + } + + $form['parameter'][$name] = array( + '#type' => 'fieldset', + '#title' => check_plain($parameter['label']), + '#description' => filter_xss(isset($parameter['description']) ? $parameter['description'] : ''), + ); + + // Init the parameter input mode. + $form_state['parameter_mode'][$name] = !isset($form_state['parameter_mode'][$name]) ? NULL : $form_state['parameter_mode'][$name]; + $form['parameter'][$name] += $this->getParameterForm($name, $parameter, $settings, $form_state['parameter_mode'][$name]); + } + + // Provide a form for editing the label and name of provided variables. + $settings = $this->element->settings; + foreach ($this->element->pluginProvidesVariables() as $var_name => $var_info) { + $form['provides'][$var_name] = array( + '#type' => 'fieldset', + '#title' => check_plain($var_info['label']), + '#description' => filter_xss(isset($var_info['description']) ? $var_info['description'] : ''), + ); + $form['provides'][$var_name]['label'] = array( + '#type' => 'textfield', + '#title' => t('Variable label'), + '#default_value' => isset($settings[$var_name . ':label']) ? $settings[$var_name . ':label'] : $var_info['label'], + '#required' => TRUE, + ); + $form['provides'][$var_name]['var'] = array( + '#type' => 'textfield', + '#title' => t('Variable name'), + '#default_value' => isset($settings[$var_name . ':var']) ? $settings[$var_name . ':var'] : $var_name, + '#description' => t('The variable name must contain only lowercase letters, numbers, and underscores and must be unique in the current scope.'), + '#element_validate' => array('rules_ui_element_machine_name_validate'), + '#required' => TRUE, + ); + } + if (!empty($form['provides'])) { + $help = '
    ' . t('Adjust the names and labels of provided variables, but note that renaming of already utilizied variables invalidates the existing uses.') . '
    '; + $form['provides'] += array( + '#tree' => TRUE, + '#prefix' => '

    ' . t('Provided variables') . '

    ' . $help, + ); + } + + // Add settings form, if specified. + if (!empty($options['show settings'])) { + $this->settingsForm($form, $form_state); + } + // Add submit button, if specified. + if (!empty($options['button'])) { + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 10, + ); + } + } + + /** + * Actually generates the parameter form for the given data type. + */ + protected function getParameterForm($name, $info, $settings, &$mode) { + $class = $this->getDataTypeClass($info['type'], $info); + $supports_input_mode = in_array('RulesDataDirectInputFormInterface', class_implements($class)); + + // Init the mode. + if (!isset($mode)) { + if (isset($settings[$name . ':select'])) { + $mode = 'selector'; + } + elseif (isset($settings[$name]) && $supports_input_mode) { + $mode = 'input'; + } + elseif (isset($info['restriction'])) { + $mode = $info['restriction']; + } + else { + // Allow the parameter to define the 'default mode' and fallback to the + // data type default. + $mode = !empty($info['default mode']) ? $info['default mode'] : call_user_func(array($class, 'getDefaultMode')); + } + } + + // For translatable parameters, pre-populate an internal translation source + // key so data type forms or input evaluators (i18n) may produce suiting + // help. + if (drupal_multilingual() && !empty($info['translatable'])) { + $parameter = $this->element->pluginParameterInfo(); + $info['custom translation language'] = !empty($parameter['language']); + } + + // Add the parameter form. + if ($mode == 'input' && $supports_input_mode) { + $form['settings'] = call_user_func(array($class, 'inputForm'), $name, $info, $settings, $this->element); + } + else { + $form['settings'] = call_user_func(array($class, 'selectionForm'), $name, $info, $settings, $this->element); + } + + // Add a link for switching the input mode when JS is enabled and a button + // to switch it without javascript, in case switching is possible. + if ($supports_input_mode && empty($info['restriction'])) { + $value = $mode == 'selector' ? t('Switch to the direct input mode') : t('Switch to data selection'); + + $form['switch_button'] = array( + '#type' => 'submit', + '#name' => 'param_' . $name, + '#attributes' => array('class' => array('rules-switch-button')), + '#parameter' => $name, + '#value' => $value, + '#submit' => array('rules_ui_parameter_replace_submit'), + '#ajax' => rules_ui_form_default_ajax('none'), + // Do not validate! + '#limit_validation_errors' => array(), + ); + } + return $form; + } + + /** + * Implements RulesPluginUIInterface. + */ + public function form_validate($form, &$form_state) { + $this->form_extract_values($form, $form_state); + $form_values = RulesPluginUI::getFormStateValues($form, $form_state); + + if (isset($form_values['provides'])) { + $vars = $this->element->availableVariables(); + foreach ($form_values['provides'] as $name => $values) { + if (isset($vars[$values['var']])) { + form_error($form['provides'][$name]['var'], t('The variable name %name is already taken.', array('%name' => $values['var']))); + } + } + } + // Settings have been updated, so process them now. + $this->element->processSettings(TRUE); + + // Make sure the current user really has access to configure this element + // as well as the used input evaluators and data processors. + if (!user_access('bypass rules access') && !$this->element->root()->access()) { + form_set_error('', t('Access violation! You have insufficient access permissions to edit this configuration.')); + } + if (!empty($form['settings'])) { + $this->settingsFormValidate($form, $form_state); + } + } + + /** + * Applies the values of the form to the element. + */ + public function form_extract_values($form, &$form_state) { + $this->element->settings = array(); + $form_values = RulesPluginUI::getFormStateValues($form, $form_state); + if (isset($form_values['parameter'])) { + foreach ($form_values['parameter'] as $name => $values) { + $this->element->settings += $values['settings']; + } + } + if (isset($form_values['provides'])) { + foreach ($form_values['provides'] as $name => $values) { + $this->element->settings[$name . ':label'] = $values['label']; + $this->element->settings[$name . ':var'] = $values['var']; + } + } + if (!empty($form['settings'])) { + $this->settingsFormExtractValues($form, $form_state); + } + } + + /** + * Implements RulesPluginUIInterface. + */ + public function form_submit($form, &$form_state) { + if (!empty($form['settings'])) { + $this->settingsFormSubmit($form, $form_state); + } + $this->element->save(); + } + + /** + * Adds the configuration settings form (label, tags, description, ..). + */ + public function settingsForm(&$form, &$form_state) { + $form_values = RulesPluginUI::getFormStateValues($form, $form_state); + // Add the settings in a separate fieldset below. + $form['settings'] = array( + '#type' => 'fieldset', + '#title' => t('Settings'), + '#collapsible' => TRUE, + '#collapsed' => empty($form_values['settings']['vars']['more']), + '#weight' => 5, + '#tree' => TRUE, + ); + $form['settings']['label'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#default_value' => $this->element->label(), + '#required' => TRUE, + '#weight' => -5, + ); + // @todo: For Drupal 8 use "owner" for generating machine names and + // module only for the modules providing default configurations. + if (!empty($this->element->module) && !empty($this->element->name) && $this->element->module == 'rules' && strpos($this->element->name, 'rules_') === 0) { + // Remove the Rules module prefix from the machine name. + $machine_name = substr($this->element->name, strlen($this->element->module) + 1); + } + else { + $machine_name = $this->element->name; + } + $form['settings']['name'] = array( + '#type' => 'machine_name', + '#default_value' => isset($machine_name) ? $machine_name : '', + // The string 'rules_' is pre-pended to machine names, so the + // maxlength must be less than the field length of 64 characters. + '#maxlength' => 58, + '#disabled' => entity_has_status('rules_config', $this->element, ENTITY_IN_CODE) && !(isset($form_state['op']) && $form_state['op'] == 'clone'), + '#machine_name' => array( + 'exists' => 'rules_config_load', + 'source' => array('settings', 'label'), + ), + '#required' => TRUE, + '#description' => t('The machine-readable name of this configuration is used by rules internally to identify the configuration. This name must contain only lowercase letters, numbers, and underscores and must be unique.'), + ); + $form['settings']['tags'] = array( + '#type' => 'textfield', + '#title' => t('Tags'), + '#default_value' => isset($this->element->tags) ? drupal_implode_tags($this->element->tags) : '', + '#autocomplete_path' => 'admin/config/workflow/rules/autocomplete_tags', + '#description' => t('Tags associated with this configuration, used for filtering in the admin interface. Separate multiple tags with commas.'), + ); + + // Show a form for editing variables for components. + if (($plugin_info = $this->element->pluginInfo()) && !empty($plugin_info['component'])) { + if ($this->element->hasStatus(ENTITY_IN_CODE)) { + $description = t('The variables used by the component. They can not be edited for configurations that are provided in code.'); + } + else { + $description = t('Variables are normally input parameters for the component – data that should be available for the component to act on. Additionaly, action components may provide variables back to the caller. Each variable must have a specified data type, a label and a unique machine readable name containing only lowercase alphanumeric characters and underscores. See the online documentation for more information about variables.', + array('@url' => rules_external_help('variables')) + ); + } + $form['settings']['vars'] = array( + '#prefix' => '
    ', + '#suffix' => '
    ', + '#tree' => TRUE, + '#element_validate' => array('rules_ui_element_variable_form_validate'), + '#theme' => 'rules_ui_variable_form', + '#title' => t('Variables'), + '#description' => $description, + // Variables can not be edited on configurations in code. + '#disabled' => $this->element->hasStatus(ENTITY_IN_CODE), + ); + + $weight = 0; + $provides = $this->element->providesVariables(); + foreach ($this->element->componentVariables() as $name => $var_info) { + $form['settings']['vars']['items'][$name] = RulesPluginUI::getVariableForm($name, $var_info, isset($provides[$name])); + $form['settings']['vars']['items'][$name]['weight']['#default_value'] = $weight++; + } + // Always add three empty forms. + for ($i = 0; $i < 3; $i++) { + $form['settings']['vars']['items'][$i] = RulesPluginUI::getVariableForm(); + $form['settings']['vars']['items'][$i]['weight']['#default_value'] = $weight++; + } + $form['settings']['vars']['more'] = array( + '#type' => 'submit', + '#value' => t('Add more'), + // Enable AJAX once #756762 is fixed. + // '#ajax' => rules_ui_form_default_ajax('none'), + '#limit_validation_errors' => array(array('vars')), + '#submit' => array('rules_form_submit_rebuild'), + ); + if (!empty($this->element->id)) { + // Display a setting to manage access. + $form['settings']['access'] = array( + '#weight' => 50, + ); + $plugin_type = $this->element instanceof RulesActionInterface ? t('action') : t('condition'); + $form['settings']['access']['access_exposed'] = array( + '#type' => 'checkbox', + '#title' => t('Configure access for using this component with a permission.'), + '#default_value' => !empty($this->element->access_exposed), + '#description' => t('By default, the @plugin-type for using this component may be only used by users that have access to configure the component. If checked, access is determined by a permission instead.', array('@plugin-type' => $plugin_type)) + ); + $form['settings']['access']['permissions'] = array( + '#type' => 'container', + '#states' => array( + 'visible' => array( + ':input[name="settings[access][access_exposed]"]' => array('checked' => TRUE), + ), + ), + ); + $form['settings']['access']['permissions']['matrix'] = $this->settingsFormPermissionMatrix(); + } + } + + // TODO: Attach field form thus description. + } + + /** + * Provides a matrix permission for the component based in the existing roles. + * + * @return + * Form elements with the matrix of permissions for a component. + */ + protected function settingsFormPermissionMatrix() { + $form['#theme'] = 'user_admin_permissions'; + $status = array(); + $options = array(); + + $role_names = user_roles(); + $role_permissions = user_role_permissions($role_names); + $component_permission = rules_permissions_by_component(array($this->element)); + $component_permission_name = key($component_permission); + + $form['permission'][$component_permission_name] = array( + '#type' => 'item', + '#markup' => $component_permission[$component_permission_name]['title'], + ); + $options[$component_permission_name] = ''; + foreach ($role_names as $rid => $name) { + if (isset($role_permissions[$rid][$component_permission_name])) { + $status[$rid][] = $component_permission_name; + } + } + + // Build the checkboxes for each role. + foreach ($role_names as $rid => $name) { + $form['checkboxes'][$rid] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => isset($status[$rid]) ? $status[$rid] : array(), + '#attributes' => array('class' => array('rid-' . $rid)), + ); + $form['role_names'][$rid] = array('#markup' => check_plain($name), '#tree' => TRUE); + } + + // Attach the default permissions page JavaScript. + $form['#attached']['js'][] = drupal_get_path('module', 'user') . '/user.permissions.js'; + + return $form; + } + + public function settingsFormExtractValues($form, &$form_state) { + $form_values = RulesPluginUI::getFormStateValues($form['settings'], $form_state); + $this->element->label = $form_values['label']; + // If the name was changed we have to redirect to the URL that contains + // the new name, instead of rebuilding on the old URL with the old name + if ($form['settings']['name']['#default_value'] != $form_values['name']) { + $module = isset($this->element->module) ? $this->element->module : 'rules'; + $this->element->name = $module . '_' . $form_values['name']; + $form_state['redirect'] = RulesPluginUI::path($this->element->name, 'edit', $this->element); + } + $this->element->tags = empty($form_values['tags']) ? array() : drupal_explode_tags($form_values['tags']); + + if (isset($form_values['vars']['items'])) { + $vars = &$this->element->componentVariables(); + $vars = array(); + if ($this->element instanceof RulesActionContainer) { + $provides = &$this->element->componentProvidesVariables(); + $provides = array(); + } + + usort($form_values['vars']['items'], 'rules_element_sort_helper'); + foreach ($form_values['vars']['items'] as $item) { + if ($item['type'] && $item['name'] && $item['label']) { + $vars[$item['name']] = array('label' => $item['label'], 'type' => $item['type']); + if (!$item['usage'][0]) { + $vars[$item['name']]['parameter'] = FALSE; + } + if ($item['usage'][1] && isset($provides)) { + $provides[] = $item['name']; + } + } + } + // Disable FAPI persistence for the variable form so renumbering works. + $input = &$form_state['input']; + foreach ($form['settings']['#parents'] as $parent) { + $input = &$input[$parent]; + } + unset($input['vars']); + } + $this->element->access_exposed = isset($form_values['access']['access_exposed']) ? $form_values['access']['access_exposed'] : FALSE; + } + + public function settingsFormValidate($form, &$form_state) { + $form_values = RulesPluginUI::getFormStateValues($form['settings'], $form_state); + if ($form['settings']['name']['#default_value'] != $form_values['name'] && rules_config_load($this->element->name)) { + form_error($form['settings']['name'], t('The machine-readable name %name is already taken.', array('%name' => $form_values['name']))); + } + } + + public function settingsFormSubmit($form, &$form_state) { + if (isset($form_state['values']['settings']['access']) && !empty($this->element->access_exposed)) { + // Save the permission matrix. + foreach ($form_state['values']['settings']['access']['permissions']['matrix']['checkboxes'] as $rid => $value) { + user_role_change_permissions($rid, $value); + } + } + } + + /** + * Returns the form for configuring the info of a single variable. + */ + public function getVariableForm($name = '', $info = array(), $provided = FALSE) { + $form['type'] = array( + '#type' => 'select', + '#options' => array(0 => '--') + RulesPluginUI::getOptions('data'), + '#default_value' => isset($info['type']) ? $info['type'] : 0, + ); + $form['label'] = array( + '#type' => 'textfield', + '#size' => 40, + '#default_value' => isset($info['label']) ? $info['label'] : '', + ); + $form['name'] = array( + '#type' => 'textfield', + '#size' => 40, + '#default_value' => $name, + '#element_validate' => array('rules_ui_element_machine_name_validate'), + ); + + $usage[0] = !isset($info['parameter']) || $info['parameter'] ? 1 : 0; + $usage[1] = $provided ? 1 : 0; + + $form['usage'] = array( + '#type' => 'select', + '#default_value' => implode('', $usage), + '#options' => array( + '10' => t('Parameter'), + '11' => t('Parameter + Provided'), + '01' => t('Provided'), + ), + ); + if ($this->element instanceof RulesConditionContainer) { + $form['usage']['#disabled'] = TRUE; + } + + // Just set the weight #default_value for the returned form. + $form['weight'] = array( + '#type' => 'weight', + ); + return $form; + } + + /** + * Returns the name of class for the given data type. + * + * @param $data_type + * The name of the data typ + * @param $parameter_info + * (optional) An array of info about the to be configured parameter. If + * given, this array is complemented with data type defaults also. + */ + public function getDataTypeClass($data_type, &$parameter_info = array()) { + $cache = rules_get_cache(); + $data_info = $cache['data_info']; + // Add in data-type defaults. + if (empty($parameter_info['ui class'])) { + $parameter_info['ui class'] = (is_string($data_type) && isset($data_info[$data_type]['ui class'])) ? $data_info[$data_type]['ui class'] : 'RulesDataUI'; + } + if (is_subclass_of($parameter_info['ui class'], 'RulesDataInputOptionsListInterface')) { + $parameter_info['options list'] = array($parameter_info['ui class'], 'optionsList'); + } + return $parameter_info['ui class']; + } + + /** + * Implements RulesPluginUIInterface. + * Show a preview of the configuration settings. + */ + public function buildContent() { + $config_name = $this->element->root()->name; + $content['label'] = array( + '#type' => 'link', + '#title' => $this->element->label(), + '#href' => $this->element->isRoot() ? RulesPluginUI::path($config_name) : RulesPluginUI::path($config_name, 'edit', $this->element), + '#prefix' => '
    ', + '#suffix' => '
    ' + ); + // Put the elements below in a "description" div. + $content['description'] = array( + '#prefix' => '
    ', + ); + $content['description']['parameter'] = array( + '#caption' => t('Parameter'), + '#theme' => 'rules_content_group', + ); + foreach ($this->element->pluginParameterInfo() as $name => $parameter) { + $element = array(); + if (!empty($this->element->settings[$name . ':select'])) { + $element['content'] = array( + '#markup' => '[' . $this->element->settings[$name . ':select'] . ']', + ); + } + elseif (isset($this->element->settings[$name]) && (!isset($parameter['default value']) || $parameter['default value'] != $this->element->settings[$name])) { + $class = $this->getDataTypeClass($parameter['type'], $parameter); + $method = empty($parameter['options list']) ? 'render' : 'renderOptionsLabel'; + // We cannot use method_exists() here as it would trigger a PHP bug, + // @see http://drupal.org/node/1258284 + $element = call_user_func(array($class, $method), $this->element->settings[$name], $name, $parameter, $this->element); + } + // Only add parameters that are really configured / not default. + if ($element) { + $content['description']['parameter'][$name] = array( + '#theme' => 'rules_parameter_configuration', + '#info' => $parameter, + ) + $element; + } + } + foreach ($this->element->providesVariables() as $name => $var_info) { + $content['description']['provides'][$name] = array( + '#theme' => 'rules_variable_view', + '#info' => $var_info, + '#name' => $name, + ); + } + if (!empty($content['description']['provides'])) { + $content['description']['provides'] += array( + '#caption' => t('Provides variables'), + '#theme' => 'rules_content_group', + ); + } + // Add integrity exception messages if there are any for this element. + try { + $this->element->integrityCheck(); + // A configuration is still marked as dirty, but already works again. + if (!empty($this->element->dirty)) { + rules_config_update_dirty_flag($this->element); + $variables = array('%label' => $this->element->label(), '%name' => $this->element->name, '@plugin' => $this->element->plugin()); + drupal_set_message(t('The @plugin %label (%name) was marked dirty, but passes the integrity check now and is active again.', $variables)); + rules_clear_cache(); + } + } + catch (RulesIntegrityException $e) { + $content['description']['integrity'] = array( + '#theme' => 'rules_content_group', + '#caption' => t('Error'), + '#attributes' => array('class' => array('rules-content-group-integrity-error')), + 'error' => array( + '#markup' => filter_xss($e->getMessage()), + ), + ); + // Also make sure the rule is marked as dirty. + if (empty($this->element->dirty)) { + rules_config_update_dirty_flag($this->element); + rules_clear_cache(); + } + } + + $content['#suffix'] = '
    '; + $content['#type'] = 'container'; + $content['#attributes']['class'][] = 'rules-element-content'; + return $content; + } + + /** + * Implements RulesPluginUIInterface. + */ + public function operations() { + $name = $this->element->root()->name; + $render = array( + '#theme' => 'links__rules', + ); + $render['#attributes']['class'][] = 'rules-operations'; + $render['#attributes']['class'][] = 'action-links'; + $render['#links']['edit'] = array( + 'title' => t('edit'), + 'href' => RulesPluginUI::path($name, 'edit', $this->element), + ); + $render['#links']['delete'] = array( + 'title' => t('delete'), + 'href' => RulesPluginUI::path($name, 'delete', $this->element), + ); + return $render; + } + + /** + * Implements RulesPluginUIInterface. + */ + public function help() {} + + + /** + * Deprecated by the controllers overviewTable() method. + */ + public static function overviewTable($conditions = array(), $options = array()) { + return rules_ui()->overviewTable($conditions, $options); + } + + /** + * Generates a path using the given operation for the element with the given + * id of the configuration with the given name. + */ + public static function path($name, $op = NULL, RulesPlugin $element = NULL, $parameter = FALSE) { + $element_id = isset($element) ? $element->elementId() : FALSE; + if (isset(self::$basePath)) { + $base_path = self::$basePath; + } + // Default to the paths used by 'rules_admin', so modules can easily re-use + // its UI. + else { + $base_path = isset($element) && $element instanceof RulesTriggerableInterface ? 'admin/config/workflow/rules/reaction' : 'admin/config/workflow/rules/components'; + } + return implode('/', array_filter(array($base_path . '/manage', $name, $op, $element_id, $parameter))); + } + + /** + * Determines the default redirect target for an edited/deleted element. This + * is a parent element which is either a rule or the configuration root. + */ + public static function defaultRedirect(RulesPlugin $element) { + while (!$element->isRoot()) { + if ($element instanceof Rule) { + return self::path($element->root()->name, 'edit', $element); + } + $element = $element->parentElement(); + } + return self::path($element->name); + } + + /** + * @see RulesUICategory::getOptions() + */ + public static function getOptions($item_type, $items = NULL) { + return RulesUICategory::getOptions($item_type, $items = NULL); + } + + public static function formDefaults(&$form, &$form_state) { + form_load_include($form_state, 'inc', 'rules', 'ui/ui.forms'); + // Add our own css. + $form['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.css'; + // Workaround for problems with jquery css in seven theme and the core + // autocomplete. + if ($GLOBALS['theme'] == 'seven') { + $form['#attached']['css'][] = drupal_get_path('module', 'rules') . '/ui/rules.ui.seven.css'; + } + + // Specify the wrapper div used by #ajax. + $form['#prefix'] = '
    '; + $form['#suffix'] = '
    '; + + // Preserve the base path in the form state. The after build handler will + // set self::$basePath again for cached forms. + if (isset(self::$basePath)) { + $form_state['_rules_base_path'] = RulesPluginUI::$basePath; + $form['#after_build'][] = 'rules_form_after_build_restore_base_path'; + } + } + + public static function getTags() { + $result = db_select('rules_tags') + ->distinct() + ->fields('rules_tags', array('tag')) + ->groupBy('tag') + ->execute() + ->fetchCol('tag'); + return drupal_map_assoc($result); + } +} + +/** + * UI for abstract plugins (conditions & actions). + */ +class RulesAbstractPluginUI extends RulesPluginUI { + + /** + * Overridden to invoke the abstract plugins form alter callback and to add + * the negation checkbox for conditions. + */ + public function form(&$form, &$form_state, $options = array()) { + parent::form($form, $form_state, $options); + + if ($this->element instanceof RulesCondition) { + $form['negate'] = array( + '#title' => t('Negate'), + '#type' => 'checkbox', + '#description' => t('If checked, the condition result is negated such that it returns TRUE if it evaluates to FALSE.'), + '#default_value' => $this->element->isNegated(), + '#weight' => 5, + ); + } + $this->element->call('form_alter', array(&$form, &$form_state, $options)); + } + + public function form_extract_values($form, &$form_state) { + parent::form_extract_values($form, $form_state); + $form_values = RulesPluginUI::getFormStateValues($form, $form_state); + if ($this->element instanceof RulesCondition && isset($form_values['negate'])) { + $this->element->negate($form_values['negate']); + } + } + + public function form_validate($form, &$form_state) { + parent::form_validate($form, $form_state); + // Validate the edited element and throw validation errors if it fails. + try { + $this->element->integrityCheck(); + } + catch (RulesIntegrityException $e) { + form_set_error(implode('][', $e->keys), $e->getMessage()); + } + } +} + +/** + * UI for Rules Container. + */ +class RulesContainerPluginUI extends RulesPluginUI { + + /** + * Generates a table for editing the contained elements. + */ + public function form(&$form, &$form_state, $options = array(), $iterator = NULL) { + parent::form($form, $form_state, $options); + $form['elements'] = array( + // Hide during creation or for embedded elements. + '#access' => empty($options['init']) && $this->element->isRoot(), + '#tree' => TRUE, + '#theme' => 'rules_elements', + '#empty' => t('None'), + '#caption' => t('Elements') + ); + $form['elements']['#attributes']['class'][] = 'rules-container-plugin'; + + // Recurse over all element childrens or use the provided iterator. + $iterator = isset($iterator) ? $iterator : $this->element->elements(); + $root_depth = $this->element->depth(); + foreach ($iterator as $key => $child) { + $id = $child->elementId(); + + // Do not render rules as container element when displayed in a rule set. + $is_container = $child instanceof RulesContainerPlugin && !($child instanceof Rule); + $form['elements'][$id] = array( + '#depth' => $child->depth() - $root_depth - 1, + '#container' => $is_container, + ); + $form['elements'][$id]['label'] = $child->buildContent(); + $form['elements'][$id]['weight'] = array( + '#type' => 'weight', + '#default_value' => $child->weight, + '#delta' => 50, + ); + $form['elements'][$id]['parent_id'] = array( + '#type' => 'hidden', + // If another iterator is passed in, the childs parent may not equal + // the current element. Thus ask the child for its parent. + '#default_value' => $child->parentElement()->elementId(), + ); + $form['elements'][$id]['element_id'] = array( + '#type' => 'hidden', + '#default_value' => $id, + ); + $form['elements'][$id]['operations'] = $child->operations(); + } + + // Alter the submit button label. + if (!empty($options['button']) && !empty($options['init'])) { + $form['submit']['#value'] = t('Continue'); + } + elseif (!empty($options['button']) && $this->element->isRoot()) { + $form['submit']['#value'] = t('Save changes'); + } + } + + /** + * Applies the values of the form to the given rule configuration. + */ + public function form_extract_values($form, &$form_state) { + parent::form_extract_values($form, $form_state); + $values = RulesPluginUI::getFormStateValues($form, $form_state); + // Now apply the new hierarchy. + if (isset($values['elements'])) { + foreach ($values['elements'] as $id => $data) { + $child = $this->element->elementMap()->lookup($id); + $child->weight = $data['weight']; + $parent = $this->element->elementMap()->lookup($data['parent_id']); + $child->setParent($parent ? $parent : $this->element); + } + $this->element->sortChildren(TRUE); + } + } + + public function operations() { + $ops = parent::operations(); + $add_ops = self::addOperations(); + $ops['#links'] += $add_ops['#links']; + return $ops; + } + + /** + * Gets the Add-* operations for the given element. + */ + public function addOperations() { + $name = $this->element->root()->name; + $render = array( + '#theme' => 'links__rules', + ); + $render['#attributes']['class'][] = 'rules-operations-add'; + $render['#attributes']['class'][] = 'action-links'; + foreach (rules_fetch_data('plugin_info') as $plugin => $info) { + if (!empty($info['embeddable']) && $this->element instanceof $info['embeddable']) { + $render['#links']['add_' . $plugin] = array( + 'title' => t('Add !name', array('!name' => $plugin)), + 'href' => RulesPluginUI::path($name, 'add', $this->element, $plugin), + ); + } + } + return $render; + } + + + public function buildContent() { + $content = parent::buildContent(); + // Don't link the title for embedded container plugins, except for rules. + if (!$this->element->isRoot() && !($this->element instanceof Rule)) { + $content['label']['#type'] = 'markup'; + $content['label']['#markup'] = check_plain($content['label']['#title']); + unset($content['label']['#title']); + } + elseif ($this->element->isRoot()) { + $content['description']['settings'] = array( + '#theme' => 'rules_content_group', + '#weight' => -4, + 'machine_name' => array( + '#markup' => t('Machine name') . ': ' . $this->element->name, + ), + 'weight' => array( + '#access' => $this->element instanceof RulesTriggerableInterface, + '#markup' => t('Weight') . ': ' . $this->element->weight, + ), + ); + if (!empty($this->element->tags)) { + $content['description']['tags'] = array( + '#theme' => 'rules_content_group', + '#caption' => t('Tags'), + 'tags' => array( + '#markup' => check_plain(drupal_implode_tags($this->element->tags)), + ), + ); + } + if ($vars = $this->element->componentVariables()) { + $content['description']['variables'] = array( + '#caption' => t('Parameter'), + '#theme' => 'rules_content_group', + ); + foreach ($vars as $name => $info) { + if (!isset($info['parameter']) || $info['parameter']) { + $content['description']['variables'][$name] = array( + '#theme' => 'rules_variable_view', + '#info' => $info, + '#name' => $name, + ); + } + } + } + } + return $content; + } +} + +/** + * UI for Rules condition container. + */ +class RulesConditionContainerUI extends RulesContainerPluginUI { + + public function form(&$form, &$form_state, $options = array(), $iterator = NULL) { + parent::form($form, $form_state, $options, $iterator); + // Add the add-* operation links. + $form['elements']['#add'] = self::addOperations(); + $form['elements']['#attributes']['class'][] = 'rules-condition-container'; + $form['elements']['#caption'] = t('Conditions'); + + // By default skip + if (!empty($options['init']) && !$this->element->isRoot()) { + $config = $this->element->root(); + $form['init_help'] = array( + '#type' => 'container', + '#id' => 'rules-plugin-add-help', + 'content' => array( + '#markup' => t('You are about to add a new @plugin to the @config-plugin %label. Use indentation to make conditions a part of this logic group. See the online documentation for more information on condition sets.', + array('@plugin' => $this->element->plugin(), + '@config-plugin' => $config->plugin(), + '%label' => $config->label(), + '@url' => rules_external_help('condition-components'))), + ), + ); + } + $form['negate'] = array( + '#title' => t('Negate'), + '#type' => 'checkbox', + '#description' => t('If checked, the condition result is negated such that it returns TRUE if it evaluates to FALSE.'), + '#default_value' => $this->element->isNegated(), + '#weight' => 5, + ); + } + + public function form_extract_values($form, &$form_state) { + parent::form_extract_values($form, $form_state); + $form_values = RulesPluginUI::getFormStateValues($form, $form_state); + if (isset($form_values['negate'])) { + $this->element->negate($form_values['negate']); + } + } +} + +/** + * UI for Rules action container. + */ +class RulesActionContainerUI extends RulesContainerPluginUI { + + public function form(&$form, &$form_state, $options = array(), $iterator = NULL) { + parent::form($form, $form_state, $options, $iterator); + // Add the add-* operation links. + $form['elements']['#add'] = self::addOperations(); + $form['elements']['#attributes']['class'][] = 'rules-action-container'; + $form['elements']['#caption'] = t('Actions'); + } +} + +/** + * Class holding category related methods. + */ +class RulesUICategory { + + /** + * Gets info about all available categories, or about a specific category. + * + * @return array + */ + public static function getInfo($category = NULL) { + $data = rules_fetch_data('category_info'); + if (isset($category)) { + return $data[$category]; + } + return $data; + } + + /** + * Returns a group label, e.g. as usable for opt-groups in a select list. + * + * @param array $item_info + * The info-array of an item, e.g. an entry of hook_rules_action_info(). + * @param bool $in_category + * (optional) Whether group labels for grouping inside a category should be + * return. Defaults to FALSE. + * + * @return string|boolean + * The group label to use, or FALSE if none can be found. + */ + public static function getItemGroup($item_info, $in_category = FALSE) { + if (isset($item_info['category']) && !$in_category) { + return self::getCategory($item_info, 'label'); + } + else if (!empty($item_info['group'])) { + return $item_info['group']; + } + return FALSE; + } + + /** + * Gets the category for the given item info array. + * + * @param array $item_info + * The info-array of an item, e.g. an entry of hook_rules_action_info(). + * @param string|null $key + * (optional) The key of the category info to return, e.g. 'label'. If none + * is given the whole info array is returned. + * + * @return array|mixed|false + * Either the whole category info array or the value of the given key. If + * no category can be found, FALSE is returned. + */ + public static function getCategory($item_info, $key = NULL) { + if (isset($item_info['category'])) { + $info = self::getInfo($item_info['category']); + return isset($key) ? $info[$key] : $info; + } + return FALSE; + } + + /** + * Returns an array of options to use with a select for the items specified + * in the given hook. + * + * @param $item_type + * The item type to get options for. One of 'data', 'event', 'condition' and + * 'action'. + * @param $items + * (optional) An array of items to restrict the options to. + * + * @return + * An array of options. + */ + public static function getOptions($item_type, $items = NULL) { + $sorted_data = array(); + $ungrouped = array(); + $data = $items ? $items : rules_fetch_data($item_type . '_info'); + foreach ($data as $name => $info) { + // Verfiy the current user has access to use it. + if (!user_access('bypass rules access') && !empty($info['access callback']) && !call_user_func($info['access callback'], $item_type, $name)) { + continue; + } + if ($group = RulesUICategory::getItemGroup($info)) { + $sorted_data[drupal_ucfirst($group)][$name] = drupal_ucfirst($info['label']); + } + else { + $ungrouped[$name] = drupal_ucfirst($info['label']); + } + } + asort($ungrouped); + foreach ($sorted_data as $key => $choices) { + asort($choices); + $sorted_data[$key] = $choices; + } + + // Sort the grouped data by category weights, defaulting to weight 0 for + // groups without a respective category. + $sorted_groups = array(); + foreach (array_keys($sorted_data) as $label) { + $sorted_groups[$label] = array('weight' => 0, 'label' => $label); + } + // Add in category weights. + foreach (RulesUICategory::getInfo() as $info) { + if (isset($sorted_groups[$info['label']])) { + $sorted_groups[$info['label']] = $info; + } + } + uasort($sorted_groups, '_rules_ui_sort_categories'); + + // Now replace weights with group content. + foreach ($sorted_groups as $group => $weight) { + $sorted_groups[$group] = $sorted_data[$group]; + } + return $ungrouped + $sorted_groups; + } +} + +/** + * Helper for sorting categories. + */ +function _rules_ui_sort_categories($a, $b) { + // @see element_sort() + $a_weight = isset($a['weight']) ? $a['weight'] : 0; + $b_weight = isset($b['weight']) ? $b['weight'] : 0; + if ($a_weight == $b_weight) { + // @see element_sort_by_title() + $a_title = isset($a['label']) ? $a['label'] : ''; + $b_title = isset($b['label']) ? $b['label'] : ''; + return strnatcasecmp($a_title, $b_title); + } + return ($a_weight < $b_weight) ? -1 : 1; +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/ui/ui.data.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/ui/ui.data.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,581 @@ +availableVariables(); + // Default to variables with the same name as the parameter. + if (isset($vars[$name])) { + $settings[$name . ':select'] = $name; + } + // If there is only one match, use it by default. + elseif (count($matches = RulesData::matchingDataSelector($vars, $info, '', 1, FALSE)) == 1) { + $settings[$name . ':select'] = rules_array_key($matches); + } + } + $form[$name . ':select'] = array( + '#type' => 'rules_data_selection', + '#title' => t('Data selector'), + '#default_value' => $settings[$name . ':select'], + '#required' => empty($info['optional']), + '#autocomplete_path' => RulesPluginUI::path($element->root()->name, 'autocomplete' . '/' . $name), + // Make the autocomplete textfield big enough so that it can display + // descriptions without word wraps. + '#size' => 75, + '#description' => t("The data selector helps you drill down into the data available to Rules. To make entity fields appear in the data selector, you may have to use the condition 'entity has field' (or 'content is of type'). More useful tips about data selection is available in the online documentation.", + array('@url' => rules_external_help('data-selection'))), + ); + + $cache = rules_get_cache(); + $form['types_help'] = array( + '#theme' => 'rules_settings_help', + '#heading' => t('Data types'), + ); + if ($info['type'] == '*') { + $type_labels[] = t('any'); + } + else { + $types = is_array($info['type']) ? $info['type'] : array($info['type']); + $type_labels = array(); + foreach ($types as $type) { + $type_labels[] = drupal_ucfirst(isset($cache['data_info'][$type]['label']) ? $cache['data_info'][$type]['label'] : $type); + } + } + $form['types_help']['#text'] = format_plural(count($type_labels), 'Select data of the type %types.', 'Select data of the types %types.', array('%types' => implode(', ', $type_labels))); + + if (!empty($info['translatable'])) { + if (empty($info['custom translation language'])) { + $text = t('If a multilingual data source (i.e. a translatable field) is given, the argument is translated to the current interface language.'); + } + else { + $text = t('If a multilingual data source (i.e. a translatable field) is given, the argument is translated to the configured language.'); + } + $form['translation'] = array( + '#theme' => 'rules_settings_help', + '#text' => $text, + '#heading' => t('Translation'), + ); + } + $form['help'] = array( + '#theme' => 'rules_data_selector_help', + '#variables' => $element->availableVariables(), + '#parameter' => $info, + ); + + // Add data processor. + $settings += array($name . ':process' => array()); + $form[$name . ':process'] = array(); + RulesDataProcessor::attachForm($form[$name . ':process'], $settings[$name . ':process'], $info, $element->availableVariables()); + return $form; + } + + /** + * Renders the value by making use of the label if an options list is available. + * + * Used for data UI classes implementing the + * RulesDataDirectInputFormInterface. + * + * In case an options list is available, the the usual render() method won't + * be invoked, instead the selected entry is rendered via this method. + * + * @todo for Drupal 8: Refactor to avoid implementations have to care about + * option lists when generating the form, but not when rendering values. + */ + public static function renderOptionsLabel($value, $name, $info, RulesPlugin $element) { + if (!empty($info['options list'])) { + $element->call('loadBasicInclude'); + $options = entity_property_options_flatten(call_user_func($info['options list'], $element, $name)); + if (!is_array($value) && isset($options[$value])) { + $value = $options[$value]; + } + elseif (is_array($value)) { + foreach ($value as $key => $single_value) { + if (isset($options[$single_value])) { + $value[$key] = $options[$single_value]; + } + } + $value = implode(', ', $value); + } + return array( + 'content' => array('#markup' => check_plain($value)), + '#attributes' => array('class' => array('rules-parameter-options-entry')), + ); + } + } + + /** + * Returns the data type and parameter information for the given arguments. + * + * This helper may be used by options list callbacks operation at data-type + * level, see RulesDataInputOptionsListInterface. + */ + public static function getTypeInfo(RulesPlugin $element, $name) { + $parameters = $element->pluginParameterInfo(); + return array($parameters[$name]['type'], $parameters[$name]); + } +} + +/** + * UI for textual data. + */ +class RulesDataUIText extends RulesDataUI implements RulesDataDirectInputFormInterface { + + public static function getDefaultMode() { + return 'input'; + } + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + if (!empty($info['options list'])) { + // Make sure the .rules.inc of the providing module is included as the + // options list callback may reside there. + $element->call('loadBasicInclude'); + $form[$name] = array( + '#type' => 'select', + '#options' => call_user_func($info['options list'], $element, $name), + ); + } + else { + $form[$name] = array( + '#type' => 'textarea', + ); + RulesDataInputEvaluator::attachForm($form, $settings, $info, $element->availableVariables()); + } + $settings += array($name => isset($info['default value']) ? $info['default value'] : NULL); + $form[$name] += array( + '#title' => t('Value'), + '#default_value' => $settings[$name], + '#required' => empty($info['optional']), + '#after_build' => array('rules_ui_element_fix_empty_after_build'), + '#rows' => 3, + ); + return $form; + } + + public static function render($value) { + return array( + 'content' => array('#markup' => check_plain($value)), + '#attributes' => array('class' => array('rules-parameter-text')), + ); + } +} + +/** + * UI for text tokens. + */ +class RulesDataUITextToken extends RulesDataUIText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + if ($form[$name]['#type'] == 'textarea') { + $form[$name]['#element_validate'][] = 'rules_ui_element_token_validate'; + $form[$name]['#description'] = t('May only contain lowercase letters, numbers, and underscores and has to start with a letter.'); + $form[$name]['#rows'] = 1; + } + return $form; + } +} + +/** + * UI for formatted text. + */ +class RulesDataUITextFormatted extends RulesDataUIText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + $settings += array($name => isset($info['default value']) ? $info['default value'] : array('value' => NULL, 'format' => NULL)); + + $form[$name]['#type'] = 'text_format'; + $form[$name]['#base_type'] = 'textarea'; + $form[$name]['#default_value'] = $settings[$name]['value']; + $form[$name]['#format'] = $settings[$name]['format']; + return $form; + } + + public static function render($value) { + return array( + 'content' => array('#markup' => check_plain($value['value'])), + '#attributes' => array('class' => array('rules-parameter-text-formatted')), + ); + } +} + + + +/** + * UI for decimal data. + */ +class RulesDataUIDecimal extends RulesDataUIText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + if (empty($info['options list'])) { + $form[$name]['#type'] = 'textfield'; + } + $form[$name]['#element_validate'][] = 'rules_ui_element_decimal_validate'; + $form[$name]['#rows'] = 1; + return $form; + } +} + +/** + * UI for integers. + */ +class RulesDataUIInteger extends RulesDataUIText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + if (empty($info['options list'])) { + $form[$name]['#type'] = 'textfield'; + } + $form[$name]['#element_validate'][] = 'rules_ui_element_integer_validate'; + return $form; + } +} + +/** + * UI for boolean data. + */ +class RulesDataUIBoolean extends RulesDataUI implements RulesDataDirectInputFormInterface { + + public static function getDefaultMode() { + return 'input'; + } + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $settings += array($name => isset($info['default value']) ? $info['default value'] : NULL); + // Note: Due to the checkbox even optional parameter always receive a value. + $form[$name] = array( + '#type' => 'checkbox', + '#title' => check_plain($info['label']), + '#default_value' => $settings[$name], + ); + return $form; + } + + public static function render($value) { + return array( + 'content' => array('#markup' => !empty($value) ? t('true') : t('false')), + '#attributes' => array('class' => array('rules-parameter-boolean')), + ); + } +} + +/** + * UI for dates. + */ +class RulesDataUIDate extends RulesDataUIText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $settings += array($name => isset($info['default value']) ? $info['default value'] : (empty($info['optional']) ? gmdate('Y-m-d H:i:s', time()) : NULL)); + + // Convert any configured timestamp into a readable format. + if (is_numeric($settings[$name])) { + $settings[$name] = gmdate('Y-m-d H:i:s', $settings[$name]); + } + $form = parent::inputForm($name, $info, $settings, $element); + $form[$name]['#type'] = 'textfield'; + $form[$name]['#element_validate'][] = 'rules_ui_element_date_validate'; + // Note that the date input evaluator takes care for parsing dates using + // strtotime() into a timestamp, which is the internal date format. + $form[$name]['#description'] = t('The date in GMT. You may enter a fixed time (like %format) or any other values in GMT known by the PHP !strtotime function (like "+1 day"). Relative dates like "+1 day" or "now" relate to the evaluation time.', + array('%format' => gmdate('Y-m-d H:i:s', time() + 86400), + '!strtotime' => l('strtotime()', 'http://php.net/strtotime'))); + + //TODO: Leverage the jquery datepicker+timepicker once a module providing + //the timpeicker is available. + return $form; + } + + public static function render($value) { + $value = is_numeric($value) ? format_date($value, 'short') : check_plain($value); + return array( + 'content' => array('#markup' => $value), + '#attributes' => array('class' => array('rules-parameter-date')), + ); + } +} + +/** + * UI for duration type parameter. + */ +class RulesDataUIDuration extends RulesDataUIText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + $form[$name]['#type'] = 'rules_duration'; + $form[$name]['#after_build'][] = 'rules_ui_element_duration_after_build'; + return $form; + } + + public static function render($value) { + $value = is_numeric($value) ? format_interval($value) : check_plain($value); + return array( + 'content' => array('#markup' => $value), + '#attributes' => array('class' => array('rules-parameter-duration')), + ); + } +} + +/** + * UI for the URI type parameter. + */ +class RulesDataUIURI extends RulesDataUIText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + $form[$name]['#rows'] = 1; + $form[$name]['#description'] = t('You may enter relative URLs like %url as well as absolute URLs like %absolute-url.', array('%url' => 'user/login?destination=node', '%absolute-url' => 'http://drupal.org')); + return $form; + } +} + +/** + * UI for lists of textual data. + */ +class RulesDataUIListText extends RulesDataUIText { + + public static function getDefaultMode() { + return 'input'; + } + + /** + * @todo This does not work for inputting textual values including "\n". + */ + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $settings += array($name => isset($info['default value']) ? $info['default value'] : NULL); + $form = parent::inputForm($name, $info, $settings, $element); + + if ($form[$name]['#type'] == 'textarea') { + // Fix up the value to be an array during after build. + $form[$name]['#delimiter'] = "\n"; + $form[$name]['#after_build'][] = 'rules_ui_list_textarea_after_build'; + $form[$name]['#pre_render'][] = 'rules_ui_list_textarea_pre_render'; + $form[$name]['#default_value'] = !empty($settings[$name]) ? implode("\n", $settings[$name]) : NULL; + $form[$name]['#description'] = t('A list of values, one on each line.'); + } + else { + $form[$name]['#multiple'] = TRUE; + } + return $form; + } + + public static function render($value) { + return array( + 'content' => array('#markup' => check_plain(implode(', ', $value))), + '#attributes' => array('class' => array('rules-parameter-list')), + ); + } +} + +/** + * UI for lists of integers. + */ +class RulesDataUIListInteger extends RulesDataUIListText { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $settings += array($name => isset($info['default value']) ? $info['default value'] : NULL); + $form = parent::inputForm($name, $info, $settings, $element); + + if ($form[$name]['#type'] == 'textarea') { + $form[$name]['#description'] = t('A list of integers, separated by commas. E.g. enter "1, 2, 3".'); + $form[$name]['#delimiter'] = ','; + $form[$name]['#default_value'] = !empty($settings[$name]) ? implode(", ", $settings[$name]) : NULL; + $form[$name]['#element_validate'][] = 'rules_ui_element_integer_list_validate'; + $form[$name]['#rows'] = 1; + } + return $form; + } +} + +/** + * UI for lists of tokens. + */ +class RulesDataUIListToken extends RulesDataUIListInteger { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + + if ($form[$name]['#type'] == 'textarea') { + $form[$name]['#description'] = t('A list of text tokens, separated by commas. E.g. enter "one, two, three".'); + $form[$name]['#element_validate'] = array('rules_ui_element_token_list_validate'); + } + return $form; + } +} + +/** + * UI for entity-based data types. + */ +class RulesDataUIEntity extends RulesDataUIText { + + public static function getDefaultMode() { + return 'selector'; + } + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + if (empty($info['options list'])) { + $form[$name]['#type'] = 'textfield'; + + $entity_info = entity_get_info($info['type']); + if (empty($entity_info['entity keys']['name'])) { + $form[$name]['#element_validate'][] = 'rules_ui_element_integer_validate'; + } + $form[$name]['#title'] = t('@entity identifier', array('@entity' => $entity_info['label'])); + $entity_label = strtolower($entity_info['label'][0]) . substr($entity_info['label'], 1); + $form[$name]['#description'] = t('Specify an identifier of a @entity.', array('@entity' => $entity_label)); + } + return $form; + } +} + +/** + * UI for exportable entity-based data types. + */ +class RulesDataUIEntityExportable extends RulesDataUIEntity { + + public static function getDefaultMode() { + return 'input'; + } +} + +/** + * Data UI variant displaying a select list of available bundle entities. + * + * This is used for "bundle entities" implemented via the 'bundle of' feature + * of entity.module. + */ +class RulesDataUIBundleEntity extends RulesDataUIEntity implements RulesDataInputOptionsListInterface { + + public static function getDefaultMode() { + return 'input'; + } + + /** + * Implements RulesDataInputOptionsListInterface::optionsList(). + */ + public static function optionsList(RulesPlugin $element, $name) { + list($data_type, $parameter_info) = RulesDataUI::getTypeInfo($element, $name); + $bundles = array(); + $entity_info = entity_get_info(); + $bundle_of_type = $entity_info[$data_type]['bundle of']; + if (isset($entity_info[$bundle_of_type]['bundles'])) { + foreach ($entity_info[$bundle_of_type]['bundles'] as $bundle_name => $bundle_info) { + $bundles[$bundle_name] = $bundle_info['label']; + } + } + return $bundles; + } +} + +/** + * UI for taxonomy vocabularies. + * + * @see RulesTaxonomyVocabularyWrapper + */ +class RulesDataUITaxonomyVocabulary extends RulesDataUIEntity implements RulesDataInputOptionsListInterface { + + public static function getDefaultMode() { + return 'input'; + } + + /** + * Implements RulesDataInputOptionsListInterface::optionsList(). + */ + public static function optionsList(RulesPlugin $element, $name) { + $options = array(); + foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocab) { + $options[$machine_name] = $vocab->name; + } + return $options; + } +} + +/** + * UI for lists of entity-based data types. + */ +class RulesDataUIListEntity extends RulesDataUIListInteger { + + public static function inputForm($name, $info, $settings, RulesPlugin $element) { + $form = parent::inputForm($name, $info, $settings, $element); + if (empty($info['options list'])) { + + $entity_info = entity_get_info(entity_property_list_extract_type($info['type'])); + if (!empty($entity_info['entity keys']['name'])) { + $form[$name]['#element_validate'] = array('rules_ui_element_token_list_validate'); + } + $form[$name]['#title'] = t('@entity identifiers', array('@entity' => $entity_info['label'])); + $entity_label = strtolower($entity_info['label'][0]) . substr($entity_info['label'], 1); + $form[$name]['#description'] = t('Specify a comma-separated list of identifiers of @entity entities.', array('@entity' => $entity_label)); + } + return $form; + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/ui/ui.forms.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/ui/ui.forms.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,954 @@ + 'rules_ui_form_ajax_reload_form', + 'wrapper' => 'rules-form-wrapper', + 'effect' => $effect, + 'speed' => 'fast', + ); +} + +/** + * Submit handler for switching the parameter input mode. + */ +function rules_ui_parameter_replace_submit($form, &$form_state) { + if (isset($form_state['triggering_element'])) { + $name = $form_state['triggering_element']['#parameter']; + $form_state['parameter_mode'][$name] = $form_state['parameter_mode'][$name] == 'selector' ? 'input' : 'selector'; + } + $form_state['rebuild'] = TRUE; +} + +/** + * General form submit handler, that rebuilds the form + */ +function rules_form_submit_rebuild($form, &$form_state) { + $form_state['rebuild'] = TRUE; +} + +/** + * Edit a rules configuration. + */ +function rules_ui_form_edit_rules_config($form, &$form_state, $rules_config, $base_path) { + RulesPluginUI::$basePath = $base_path; + $form_state += array('rules_element' => $rules_config); + // Add the rule configuration's form. + $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE)); + $form['#validate'] = array('rules_ui_form_rules_config_validate'); + return $form; +} + +/** + * General rules configuration form validation callback. Also populates the + * rules configuration with the form values. + */ +function rules_ui_form_rules_config_validate($form, &$form_state) { + $form_state['rules_element']->form_validate($form, $form_state); +} + +/** + * Edit a rules configuration form submit callback. + */ +function rules_ui_form_edit_rules_config_submit($form, &$form_state) { + $form_state['rules_element']->form_submit($form, $form_state); + drupal_set_message(t('Your changes have been saved.')); + if (empty($form_state['redirect'])) { + $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']); + } +} + +/** + * Clone a rules configuration form. + */ +function rules_ui_form_clone_rules_config($form, &$form_state, $rules_config, $base_path) { + RulesPluginUI::$basePath = $base_path; + $rules_config = clone $rules_config; + $rules_config->id = NULL; + $rules_config->name = ''; + $rules_config->label .= ' (' . t('cloned') . ')'; + $rules_config->status = ENTITY_CUSTOM; + + $form['#validate'][] = 'rules_ui_form_rules_config_validate'; + $form['#submit'][] = 'rules_ui_form_edit_rules_config_submit'; + $form_state += array('rules_element' => $rules_config, 'op' => 'clone'); + + // Add the rule configuration's form. + $rules_config->form($form, $form_state, array('show settings' => TRUE, 'button' => TRUE, 'init' => TRUE)); + + // Open the settings fieldset so altering the name is easier. + $form['settings']['#collapsed'] = FALSE; + return $form; +} + +/** + * A simple form just showing a textarea with the export. + */ +function rules_ui_form_export_rules_config($form, &$form_state, $rules_config, $base_path) { + $form['export'] = array( + '#type' => 'textarea', + '#title' => t('Export'), + '#description' => t('For importing copy the content of the text area and paste it into the import page.'), + '#rows' => 25, + '#default_value' => $rules_config->export(), + ); + return $form; +} + +/** + * Configuration form to directly execute a rules configuration. + */ +function rules_ui_form_execute_rules_config($form, &$form_state, $rules_config, $base_path) { + // Only components can be executed. + if (!($rules_config instanceof RulesTriggerableInterface)) { + RulesPluginUI::$basePath = $base_path; + // Create either the appropriate action or condition element. + $element = rules_plugin_factory($rules_config instanceof RulesActionInterface ? 'action' : 'condition', 'component_' . $rules_config->name); + $form['exec_help'] = array( + '#prefix' => '

    ', + '#markup' => t('This form allows you to manually trigger the execution of the @plugin "%label". If this component requires any parameters, input the suiting execution arguments below.', array('@plugin' => $rules_config->plugin(), '%label' => $rules_config->label())), + '#suffix' => '

    ', + ); + $element->form($form, $form_state); + + // For conditions hide the option to negate them. + if (isset($form['negate'])) { + $form['negate']['#access'] = FALSE; + } + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Execute'), + '#weight' => 20, + ); + // Re-use the validation callback, which will also populate the action with + // the configuration settings in the form. + $form['#validate'] = array('rules_ui_form_rules_config_validate'); + return $form; + } + drupal_not_found(); + exit; +} + +/** + * Submit callback for directly executing a component. + */ +function rules_ui_form_execute_rules_config_submit($form, &$form_state) { + $element = $form_state['rules_element']; + $result = $element->execute(); + if ($element instanceof RulesActionInterface) { + drupal_set_message(t('Component %label has been executed.', array('%label' => $element->label()))); + } + else { + drupal_set_message(t('Component %label evaluated to %result.', array('%label' => $element->label(), '%result' => $result ? 'true' : 'false'))); + } +} + +/** + * Gets the confirmation question for valid operations, or else FALSE. + */ +function rules_ui_confirm_operations($op, $rules_config) { + $vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label()); + + switch ($op) { + case 'enable': + return array(t('Are you sure you want to enable the %plugin %label?', $vars), ''); + case 'disable': + return array(t('Are you sure you want to disable the %plugin %label?', $vars), ''); + case 'revert': + return array(t('Are you sure you want to revert the %plugin %label?', $vars), t('This action cannot be undone.')); + case 'delete': + return array(t('Are you sure you want to delete the %plugin %label?', $vars), t('This action cannot be undone.')); + default: + return FALSE; + } +} + +/** + * Confirmation form for applying the operation to the config. + */ +function rules_ui_form_rules_config_confirm_op($form, &$form_state, $rules_config, $op, $base_path) { + if (list($confirm_question, $description) = rules_ui_confirm_operations($op, $rules_config)) { + RulesPluginUI::$basePath = $base_path; + $form_state += array('rules_config' => $rules_config, 'op' => $op); + return confirm_form($form, $confirm_question, $base_path, $description, t('Confirm'), t('Cancel')); + } + else { + drupal_not_found(); + exit; + } +} + +/** + * Applies the operation and returns the message to show to the user. Also the + * operation is logged to the watchdog. Note that the string is defined two + * times so that the translation extractor can find it. + */ +function rules_ui_confirm_operation_apply($op, $rules_config) { + $vars = array('%plugin' => $rules_config->plugin(), '%label' => $rules_config->label()); + $edit_link = l(t('edit'), RulesPluginUI::path($rules_config->name)); + + switch ($op) { + case 'enable': + $rules_config->active = TRUE; + $rules_config->save(); + watchdog('rules', 'Enabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link); + return t('Enabled %plugin %label.', $vars); + + case 'disable': + $rules_config->active = FALSE; + $rules_config->save(); + watchdog('rules', 'Disabled %plugin %label.', $vars, WATCHDOG_NOTICE, $edit_link); + return t('Disabled %plugin %label.', $vars); + + case 'revert': + $rules_config->delete(); + watchdog('rules', 'Reverted %plugin %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link); + return t('Reverted %plugin %label to the defaults.', $vars); + + case 'delete': + $rules_config->delete(); + watchdog('rules', 'Deleted %plugin %label.', $vars); + return t('Deleted %plugin %label.', $vars); + } +} + +/** + * Rule config deletion form submit callback. + */ +function rules_ui_form_rules_config_confirm_op_submit($form, &$form_state) { + if ($form_state['values']['confirm']) { + $msg = rules_ui_confirm_operation_apply($form_state['op'], $form_state['rules_config']); + drupal_set_message($msg); + } +} + +/** + * Add a new element a rules configuration. + */ +function rules_ui_add_element($form, &$form_state, $rules_config, $plugin_name, RulesContainerPlugin $parent, $base_path) { + $cache = rules_get_cache(); + if (!isset($cache['plugin_info'][$plugin_name]['class'])) { + drupal_not_found(); + exit; + } + RulesPluginUI::$basePath = $base_path; + $plugin_is_abstract = in_array('RulesAbstractPlugin', class_parents($cache['plugin_info'][$plugin_name]['class'])); + // In the first step create the element and in the second step show its edit + // form. + if ($plugin_is_abstract && !isset($form_state['rules_element'])) { + RulesPluginUI::formDefaults($form, $form_state); + $form_state += array('parent_element' => $parent, 'plugin' => $plugin_name); + + $form['element_name'] = array( + '#type' => 'select', + '#title' => t('Select the %element to add', array('%element' => $plugin_name)), + '#options' => RulesPluginUI::getOptions($plugin_name), + '#ajax' => rules_ui_form_default_ajax() + array( + 'trigger_as' => array('name' => 'continue'), + ), + ); + $form['continue'] = array( + '#type' => 'submit', + '#name' => 'continue', + '#value' => t('Continue'), + '#ajax' => rules_ui_form_default_ajax(), + ); + } + elseif (!$plugin_is_abstract) { + // Create the initial, empty element. + $element = rules_plugin_factory($plugin_name); + // Always add the new element at the bottom, thus set an appropriate weight. + $iterator = $parent->getIterator(); + if ($sibling = end($iterator)) { + $element->weight = $sibling->weight + 1; + } + $element->setParent($parent); + $form_state['rules_element'] = $element; + } + + if (isset($form_state['rules_element'])) { + $form_state['rules_element']->form($form, $form_state, array('button' => TRUE, 'init' => TRUE)); + $form['#validate'][] = 'rules_ui_edit_element_validate'; + $form['#submit'][] = 'rules_ui_edit_element_submit'; + } + return $form; +} + +/** + * Add element submit callback. + * Used for "abstract plugins" to create the initial element object with the + * given implemenation name and rebuild the form. + */ +function rules_ui_add_element_submit($form, &$form_state) { + $element = rules_plugin_factory($form_state['plugin'], $form_state['values']['element_name']); + + // Always add the new element at the bottom, thus set an appropriate weight. + $iterator = $form_state['parent_element']->getIterator(); + if ($sibling = end($iterator)) { + $element->weight = $sibling->weight + 1; + } + // Clear the element settings so they won't be processed on serialization as + // there is nothing to be processed yet. + $element->settings = array(); + $element->setParent($form_state['parent_element']); + + $form_state['rules_element'] = $element; + $form_state['rebuild'] = TRUE; +} + +/** + * Delete elements. + */ +function rules_ui_delete_element($form, &$form_state, $rules_config, $rules_element, $base_path) { + RulesPluginUI::$basePath = $base_path; + + if (empty($form_state['rules_config'])) { + // Before modifying the rules config we have to clone it, so any + // modifications won't appear in the static cache of the loading controller. + $rules_config = clone $rules_config; + // Also get the element from the cloned config. + $rules_element = $rules_config->elementMap()->lookup($rules_element->elementId()); + + $form_state['rules_config'] = $rules_config; + $form_state['rules_element'] = $rules_element; + $form_state['element_parent'] = $rules_element->parentElement(); + } + + // Try deleting the element and warn the user if something breaks, but + // save the parent for determining the right redirect target on submit. + $removed_plugin = $form_state['rules_element']->plugin(); + $rules_element->delete(); + + if (empty($rules_config->dirty) && empty($form_state['input'])) { + try { + $rules_config->integrityCheck(); + } + catch (RulesIntegrityException $e) { + $args = array( + '@plugin' => $e->element->plugin(), + '%label' => $e->element->label(), + '@removed-plugin' => $removed_plugin, + '!url' => url(RulesPluginUI::path($form_state['rules_config']->name, 'edit', $e->element)), + ); + drupal_set_message(t('Deleting this @removed-plugin would break your configuration as some of its provided variables are utilized by the @plugin %label.', $args), 'warning'); + } + } + + $confirm_question = t('Are you sure you want to delete the %element_plugin %element_name?', array('%element_plugin' => $rules_element->plugin(), '%element_name' => $rules_element->label(), '%plugin' => $rules_config->plugin(), '%label' => $rules_config->label())); + return confirm_form($form, $confirm_question, RulesPluginUI::path($rules_config->name), t('This action cannot be undone.'), t('Delete'), t('Cancel')); +} + +/** + * Rule config deletion form submit callback. + */ +function rules_ui_delete_element_submit($form, &$form_state) { + $rules_config = $form_state['rules_config']; + $rules_config->save(); + if (empty($form_state['redirect'])) { + $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['element_parent']); + } +} + + +/** + * Configure a rule element. + */ +function rules_ui_edit_element($form, &$form_state, $rules_config, $element, $base_path) { + RulesPluginUI::$basePath = $base_path; + $form_state += array('rules_element' => $element); + $form_state['rules_element']->form($form, $form_state, array('button' => TRUE)); + return $form; +} + +/** + * Validate the element configuration. + */ +function rules_ui_edit_element_validate($form, &$form_state) { + $form_state['rules_element']->form_validate($form, $form_state); +} + +/** + * Submit the element configuration. + */ +function rules_ui_edit_element_submit($form, &$form_state) { + $form_state['rules_element']->form_submit($form, $form_state); + drupal_set_message(t('Your changes have been saved.')); + if (empty($form_state['redirect'])) { + $form_state['redirect'] = RulesPluginUI::defaultRedirect($form_state['rules_element']); + } +} + +/** + * Form builder for the "add event" page. + */ +function rules_ui_add_event_page($form, &$form_state, RulesTriggerableInterface $rules_config, $base_path) { + RulesPluginUI::$basePath = $base_path; + RulesPluginUI::formDefaults($form, $form_state); + $form = rules_ui_add_event($form, $form_state, $rules_config, $base_path); + $form['#validate'][] = 'rules_ui_add_event_validate'; + return $form; +} + +/** + * Submit the event configuration. + */ +function rules_ui_add_event_page_submit($form, &$form_state) { + rules_ui_add_event_apply($form, $form_state); + $rules_config = $form_state['rules_config']; + + // Tell the user if this breaks something, but let him proceed. + if (empty($rules_config->dirty)) { + try { + $rules_config->integrityCheck(); + } + catch (RulesIntegrityException $e) { + $warning = TRUE; + drupal_set_message(t('Added the event, but it does not provide all variables utilized.'), 'warning'); + } + } + $rules_config->save(); + if (!isset($warning)) { + $events = rules_fetch_data('event_info'); + $label = $events[$form_state['values']['event']]['label']; + drupal_set_message(t('Added event %event.', array('%event' => $label))); + } +} + +/** + * Add a new event. + */ +function rules_ui_add_event($form, &$form_state, RulesReactionRule $rules_config, $base_path) { + $form_state += array('rules_config' => $rules_config); + $events = array_diff_key(rules_fetch_data('event_info'), array_flip($rules_config->events())); + + $form['help'] = array( + '#markup' => t('Select the event to add. However note that all added events need to provide all variables that should be available to your rule.'), + ); + $form['event'] = array( + '#type' => 'select', + '#title' => t('React on event'), + '#options' => RulesPluginUI::getOptions('event', $events), + '#description' => t('Whenever the event occurs, rule evaluation is triggered.'), + '#ajax' => rules_ui_form_default_ajax(), + '#required' => TRUE, + ); + if (!empty($form_state['values']['event'])) { + $handler = rules_get_event_handler($form_state['values']['event']); + $form['event_settings'] = $handler->buildForm($form_state); + } + else { + $form['event_settings'] = array(); + } + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Add'), + ); + $form_state['redirect'] = RulesPluginUI::path($rules_config->name); + return $form; +} + +/** + * Validation callback for adding an event. + */ +function rules_ui_add_event_validate($form, $form_state) { + $handler = rules_get_event_handler($form_state['values']['event']); + $handler->extractFormValues($form['event_settings'], $form_state); + try { + $handler->validate(); + } + catch (RulesIntegrityException $e) { + form_set_error(implode('][', $e->keys), $e->getMessage()); + } +} + +/** + * Submit callback that just adds the selected event. + * + * @see rules_admin_add_reaction_rule() + */ +function rules_ui_add_event_apply($form, &$form_state) { + $handler = rules_get_event_handler($form_state['values']['event']); + $handler->extractFormValues($form['event_settings'], $form_state); + $form_state['rules_config']->event($form_state['values']['event'], $handler->getSettings()); +} + +/** + * Form to remove a event from a rule. + */ +function rules_ui_remove_event($form, &$form_state, $rules_config, $event, $base_path) { + RulesPluginUI::$basePath = $base_path; + $form_state += array('rules_config' => $rules_config, 'rules_event' => $event); + $event_info = rules_get_event_info($event); + $form_state['event_label'] = $event_info['label']; + $confirm_question = t('Are you sure you want to remove the event?'); + return confirm_form($form, $confirm_question, RulesPluginUI::path($rules_config->name), t('You are about to remove the event %event.', array('%event' => $form_state['event_label'])), t('Remove'), t('Cancel')); +} + +/** + * Submit the event configuration. + */ +function rules_ui_remove_event_submit($form, &$form_state) { + $rules_config = $form_state['rules_config']; + $rules_config->removeEvent($form_state['rules_event']); + // Tell the user if this breaks something, but let him proceed. + if (empty($rules_config->dirty)) { + try { + $rules_config->integrityCheck(); + } + catch (RulesIntegrityException $e) { + $warning = TRUE; + drupal_set_message(t('Removed the event, but it had provided some variables which are now missing.'), 'warning'); + } + } + $rules_config->save(); + if (!isset($warning)) { + drupal_set_message(t('Event %event has been removed.', array('%event' => $form_state['event_label']))); + } + $form_state['redirect'] = RulesPluginUI::path($rules_config->name); +} + +/** + * Import form for rule configurations. + */ +function rules_ui_import_form($form, &$form_state, $base_path) { + RulesPluginUI::$basePath = $base_path; + RulesPluginUI::formDefaults($form, $form_state); + $form['import'] = array( + '#type' => 'textarea', + '#title' => t('Import'), + '#description' => t('Paste an exported Rules configuration here.'), + '#rows' => 20, + ); + $form['overwrite'] = array( + '#title' => t('Overwrite'), + '#type' => 'checkbox', + '#description' => t('If checked, any existing configuration with the same identifier will be replaced by the import.'), + '#default_value' => FALSE, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import'), + ); + return $form; +} + +/** + * Validation callback for the import form. + */ +function rules_ui_import_form_validate($form, &$form_state) { + if ($rules_config = rules_import($form_state['values']['import'], $error_msg)) { + // Store the successfully imported entity in $form_state. + $form_state['rules_config'] = $rules_config; + if (!$form_state['values']['overwrite']) { + // Check for existing entities with the same identifier. + if (rules_config_load($rules_config->name)) { + $vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label()); + form_set_error('import', t('Import of @entity %label failed, a @entity with the same machine name already exists. Check the overwrite option to replace it.', $vars)); + } + } + try { + $rules_config->integrityCheck(); + } + catch (RulesIntegrityException $e) { + form_set_error('import', t('Integrity check for the imported configuration failed. Error message: %message.', array('%message' => $e->getMessage()))); + } + if (!user_access('bypass rules access') && !$rules_config->access()) { + form_set_error('import', t('You have insufficient access permissions for importing this Rules configuration.')); + } + } + else { + form_set_error('import', t('Import failed.')); + if ($error_msg) { + drupal_set_message($error_msg, 'error'); + } + } +} + +/** + * Submit callback for the import form. + */ +function rules_ui_import_form_submit($form, &$form_state) { + $rules_config = $form_state['rules_config']; + + if ($existing_config = rules_config_load($rules_config->name)) { + // Copy DB id and remove the new indicator to overwrite the existing record. + $rules_config->id = $existing_config->id; + unset($rules_config->is_new); + } + $rules_config->save(); + $vars = array('@entity' => t('Rules configuration'), '%label' => $rules_config->label()); + watchdog('rules_config', 'Imported @entity %label.', $vars); + drupal_set_message(t('Imported @entity %label.', $vars)); + $form_state['redirect'] = RulesPluginUI::$basePath; +} + +/** + * FAPI process callback for the data selection widget. + * This finalises the auto completion callback path by appending the form build + * id. + */ +function rules_data_selection_process($element, &$form_state, $form) { + $element['#autocomplete_path'] .= '/' . $form['#build_id']; + $form_state['cache'] = TRUE; + return $element; +} + +/** + * Autocomplete data selection results. + */ +function rules_ui_form_data_selection_auto_completion($parameter, $form_build_id, $string = '') { + // Get the form and its state from the cache to get the currently edited + // or created element. + $form_state = form_state_defaults(); + $form = form_get_cache($form_build_id, $form_state); + if (!isset($form_state['rules_element'])) { + return; + } + $element = $form_state['rules_element']; + + $params = $element->pluginParameterInfo(); + $matches = array(); + if (isset($params[$parameter])) { + $parts = explode(':', $string); + // Remove the last part as it might be unfinished. + $last_part = array_pop($parts); + $selector = implode(':', $parts); + + // Start with the partly given selector or from scratch. + $result = array(); + if ($selector && $wrapper = $element->applyDataSelector($selector)) { + $result = RulesData::matchingDataSelector($wrapper, $params[$parameter], $selector . ':', 0); + } + elseif (!$selector) { + $result = RulesData::matchingDataSelector($element->availableVariables(), $params[$parameter], '', 0); + } + + foreach ($result as $selector => $info) { + // If we have an uncomplete last part, take it into account now. + $attributes = array(); + if (!$last_part || strpos($selector, $string) === 0) { + $attributes['class'][] = 'rules-dsac-item'; + $attributes['title'] = isset($info['description']) ? strip_tags($info['description']) : ''; + if ($selector[strlen($selector) - 1] == ':') { + $attributes['class'][] = 'rules-dsac-group'; + $text = check_plain($selector) . '... (' . check_plain($info['label']) . ')'; + } + else { + $text = check_plain($selector) . ' (' . check_plain($info['label']) . ')'; + } + $matches[$selector] = "$text"; + } + } + } + drupal_json_output($matches); +} + + +/** + * FAPI validation of an integer element. Copy of the private function + * _element_validate_integer(). + */ +function rules_ui_element_integer_validate($element, &$form_state) {; + $value = $element['#value']; + if (isset($value) && $value !== '' && (!is_numeric($value) || intval($value) != $value)) { + form_error($element, t('%name must be an integer value.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element')))); + } +} + +/** + * FAPI validation of a decimal element. Improved version of the private + * function _element_validate_number(). + */ +function rules_ui_element_decimal_validate($element, &$form_state) { + // Substitute the decimal separator ",". + $value = strtr($element['#value'], ',', '.'); + if ($value != '' && !is_numeric($value)) { + form_error($element, t('%name must be a number.', array('%name' => $element['#title']))); + } + elseif ($value != $element['#value']) { + form_set_value($element, $value, $form_state); + } +} + +/** + * FAPI validation of a date element. Makes sure the specified date format is + * correct and converts date values specifiy a fixed (= non relative) date to + * a timestamp. Relative dates are handled by the date input evaluator. + */ +function rules_ui_element_date_validate($element, &$form_state) { + $value = $element['#value']; + if ($value == '' || (is_numeric($value) && intval($value) == $value)) { + // The value is a timestamp. + return; + } + elseif (is_string($value) && RulesDateInputEvaluator::gmstrtotime($value) === FALSE) { + form_error($element, t('Wrong date format. Specify the date in the format %format.', array('%format' => gmdate('Y-m-d H:i:s', time() + 86400)))); + } + elseif (is_string($value) && RulesDateInputEvaluator::isFixedDateString($value)) { + // As the date string specifies a fixed format, we can convert it now. + $value = RulesDateInputEvaluator::gmstrtotime($value); + form_set_value($element, $value, $form_state); + } +} + +/** + * FAPI process callback for the duration element type. + */ +function rules_ui_element_duration_process($element, &$form_state) { + $element['value'] = array( + '#type' => 'textfield', + '#size' => 8, + '#element_validate' => array('rules_ui_element_integer_validate'), + '#default_value' => $element['#default_value'], + '#required' => !empty($element['#required']), + ); + $element['multiplier'] = array( + '#type' => 'select', + '#options' => rules_ui_element_duration_multipliers(), + '#default_value' => 1, + ); + + // Put the child elements in a container-inline div. + $element['value']['#prefix'] = '
    '; + $element['multiplier']['#suffix'] = '
    '; + + // Set an appropriate multiplier. + if (!empty($element['value']['#default_value'])) { + foreach (array_keys(rules_ui_element_duration_multipliers()) as $m) { + if ($element['value']['#default_value'] % $m == 0) { + $element['multiplier']['#default_value'] = $m; + } + } + // Divide value by the multiplier, so the display is correct. + $element['value']['#default_value'] /= $element['multiplier']['#default_value']; + } + return $element; +} + +/** + * Defines possible duration multiplier. + */ +function rules_ui_element_duration_multipliers() { + return array( + 1 => t('seconds'), + 60 => t('minutes'), + 3600 => t('hours'), + // Just use approximate numbers for days (might last 23h on DST change), + // months and years. + 86400 => t('days'), + 86400 * 30 => t('months'), + 86400 * 30 * 12 => t('years'), + ); +} + +/** + * Helper function to determine the value for a rules duration form + * element. + */ +function rules_ui_element_duration_value($element, $input = FALSE) { + // This runs before child elements are processed, so we cannot calculate the + // value here. But we have to make sure the value is an array, so the form + // API is able to process the children to set their values in the array. Thus + // once the form API has finished processing the element, the value is an + // array containing the child element values. Then finally the after build + // callback converts it back to the numeric value and sets that. + return array(); +} + +/** + * FAPI after build callback for the duration parameter type form. + * Fixes up the form value by applying the multiplier. + */ +function rules_ui_element_duration_after_build($element, &$form_state) { + if ($element['value']['#value'] !== '') { + $element['#value'] = $element['value']['#value'] * $element['multiplier']['#value']; + form_set_value($element, $element['#value'], $form_state); + } + else { + $element['#value'] = NULL; + form_set_value($element, NULL, $form_state); + } + return $element; +} + +/** + * FAPI after build callback to ensure empty form elements result in no value. + */ +function rules_ui_element_fix_empty_after_build($element, &$form_state) { + if (isset($element['#value']) && $element['#value'] === '') { + $element['#value'] = NULL; + form_set_value($element, NULL, $form_state); + } + // Work-a-round for the text_format element. + elseif ($element['#type'] == 'text_format' && !isset($element['value']['#value'])) { + form_set_value($element, NULL, $form_state); + } + return $element; +} + + +/** + * FAPI after build callback for specifying a list of values. + * + * Turns the textual value in an array by splitting the text in chunks using the + * delimiter set at $element['#delimiter']. + */ +function rules_ui_list_textarea_after_build($element, &$form_state) { + $element['#value'] = $element['#value'] ? explode($element['#delimiter'], $element['#value']) : array(); + $element['#value'] = array_map('trim', $element['#value']); + form_set_value($element, $element['#value'], $form_state); + return $element; +} + +/** + * FAPI pre render callback. Turns the value back to a string for rendering. + * + * @see rules_ui_list_textarea_after_build() + */ +function rules_ui_list_textarea_pre_render($element) { + $element['#value'] = implode($element['#delimiter'], $element['#value']); + return $element; +} + +/** + * FAPI callback to validate a list of integers. + */ +function rules_ui_element_integer_list_validate($element, &$form_state) { + foreach ($element['#value'] as $value) { + if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) { + form_error($element, t('Each value must be an integer.')); + } + } +} + +/** + * FAPI callback to validate a token. + */ +function rules_ui_element_token_validate($element) { + $value = $element['#value']; + if (isset($value) && $value !== '' && !entity_property_verify_data_type($value, 'token')) { + form_error($element, t('%name may only contain lowercase letters, numbers, and underscores and has to start with a letter.', array('%name' => isset($element['#title']) ? $element['#title'] : t('Element')))); + } +} + +/** + * FAPI callback to validate a list of tokens. + */ +function rules_ui_element_token_list_validate($element, &$form_state) { + foreach ($element['#value'] as $value) { + if ($value !== '' && !entity_property_verify_data_type($value, 'token')) { + form_error($element, t('Each value may only contain lowercase letters, numbers, and underscores and has to start with a letter.')); + } + } +} + +/** + * FAPI callback to validate a machine readable name. + */ +function rules_ui_element_machine_name_validate($element, &$form_state) { + if ($element['#value'] && !preg_match('!^[a-z0-9_]+$!', $element['#value'])) { + form_error($element, t('Machine-readable names must contain only lowercase letters, numbers, and underscores.')); + } +} + +/** + * FAPI callback to validate the form for editing variable info. + * @see RulesPluginUI::getVariableForm() + */ +function rules_ui_element_variable_form_validate($elements, &$form_state) { + $names = array(); + foreach (element_children($elements['items']) as $item_key) { + $element = &$elements['items'][$item_key]; + if ($element['name']['#value'] || $element['type']['#value'] || $element['label']['#value']) { + foreach (array('name' => t('Machine name'), 'label' => t('Label'), 'type' => t('Data type')) as $key => $title) { + if (!$element[$key]['#value']) { + form_error($element[$key], t('!name field is required.', array('!name' => $title))); + } + } + if (isset($names[$element['name']['#value']])) { + form_error($element['name'], t('The machine-readable name %name is already taken.', array('%name' => $element['name']['#value']))); + } + $names[$element['name']['#value']] = TRUE; + } + } +} + +/** + * Helper to sort elements by their 'weight' key. + */ +function rules_element_sort_helper($a, $b) { + $a += array('weight' => 0); + $b += array('weight' => 0); + if ($a['weight'] == $b['weight']) { + return 0; + } + return ($a['weight'] < $b['weight']) ? -1 : 1; +} + +/** + * Form after build handler to set the static base path. + * + * @see RulesPluginUI::formDefaults() + */ +function rules_form_after_build_restore_base_path($form, &$form_state) { + if (isset($form_state['_rules_base_path'])) { + RulesPluginUI::$basePath = $form_state['_rules_base_path']; + } + return $form; +} + +/** + * AJAX page callback to load tag suggestions. + * + * Largely copied from taxonomy_autocomplete(). + */ +function rules_autocomplete_tags($tags_typed = '') { + // The user enters a comma-separated list of tags. We only autocomplete the + // last tag. + $tags_typed = drupal_explode_tags($tags_typed); + $tag_last = drupal_strtolower(array_pop($tags_typed)); + + $tag_matches = array(); + if ($tag_last != '') { + $query = db_select('rules_tags', 'rt'); + // Do not select already entered terms. + if (!empty($tags_typed)) { + $query->condition('rt.tag', $tags_typed, 'NOT IN'); + } + // Select rows that match by tag name. + $tags_return = $query + ->distinct() + ->fields('rt', array('tag')) + ->condition('rt.tag', '%' . db_like($tag_last) . '%', 'LIKE') + ->groupBy('rt.tag') + ->range(0, 10) + ->execute() + ->fetchCol('rt.tag'); + + $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : ''; + + foreach ($tags_return as $name) { + $n = $name; + // Tag names containing commas or quotes must be wrapped in quotes. + if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) { + $n = '"' . str_replace('"', '""', $name) . '"'; + } + $tag_matches[$prefix . $n] = check_plain($name); + } + } + drupal_json_output($tag_matches); +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/ui/ui.plugins.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/ui/ui.plugins.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,254 @@ +rule = $object; + $this->conditions = $this->rule->conditionContainer(); + } + + public function form(&$form, &$form_state, $options = array()) { + $form_state['rules_element'] = $this->rule; + $label = $this->element->label(); + // Automatically add a counter to unlabelled rules. + if ($label == t('unlabeled') && !$this->element->isRoot() && !empty($options['init'])) { + $parent = $this->element->parentElement(); + $label .= ' ' . count($parent->getIterator()); + } + // Components have already a label. If used inside a rule-set add a label + // though. It's called 'Name' in the UI though. + if (!$this->element->isRoot()) { + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Name'), + '#default_value' => empty($options['init']) ? $label : '', + '#required' => TRUE, + '#weight' => 5, + '#description' => t('A human-readable name shortly describing the rule.'), + ); + } + + $form += array('conditions' => array('#weight' => -5, '#tree' => TRUE)); + $this->conditions->form($form['conditions'], $form_state); + unset($form['conditions']['negate']); + + // Add actions form. + $iterator = new RecursiveIteratorIterator($this->rule->actions(), RecursiveIteratorIterator::SELF_FIRST); + parent::form($form, $form_state, $options, $iterator); + // Hide nested elements during creation. + $form['elements']['#access'] = empty($options['init']); + $form['conditions']['elements']['#access'] = empty($options['init']); + + $form_state['redirect'] = RulesPluginUI::path($this->element->root()->name, 'edit', $this->element); + if (!empty($options['button'])) { + $form['submit']['#value'] = t('Save changes'); + } + } + + /** + * Applies the values of the form to the rule configuration. + */ + function form_extract_values($form, &$form_state) { + $form_values = RulesPluginUI::getFormStateValues($form, $form_state); + // Run condition and action container value extraction. + if (isset($form['conditions'])) { + $this->conditions->extender('RulesConditionContainerUI')->form_extract_values($form['conditions'], $form_state); + } + if (!empty($form_values['label'])) { + $this->element->label = $form_values['label']; + } + parent::form_extract_values($form, $form_state); + } + + + public function operations() { + // When rules are listed only show the edit and delete operations. + $ops = parent::operations(); + $ops['#links'] = array_intersect_key($ops['#links'], array_flip(array('edit', 'delete'))); + return $ops; + } +} + +/** + * Reaction rule specific UI. + */ +class RulesReactionRuleUI extends RulesRuleUI { + + public function form(&$form, &$form_state, $options = array()) { + $form['events'] = array( + '#type' => 'container', + '#weight' => -10, + '#access' => empty($options['init']), + ); + + $form['events']['table'] = array( + '#theme' => 'table', + '#caption' => 'Events', + '#header' => array('Event', 'Operations'), + '#empty' => t('None'), + ); + $form['events']['table']['#attributes']['class'][] = 'rules-elements-table'; + foreach ($this->rule->events() as $event_name) { + $event_handler = rules_get_event_handler($event_name, $this->rule->getEventSettings($event_name)); + + $event_operations = array( + '#theme' => 'links__rules', + '#attributes' => array( + 'class' => array( + 'rules-operations', + 'action-links', + 'rules_rule_event', + ), + ), + '#links' => array( + 'delete_event' => array( + 'title' => t('delete'), + 'href' => RulesPluginUI::path($this->rule->name, 'delete/event/' . $event_name), + 'query' => drupal_get_destination(), + ), + ), + ); + + $form['events']['table']['#rows'][$event_name] = array( + 'data' => array( + $event_handler->summary(), + array('data' => $event_operations), + ), + ); + } + + // Add the "add event" row. + $cell['colspan'] = 3; + $cell['data']['#theme'] = 'links__rules'; + $cell['data']['#attributes']['class'][] = 'rules-operations-add'; + $cell['data']['#attributes']['class'][] = 'action-links'; + $cell['data']['#links']['add_event'] = array( + 'title' => t('Add event'), + 'href' => RulesPluginUI::path($this->rule->name, 'add/event'), + 'query' => drupal_get_destination(), + ); + $form['events']['table']['#rows'][] = array('data' => array($cell), 'class' => array('rules-elements-add')); + + parent::form($form, $form_state, $options); + unset($form['label']); + } + + /** + * Adds the configuration settings form (label, tags, description, ..). + */ + public function settingsForm(&$form, &$form_state) { + parent::settingsForm($form, $form_state); + $form['settings']['active'] = array( + '#type' => 'checkbox', + '#title' => t('Active'), + '#default_value' => !isset($this->rule->active) || $this->rule->active, + ); + $form['settings']['weight'] = array( + '#type' => 'weight', + '#title' => t('Weight'), + '#default_value' => $this->element->weight, + '#weight' => 5, + '#delta' => 10, + '#description' => t('Order rules that react on the same event. Rules with a higher weight are evaluated after rules with less weight.'), + ); + unset($form['settings']['component_provides']); + } + + public function settingsFormExtractValues($form, &$form_state) { + $form_values = RulesPluginUI::getFormStateValues($form['settings'], $form_state); + parent::settingsFormExtractValues($form, $form_state); + $this->rule->active = $form_values['active']; + $this->rule->weight = $form_values['weight']; + } +} + +/** + * Rule set specific UI. + */ +class RulesRuleSetUI extends RulesActionContainerUI { + + public function form(&$form, &$form_state, $options = array(), $iterator = NULL) { + // Pass an iterator just iterating over the rules, thus no further child + // elements will be displayed. + parent::form($form, $form_state, $options, $this->element->getIterator()); + // Only show the add rule link. + $form['elements']['#add']['#links'] = array_intersect_key($form['elements']['#add']['#links'], array('add_rule' => 1)); + $form['elements']['#attributes']['class'][] = 'rules-rule-set'; + $form['elements']['#caption'] = t('Rules'); + } +} + +/** + * UI for Rules loops. + */ +class RulesLoopUI extends RulesActionContainerUI { + + public function form(&$form, &$form_state, $options = array()) { + parent::form($form, $form_state, $options); + $settings = $this->element->settings; + + $form['item'] = array( + '#type' => 'fieldset', + '#title' => t('Current list item'), + '#description' => t('The variable used for holding each list item in the loop. This variable will be available inside the loop only.'), + '#tree' => TRUE, + ); + $form['item']['label'] = array( + '#type' => 'textfield', + '#title' => t('Variable label'), + '#default_value' => $settings['item:label'], + '#required' => TRUE, + ); + $form['item']['var'] = array( + '#type' => 'textfield', + '#title' => t('Variable name'), + '#default_value' => $settings['item:var'], + '#description' => t('The variable name must contain only lowercase letters, numbers, and underscores and must be unique in the current scope.'), + '#element_validate' => array('rules_ui_element_machine_name_validate'), + '#required' => TRUE, + ); + } + + function form_extract_values($form, &$form_state) { + parent::form_extract_values($form, $form_state); + $form_values = RulesPluginUI::getFormStateValues($form, $form_state); + + $this->element->settings['item:var'] = $form_values['item']['var']; + $this->element->settings['item:label'] = $form_values['item']['label']; + } + + public function form_validate($form, &$form_state) { + parent::form_validate($form, $form_state); + + $vars = $this->element->availableVariables(); + $name = $this->element->settings['item:var']; + if (isset($vars[$name])) { + form_error($form['item']['var'], t('The variable name %name is already taken.', array('%name' => $name))); + } + } + + public function buildContent() { + $content = parent::buildContent(); + + $content['description']['item'] = array( + '#caption' => t('List item'), + '#theme' => 'rules_content_group', + ); + $content['description']['item']['var'] = array( + '#theme' => 'rules_variable_view', + '#info' => $this->element->listItemInfo(), + '#name' => $this->element->settings['item:var'], + ); + return $content; + } +} diff -r b28be78d8160 -r ce11bbd8f642 modules/rules/ui/ui.theme.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/rules/ui/ui.theme.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,287 @@ + $element['#depth'])) . drupal_render($element['label']); + + $row[] = drupal_render($element['weight']) . drupal_render($element['parent_id']) . drupal_render($element['element_id']); + $row[] = array('class' => 'rules-operations', 'data' => $element['operations']); + + $row = array('data' => $row) + $element['#attributes']; + $row['class'][] = 'draggable'; + if (!$element['#container']) { + $row['class'][] = 'tabledrag-leaf'; + } + $form['#rows'][] = $row; + } + if (!empty($form['#rows'])) { + drupal_add_tabledrag($form['#attributes']['id'], 'match', 'parent', 'rules-parent-id', 'rules-parent-id', 'rules-element-id', TRUE, 10); + drupal_add_tabledrag($form['#attributes']['id'], 'order', 'sibling', 'rules-element-weight'); + } + else { + $form['#rows'][] = array(array('data' => t('None'), 'colspan' => 3)); + } + if (!empty($form['#add'])) { + $row = array(); + $row[] = array('data' => $form['#add'], 'colspan' => 3); + $form['#rows'][] = array('data' => $row, 'class' => array('rules-elements-add')); + } + + // Add a wrapping div. + return '
    ' . drupal_render($form) . '
    '; +} + +/** + * Themes the rules form for editing the used variables. + * + * @see RulesPluginUI::getVariableForm() + * @ingroup themeable + */ +function theme_rules_ui_variable_form($variables) { + $elements = $variables['element']; + + $table['#theme'] = 'table'; + $table['#header'] = array(t('Data type'), t('Label'), t('Machine name'), t('Usage'), array('data' => t('Weight'), 'class' => array('tabledrag-hide'))); + $table['#attributes']['id'] = 'rules-' . drupal_html_id($elements['#title']) . '-id'; + + foreach (element_children($elements['items']) as $key) { + $element = &$elements['items'][$key]; + // Add special classes to be used for tabledrag.js. + $element['weight']['#attributes']['class'] = array('rules-element-weight'); + + $row = array(); + $row[] = array('data' => $element['type']); + $row[] = array('data' => $element['label']); + $row[] = array('data' => $element['name']); + $row[] = array('data' => $element['usage']); + $row[] = array('data' => $element['weight']); + $row = array('data' => $row) + $element['#attributes']; + $row['class'][] = 'draggable'; + $table['#rows'][] = $row; + } + $elements['items']['#printed'] = TRUE; + if (!empty($table['#rows'])) { + drupal_add_tabledrag($table['#attributes']['id'], 'order', 'sibling', 'rules-element-weight'); + } + + // Theme it like a form item, but with the description above the content. + $attributes['class'][] = 'form-item'; + $attributes['class'][] = 'rules-variables-form'; + + $output = '' . "\n"; + $output .= theme('form_element_label', $variables); + if (!empty($elements['#description'])) { + $output .= '
    ' . $elements['#description'] . "
    \n"; + } + $output .= ' ' . drupal_render($table) . "\n"; + // Add in any further children elements. + foreach (element_children($elements, TRUE) as $key) { + $output .= drupal_render($elements[$key]); + } + $output .= "\n"; + return $output; +} + +/** + * Themes a view of multiple configuration items. + * @ingroup themeable + */ +function theme_rules_content_group($variables) { + $element = $variables['element']; + $output = array(); + foreach (element_children($element) as $key) { + $output[] = drupal_render($element[$key]); + } + $output = array_filter($output); + $heading = !empty($element['#caption']) ? "" . $element['#caption'] . ': ' : ''; + if (!empty($output)) { + $element['#attributes']['class'][] = 'rules-element-content-group'; + return '' . $heading . implode(', ', $output) . ''; + } +} + +/** + * Themes the view of a single parameter configuration. + * @ingroup themeable + */ +function theme_rules_parameter_configuration($variables) { + $element = $variables['element']; + $content = drupal_render_children($element); + // Add the full content to the span's title, but don't use drupal_attributes + // for that as this would invoke check_plain() again. + $title = strip_tags($content); + $element['#attributes']['class'][] = 'rules-parameter-configuration'; + $attributes = drupal_attributes($element['#attributes']) . " title='$title'"; + + $label_attributes['class'][] = 'rules-parameter-label'; + if (!empty($element['#info']['description'])) { + $label_attributes['title'] = $element['#info']['description']; + } + $label_attributes = drupal_attributes($label_attributes); + + $output = "" . check_plain($element['#info']['label']) . ': '; + $output .= "" . truncate_utf8($content, 30, TRUE, TRUE) . ""; + return $output; +} + +/** + * Themes info about variables. + * @ingroup themeable + */ +function theme_rules_variable_view($variables) { + $element = $variables['element']; + + $label_attributes['class'][] = 'rules-variable-label'; + $label_attributes['title'] = ''; + if (!empty($element['#info']['description'])) { + $label_attributes['title'] = $element['#info']['description'] . ' '; + } + $label_attributes['title'] .= t('Data type: !type', array('!type' => $element['#info']['type'])); + $label_attributes = drupal_attributes($label_attributes); + + $output = check_plain($element['#info']['label']); + $output .= ' (' . check_plain($element['#name']) . ')'; + return "" . $output . ''; +} + +/** + * Themes help for using the data selector. + * @ingroup themeable + */ +function theme_rules_data_selector_help($variables) { + $variables_info = $variables['variables']; + $param_info = $variables['parameter']; + + $render = array( + '#type' => 'fieldset', + '#title' => t('Data selectors'), + '#pre_render' => array(), + '#attributes' => array(), + ); + // Make it manually collapsible as we cannot use #collapsible without the + // FAPI element processor. + $render['#attached']['js'][] = 'misc/collapse.js'; + $render['#attributes']['class'][] = 'collapsible'; + $render['#attributes']['class'][] = 'collapsed'; + + $render['table'] = array( + '#theme' => 'table', + '#header' => array(t('Selector'), t('Label'), t('Description')), + ); + foreach (RulesData::matchingDataSelector($variables_info, $param_info) as $selector => $info) { + $info += array('label' => '', 'description' => ''); + $render['table']['#rows'][] = array(check_plain($selector), check_plain(drupal_ucfirst($info['label'])), check_plain($info['description'])); + } + return drupal_render($render); +} + +/** + * Themes the rules log debug output. + * @ingroup themeable + */ +function theme_rules_log($variables) { + $element = $variables['element']; + drupal_add_css(drupal_get_path('module', 'rules') . '/ui/rules.ui.css'); + // Add jquery ui core css and functions, which are needed for the icons. + drupal_add_library('system', 'ui'); + drupal_add_js(drupal_get_path('module', 'rules') . '/ui/rules.debug.js'); + + $output = '
    '; + + $output .= '

    '; + $output .= ''; + $output .= ' '; + $output .= t('Rules evaluation log'); + $output .= ''; + $output .= ''; + $output .= '-' . t('Open all') . '-'; + $output .= '

    '; + + $output .= '
    '; + $output .= $element['#children']; + $output .= '
    '; + $output .= '
    '; + + return $output; +} + +/** + * Theme rules debug log elements. + * @ingroup themeable + */ +function theme_rules_debug_element($variables) { + $output = '
    '; + $output .= ''; + $output .= $variables['log']; + $output .= '
    '; + return $output; +} + +/** + * Themes rules autocomplete forms. + * @ingroup themeable + */ +function theme_rules_autocomplete($variables) { + $element = $variables['element']; + drupal_add_js(drupal_get_path('module', 'rules') . '/ui/rules.autocomplete.js'); + drupal_add_library('system', 'ui.autocomplete'); + drupal_add_library('system', 'ui.button'); + + $element['#attributes']['type'] = 'text'; + element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength')); + _form_set_class($element, array('form-text', 'rules-autocomplete')); + + $id = $element['#attributes']['id']; + $id_button = drupal_html_id($id . '-button'); + + $js_vars['rules_autocomplete'][$id] = array( + 'inputId' => $id, + 'source' => url($element['#autocomplete_path'], array('absolute' => TRUE)), + ); + drupal_add_js($js_vars, 'setting'); + + $output = '
    '; + $output .= ''; + $output .= '
    '; + + return $output; +} + +/** + * General theme function for displaying settings related help. + * @ingroup themeable + */ +function theme_rules_settings_help($variables) { + $text = $variables['text']; + $heading = $variables['heading']; + return "

    $heading: $text

    "; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/.DS_Store Binary file sites/all/.DS_Store has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/.DS_Store Binary file sites/all/libraries/ARC2/.DS_Store has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/.gitignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/.gitignore Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,4 @@ +*.DS_Store +plugins/* +triggers/* +tests/coverage/* diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/ARC2.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/ARC2.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,484 @@ + + * @package ARC2 + */ + +/* E_STRICT hack */ +if (function_exists('date_default_timezone_get')) { + date_default_timezone_set(@date_default_timezone_get()); +} + +class ARC2 { + + static function getVersion() { + return '2011-12-01'; + } + + /* */ + + static function getIncPath($f = '') { + $r = realpath(dirname(__FILE__)) . '/'; + $dirs = array( + 'plugin' => 'plugins', + 'trigger' => 'triggers', + 'store' => 'store', + 'serializer' => 'serializers', + 'extractor' => 'extractors', + 'sparqlscript' => 'sparqlscript', + 'parser' => 'parsers', + ); + foreach ($dirs as $k => $dir) { + if (preg_match('/' . $k . '/i', $f)) { + return $r . $dir . '/'; + } + } + return $r; + } + + static function getScriptURI() { + if (isset($_SERVER) && (isset($_SERVER['SERVER_NAME']) || isset($_SERVER['HTTP_HOST']))) { + $proto = preg_replace('/^([a-z]+)\/.*$/', '\\1', strtolower($_SERVER['SERVER_PROTOCOL'])); + $port = $_SERVER['SERVER_PORT']; + $server = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']; + $script = $_SERVER['SCRIPT_NAME']; + /* https */ + if (($proto == 'http') && $port == 443) { + $proto = 'https'; + $port = 80; + } + return $proto . '://' . $server . ($port != 80 ? ':' . $port : '') . $script; + } + elseif (isset($_SERVER['SCRIPT_FILENAME'])) { + return 'file://' . realpath($_SERVER['SCRIPT_FILENAME']); + } + return 'http://localhost/unknown_path'; + } + + static function getRequestURI() { + if (isset($_SERVER) && isset($_SERVER['REQUEST_URI'])) { + return preg_replace('/^([a-z]+)\/.*$/', '\\1', strtolower($_SERVER['SERVER_PROTOCOL'])) . + '://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME']) . + ($_SERVER['SERVER_PORT'] != 80 ? ':' . $_SERVER['SERVER_PORT'] : '') . + $_SERVER['REQUEST_URI']; + } + return ARC2::getScriptURI(); + } + + static function inc($f, $path = '') { + $prefix = 'ARC2'; + if (preg_match('/^([^\_]+)\_(.*)$/', $f, $m)) { + $prefix = $m[1]; + $f = $m[2]; + } + $inc_path = $path ? $path : ARC2::getIncPath($f); + $path = $inc_path . $prefix . '_' . urlencode($f) . '.php'; + if (file_exists($path)) return include_once($path); + /* safe-mode hack */ + if (@include_once($path)) return 1; + /* try other path */ + if ($prefix != 'ARC2') { + $path = $inc_path . strtolower($prefix) . '/' . $prefix . '_' . urlencode($f) . '.php'; + if (file_exists($path)) return include_once($path); + /* safe-mode hack */ + if (@include_once($path)) return 1; + } + return 0; + } + + /* */ + + static function mtime(){ + return microtime(true); + } + + static function x($re, $v, $options = 'si') { + return preg_match("/^\s*" . $re . "(.*)$/" . $options, $v, $m) ? $m : false; + } + + /* */ + + static function getFormat($val, $mtype = '', $ext = '') { + ARC2::inc('getFormat'); + return ARC2_getFormat($val, $mtype, $ext); + } + + static function getPreferredFormat($default = 'plain') { + ARC2::inc('getPreferredFormat'); + return ARC2_getPreferredFormat($default); + } + + /* */ + + static function toUTF8($v) { + if (urlencode($v) === $v) return $v; + //if (utf8_decode($v) == $v) return $v; + $v = (strpos(utf8_decode(str_replace('?', '', $v)), '?') === false) ? utf8_decode($v) : $v; + /* custom hacks, mainly caused by bugs in PHP's json_decode */ + $mappings = array( + '%18' => '‘', + '%19' => '’', + '%1C' => '“', + '%1D' => '”', + '%1E' => '„', + '%10' => '‐', + '%12' => '−', + '%13' => '–', + '%14' => '—', + '%26' => '&', + ); + $froms = array_keys($mappings); + $tos = array_values($mappings); + foreach ($froms as $i => $from) $froms[$i] = urldecode($from); + $v = str_replace($froms, $tos, $v); + /* utf8 tweaks */ + return preg_replace_callback('/([\x00-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xf7][\x80-\xbf]{3}|[\xf8-\xfb][\x80-\xbf]{4}|[\xfc-\xfd][\x80-\xbf]{5}|[^\x00-\x7f])/', array('ARC2', 'getUTF8Char'), $v); + } + + static function getUTF8Char($v) { + $val = $v[1]; + if (strlen(trim($val)) === 1) return utf8_encode($val); + if (preg_match('/^([\x00-\x7f])(.+)/', $val, $m)) return $m[1] . ARC2::toUTF8($m[2]); + return $val; + } + + /* */ + + static function splitURI($v) { + /* the following namespaces may lead to conflated URIs, + * we have to set the split position manually + */ + if (strpos($v, 'www.w3.org')) { + $specials = array( + 'http://www.w3.org/XML/1998/namespace', + 'http://www.w3.org/2005/Atom', + 'http://www.w3.org/1999/xhtml', + ); + foreach ($specials as $ns) { + if (strpos($v, $ns) === 0) { + $local_part = substr($v, strlen($ns)); + if (!preg_match('/^[\/\#]/', $local_part)) { + return array($ns, $local_part); + } + } + } + } + /* auto-splitting on / or # */ + //$re = '^(.*?)([A-Z_a-z][-A-Z_a-z0-9.]*)$'; + if (preg_match('/^(.*[\/\#])([^\/\#]+)$/', $v, $m)) return array($m[1], $m[2]); + /* auto-splitting on last special char, e.g. urn:foo:bar */ + if (preg_match('/^(.*[\:\/])([^\:\/]+)$/', $v, $m)) return array($m[1], $m[2]); + return array($v, ''); + } + + /* */ + + static function getSimpleIndex($triples, $flatten_objects = 1, $vals = '') { + $r = array(); + foreach ($triples as $t) { + $skip_t = 0; + foreach (array('s', 'p', 'o') as $term) { + $$term = $t[$term]; + /* template var */ + if (isset($t[$term . '_type']) && ($t[$term . '_type'] == 'var')) { + $val = isset($vals[$$term]) ? $vals[$$term] : ''; + $skip_t = isset($vals[$$term]) ? $skip_t : 1; + $type = ''; + $type = !$type && isset($vals[$$term . ' type']) ? $vals[$$term . ' type'] : $type; + $type = !$type && preg_match('/^\_\:/', $val) ? 'bnode' : $type; + if ($term == 'o') { + $type = !$type && (preg_match('/\s/s', $val) || !preg_match('/\:/', $val)) ? 'literal' : $type; + $type = !$type && !preg_match('/[\/]/', $val) ? 'literal' : $type; + } + $type = !$type ? 'uri' : $type; + $t[$term . '_type'] = $type; + $$term = $val; + } + } + if ($skip_t) { + continue; + } + if (!isset($r[$s])) $r[$s] = array(); + if (!isset($r[$s][$p])) $r[$s][$p] = array(); + if ($flatten_objects) { + if (!in_array($o, $r[$s][$p])) $r[$s][$p][] = $o; + } + else { + $o = array('value' => $o); + foreach (array('lang', 'type', 'datatype') as $suffix) { + if (isset($t['o_' . $suffix]) && $t['o_' . $suffix]) { + $o[$suffix] = $t['o_' . $suffix]; + } + elseif (isset($t['o ' . $suffix]) && $t['o ' . $suffix]) { + $o[$suffix] = $t['o ' . $suffix]; + } + } + if (!in_array($o, $r[$s][$p])) { + $r[$s][$p][] = $o; + } + } + } + return $r; + } + + static function getTriplesFromIndex($index) { + $r = array(); + foreach ($index as $s => $ps) { + foreach ($ps as $p => $os) { + foreach ($os as $o) { + $r[] = array( + 's' => $s, + 'p' => $p, + 'o' => $o['value'], + 's_type' => preg_match('/^\_\:/', $s) ? 'bnode' : 'uri', + 'o_type' => $o['type'], + 'o_datatype' => isset($o['datatype']) ? $o['datatype'] : '', + 'o_lang' => isset($o['lang']) ? $o['lang'] : '', + ); + } + } + } + return $r; + } + + static function getMergedIndex() { + $r = array(); + foreach (func_get_args() as $index) { + foreach ($index as $s => $ps) { + if (!isset($r[$s])) $r[$s] = array(); + foreach ($ps as $p => $os) { + if (!isset($r[$s][$p])) $r[$s][$p] = array(); + foreach ($os as $o) { + if (!in_array($o, $r[$s][$p])) { + $r[$s][$p][] = $o; + } + } + } + } + } + return $r; + } + + static function getCleanedIndex() {/* removes triples from a given index */ + $indexes = func_get_args(); + $r = $indexes[0]; + for ($i = 1, $i_max = count($indexes); $i < $i_max; $i++) { + $index = $indexes[$i]; + foreach ($index as $s => $ps) { + if (!isset($r[$s])) continue; + foreach ($ps as $p => $os) { + if (!isset($r[$s][$p])) continue; + $r_os = $r[$s][$p]; + $new_os = array(); + foreach ($r_os as $r_o) { + $r_o_val = is_array($r_o) ? $r_o['value'] : $r_o; + $keep = 1; + foreach ($os as $o) { + $del_o_val = is_array($o) ? $o['value'] : $o; + if ($del_o_val == $r_o_val) { + $keep = 0; + break; + } + } + if ($keep) { + $new_os[] = $r_o; + } + } + if ($new_os) { + $r[$s][$p] = $new_os; + } + else { + unset($r[$s][$p]); + } + } + } + } + /* check r */ + $has_data = 0; + foreach ($r as $s => $ps) { + if ($ps) { + $has_data = 1; + break; + } + } + return $has_data ? $r : array(); + } + + /* */ + + static function getStructType($v) { + /* string */ + if (is_string($v)) return 'string'; + /* flat array, numeric keys */ + if (in_array(0, array_keys($v))) {/* numeric keys */ + /* simple array */ + if (!is_array($v[0])) return 'array'; + /* triples */ + //if (isset($v[0]) && isset($v[0]['s']) && isset($v[0]['p'])) return 'triples'; + if (in_array('p', array_keys($v[0]))) return 'triples'; + } + /* associative array */ + else { + /* index */ + foreach ($v as $s => $ps) { + if (!is_array($ps)) break; + foreach ($ps as $p => $os) { + if (!is_array($os) || !is_array($os[0])) break; + if (in_array('value', array_keys($os[0]))) return 'index'; + } + } + } + /* array */ + return 'array'; + } + + /* */ + + static function getComponent($name, $a = '', $caller = '') { + ARC2::inc($name); + $prefix = 'ARC2'; + if (preg_match('/^([^\_]+)\_(.+)$/', $name, $m)) { + $prefix = $m[1]; + $name = $m[2]; + } + $cls = $prefix . '_' . $name; + if (!$caller) $caller = new stdClass(); + return new $cls($a, $caller); + } + + /* graph */ + + static function getGraph($a = '') { + return ARC2::getComponent('Graph', $a); + } + + /* resource */ + + static function getResource($a = '') { + return ARC2::getComponent('Resource', $a); + } + + /* reader */ + + static function getReader($a = '') { + return ARC2::getComponent('Reader', $a); + } + + /* parsers */ + + static function getParser($prefix, $a = '') { + return ARC2::getComponent($prefix . 'Parser', $a); + } + + static function getRDFParser($a = '') { + return ARC2::getParser('RDF', $a); + } + + static function getRDFXMLParser($a = '') { + return ARC2::getParser('RDFXML', $a); + } + + static function getTurtleParser($a = '') { + return ARC2::getParser('Turtle', $a); + } + + static function getRSSParser($a = '') { + return ARC2::getParser('RSS', $a); + } + + static function getSemHTMLParser($a = '') { + return ARC2::getParser('SemHTML', $a); + } + + static function getSPARQLParser($a = '') { + return ARC2::getComponent('SPARQLParser', $a); + } + + static function getSPARQLPlusParser($a = '') { + return ARC2::getParser('SPARQLPlus', $a); + } + + static function getSPARQLXMLResultParser($a = '') { + return ARC2::getParser('SPARQLXMLResult', $a); + } + + static function getJSONParser($a = '') { + return ARC2::getParser('JSON', $a); + } + + static function getSGAJSONParser($a = '') { + return ARC2::getParser('SGAJSON', $a); + } + + static function getCBJSONParser($a = '') { + return ARC2::getParser('CBJSON', $a); + } + + static function getSPARQLScriptParser($a = '') { + return ARC2::getParser('SPARQLScript', $a); + } + + /* store */ + + static function getStore($a = '', $caller = '') { + return ARC2::getComponent('Store', $a, $caller); + } + + static function getStoreEndpoint($a = '', $caller = '') { + return ARC2::getComponent('StoreEndpoint', $a, $caller); + } + + static function getRemoteStore($a = '', $caller = '') { + return ARC2::getComponent('RemoteStore', $a, $caller); + } + + static function getMemStore($a = '') { + return ARC2::getComponent('MemStore', $a); + } + + /* serializers */ + + static function getSer($prefix, $a = '') { + return ARC2::getComponent($prefix . 'Serializer', $a); + } + + static function getTurtleSerializer($a = '') { + return ARC2::getSer('Turtle', $a); + } + + static function getRDFXMLSerializer($a = '') { + return ARC2::getSer('RDFXML', $a); + } + + static function getNTriplesSerializer($a = '') { + return ARC2::getSer('NTriples', $a); + } + + static function getRDFJSONSerializer($a = '') { + return ARC2::getSer('RDFJSON', $a); + } + + static function getPOSHRDFSerializer($a = '') {/* deprecated */ + return ARC2::getSer('POSHRDF', $a); + } + + static function getMicroRDFSerializer($a = '') { + return ARC2::getSer('MicroRDF', $a); + } + + static function getRSS10Serializer($a = '') { + return ARC2::getSer('RSS10', $a); + } + + /* sparqlscript */ + + static function getSPARQLScriptProcessor($a = '') { + return ARC2::getComponent('SPARQLScriptProcessor', $a); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/ARC2_Class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/ARC2_Class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,533 @@ + + * @homepage + * @package ARC2 + */ + +class ARC2_Class { + + function __construct($a, &$caller) { + $this->a = is_array($a) ? $a : array(); + $this->caller = $caller; + $this->__init(); + } + + function __init() {/* base, time_limit */ + if (!$_POST && isset($GLOBALS['HTTP_RAW_POST_DATA'])) parse_str($GLOBALS['HTTP_RAW_POST_DATA'], $_POST); /* php5 bug */ + $this->inc_path = ARC2::getIncPath(); + $this->ns_count = 0; + $rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->nsp = array($rdf => 'rdf'); + $this->used_ns = array($rdf); + $this->ns = array_merge(array('rdf' => $rdf), $this->v('ns', array(), $this->a)); + + $this->base = $this->v('base', ARC2::getRequestURI(), $this->a); + $this->errors = array(); + $this->warnings = array(); + $this->adjust_utf8 = $this->v('adjust_utf8', 0, $this->a); + $this->max_errors = $this->v('max_errors', 25, $this->a); + $this->has_pcre_unicode = @preg_match('/\pL/u', 'test');/* \pL = block/point which is a Letter */ + } + + /* */ + + function v($name, $default = false, $o = false) {/* value if set */ + if ($o === false) $o = $this; + if (is_array($o)) { + return isset($o[$name]) ? $o[$name] : $default; + } + return isset($o->$name) ? $o->$name : $default; + } + + function v1($name, $default = false, $o = false) {/* value if 1 (= not empty) */ + if ($o === false) $o = $this; + if (is_array($o)) { + return (isset($o[$name]) && $o[$name]) ? $o[$name] : $default; + } + return (isset($o->$name) && $o->$name) ? $o->$name : $default; + } + + function m($name, $a = false, $default = false, $o = false) {/* call method */ + if ($o === false) $o = $this; + return method_exists($o, $name) ? $o->$name($a) : $default; + } + + /* */ + + function camelCase($v, $lc_first = 0, $keep_boundaries = 0) { + $r = ucfirst($v); + while (preg_match('/^(.*)[^a-z0-9](.*)$/si', $r, $m)) { + /* don't fuse 2 upper-case chars */ + if ($keep_boundaries && $m[1]) { + $boundary = substr($m[1], -1); + if (strtoupper($boundary) == $boundary) $m[1] .= 'CAMELCASEBOUNDARY'; + } + $r = $m[1] . ucfirst($m[2]); + } + $r = str_replace('CAMELCASEBOUNDARY', '_', $r); + if ((strlen($r) > 1) && $lc_first && !preg_match('/[A-Z]/', $r[1])) $r = strtolower($r[0]) . substr($r, 1); + return $r; + } + + function deCamelCase($v, $uc_first = 0) { + $r = str_replace('_', ' ', $v); + $r = preg_replace('/([a-z0-9])([A-Z])/e', '"\\1 " . strtolower("\\2")', $r); + return $uc_first ? ucfirst($r) : $r; + } + + /** + * Tries to extract a somewhat human-readable label from a URI. + */ + + function extractTermLabel($uri, $loops = 0) { + list($ns, $r) = $this->splitURI($uri); + /* encode apostrophe + s */ + $r = str_replace('%27s', '_apostrophes_', $r); + /* normalize */ + $r = $this->deCamelCase($this->camelCase($r, 1, 1)); + /* decode apostrophe + s */ + $r = str_replace(' apostrophes ', "'s ", $r); + /* typical RDF non-info URI */ + if (($loops < 1) && preg_match('/^(self|it|this|me)$/i', $r)) { + return $this->extractTermLabel(preg_replace('/\#.+$/', '', $uri), $loops + 1); + } + /* trailing hash or slash */ + if ($uri && !$r && ($loops < 2)) { + return $this->extractTermLabel(preg_replace('/[\#\/]$/', '', $uri), $loops + 1); + } + /* a de-camel-cased URL (will look like "www example com") */ + if (preg_match('/^www (.+ [a-z]{2,4})$/', $r, $m)) { + return $this->getPrettyURL($uri); + } + return $r; + } + + /** + * Generates a less ugly in-your-face URL. + */ + + function getPrettyURL($r) { + $r = rtrim($r, '/'); + $r = preg_replace('/^https?\:\/\/(www\.)?/', '', $r); + return $r; + } + + /* */ + + function addError($v) { + if (!in_array($v, $this->errors)) { + $this->errors[] = $v; + } + if ($this->caller && method_exists($this->caller, 'addError')) { + $glue = strpos($v, ' in ') ? ' via ' : ' in '; + $this->caller->addError($v . $glue . get_class($this)); + } + if (count($this->errors) > $this->max_errors) { + die('Too many errors (limit: ' . $this->max_errors . '): ' . print_r($this->errors, 1)); + } + return false; + } + + function getErrors() { + return $this->errors; + } + + function getWarnings() { + return $this->warnings; + } + + function resetErrors() { + $this->errors = array(); + if ($this->caller && method_exists($this->caller, 'resetErrors')) { + $this->caller->resetErrors(); + } + } + + /* */ + + function splitURI($v) { + return ARC2::splitURI($v); + } + + /* */ + + function getPName($v, $connector = ':') { + /* is already a pname */ + $ns = $this->getPNameNamespace($v, $connector); + if ($ns) { + if (!in_array($ns, $this->used_ns)) $this->used_ns[] = $ns; + return $v; + } + /* new pname */ + $parts = $this->splitURI($v); + if ($parts) { + /* known prefix */ + foreach ($this->ns as $prefix => $ns) { + if ($parts[0] == $ns) { + if (!in_array($ns, $this->used_ns)) $this->used_ns[] = $ns; + return $prefix . $connector . $parts[1]; + } + } + /* new prefix */ + $prefix = $this->getPrefix($parts[0]); + return $prefix . $connector . $parts[1]; + } + return $v; + } + + function getPNameNamespace($v, $connector = ':') { + $re = '/^([a-z0-9\_\-]+)\:([a-z0-9\_\-\.\%]+)$/i'; + if ($connector != ':') { + $connectors = array('\:', '\-', '\_', '\.'); + $chars = join('', array_diff($connectors, array($connector))); + $re = '/^([a-z0-9' . $chars . ']+)\\' . $connector . '([a-z0-9\_\-\.\%]+)$/i'; + } + if (!preg_match($re, $v, $m)) return 0; + if (!isset($this->ns[$m[1]])) return 0; + return $this->ns[$m[1]]; + } + + function setPrefix($prefix, $ns) { + $this->ns[$prefix] = $ns; + $this->nsp[$ns] = $prefix; + return $this; + } + + function getPrefix($ns) { + if (!isset($this->nsp[$ns])) { + $this->ns['ns' . $this->ns_count] = $ns; + $this->nsp[$ns] = 'ns' . $this->ns_count; + $this->ns_count++; + } + if (!in_array($ns, $this->used_ns)) $this->used_ns[] = $ns; + return $this->nsp[$ns]; + } + + function expandPName($v, $connector = ':') { + $re = '/^([a-z0-9\_\-]+)\:([a-z0-9\_\-\.\%]+)$/i'; + if ($connector != ':') { + $connectors = array(':', '-', '_', '.'); + $chars = '\\' . join('\\', array_diff($connectors, array($connector))); + $re = '/^([a-z0-9' . $chars . ']+)\\' . $connector . '([a-z0-9\_\-\.\%]+)$/Ui'; + } + if (preg_match($re, $v, $m) && isset($this->ns[$m[1]])) { + return $this->ns[$m[1]] . $m[2]; + } + return $v; + } + + function expandPNames($index) { + $r = array(); + foreach ($index as $s => $ps) { + $s = $this->expandPName($s); + $r[$s] = array(); + foreach ($ps as $p => $os) { + $p = $this->expandPName($p); + if (!is_array($os)) $os = array($os); + foreach ($os as $i => $o) { + if (!is_array($o)) { + $o_val = $this->expandPName($o); + $o_type = preg_match('/^[a-z]+\:[^\s\<\>]+$/si', $o_val) ? 'uri' : 'literal'; + $o = array('value' => $o_val, 'type' => $o_type); + } + $os[$i] = $o; + } + $r[$s][$p] = $os; + } + } + return $r; + } + + /* */ + + function calcURI($path, $base = "") { + /* quick check */ + if (preg_match("/^[a-z0-9\_]+\:/i", $path)) {/* abs path or bnode */ + return $path; + } + if (preg_match('/^\$\{.*\}/', $path)) {/* placeholder, assume abs URI */ + return $path; + } + if (preg_match("/^\/\//", $path)) {/* net path, assume http */ + return 'http:' . $path; + } + /* other URIs */ + $base = $base ? $base : $this->base; + $base = preg_replace('/\#.*$/', '', $base); + if ($path === true) {/* empty (but valid) URIref via turtle parser: <> */ + return $base; + } + $path = preg_replace("/^\.\//", '', $path); + $root = preg_match('/(^[a-z0-9]+\:[\/]{1,3}[^\/]+)[\/|$]/i', $base, $m) ? $m[1] : $base; /* w/o trailing slash */ + $base .= ($base == $root) ? '/' : ''; + if (preg_match('/^\//', $path)) {/* leading slash */ + return $root . $path; + } + if (!$path) { + return $base; + } + if (preg_match('/^([\#\?])/', $path, $m)) { + return preg_replace('/\\' .$m[1]. '.*$/', '', $base) . $path; + } + if (preg_match('/^(\&)(.*)$/', $path, $m)) {/* not perfect yet */ + return preg_match('/\?/', $base) ? $base . $m[1] . $m[2] : $base . '?' . $m[2]; + } + if (preg_match("/^[a-z0-9]+\:/i", $path)) {/* abs path */ + return $path; + } + /* rel path: remove stuff after last slash */ + $base = substr($base, 0, strrpos($base, '/')+1); + /* resolve ../ */ + while (preg_match('/^(\.\.\/)(.*)$/', $path, $m)) { + $path = $m[2]; + $base = ($base == $root.'/') ? $base : preg_replace('/^(.*\/)[^\/]+\/$/', '\\1', $base); + } + return $base . $path; + } + + /* */ + + function calcBase($path) { + $r = $path; + $r = preg_replace('/\#.*$/', '', $r);/* remove hash */ + $r = preg_replace('/^\/\//', 'http://', $r);/* net path (//), assume http */ + if (preg_match('/^[a-z0-9]+\:/', $r)) {/* scheme, abs path */ + while (preg_match('/^(.+\/)(\.\.\/.*)$/U', $r, $m)) { + $r = $this->calcURI($m[1], $m[2]); + } + return $r; + } + return 'file://' . realpath($r);/* real path */ + } + + /* */ + + function getResource($uri, $store_or_props = '') { + $res = ARC2::getResource($this->a); + $res->setURI($uri); + if (is_array($store_or_props)) { + $res->setProps($store_or_props); + } + else { + $res->setStore($store_or_props); + } + return $res; + } + + function toIndex($v) { + if (is_array($v)) { + if (isset($v[0]) && isset($v[0]['s'])) return ARC2::getSimpleIndex($v, 0); + return $v; + } + $parser = ARC2::getRDFParser($this->a); + if ($v && !preg_match('/\s/', $v)) {/* assume graph URI */ + $parser->parse($v); + } + else { + $parser->parse('', $v); + } + return $parser->getSimpleIndex(0); + } + + function toTriples($v) { + if (is_array($v)) { + if (isset($v[0]) && isset($v[0]['s'])) return $v; + return ARC2::getTriplesFromIndex($v); + } + $parser = ARC2::getRDFParser($this->a); + if ($v && !preg_match('/\s/', $v)) {/* assume graph URI */ + $parser->parse($v); + } + else { + $parser->parse('', $v); + } + return $parser->getTriples(); + } + + /* */ + + function toNTriples($v, $ns = '', $raw = 0) { + ARC2::inc('NTriplesSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_NTriplesSerializer(array_merge($this->a, array('ns' => $ns)), $this); + return (isset($v[0]) && isset($v[0]['s'])) ? $ser->getSerializedTriples($v, $raw) : $ser->getSerializedIndex($v, $raw); + } + + function toTurtle($v, $ns = '', $raw = 0) { + ARC2::inc('TurtleSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_TurtleSerializer(array_merge($this->a, array('ns' => $ns)), $this); + return (isset($v[0]) && isset($v[0]['s'])) ? $ser->getSerializedTriples($v, $raw) : $ser->getSerializedIndex($v, $raw); + } + + function toRDFXML($v, $ns = '', $raw = 0) { + ARC2::inc('RDFXMLSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_RDFXMLSerializer(array_merge($this->a, array('ns' => $ns)), $this); + return (isset($v[0]) && isset($v[0]['s'])) ? $ser->getSerializedTriples($v, $raw) : $ser->getSerializedIndex($v, $raw); + } + + function toRDFJSON($v, $ns = '') { + ARC2::inc('RDFJSONSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_RDFJSONSerializer(array_merge($this->a, array('ns' => $ns)), $this); + return (isset($v[0]) && isset($v[0]['s'])) ? $ser->getSerializedTriples($v) : $ser->getSerializedIndex($v); + } + + function toRSS10($v, $ns = '') { + ARC2::inc('RSS10Serializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_RSS10Serializer(array_merge($this->a, array('ns' => $ns)), $this); + return (isset($v[0]) && isset($v[0]['s'])) ? $ser->getSerializedTriples($v) : $ser->getSerializedIndex($v); + } + + function toLegacyXML($v, $ns = '') { + ARC2::inc('LegacyXMLSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_LegacyXMLSerializer(array_merge($this->a, array('ns' => $ns)), $this); + return $ser->getSerializedArray($v); + } + + function toLegacyJSON($v, $ns = '') { + ARC2::inc('LegacyJSONSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_LegacyJSONSerializer(array_merge($this->a, array('ns' => $ns)), $this); + return $ser->getSerializedArray($v); + } + + function toLegacyHTML($v, $ns = '') { + ARC2::inc('LegacyHTMLSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $ser = new ARC2_LegacyHTMLSerializer(array_merge($this->a, array('ns' => $ns)), $this); + return $ser->getSerializedArray($v); + } + + function toHTML($v, $ns = '', $label_store = '') { + ARC2::inc('MicroRDFSerializer'); + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $conf = array_merge($this->a, array('ns' => $ns)); + if ($label_store) $conf['label_store'] = $label_store; + $ser = new ARC2_MicroRDFSerializer($conf, $this); + return (isset($v[0]) && isset($v[0]['s'])) ? $ser->getSerializedTriples($v) : $ser->getSerializedIndex($v); + } + + /* */ + + function getFilledTemplate($t, $vals, $g = '') { + $parser = ARC2::getTurtleParser(); + $parser->parse($g, $this->getTurtleHead() . $t); + return $parser->getSimpleIndex(0, $vals); + } + + function getTurtleHead() { + $r = ''; + $ns = $this->v('ns', array(), $this->a); + foreach ($ns as $k => $v) { + $r .= "@prefix " . $k . ": <" .$v. "> .\n"; + } + return $r; + } + + function completeQuery($q, $ns = '') { + if (!$ns) $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $added_prefixes = array(); + $prologue = ''; + foreach ($ns as $k => $v) { + $k = rtrim($k, ':'); + if (in_array($k, $added_prefixes)) continue; + if (preg_match('/(^|\s)' . $k . ':/s', $q) && !preg_match('/PREFIX\s+' . $k . '\:/is', $q)) { + $prologue .= "\n" . 'PREFIX ' . $k . ': <' . $v . '>'; + } + $added_prefixes[] = $k; + } + return $prologue . "\n" . $q; + } + + /* */ + + function toUTF8($str) { + return $this->adjust_utf8 ? ARC2::toUTF8($str) : $str; + } + + function toDataURI($str) { + return 'data:text/plain;charset=utf-8,' . rawurlencode($str); + } + + function fromDataURI($str) { + return str_replace('data:text/plain;charset=utf-8,', '', rawurldecode($str)); + } + + /* prevent SQL injections via SPARQL REGEX */ + + function checkRegex($str) { + return addslashes($str); // @@todo extend + } + + /* Microdata methods */ + + function getMicrodataAttrs($id, $type = '') { + $type = $type ? $this->expandPName($type) : $this->expandPName('owl:Thing'); + return 'itemscope="" itemtype="' . htmlspecialchars($type) . '" itemid="' . htmlspecialchars($id) . '"'; + } + + function mdAttrs($id, $type = '') { + return $this->getMicrodataAttrs($id, $type); + } + + /* central DB query hook */ + + function queryDB($sql, $con, $log_errors = 0) { + $t1 = ARC2::mtime(); + $r = mysql_query($sql, $con); + if (0) { + $t2 = ARC2::mtime() - $t1; + $call_obj = $this; + $call_path = ''; + while ($call_obj) { + $call_path = get_class($call_obj) . ' / ' . $call_path; + $call_obj = isset($call_obj->caller) ? $call_obj->caller : false; + } + echo "\n" . $call_path . " needed " . $t2 . ' secs for ' . str_replace("\n" , ' ', $sql);; + } + if ($log_errors && ($er = mysql_error($con))) $this->addError($er); + return $r; + } + + /** + * Shortcut method to create an RDF/XML backup dump from an RDF Store object. + */ + function backupStoreData($store, $target_path, $offset = 0) { + $limit = 10; + $q = ' + SELECT DISTINCT ?s WHERE { + ?s ?p ?o . + } + ORDER BY ?s + LIMIT ' . $limit . ' + ' . ($offset ? 'OFFSET ' . $offset : '') . ' + '; + $rows = $store->query($q, 'rows'); + $tc = count($rows); + $full_tc = $tc + $offset; + $mode = $offset ? 'ab' : 'wb'; + $fp = fopen($target_path, $mode); + foreach ($rows as $row) { + $index = $store->query('DESCRIBE <' . $row['s'] . '>', 'raw'); + if ($index) { + $doc = $this->toRDFXML($index); + fwrite($fp, $doc . "\n\n"); + } + } + fclose($fp); + if ($tc == 10) { + set_time_limit(300); + $this->backupStoreData($store, $target_path, $offset + $limit); + } + return $full_tc; + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/ARC2_Graph.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/ARC2_Graph.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,178 @@ + + * @license W3C Software License + * @homepage + * @package ARC2 +*/ + +ARC2::inc('Class'); + +class ARC2_Graph extends ARC2_Class { + + protected $index; + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->index = array(); + } + + function setIndex($index) { + $this->index = $index; + return $this; + } + + function getIndex() { + return $this->index; + } + + function addIndex($index) { + $this->index = ARC2::getMergedIndex($this->index, $index); + return $this; + } + + function addGraph($graph) { + // namespaces + foreach ($graph->ns as $prefix => $ns) { + $this->setPrefix($prefix, $ns); + } + // index + $this->addIndex($graph->getIndex()); + return $this; + } + + function addRdf($data, $format = null) { + if ($format == 'json') { + return $this->addIndex(json_decode($data, true)); + } + else {// parse any other rdf format + return $this->addIndex($this->toIndex($data)); + } + } + + function hasSubject($s) { + return isset($this->index[$s]); + } + + function hasTriple($s, $p, $o) { + if (!is_array($o)) { + return $this->hasLiteralTriple($s, $p, $o) || $this->hasLinkTriple($s, $p, $o); + } + if (!isset($this->index[$s])) return false; + $p = $this->expandPName($p); + if (!isset($this->index[$s][$p])) return false; + return in_array($o, $this->index[$s][$p]); + } + + function hasLiteralTriple($s, $p, $o) { + if (!isset($this->index[$s])) return false; + $p = $this->expandPName($p); + if (!isset($this->index[$s][$p])) return false; + $os = $this->getObjects($s, $p, false); + foreach ($os as $object) { + if ($object['value'] == $o && $object['type'] == 'literal') { + return true; + } + } + return false; + } + + function hasLinkTriple($s, $p, $o) { + if (!isset($this->index[$s])) return false; + $p = $this->expandPName($p); + if (!isset($this->index[$s][$p])) return false; + $os = $this->getObjects($s, $p, false); + foreach ($os as $object) { + if ($object['value'] == $o && ($object['type'] == 'uri' || $object['type'] == 'bnode')) { + return true; + } + } + return false; + } + + function addTriple($s, $p, $o, $oType = 'literal') { + $p = $this->expandPName($p); + if (!is_array($o)) $o = array('value' => $o, 'type' => $oType); + if ($this->hasTriple($s, $p, $o)) return; + if (!isset($this->index[$s])) $this->index[$s] = array(); + if (!isset($this->index[$s][$p])) $this->index[$s][$p] = array(); + $this->index[$s][$p][] = $o; + return $this; + } + + function getSubjects($p = null, $o = null) { + if (!$p && !$o) return array_keys($this->index); + $result = array(); + foreach ($this->index as $s => $ps) { + foreach ($ps as $predicate => $os) { + if ($p && $predicate != $p) continue; + foreach ($os as $object) { + if (!$o) { + $result[] = $s; + break; + } + else if (is_array($o) && $object == $o) { + $result[] = $s; + break; + } + else if ($o && $object['value'] == $o) { + $result[] = $s; + break; + } + } + } + } + return array_unique($result); + } + + function getPredicates($s = null) { + $result = array(); + $index = $s ? (array($s => isset($this->index[$s]) ? $this->index[$s] : array())) : $this->index; + foreach ($index as $subject => $ps) { + if ($s && $s != $subject) continue; + $result = array_merge($result, array_keys($ps)); + } + return array_unique($result); + } + + function getObjects($s, $p, $plain = false) { + if (!isset($this->index[$s])) return array(); + $p = $this->expandPName($p); + if (!isset($this->index[$s][$p])) return array(); + $os = $this->index[$s][$p]; + if ($plain) { + array_walk($os, function(&$o) { + $o = $o['value']; + }); + } + return $os; + } + + function getObject($s, $p, $plain = false, $default = null) { + $os = $this->getObjects($s, $p, $plain); + return empty($os) ? $default : $os[0]; + } + + function getNTriples() { + return parent::toNTriples($this->index, $this->ns); + } + + function getTurtle() { + return parent::toTurtle($this->index, $this->ns); + } + + function getRDFXML() { + return parent::toRDFXML($this->index, $this->ns); + } + + function getJSON() { + return json_encode($this->index); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/ARC2_Reader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/ARC2_Reader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,420 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_Reader extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* inc_path, proxy_host, proxy_port, proxy_skip, http_accept_header, http_user_agent_header, max_redirects */ + parent::__init(); + $this->http_method = $this->v('http_method', 'GET', $this->a); + $this->message_body = $this->v('message_body', '', $this->a);; + $this->http_accept_header = $this->v('http_accept_header', 'Accept: application/rdf+xml; q=0.9, text/turtle; q=0.8, */*; q=0.1', $this->a); + $this->http_user_agent_header = $this->v('http_user_agent_header', 'User-Agent: ARC Reader (http://arc.semsol.org/)', $this->a); + $this->http_custom_headers = $this->v('http_custom_headers', '', $this->a); + $this->max_redirects = $this->v('max_redirects', 3, $this->a); + $this->format = $this->v('format', false, $this->a); + $this->redirects = array(); + $this->stream_id = ''; + $this->timeout = $this->v('reader_timeout', 30, $this->a); + $this->response_headers = array(); + $this->digest_auth = 0; + $this->auth_infos = $this->v('reader_auth_infos', array(), $this->a); + } + + /* */ + + function setHTTPMethod($v) { + $this->http_method = $v; + } + + function setMessageBody($v) { + $this->message_body = $v; + } + + function setAcceptHeader($v) { + $this->http_accept_header = $v; + } + + function setCustomHeaders($v) { + $this->http_custom_headers = $v; + } + + function addCustomHeaders($v) { + if ($this->http_custom_headers) $this->http_custom_headers .= "\r\n"; + $this->http_custom_headers .= $v; + } + + /* */ + + function activate($path, $data = '', $ping_only = 0, $timeout = 0) { + $this->setCredentials($path); + $this->ping_only = $ping_only; + if ($timeout) $this->timeout = $timeout; + $id = md5($path . ' ' . $data); + if ($this->stream_id != $id) { + $this->stream_id = $id; + /* data uri? */ + if (!$data && preg_match('/^data\:([^\,]+)\,(.*)$/', $path, $m)) { + $path = ''; + $data = preg_match('/base64/', $m[1]) ? base64_decode($m[2]) : rawurldecode($m[2]); + } + $this->base = $this->calcBase($path); + $this->uri = $this->calcURI($path, $this->base); + $this->stream = ($data) ? $this->getDataStream($data) : $this->getSocketStream($this->base, $ping_only); + if ($this->stream && !$this->ping_only) { + $this->getFormat(); + } + } + } + + /* + * HTTP Basic/Digest + Proxy authorization can be defined in the + * arc_reader_credentials config setting: + + 'arc_reader_credentials' => array( + 'http://basic.example.com/' => 'user:pass', // shortcut for type=basic + 'http://digest.example.com/' => 'user::pass', // shortcut for type=digest + 'http://proxy.example.com/' => array('type' => 'basic', 'proxy', 'user' => 'user', 'pass' => 'pass'), + ), + + */ + + function setCredentials($url) { + if (!$creds = $this->v('arc_reader_credentials', array(), $this->a)) return 0; + foreach ($creds as $pattern => $creds) { + /* digest shortcut (user::pass) */ + if (!is_array($creds) && preg_match('/^(.+)\:\:(.+)$/', $creds, $m)) { + $creds = array('type' => 'digest', 'user' => $m[1], 'pass' => $m[2]); + } + /* basic shortcut (user:pass) */ + if (!is_array($creds) && preg_match('/^(.+)\:(.+)$/', $creds, $m)) { + $creds = array('type' => 'basic', 'user' => $m[1], 'pass' => $m[2]); + } + if (!is_array($creds)) return 0; + $regex = '/' . preg_replace('/([\:\/\.\?])/', '\\\\\1', $pattern) . '/'; + if (!preg_match($regex, $url)) continue; + $mthd = 'set' . $this->camelCase($creds['type']) . 'AuthCredentials'; + if (method_exists($this, $mthd)) $this->$mthd($creds, $url); + } + } + + function setBasicAuthCredentials($creds) { + $auth = 'Basic ' . base64_encode($creds['user'] . ':' . $creds['pass']); + $h = in_array('proxy', $creds) ? 'Proxy-Authorization' : 'Authorization'; + $this->addCustomHeaders($h . ': ' . $auth); + //echo $h . ': ' . $auth . print_r($creds, 1); + } + + function setDigestAuthCredentials($creds, $url) { + $path = $this->v1('path', '/', parse_url($url)); + $auth = ''; + $hs = $this->getResponseHeaders(); + /* initial 401 */ + $h = $this->v('www-authenticate', '', $hs); + if ($h && preg_match('/Digest/i', $h)) { + $auth = 'Digest '; + /* Digest realm="$realm", nonce="$nonce", qop="auth", opaque="$opaque" */ + $ks = array('realm', 'nonce', 'opaque');/* skipping qop, assuming "auth" */ + foreach ($ks as $i => $k) { + $$k = preg_match('/' . $k . '=\"?([^\"]+)\"?/i', $h, $m) ? $m[1] : ''; + $auth .= ($i ? ', ' : '') . $k . '="' . $$k . '"'; + $this->auth_infos[$k] = $$k; + } + $this->auth_infos['auth'] = $auth; + $this->auth_infos['request_count'] = 1; + } + /* initial 401 or repeated request */ + if ($this->v('auth', 0, $this->auth_infos)) { + $qop = 'auth'; + $auth = $this->auth_infos['auth']; + $rc = $this->auth_infos['request_count']; + $realm = $this->auth_infos['realm']; + $nonce = $this->auth_infos['nonce']; + $ha1 = md5($creds['user'] . ':' . $realm . ':' . $creds['pass']); + $ha2 = md5($this->http_method . ':' . $path); + $nc = dechex($rc); + $cnonce = dechex($rc * 2); + $resp = md5($ha1 . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . $qop . ':' . $ha2); + $auth .= ', username="' . $creds['user'] . '"' . + ', uri="' . $path . '"' . + ', qop=' . $qop . '' . + ', nc=' . $nc . + ', cnonce="' . $cnonce . '"' . + ', uri="' . $path . '"' . + ', response="' . $resp . '"' . + ''; + $this->auth_infos['request_count'] = $rc + 1; + } + if (!$auth) return 0; + $h = in_array('proxy', $creds) ? 'Proxy-Authorization' : 'Authorization'; + $this->addCustomHeaders($h . ': ' . $auth); + } + + /* */ + + function useProxy($url) { + if (!$this->v1('proxy_host', 0, $this->a)) { + return false; + } + $skips = $this->v1('proxy_skip', array(), $this->a); + foreach ($skips as $skip) { + if (strpos($url, $skip) !== false) { + return false; + } + } + return true; + } + + /* */ + + function createStream($path, $data = '') { + $this->base = $this->calcBase($path); + $this->stream = ($data) ? $this->getDataStream($data) : $this->getSocketStream($this->base); + } + + function getDataStream($data) { + return array('type' => 'data', 'pos' => 0, 'headers' => array(), 'size' => strlen($data), 'data' => $data, 'buffer' => ''); + } + + function getSocketStream($url) { + if ($url == 'file://') { + return $this->addError('Error: file does not exists or is not accessible'); + } + $parts = parse_url($url); + $mappings = array('file' => 'File', 'http' => 'HTTP', 'https' => 'HTTP'); + if ($scheme = $this->v(strtolower($parts['scheme']), '', $mappings)) { + return $this->m('get' . $scheme . 'Socket', $url, $this->getDataStream('')); + } + } + + function getFileSocket($url) { + $parts = parse_url($url); + $s = file_exists($parts['path']) ? @fopen($parts['path'], 'rb') : false; + if (!$s) { + return $this->addError('Socket error: Could not open "' . $parts['path'] . '"'); + } + return array('type' => 'socket', 'socket' =>& $s, 'headers' => array(), 'pos' => 0, 'size' => filesize($parts['path']), 'buffer' => ''); + } + + function getHTTPSocket($url, $redirs = 0, $prev_parts = '') { + $parts = parse_url($url); + /* relative redirect */ + if (!isset($parts['scheme']) && $prev_parts) $parts['scheme'] = $prev_parts['scheme']; + if (!isset($parts['host']) && $prev_parts) $parts['host'] = $prev_parts['host']; + /* no scheme */ + if (!$this->v('scheme', '', $parts)) return $this->addError('Socket error: Missing URI scheme.'); + /* port tweaks */ + $parts['port'] = ($parts['scheme'] == 'https') ? $this->v1('port', 443, $parts) : $this->v1('port', 80, $parts); + $nl = "\r\n"; + $http_mthd = strtoupper($this->http_method); + if ($this->v1('user', 0, $parts) || $this->useProxy($url)) { + $h_code = $http_mthd . ' ' . $url; + } + else { + $h_code = $http_mthd . ' ' . $this->v1('path', '/', $parts) . (($v = $this->v1('query', 0, $parts)) ? '?' . $v : '') . (($v = $this->v1('fragment', 0, $parts)) ? '#' . $v : ''); + } + $scheme_default_port = ($parts['scheme'] == 'https') ? 443 : 80; + $port_code = ($parts['port'] != $scheme_default_port) ? ':' . $parts['port'] : ''; + $h_code .= ' HTTP/1.0' . $nl. + 'Host: ' . $parts['host'] . $port_code . $nl . + (($v = $this->http_accept_header) ? $v . $nl : '') . + (($v = $this->http_user_agent_header) && !preg_match('/User\-Agent\:/', $this->http_custom_headers) ? $v . $nl : '') . + (($http_mthd == 'POST') ? 'Content-Length: ' . strlen($this->message_body) . $nl : '') . + ($this->http_custom_headers ? trim($this->http_custom_headers) . $nl : '') . + $nl . + ''; + /* post body */ + if ($http_mthd == 'POST') { + $h_code .= $this->message_body . $nl; + } + /* connect */ + if ($this->useProxy($url)) { + $s = @fsockopen($this->a['proxy_host'], $this->a['proxy_port'], $errno, $errstr, $this->timeout); + } + elseif (($parts['scheme'] == 'https') && function_exists('stream_socket_client')) { + // SSL options via config array, code by Hannes Muehleisen (muehleis@informatik.hu-berlin.de) + $context = stream_context_create(); + foreach ($this->a as $k => $v) { + if (preg_match('/^arc_reader_ssl_(.+)$/', $k, $m)) { + stream_context_set_option($context, 'ssl', $m[1], $v); + } + } + $s = stream_socket_client('ssl://' . $parts['host'] . ":" . $parts['port'], $errno, $errstr, $this->timeout, STREAM_CLIENT_CONNECT, $context); + } + elseif ($parts['scheme'] == 'https') { + $s = @fsockopen('ssl://' . $parts['host'], $parts['port'], $errno, $errstr, $this->timeout); + } + elseif ($parts['scheme'] == 'http') { + $s = @fsockopen($parts['host'], $parts['port'], $errno, $errstr, $this->timeout); + } + if (!$s) { + return $this->addError('Socket error: Could not connect to "' . $url . '" (proxy: ' . ($this->useProxy($url) ? '1' : '0') . '): ' . $errstr); + } + /* request */ + fwrite($s, $h_code); + /* timeout */ + if ($this->timeout) { + //stream_set_blocking($s, false); + stream_set_timeout($s, $this->timeout); + } + /* response headers */ + $h = array(); + $this->response_headers = $h; + if (!$this->ping_only) { + do { + $line = trim(fgets($s, 4096)); + $info = stream_get_meta_data($s); + if (preg_match("/^HTTP[^\s]+\s+([0-9]{1})([0-9]{2})(.*)$/i", $line, $m)) {/* response code */ + $error = in_array($m[1], array('4', '5')) ? $m[1] . $m[2] . ' ' . $m[3] : ''; + $error = ($m[1].$m[2] == '304') ? '304 '.$m[3] : $error; + $h['response-code'] = $m[1] . $m[2]; + $h['error'] = $error; + $h['redirect'] = ($m[1] == '3') ? true : false; + } + elseif (preg_match('/^([^\:]+)\:\s*(.*)$/', $line, $m)) {/* header */ + $h_name = strtolower($m[1]); + if (!isset($h[$h_name])) {/* 1st value */ + $h[$h_name] = trim($m[2]); + } + elseif (!is_array($h[$h_name])) {/* 2nd value */ + $h[$h_name] = array($h[$h_name], trim($m[2])); + } + else {/* more values */ + $h[$h_name][] = trim($m[2]); + } + } + } while(!$info['timed_out'] && !feof($s) && $line); + $h['format'] = strtolower(preg_replace('/^([^\s]+).*$/', '\\1', $this->v('content-type', '', $h))); + $h['encoding'] = preg_match('/(utf\-8|iso\-8859\-1|us\-ascii)/', $this->v('content-type', '', $h), $m) ? strtoupper($m[1]) : ''; + $h['encoding'] = preg_match('/charset=\s*([^\s]+)/si', $this->v('content-type', '', $h), $m) ? strtoupper($m[1]) : $h['encoding']; + $this->response_headers = $h; + /* result */ + if ($info['timed_out']) { + return $this->addError('Connection timed out after ' . $this->timeout . ' seconds'); + } + /* error */ + if ($v = $this->v('error', 0, $h)) { + /* digest auth */ + /* 401 received */ + if (preg_match('/Digest/i', $this->v('www-authenticate', '', $h)) && !$this->digest_auth) { + $this->setCredentials($url); + $this->digest_auth = 1; + return $this->getHTTPSocket($url); + } + return $this->addError($error . ' "' . (!feof($s) ? trim(strip_tags(fread($s, 128))) . '..."' : '')); + } + /* redirect */ + if ($this->v('redirect', 0, $h) && ($new_url = $this->v1('location', 0, $h))) { + fclose($s); + $this->redirects[$url] = $new_url; + $this->base = $new_url; + if ($redirs > $this->max_redirects) { + return $this->addError('Max numbers of redirects exceeded.'); + } + return $this->getHTTPSocket($new_url, $redirs+1, $parts); + } + } + if ($this->timeout) { + stream_set_blocking($s, true); + } + return array('type' => 'socket', 'url' => $url, 'socket' =>& $s, 'headers' => $h, 'pos' => 0, 'size' => $this->v('content-length', 0, $h), 'buffer' => ''); + } + + function readStream($buffer_xml = true, $d_size = 1024) { + //if (!$s = $this->v('stream')) return ''; + if (!$s = $this->v('stream')) return $this->addError('missing stream in "readStream" ' . $this->uri); + $s_type = $this->v('type', '', $s); + $r = $s['buffer']; + $s['buffer'] = ''; + if ($s['size']) $d_size = min($d_size, $s['size'] - $s['pos']); + /* data */ + if ($s_type == 'data') { + $d = ($d_size > 0) ? substr($s['data'], $s['pos'], $d_size) : ''; + } + /* socket */ + elseif ($s_type == 'socket') { + $d = ($d_size > 0) && !feof($s['socket']) ? fread($s['socket'], $d_size) : ''; + } + $eof = $d ? false : true; + /* chunked despite HTTP 1.0 request */ + if (isset($s['headers']) && isset($s['headers']['transfer-encoding']) && ($s['headers']['transfer-encoding'] == 'chunked')) { + $d = preg_replace('/(^|[\r\n]+)[0-9a-f]{1,4}[\r\n]+/', '', $d); + } + $s['pos'] += strlen($d); + if ($buffer_xml) {/* stop after last closing xml tag (if available) */ + if (preg_match('/^(.*\>)([^\>]*)$/s', $d, $m)) { + $d = $m[1]; + $s['buffer'] = $m[2]; + } + elseif (!$eof) { + $s['buffer'] = $r . $d; + $this->stream = $s; + return $this->readStream(true, $d_size); + } + } + $this->stream = $s; + return $r . $d; + } + + function closeStream() { + if (isset($this->stream)) { + if ($this->v('type', 0, $this->stream) == 'socket' && !empty($this->stream['socket'])) { + @fclose($this->stream['socket']); + } + unset($this->stream); + } + } + + /* */ + + function getFormat() { + if (!$this->format) { + if (!$this->v('stream')) { + return $this->addError('missing stream in "getFormat"'); + } + $v = $this->readStream(false); + $mtype = $this->v('format', '', $this->stream['headers']); + $this->stream['buffer'] = $v . $this->stream['buffer']; + $ext = preg_match('/\.([^\.]+)$/', $this->uri, $m) ? $m[1] : ''; + $this->format = ARC2::getFormat($v, $mtype, $ext); + } + return $this->format; + } + + /* */ + + function getResponseHeaders() { + if (isset($this->stream) && isset($this->stream['headers'])) { + return $this->stream['headers']; + } + return $this->response_headers; + } + + function getEncoding($default = 'UTF-8') { + return $this->v1('encoding', $default, $this->stream['headers']); + } + + function getRedirects() { + return $this->redirects; + } + + function getAuthInfos() { + return $this->auth_infos; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/ARC2_Resource.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/ARC2_Resource.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,150 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 + * @version 2011-01-19 +*/ + +ARC2::inc('Class'); + +class ARC2_Resource extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->uri = ''; + $this->index = array(); + $this->fetched = array(); + $this->store = ''; + } + + /* */ + + function setURI($uri) { + $this->uri = $uri; + } + + function setIndex($index) { + $this->index = $index; + } + + function getIndex($index) { + return $this->index; + } + + function setProps($props, $s = '') { + if (!$s) $s = $this->uri; + $this->index[$s] = $props; + } + + function setProp($p, $os, $s = '') { + if (!$s) $s = $this->uri; + /* single plain value */ + if (!is_array($os)) $os = array('value' => $os, 'type' => 'literal'); + /* single array value */ + if (isset($os['value'])) $os = array($os); + /* list of values */ + foreach ($os as $i => $o) { + if (!is_array($o)) $os[$i] = array('value' => $o, 'type' => 'literal'); + } + $this->index[$s][$this->expandPName($p)] = $os; + } + + /* add a relation to a URI. Allows for instance $res->setRel('rdf:type', 'doap:Project') */ + function setRel($p, $r, $s = '') { + if(!is_array($r)) { + $uri = array ( + 'type' => 'uri', + 'value' => $this->expandPName($r)); + $this->setProp($p, $uri, $s); + } else { + if (!$s) $s = $this->uri; + foreach($r as $i => $x) { + if(!is_array($x)) { + $uri = array ( + 'type' => 'uri', + 'value' => $this->expandPName($x)); + $r[$i] = $uri; + } + } + $this->index[$s][$this->expandPName($p)] = $r; + } + } + + /* Specialize setProp to set an xsd:dateTime typed literal. Example : $res->setPropXSDdateTime('dcterms:created', date('c')) */ + function setPropXSDdateTime($p, $dt, $s = '') { + $datecreated=array('value' => $dt, + 'type' => 'literal', + 'datatype' => 'http://www.w3.org/2001/XMLSchema#dateTime'); + $this->setProp($p, $datecreated, $s); + } + + function setStore($store) { + $this->store = $store; + } + + /* */ + + function fetchData($uri = '') { + if (!$uri) $uri = $this->uri; + if (!$uri) return 0; + if (in_array($uri, $this->fetched)) return 0; + $this->index[$uri] = array(); + if ($this->store) { + $index = $this->store->query('CONSTRUCT { <' . $uri . '> ?p ?o . } WHERE { <' . $uri . '> ?p ?o . } ', 'raw'); + } + else { + $index = $this->toIndex($uri); + } + $this->index = ARC2::getMergedIndex($this->index, $index); + $this->fetched[] = $uri; + } + + /* */ + + function getProps($p = '', $s = '') { + if (!$s) $s = $this->uri; + if (!$s) return array(); + if (!isset($this->index[$s])) $this->fetchData($s); + if (!$p) return $this->index[$s]; + return $this->v($this->expandPName($p), array(), $this->index[$s]); + } + + function getProp($p, $s = '') { + $props = $this->getProps($p, $s); + return $props ? $props[0] : ''; + } + + function getPropValue($p, $s = '') { + $prop = $this->getProp($p, $s); + return $prop ? $prop['value'] : ''; + } + + function getPropValues($p, $s = '') { + $r = array(); + $props = $this->getProps($p, $s); + foreach ($props as $prop) { + $r[] = $prop['value']; + } + return $r; + } + + function hasPropValue($p, $o, $s = '') { + $props = $this->getProps($p, $s); + $o = $this->expandPName($o); + foreach ($props as $prop) { + if ($prop['value'] == $o) return 1; + } + return 0; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/ARC2_getFormat.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/ARC2_getFormat.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ + + * @license http://arc.semsol.org/license + * @package ARC2 + * @version 2010-11-16 +*/ + +function ARC2_getFormat($v, $mtype = '', $ext = '') { + $r = false; + /* mtype check (atom, rdf/xml, turtle, n3, mp3, jpg) */ + $r = (!$r && preg_match('/\/atom\+xml/', $mtype)) ? 'atom' : $r; + $r = (!$r && preg_match('/\/rdf\+xml/', $mtype)) ? 'rdfxml' : $r; + $r = (!$r && preg_match('/\/(x\-)?turtle/', $mtype)) ? 'turtle' : $r; + $r = (!$r && preg_match('/\/rdf\+n3/', $mtype)) ? 'n3' : $r; + $r = (!$r && preg_match('/\/sparql-results\+xml/', $mtype)) ? 'sparqlxml' : $r; + /* xml sniffing */ + if ( + !$r && + /* starts with angle brackets */ + preg_match('/^\s*\<[^\s]/s', $v) && + /* has an xmlns:* declaration or a matching pair of tags */ + (preg_match('/\sxmlns\:?/', $v) || preg_match('/\<([^\s]+).+\<\/\\1\>/s', $v)) // && + ) { + while (preg_match('/^\s*\<\?xml[^\r\n]+\?\>\s*/s', $v)) { + $v = preg_replace('/^\s*\<\?xml[^\r\n]+\?\>\s*/s', '', $v); + } + while (preg_match('/^\s*\<\!--.+?--\>\s*/s', $v)) { + $v = preg_replace('/^\s*\<\!--.+?--\>\s*/s', '', $v); + } + /* doctype checks (html, rdf) */ + $r = (!$r && preg_match('/^\s*\<\!DOCTYPE\s+html[\s|\>]/is', $v)) ? 'html' : $r; + $r = (!$r && preg_match('/^\s*\<\!DOCTYPE\s+[a-z0-9\_\-]\:RDF\s/is', $v)) ? 'rdfxml' : $r; + /* markup checks */ + $v = preg_replace('/^\s*\<\!DOCTYPE\s.*\]\>/is', '', $v); + $r = (!$r && preg_match('/^\s*\]*version/s', $v)) ? 'rss' : $r; + $r = (!$r && preg_match('/^\s*\]+http\:\/\/www\.w3\.org\/2005\/Atom/s', $v)) ? 'atom' : $r; + $r = (!$r && preg_match('/^\s*\]/is', $v)) ? 'html' : $r; + $r = (!$r && preg_match('/^\s*\]+http\:\/\/www\.w3\.org\/2005\/sparql\-results\#/s', $v)) ? 'sparqlxml' : $r; + $r = (!$r && preg_match('/^\s*\<[^\>]+http\:\/\/www\.w3\.org\/2005\/sparql\-results#/s', $v)) ? 'srx' : $r; + $r = (!$r && preg_match('/^\s*\<[^\s]*RDF[\s\>]/s', $v)) ? 'rdfxml' : $r; + $r = (!$r && preg_match('/^\s*\<[^\>]+http\:\/\/www\.w3\.org\/1999\/02\/22\-rdf/s', $v)) ? 'rdfxml' : $r; + + $r = !$r ? 'xml' : $r; + } + /* json|jsonp */ + if (!$r && preg_match('/^[a-z0-9\.\(]*\s*[\{\[].*/s', trim($v))) { + /* google social graph api */ + $r = (!$r && preg_match('/\"canonical_mapping\"/', $v)) ? 'sgajson' : $r; + /* crunchbase api */ + $r = (!$r && preg_match('/\"permalink\"/', $v)) ? 'cbjson' : $r; + + $r = !$r ? 'json' : $r; + } + /* turtle/n3 */ + $r = (!$r && preg_match('/\@(prefix|base)/i', $v)) ? 'turtle' : $r; + $r = (!$r && preg_match('/^(ttl)$/', $ext)) ? 'turtle' : $r; + $r = (!$r && preg_match('/^(n3)$/', $ext)) ? 'n3' : $r; + /* ntriples */ + $r = (!$r && preg_match('/^\s*(_:|<).+?\s+<[^>]+?>\s+\S.+?\s*\.\s*$/sm', $v)) ? 'ntriples' : $r; + $r = (!$r && preg_match('/^(nt)$/', $ext)) ? 'ntriples' : $r; + return $r; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/ARC2_getPreferredFormat.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/ARC2_getPreferredFormat.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,50 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +function ARC2_getPreferredFormat($default = 'plain') { + $formats = array( + 'html' => 'HTML', 'text/html' => 'HTML', 'xhtml+xml' => 'HTML', + 'rdfxml' => 'RDFXML', 'rdf+xml' => 'RDFXML', + 'ntriples' => 'NTriples', + 'rdf+n3' => 'Turtle', 'x-turtle' => 'Turtle', 'turtle' => 'Turtle', 'text/turtle' => 'Turtle', + 'rdfjson' => 'RDFJSON', 'json' => 'RDFJSON', + 'xml' => 'XML', + 'legacyjson' => 'LegacyJSON' + ); + $prefs = array(); + $o_vals = array(); + /* accept header */ + $vals = explode(',', $_SERVER['HTTP_ACCEPT']); + if ($vals) { + foreach ($vals as $val) { + if (preg_match('/(rdf\+n3|(x\-|text\/)turtle|rdf\+xml|text\/html|xhtml\+xml|xml|json)/', $val, $m)) { + $o_vals[$m[1]] = 1; + if (preg_match('/\;q\=([0-9\.]+)/', $val, $sub_m)) { + $o_vals[$m[1]] = 1 * $sub_m[1]; + } + } + } + } + /* arg */ + if (isset($_GET['format'])) $o_vals[$_GET['format']] = 1.1; + /* rank */ + arsort($o_vals); + foreach ($o_vals as $val => $prio) { + $prefs[] = $val; + } + /* default */ + $prefs[] = $default; + foreach ($prefs as $pref) { + if (isset($formats[$pref])) { + return $formats[$pref]; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/README.md --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/README.md Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,8 @@ +ARC2 +==== + +ARC2 is a PHP 5.3 library for working with RDF. +It also provides a MySQL-based triplestore with SPARQL support. + +Feature-wise, ARC2 is now in a stable state with no further feature additions planned. +Issues are still being fixed and Pull Requests are welcome, though. \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/build.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/build.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/composer.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/composer.json Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,21 @@ +{ + "name": "semsol/arc2", + "type": "library", + "description": "Semsol's ARC2 RDF library", + "keywords": ["rdf","sparql"], + "homepage": "https://github.com/semsol/arc2", + "license": "W3C", + "authors": [ + { + "name": "Benji Nowack", + "email": "mail@bnowack.de", + "homepage": "http://bnowack.de/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "classmap": ["./","parsers/","serializers/"] + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_DcExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_DcExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,80 @@ +a['ns']['dc'] = 'http://purl.org/dc/elements/1.1/'; + } + + /* */ + + function extractRDF() { + $t_vals = array(); + $t = ''; + foreach ($this->nodes as $n) { + foreach (array('title', 'link', 'meta') as $tag) { + if ($n['tag'] == $tag) { + $m = 'extract' . ucfirst($tag); + list ($t_vals, $t) = $this->$m($n, $t_vals, $t); + } + } + } + if ($t) { + $doc = $this->getFilledTemplate($t, $t_vals, $n['doc_base']); + $this->addTs(ARC2::getTriplesFromIndex($doc)); + } + } + + /* */ + + function extractTitle($n, $t_vals, $t) { + if ($t_vals['title'] = $this->getPlainContent($n)) { + $t .= '<' . $n['doc_url'] . '> dc:title ?title . '; + } + return array($t_vals, $t); + } + + /* */ + + function extractLink($n, $t_vals, $t) { + if ($this->hasRel($n, 'alternate') || $this->hasRel($n, 'meta')) { + if ($href = $this->v('href uri', '', $n['a'])) { + $t .= '<' . $n['doc_url'] . '> rdfs:seeAlso <' . $href . '> . '; + if ($v = $this->v('type', '', $n['a'])) { + $t .= '<' .$href. '> dc:format "' . $v . '" . '; + } + if ($v = $this->v('title', '', $n['a'])) { + $t .= '<' .$href. '> dc:title "' . $v . '" . '; + } + } + } + return array($t_vals, $t); + } + + function extractMeta($n, $t_vals, $t) { + if ($this->hasAttribute('http-equiv', $n, 'Content-Type') || $this->hasAttribute('http-equiv', $n, 'content-type')) { + if ($v = $this->v('content', '', $n['a'])) { + $t .= '<' . $n['doc_url'] . '> dc:format "' . $v . '" . '; + } + } + return array($t_vals, $t); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_ErdfExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_ErdfExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,284 @@ +caller->detected_formats['erdf'])) return 0; + $root_node = $this->getRootNode(); + $base = $this->getDocBase(); + $ns = $this->getNamespaces(); + $context = array( + 'base' => $base, + 'prev_res' => $base, + 'cur_res' => $base, + 'ns' => $ns, + 'lang' => '', + ); + $this->processNode($root_node, $context); + } + + /* */ + + function getRootNode() { + foreach ($this->nodes as $id => $node) { + if ($node['tag'] == 'html') { + return $node; + } + } + return $this->nodes[0]; + } + + function getNamespaces() { + $r = array( + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#' + ); + foreach ($this->nodes as $id => $node) { + if (preg_match('/^(link|a)$/', $node['tag']) && isset($node['a']['rel']) && preg_match('/schema\.([^\s]+)/is', $node['a']['rel'], $m) && isset($node['a']['href uri'])) { + $r[$m[1]] = $node['a']['href uri']; + } + } + return $r; + } + + /* */ + + function processNode($n, $ct) { + /* context */ + //$ct['lang'] = $this->v('xml:lang', $ct['lang'], $n['a']); + $ct['lang'] = ''; + $ct['prop_uris'] = $this->getPropertyURIs($n, $ct); + $ct['prev_res'] = $ct['cur_res']; + $ct['cur_res'] = $this->getCurrentResourceURI($n, $ct); + $ct['cur_obj_id'] = $this->getCurrentObjectID($n, $ct); + $ct['cur_obj_literal'] = $this->getCurrentObjectLiteral($n, $ct); + /* triple production (http://research.talis.com/2005/erdf/wiki/Main/SummaryOfTripleProductionRules) */ + foreach ($ct['prop_uris'] as $type => $uris) { + foreach ($uris as $uri) { + $rdf_type = preg_match('/^ /', $uri) ? 1 : 0; + /* meta + name */ + if (($type == 'name') && ($n['tag'] == 'meta')) { + $t = array( + 's' => $ct['cur_res'], + 's_type' => 'uri', + 'p' => $uri, + 'o' => $ct['cur_obj_literal']['value'], + 'o_type' => 'literal', + 'o_lang' => $ct['cur_obj_literal']['datatype'] ? '' : $ct['cur_obj_literal']['lang'], + 'o_datatype' => $ct['cur_obj_literal']['datatype'], + ); + $this->addT($t); + } + /* class */ + if ($type == 'class') { + if ($rdf_type) { + $s = $this->v('href uri', $ct['cur_res'], $n['a']); + $s = $this->v('src uri', $s, $n['a']); + $t = array( + 's' => $s, + 's_type' => 'uri', + 'p' => $ct['ns']['rdf'] . 'type', + 'o' => trim($uri), + 'o_type' => 'uri', + 'o_lang' => '', + 'o_datatype' => '', + ); + } + elseif (isset($n['a']['id'])) {/* used as object */ + $t = array( + 's' => $ct['prev_res'], + 's_type' => 'uri', + 'p' => $uri, + 'o' => $ct['cur_res'], + 'o_type' => 'uri', + 'o_lang' => '', + 'o_datatype' => '', + ); + } + else { + $t = array( + 's' => $ct['cur_res'], + 's_type' => 'uri', + 'p' => $uri, + 'o' => $ct['cur_obj_literal']['value'], + 'o_type' => 'literal', + 'o_lang' => $ct['cur_obj_literal']['datatype'] ? '' : $ct['cur_obj_literal']['lang'], + 'o_datatype' => $ct['cur_obj_literal']['datatype'], + ); + if (($o = $this->v('src uri', '', $n['a'])) || ($o = $this->v('href uri', '', $n['a']))) { + if (!$ct['prop_uris']['rel'] && !$ct['prop_uris']['rev']) { + $t['o'] = $o; + $t['o_type'] = 'uri'; + $t['o_lang'] = ''; + $t['o_datatype'] = ''; + } + } + } + $this->addT($t); + } + /* rel */ + if ($type == 'rel') { + if (($o = $this->v('src uri', '', $n['a'])) || ($o = $this->v('href uri', '', $n['a']))) { + $t = array( + 's' => $ct['cur_res'], + 's_type' => 'uri', + 'p' => $uri, + 'o' => $o, + 'o_type' => 'uri', + 'o_lang' => '', + 'o_datatype' => '', + ); + $this->addT($t); + } + } + /* rev */ + if ($type == 'rev') { + if (($s = $this->v('src uri', '', $n['a'])) || ($s = $this->v('href uri', '', $n['a']))) { + $t = array( + 's' => $s, + 's_type' => 'uri', + 'p' => $uri, + 'o' => $ct['cur_res'], + 'o_type' => 'uri', + 'o_lang' => '', + 'o_datatype' => '', + ); + $this->addT($t); + } + } + } + } + /* imgs */ + if ($n['tag'] == 'img') { + if (($s = $this->v('src uri', '', $n['a'])) && $ct['cur_obj_literal']['value']) { + $t = array( + 's' => $s, + 's_type' => 'uri', + 'p' => $ct['ns']['rdfs'] . 'label', + 'o' => $ct['cur_obj_literal']['value'], + 'o_type' => 'literal', + 'o_lang' => $ct['cur_obj_literal']['datatype'] ? '' : $ct['cur_obj_literal']['lang'], + 'o_datatype' => $ct['cur_obj_literal']['datatype'], + ); + $this->addT($t); + } + } + /* anchors */ + if ($n['tag'] == 'a') { + if (($s = $this->v('href uri', '', $n['a'])) && $ct['cur_obj_literal']['value']) { + $t = array( + 's' => $s, + 's_type' => 'uri', + 'p' => $ct['ns']['rdfs'] . 'label', + 'o' => $ct['cur_obj_literal']['value'], + 'o_type' => 'literal', + 'o_lang' => $ct['cur_obj_literal']['datatype'] ? '' : $ct['cur_obj_literal']['lang'], + 'o_datatype' => $ct['cur_obj_literal']['datatype'], + ); + $this->addT($t); + } + } + /* recurse */ + if ($n['tag'] == 'a') { + $ct['cur_res'] = $ct['cur_obj_id']; + } + $sub_nodes = $this->getSubNodes($n); + foreach ($sub_nodes as $sub_node) { + $this->processNode($sub_node, $ct); + } + } + + /* */ + + function getPropertyURIs($n, $ct) { + $r = array(); + foreach (array('rel', 'rev', 'class', 'name', 'src') as $type) { + $r[$type] = array(); + $vals = $this->v($type . ' m', array(), $n['a']); + foreach ($vals as $val) { + if (!trim($val)) continue; + list($uri, $sub_v) = $this->xQname(trim($val, '- '), $ct['base'], $ct['ns'], $type); + if (!$uri) continue; + $rdf_type = preg_match('/^-/', trim($val)) ? 1 : 0; + $r[$type][] = $rdf_type ? ' ' . $uri : $uri; + } + } + return $r; + } + + function getCurrentResourceURI($n, $ct) { + if (isset($n['a']['id'])) { + list($r, $sub_v) = $this->xURI('#' . $n['a']['id'], $ct['base'], $ct['ns']); + return $r; + } + return $ct['cur_res']; + } + + function getCurrentObjectID($n, $ct) { + foreach (array('href', 'src') as $a) { + if (isset($n['a'][$a])) { + list($r, $sub_v) = $this->xURI($n['a'][$a], $ct['base'], $ct['ns']); + return $r; + } + } + return $this->createBnodeID(); + } + + function getCurrentObjectLiteral($n, $ct) { + $r = array('value' => '', 'lang' => $ct['lang'], 'datatype' => ''); + if (isset($n['a']['content'])) { + $r['value'] = $n['a']['content']; + } + elseif (isset($n['a']['title'])) { + $r['value'] = $n['a']['title']; + } + else { + $r['value'] = $this->getPlainContent($n); + } + return $r; + } + + /* */ + + function xURI($v, $base, $ns, $attr_type = '') { + if ((list($sub_r, $sub_v) = $this->xQname($v, $base, $ns)) && $sub_r) { + return array($sub_r, $sub_v); + } + if (preg_match('/^(rel|rev|class|name)$/', $attr_type) && preg_match('/^[a-z0-9]+$/', $v)) { + return array(0, $v); + } + return array($this->calcURI($v, $base), ''); + } + + function xQname($v, $base, $ns) { + if ($sub_r = $this->x('([a-z0-9\-\_]+)[\-\.]([a-z0-9\-\_]+)', $v)) { + if (isset($ns[$sub_r[1]])) { + return array($ns[$sub_r[1]] . $sub_r[2], ''); + } + } + return array(0, $v); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_MicroformatsExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_MicroformatsExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,178 @@ +terms = $this->getTerms(); + $this->ns_prefix = 'mf'; + $this->a['ns']['mf'] = 'http://poshrdf.org/ns/mf#'; + $this->caller->detected_formats['posh-rdf'] = 1; + } + + /* */ + + function preProcessNode($n) { + if (!$n) return $n; + /* remove existing poshRDF hooks */ + if (!is_array($n['a'])) $n['a'] = array(); + $n['a']['class'] = isset($n['a']['class']) ? preg_replace('/\s?rdf\-(s|p|o|o-xml)/', '', $n['a']['class']): ''; + if (!isset($n['a']['rel'])) $n['a']['rel'] = ''; + /* inject poshRDF hooks */ + foreach ($this->terms as $term => $infos) { + if ((!in_array('rel', $infos) && $this->hasClass($n, $term)) || $this->hasRel($n, $term)) { + if ($this->v('scope', '', $infos)) $infos[] = 'p'; + foreach (array('s', 'p', 'o', 'o-xml') as $type) { + if (in_array($type, $infos)) { + $n['a']['class'] .= ' rdf-' . $type; + $n['a']['class'] = preg_replace('/(^|\s)' . $term . '(\s|$)/s', '\\1mf-' . $term . '\\2', $n['a']['class']); + $n['a']['rel'] = preg_replace('/(^|\s)' . $term . '(\s|$)/s', '\\1mf-' . $term . '\\2', $n['a']['rel']); + } + } + } + } + $n['a']['class m'] = preg_split('/ /', $n['a']['class']); + $n['a']['rel m'] = preg_split('/ /', $n['a']['rel']); + return $n; + } + + function getPredicates($n, $ns) { + $ns = array('mf' => $ns['mf']); + return parent::getPredicates($n, $ns); + } + + function tweakObject($o, $p, $ct) { + $ns = $ct['ns']['mf']; + /* rel-tag, skill => extract from URL */ + if (in_array($p, array($ns . 'tag', $ns . 'skill'))) { + $o = preg_replace('/^.*\/([^\/]+)/', '\\1', trim($o, '/')); + $o = urldecode(rawurldecode($o)); + } + return $o; + } + + /* */ + + function getTerms() { + /* no need to define 'p' if scope is not empty */ + return array( + 'acquaintance' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'additional-name' => array('o', 'scope' => array('n')), + 'adr' => array('s', 'o', 'scope' => array('_doc', 'vcard')), + 'affiliation' => array('s', 'o', 'scope' => array('hresume')), + 'author' => array('s', 'o', 'scope' => array('hentry')), + 'bday' => array('o', 'scope' => array('vcard')), + 'bio' => array('o', 'scope' => array('vcard')), + 'best' => array('o', 'scope' => array('hreview')), + 'bookmark' => array('o', 'scope' => array('_doc', 'hentry', 'hreview')), + 'class' => array('o', 'scope' => array('vcard', 'vevent')), + 'category' => array('o', 's', 'scope' => array('vcard', 'vevent')), + 'child' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'co-resident' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'co-worker' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'colleague' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'contact' => array('o', 'scope' => array('_doc', 'hresume', 'hentry')), + 'country-name' => array('o', 'scope' => array('adr')), + 'crush' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'date' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'description' => array('o', 'scope' => array('vevent', 'hreview', 'xfolkentry')), + 'directory' => array('o', 'rel', 'scope' => array('_doc', 'hfeed', 'hentry', 'hreview')), + 'dtend' => array('o', 'scope' => array('vevent')), + 'dtreviewed' => array('o', 'scope' => array('hreview')), + 'dtstamp' => array('o', 'scope' => array('vevent')), + 'dtstart' => array('o', 'scope' => array('vevent')), + 'duration' => array('o', 'scope' => array('vevent')), + 'education' => array('s', 'o', 'scope' => array('hresume')), + 'email' => array('s', 'o', 'scope' => array('vcard')), + 'entry-title' => array('o', 'scope' => array('hentry')), + 'entry-content' => array('o-xml', 'scope' => array('hentry')), + 'entry-summary' => array('o', 'scope' => array('hentry')), + 'experience' => array('s', 'o', 'scope' => array('hresume')), + 'extended-address' => array('o', 'scope' => array('adr')), + 'family-name' => array('o', 'scope' => array('n')), + 'fn' => array('o', 'plain', 'scope' => array('vcard', 'item')), + 'friend' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'geo' => array('s', 'scope' => array('_doc', 'vcard', 'vevent')), + 'given-name' => array('o', 'scope' => array('n')), + 'hentry' => array('s', 'o', 'scope' => array('_doc', 'hfeed')), + 'hfeed' => array('s', 'scope' => array('_doc')), + 'honorific-prefix' => array('o', 'scope' => array('n')), + 'honorific-suffix' => array('o', 'scope' => array('n')), + 'hresume' => array('s', 'scope' => array('_doc')), + 'hreview' => array('s', 'scope' => array('_doc')), + 'item' => array('s', 'scope' => array('hreview')), + 'key' => array('o', 'scope' => array('vcard')), + 'kin' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'label' => array('o', 'scope' => array('vcard')), + 'last-modified' => array('o', 'scope' => array('vevent')), + 'latitude' => array('o', 'scope' => array('geo')), + 'license' => array('o', 'rel', 'scope' => array('_doc', 'hfeed', 'hentry', 'hreview')), + 'locality' => array('o', 'scope' => array('adr')), + 'location' => array('o', 'scope' => array('vevent')), + 'logo' => array('o', 'scope' => array('vcard')), + 'longitude' => array('o', 'scope' => array('geo')), + 'mailer' => array('o', 'scope' => array('vcard')), + 'me' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'met' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'muse' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'n' => array('s', 'o', 'scope' => array('vcard')), + 'neighbor' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'nickname' => array('o', 'plain', 'scope' => array('vcard')), + 'nofollow' => array('o', 'rel', 'scope' => array('_doc')), + 'note' => array('o', 'scope' => array('vcard')), + 'org' => array('o', 'xplain', 'scope' => array('vcard')), + 'parent' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'permalink' => array('o', 'scope' => array('hreview')), + 'photo' => array('o', 'scope' => array('vcard', 'item')), + 'post-office-box' => array('o', 'scope' => array('adr')), + 'postal-code' => array('o', 'scope' => array('adr')), + 'publication' => array('s', 'o', 'scope' => array('hresume')), + 'published' => array('o', 'scope' => array('hentry')), + 'rating' => array('o', 'scope' => array('hreview')), + 'region' => array('o', 'scope' => array('adr')), + 'rev' => array('o', 'scope' => array('vcard')), + 'reviewer' => array('s', 'o', 'scope' => array('hreview')), + 'role' => array('o', 'plain', 'scope' => array('vcard')), + 'sibling' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'skill' => array('o', 'scope' => array('hresume')), + 'sort-string' => array('o', 'scope' => array('vcard')), + 'sound' => array('o', 'scope' => array('vcard')), + 'spouse' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'status' => array('o', 'plain', 'scope' => array('vevent')), + 'street-address' => array('o', 'scope' => array('adr')), + 'summary' => array('o', 'scope' => array('vevent', 'hreview', 'hresume')), + 'sweetheart' => array('o', 'rel', 'scope' => array('_doc', 'hentry')), + 'tag' => array('o', 'rel', 'scope' => array('_doc', 'category', 'hfeed', 'hentry', 'skill', 'hreview', 'xfolkentry')), + 'taggedlink' => array('o', 'scope' => array('xfolkentry')), + 'title' => array('o', 'scope' => array('vcard')), + 'type' => array('o', 'scope' => array('adr', 'email', 'hreview', 'tel')), + 'tz' => array('o', 'scope' => array('vcard')), + 'uid' => array('o', 'scope' => array('vcard', 'vevent')), + 'updated' => array('o', 'scope' => array('hentry')), + 'url' => array('o', 'scope' => array('vcard', 'vevent', 'item')), + 'value' => array('o', 'scope' => array('email', 'adr', 'tel')), + 'vcard' => array('s', 'scope' => array('author', 'reviewer', 'affiliation', 'contact')), + 'version' => array('o', 'scope' => array('hreview')), + 'vevent' => array('s', 'scope' => array('_doc')), + 'worst' => array('o', 'scope' => array('hreview')), + 'xfolkentry' => array('s', 'scope' => array('_doc')), + ); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_OpenidExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_OpenidExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,62 @@ +a['ns']['foaf'] = 'http://xmlns.com/foaf/0.1/'; + } + + /* */ + + function extractRDF() { + $t_vals = array(); + $t = ''; + foreach ($this->nodes as $n) { + if (isset($n['tag']) && $n['tag'] == 'link') { + $m = 'extract' . ucfirst($n['tag']); + list ($t_vals, $t) = $this->$m($n, $t_vals, $t); + } + } + if ($t) { + $doc = $this->getFilledTemplate($t, $t_vals, $n['doc_base']); + $this->addTs(ARC2::getTriplesFromIndex($doc)); + } + } + + /* */ + + function extractLink($n, $t_vals, $t) { + if ($this->hasRel($n, 'openid.server')) { + if ($href = $this->v('href uri', '', $n['a'])) { + $t_vals['doc_owner'] = $this->getDocOwnerID($n); + $t_vals['doc_id'] = $this->getDocID($n); + $t .= '?doc_owner foaf:homepage ?doc_id ; foaf:openid ?doc_id . '; + } + } + if ($this->hasRel($n, 'openid.delegate')) { + if ($href = $this->v('href uri', '', $n['a'])) { + $t_vals['doc_owner'] = $this->getDocOwnerID($n); + $t .= '?doc_owner foaf:homepage <' . $href . '> ; foaf:openid <' . $href . '> . '; + } + } + return array($t_vals, $t); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_PoshRdfExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_PoshRdfExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,254 @@ +terms = $this->v('posh_terms', array(), $this->a); + $this->ns_prefix = 'posh'; + $this->a['ns'] += array( + 'an' => 'http://www.w3.org/2000/10/annotation-ns#', + 'content' => 'http://purl.org/rss/1.0/modules/content/', + 'dc' => 'http://purl.org/dc/elements/1.1/', + 'dct' => 'http://purl.org/dc/terms/', + 'foaf' => 'http://xmlns.com/foaf/0.1/', + 'geo' => 'http://www.w3.org/2003/01/geo/wgs84_pos#', + 'ical' => 'http://www.w3.org/2002/12/cal/icaltzd#', + 'owl' => 'http://www.w3.org/2002/07/owl#', + 'posh' => 'http://poshrdf.org/ns/posh/', + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#', + 'rev' => 'http://www.purl.org/stuff/rev#', + 'rss' => 'http://purl.org/rss/1.0/', + 'sioc' => 'http://rdfs.org/sioc/ns#', + 'skos' => 'http://www.w3.org/2008/05/skos#', + 'uri' => 'http://www.w3.org/2006/uri#', + 'vcard' => 'http://www.w3.org/2006/vcard/ns#', + 'xfn' => 'http://gmpg.org/xfn/11#', + 'xml' => 'http://www.w3.org/XML/1998/namespace', + 'xsd' => 'http://www.w3.org/2001/XMLSchema#', + ); + } + + /* */ + + function extractRDF() { + if (!isset($this->caller->detected_formats['posh-rdf'])) return 0; + $n = $this->getRootNode(); + $base = $this->getDocBase(); + $context = array( + 'id' => $n['id'], + 'tag' => $n['tag'], + 'base' => $base, + 's' => array(array('_doc', $base)), + 'next_s' => array('_doc', $base), + 'ps' => array(), + 'ns' => $this->a['ns'], + 'lang' => '', + 'rpointer' => '', + ); + $ct = $this->processNode($n, $context, 0, 1); + } + + /* */ + + function getRootNode() { + foreach ($this->nodes as $id => $node) { + if ($node['tag'] == 'html') { + return $node; + } + } + return $this->nodes[0]; + } + + /* */ + + function processNode($n, $ct, $level, $pos) { + $n = $this->preProcessNode($n); + /* local context */ + $lct = array_merge($ct, array( + 'ns' => array_merge($ct['ns'], $this->v('xmlns', array(), $n['a'])), + 'rpointer' => isset($n['a']['id']) ? $n['a']['id'] : ($n['tag'] == 'cdata' ? '' : $ct['rpointer'] . '/' . $pos), + 'tag' => $n['tag'], + 'id' => $n['id'], + 'lang' => $this->v('xml:lang', $ct['lang'], $n['a']), + )); + /* s stack */ + $next_s_key = $lct['next_s'][0]; + $next_s_val = $lct['next_s'][1]; + if ($lct['s'][0][0] != $next_s_key) { + $lct['s'] = array_merge(array($lct['next_s']), $lct['s']); + } + else { + $lct['s'][0][1] = $next_s_val; + } + /* new s */ + if ($this->hasClass($n, 'rdf-s')) { + $lct['next_s'] = array($n['a']['class'], $this->getSubject($n, $lct)); + //echo "\ns: " . print_r($lct['next_s'], 1); + } + /* p */ + if ($this->hasClass($n, 'rdf-p') || $this->hasRel($n, 'rdf-p')) { + if ($ps = $this->getPredicates($n, $lct['ns'])) { + $lct['ps'] = $ps; + $this->addPoshTypes($lct); + } + } + /* o */ + $cls = $this->v('class', '', $n['a']); + if ($lct['ps'] && preg_match('/(^|\s)rdf\-(o|o\-(xml|dateTime|float|integer|boolean))($|\s)/s', $cls, $m)) { + $this->addTriples($n, $lct, $m[3]); + } + /* sub-nodes */ + if ($sub_nodes = $this->getSubNodes($n)) { + $cur_ct = $lct; + $sub_pos = 1; + foreach ($sub_nodes as $i => $sub_node) { + if (in_array($sub_node['tag'], array('cdata', 'comment'))) continue; + $sub_ct = $this->processNode($sub_node, $cur_ct, $level + 1, $sub_pos); + $sub_pos++; + $cur_ct['next_s'] = $sub_ct['next_s']; + $cur_ct['ps'] = $sub_ct['ps']; + } + } + return $lct; + } + + /* */ + + function getSubject($n, $ct) { + foreach (array('href uri', 'src uri', 'title', 'value') as $k) { + if (isset($n['a'][$k])) return $n['a'][$k]; + } + /* rpointer */ + return $ct['base'] . '#resource(' . $ct['rpointer'] . ')'; + } + + function getPredicates($n, $ns) { + $r = array(); + /* try pnames */ + $vals = array_merge($this->v('class m', array(), $n['a']), $this->v('rel m', array(), $n['a'])); + foreach ($vals as $val) { + if (!preg_match('/^([a-z0-9]+)\-([a-z0-9\-\_]+)$/i', $val, $m)) continue; + if (!isset($ns[$m[1]])) continue; + if (preg_match('/^rdf-(s|p|o|o-(xml|dateTime|float|integer|boolean))$/', $val)) continue; + $r[] = $ns[$m[1]] . $m[2]; + } + /* try other attributes */ + if (!$r) { + foreach (array('href uri', 'title') as $k) { + if (isset($n['a'][$k])) { + $r[] = $n['a'][$k]; + break; + } + } + } + return $r; + } + + function addTriples($n, $ct, $o_type) { + foreach (array('href uri', 'src uri', 'title', 'value') as $k) { + if (isset($n['a'][$k])) { + $node_o = $n['a'][$k]; + break; + } + } + if (!isset($node_o) && $this->hasClass($n, 'rdf-s')) { + $node_o = $ct['next_s'][1]; + } + $lit_o = ($o_type == 'xml') ? $this->getContent($n) : $this->getPlainContent($n); + $posh_ns = $ct['ns'][$this->ns_prefix]; + $rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $xsd = 'http://www.w3.org/2001/XMLSchema#'; + foreach ($ct['ps'] as $p) { + $p_key = str_replace($posh_ns, '', $p); + /* dt or obj */ + $o = $this->isDatatypeProperty($p_key) ? $lit_o : (isset($node_o) ? $node_o : $lit_o); + if (!$o) continue; + if (!$s = $this->getContainerSubject($ct, $p_key)) continue; + $lang = (($o == $lit_o) && !$o_type) ? $ct['lang'] : ''; + $o = $this->tweakObject($o, $p, $ct); + $this->addT(array( + 's' => $this->getContainerSubject($ct, $p_key), + 's_type' => preg_match('/^\_\:/', $s) ? 'bnode' : 'uri', + 'p' => $p, + 'o' => $o, + 'o_type' => $this->getObjectType($o, $p_key), + 'o_lang' => $lang, + 'o_datatype' => ($o_type == 'xml') ? $rdf . 'XMLLiteral' : ($o_type ? $xsd . $o_type : ''), + )); + } + } + + function addPoshTypes($ct) { + $posh_ns = $ct['ns'][$this->ns_prefix]; + foreach ($ct['ps'] as $p) { + $p_key = str_replace($posh_ns, '', $p); + if (!$this->isSubject($p_key)) continue; + $s = $ct['next_s'][1]; + $this->addT(array( + 's' => $s, + 's_type' => preg_match('/^\_\:/', $s) ? 'bnode' : 'uri', + 'p' => $ct['ns']['rdf'] . 'type', + 'o' => $posh_ns . ucfirst($p_key), + 'o_type' => 'uri', + 'o_lang' => '', + 'o_datatype' => '', + )); + } + } + + /* */ + + function preProcessNode($n) { + return $n; + } + + function getContainerSubject($ct, $term) { + if (!isset($this->terms[$term])) return $ct['s'][0][1]; + $scope = $this->v('scope', array(), $this->terms[$term]); + if (!$scope) return $ct['s'][0][1]; + $scope_re = join('|', $scope); + foreach ($ct['s'] as $s) { + if (preg_match('/(^|\s)(' . $scope_re. ')($|\s)/s', str_replace($this->ns_prefix . '-', '', $s[0]))) return $s[1]; + } + return 0; + } + + function isSubject($term) { + if (!isset($this->terms[$term])) return 0; + return in_array('s', $this->terms[$term]); + } + + function isDatatypeProperty($term) { + if (!isset($this->terms[$term])) return 0; + return in_array('plain', $this->terms[$term]); + } + + function getObjectType($o, $term) { + if ($this->isDatatypeProperty($term)) return 'literal'; + if (strpos($o, ' ')) return 'literal'; + return preg_match('/^([a-z0-9\_]+)\:[^\s]+$/s', $o, $m) ? ($m[1] == '_' ? 'bnode' : 'uri') : 'literal'; + } + + function tweakObject($o, $p, $ct) { + return $o; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_RDFExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_RDFExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,237 @@ +nodes = $this->caller->getNodes(); + $this->index = $this->caller->getNodeIndex(); + $this->bnode_prefix = $this->v('bnode_prefix', 'arc' . substr(md5(uniqid(rand())), 0, 4) . 'b', $this->a); + $this->bnode_id = 0; + $this->keep_cdata_ws = $this->v('keep_cdata_whitespace', 0, $this->a); + if (!isset($this->a['ns'])) $this->a['ns'] = array('rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); + } + + /* */ + + function x($re, $v, $options = 'si') { + return ARC2::x($re, $v, $options); + } + + function createBnodeID(){ + $this->bnode_id++; + return '_:' . $this->bnode_prefix . $this->bnode_id; + } + + /* */ + + function extractRDF() { + } + + /* */ + + function addTs($ts) { + foreach ($ts as $t) { + $this->caller->addT($t); + } + } + + function addT($t) { + return $this->caller->addT($t); + } + + /* */ + + function getSubNodes($n) { + return $this->v($n['id'], array(), $this->index); + } + + function getParentNode($n) { + return isset($this->nodes[$n['p_id']]) ? $this->nodes[$n['p_id']] : 0; + } + + /* */ + + function getSubNodesByClass($n, $cls, $skip_self = 0) { + if (!$skip_self && $this->hasClass($n, $cls)) { + return array($n); + } + $r = array(); + $sns = $this->getSubNodes($n); + foreach ($sns as $sn) { + if ($sub_r = $this->getSubNodesByClass($sn, $cls, 0)) { + $r = array_merge($r, $sub_r); + } + } + return $r; + } + + function getSubNodeByClass($n, $cls, $skip_self = 0) { + if (!$skip_self && $this->hasClass($n, $cls)) { + return $n; + } + $sns = $this->getSubNodes($n); + foreach ($sns as $sn) { + if ($sub_r = $this->getSubNodeByClass($sn, $cls, 0)) { + return $sub_r; + } + } + return 0; + } + + function getParentNodeByClass($n, $cls, $skip_self = 0) { + if (!$skip_self && $this->hasClass($n, $cls)) { + return $n; + } + if ($pn = $this->getParentNode($n)) { + if ($sub_r = $this->getParentNodeByClass($pn, $cls, 0)) { + return $sub_r; + } + } + return 0; + } + + /* */ + + function hasAttribute($a, $n, $v) { + $vs = is_array($v) ? $v : array($v); + $a_vs = $this->v($a . ' m', array(), $n['a']); + return array_intersect($vs, $a_vs) ? 1 : 0; + } + + function hasClass($n, $v) { + return $this->hasAttribute('class', $n, $v); + } + + function hasRel($n, $v) { + return $this->hasAttribute('rel', $n, $v); + } + + /* */ + + function getDocBase() { + $root_node = $this->getRootNode(); + $r = $root_node['doc_base']; + foreach ($this->getSubNodes($root_node) as $root_child) { + if ($root_child['tag'] == 'head') { + foreach ($this->getSubNodes($root_child) as $head_child) { + if ($head_child['tag'] == 'base') { + $r = $head_child['a']['href']; + break; + } + } + } + } + return $r; + } + + /* */ + + function getPlainContent($n, $trim = 1, $use_img_alt = 1) { + if ($n['tag'] == 'comment') { + $r = ''; + } + elseif ($n['tag'] == 'cdata') { + $r = $n['a']['value']; + } + elseif (trim($this->v('cdata', '', $n))) { + $r = $n['cdata']; + $sub_nodes = $this->getSubNodes($n); + foreach ($sub_nodes as $sub_n) { + $r .= $this->getPlainContent($sub_n, 0, $use_img_alt); + } + } + elseif (($n['tag'] == 'img') && $use_img_alt && isset($n['a']['alt'])) { + $r = $n['a']['alt']; + } + else { + $r = ''; + $sub_nodes = $this->getSubNodes($n); + foreach ($sub_nodes as $sub_n) { + $r .= $this->getPlainContent($sub_n, 0, $use_img_alt); + } + } + $r = preg_replace('/\s/s', ' ', $r); + $r = preg_replace('/\s\s*/s', ' ', $r); + return $trim ? trim($r) : $r; + } + + function getContent($n, $outer = 0, $trim = 1) { + //echo '
    ' . htmlspecialchars(print_r($n, 1)) . '
    '; + if ($n['tag'] == 'comment') { + $r = ''; + } + elseif ($n['tag'] == 'cdata') { + $r = $n['a']['value']; + } + else { + $r = ''; + if ($outer) { + $r .= '<' . $n['tag']; + asort($n['a']); + if (isset($n['a']['xmlns']) && $n['a']['xmlns']['']) { + $r .= ' xmlns="' . $n['a']['xmlns'][''] . '"'; + } + foreach ($n['a'] as $a => $val) { + if (!is_array($val) && isset($n['a'][$a . ' uri'])) $val = $n['a'][$a . ' uri']; + $r .= preg_match('/^[^\s]+$/', $a) && !is_array($val) ? ' ' . $a . '="' . addslashes($val) . '"' : ''; + } + $r .= $n['empty'] ? '/>' : '>'; + } + if (!$n['empty']) { + $r .= $this->v('cdata', '', $n); + $sub_nodes = $this->getSubNodes($n); + foreach ($sub_nodes as $sub_n) { + $r .= $this->getContent($sub_n, 1, 0); + } + if ($outer) { + $r .= ''; + } + } + } + return ($trim && !$this->keep_cdata_ws) ? trim($r) : $r; + } + + /* */ + + function getDocID($n) { + $id = $n['id']; + $k = 'doc_' . $id; + if (!isset($this->caller->cache[$k])) { + $this->caller->cache[$k] = $n['doc_url']; + } + return $this->caller->cache[$k]; + } + + function getDocOwnerID($n) { + return '_:owner_of_' . $this->normalize($this->getDocID($n)); + } + + /* */ + + function normalize($v) { + $v = preg_replace('/[\W\s]+/is', '_', strip_tags(strtolower($v))); + $v = preg_replace('/http/', '', $v); + $v = preg_replace('/[\_]+/', '_', $v); + //$v = substr($v, 0, 30); + $v = trim($v, '_'); + return $v; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_RdfaExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_RdfaExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,385 @@ +' . htmlspecialchars(print_r($this->nodes, 1)) . ''; + if (!isset($this->caller->detected_formats['rdfa'])) return 0; + $root_node = $this->getRootNode(); + //$base = $this->v('xml:base', $this->getDocBase(), $root_node['a']); + $base = $this->getDocBase(); + $context = array( + 'base' => $base, + 'p_s' => $base, + 'p_o' => '', + 'ns' => array(), + 'inco_ts' => array(), + 'lang' => '', + ); + $this->processNode($root_node, $context, 0); + } + + /* */ + + function getRootNode() { + foreach ($this->nodes as $id => $node) { + if ($node['tag'] == 'html') { + return $node; + } + } + return $this->nodes[0]; + } + + /* */ + + function processNode($n, $ct, $level) { + if ($n['tag']=='cdata' || $n['tag']=='comment') return null; /* patch by tobyink */ + $ts_added = 0; + /* step 1 */ + $lct = array(); + $lct['prev_s'] = $this->v('prev_s', $this->v('p_s', '', $ct), $ct); + $lct['recurse'] = 1; + $lct['skip'] = 0; + $lct['new_s'] = ''; + $lct['cur_o_res'] = ''; + $lct['inco_ts'] = array(); + $lct['base'] = $ct['base']; + //$lct['base'] = $this->v('xml:base', $ct['base'], $n['a']); + /* step 2 */ + $lct['ns'] = array_merge($ct['ns'], $this->v('xmlns', array(), $n['a'])); + /* step 3 */ + $lct['lang'] = $this->v('xml:lang', $ct['lang'], $n['a']); + /* step 4 */ + $rel_uris = $this->getAttributeURIs($n, $ct, $lct, 'rel'); + $rev_uris = $this->getAttributeURIs($n, $ct, $lct, 'rev'); + if (!$rel_uris && !$rev_uris) { + foreach (array('about', 'src', 'resource', 'href') as $attr) { + if (isset($n['a'][$attr]) && (list($uri, $sub_v) = $this->xURI($n['a'][$attr], $lct['base'], $lct['ns'], '', $lct)) && $uri) { + $lct['new_s'] = $uri; + break; + } + } + if (!$lct['new_s']) { + if (preg_match('/(head|body)/i', $n['tag'])) { + $lct['new_s'] = $lct['base']; + } + elseif ($this->getAttributeURIs($n, $ct, $lct, 'typeof')) { + $lct['new_s'] = $this->createBnodeID(); + } + elseif ($ct['p_o']) { + $lct['new_s'] = $ct['p_o']; + //$lct['skip'] = 1; + if(!isset($n['a']['property'])) $lct['skip'] = 1;/* patch by masaka */ + } + } + } + /* step 5 */ + else { + foreach (array('about', 'src') as $attr) { + if (isset($n['a'][$attr]) && (list($uri, $sub_v) = $this->xURI($n['a'][$attr], $lct['base'], $lct['ns'], '', $lct)) && $uri) { + $lct['new_s'] = $uri; + break; + } + } + if (!$lct['new_s']) { + if (preg_match('/(head|body)/i', $n['tag'])) { + $lct['new_s'] = $lct['base']; + } + elseif ($this->getAttributeURIs($n, $ct, $lct, 'typeof')) { + $lct['new_s'] = $this->createBnodeID(); + } + elseif ($ct['p_o']) { + $lct['new_s'] = $ct['p_o']; + } + } + foreach (array('resource', 'href') as $attr) { + if (isset($n['a'][$attr]) && (list($uri, $sub_v) = $this->xURI($n['a'][$attr], $lct['base'], $lct['ns'], '', $lct)) && $uri) { + $lct['cur_o_res'] = $uri; + break; + } + } + } + /* step 6 */ + if ($lct['new_s']) { + if ($uris = $this->getAttributeURIs($n, $ct, $lct, 'typeof')) { + foreach ($uris as $uri) { + $this->addT(array( + 's' => $lct['new_s'], + 's_type' => preg_match('/^\_\:/', $lct['new_s']) ? 'bnode' : 'uri', + 'p' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', + 'o' => $uri, + 'o_type' => 'uri', + 'o_lang' => '', + 'o_datatype' => '', + )); + $ts_added = 1; + } + } + /* step 7 */ + if ($lct['cur_o_res']) { + if ($rel_uris) { + foreach ($rel_uris as $uri) { + $this->addT(array( + 's' => $lct['new_s'], + 's_type' => preg_match('/^\_\:/', $lct['new_s']) ? 'bnode' : 'uri', + 'p' => $uri, + 'o' => $lct['cur_o_res'], + 'o_type' => preg_match('/^\_\:/', $lct['cur_o_res']) ? 'bnode' : 'uri', + 'o_lang' => '', + 'o_datatype' => '', + )); + $ts_added = 1; + } + } + if ($rev_uris) { + foreach ($rev_uris as $uri) { + $this->addT(array( + 's' => $lct['cur_o_res'], + 's_type' => preg_match('/^\_\:/', $lct['cur_o_res']) ? 'bnode' : 'uri', + 'p' => $uri, + 'o' => $lct['new_s'], + 'o_type' => preg_match('/^\_\:/', $lct['new_s']) ? 'bnode' : 'uri', + 'o_lang' => '', + 'o_datatype' => '', + )); + $ts_added = 1; + } + } + } + } + /* step 8 */ + if (!$lct['cur_o_res']) { + if ($rel_uris || $rev_uris) { + $lct['cur_o_res'] = $this->createBnodeID(); + foreach ($rel_uris as $uri) { + $lct['inco_ts'][] = array('p' => $uri, 'dir' => 'fwd'); + } + foreach ($rev_uris as $uri) { + $lct['inco_ts'][] = array('p' => $uri, 'dir' => 'rev'); + } + } + } + /* step 10 */ + if (!$lct['skip'] && ($new_s = $lct['new_s'])) { + //if ($new_s = $lct['new_s']) { + if ($uris = $this->getAttributeURIs($n, $ct, $lct, 'property')) { + foreach ($uris as $uri) { + $lct['cur_o_lit'] = $this->getCurrentObjectLiteral($n, $lct, $ct); + $this->addT(array( + 's' => $lct['new_s'], + 's_type' => preg_match('/^\_\:/', $lct['new_s']) ? 'bnode' : 'uri', + 'p' => $uri, + 'o' => $lct['cur_o_lit']['value'], + 'o_type' => 'literal', + 'o_lang' => $lct['cur_o_lit']['lang'], + 'o_datatype' => $lct['cur_o_lit']['datatype'], + )); + $ts_added = 1; + if ($lct['cur_o_lit']['datatype'] == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral') { + $lct['recurse'] = 0; + } + } + } + } + /* step 11 (10) */ + $complete_triples = 0; + if ($lct['recurse']) { + if ($lct['skip']) { + $new_ct = array_merge($ct, array('base' => $lct['base'], 'lang' => $lct['lang'], 'ns' => $lct['ns'])); + } + else { + $new_ct = array( + 'base' => $lct['base'], + 'p_s' => $lct['new_s'] ? $lct['new_s'] : $ct['p_s'], + 'p_o' => $lct['cur_o_res'] ? $lct['cur_o_res'] : ($lct['new_s'] ? $lct['new_s'] : $ct['p_s']), + 'ns' => $lct['ns'], + 'inco_ts' => $lct['inco_ts'], + 'lang' => $lct['lang'] + ); + } + $sub_nodes = $this->getSubNodes($n); + foreach ($sub_nodes as $sub_node) { + if ($this->processNode($sub_node, $new_ct, $level+1)) { + $complete_triples = 1; + } + } + } + /* step 12 (11) */ + $other = 0; + if ($ts_added || $complete_triples || ($lct['new_s'] && !preg_match('/^\_\:/', $lct['new_s'])) || ($other == 1)) { + //if (!$lct['skip'] && ($complete_triples || ($lct['new_s'] && !preg_match('/^\_\:/', $lct['new_s'])))) { + foreach ($ct['inco_ts'] as $inco_t) { + if ($inco_t['dir'] == 'fwd') { + $this->addT(array( + 's' => $ct['p_s'], + 's_type' => preg_match('/^\_\:/', $ct['p_s']) ? 'bnode' : 'uri', + 'p' => $inco_t['p'], + 'o' => $lct['new_s'], + 'o_type' => preg_match('/^\_\:/', $lct['new_s']) ? 'bnode' : 'uri', + 'o_lang' => '', + 'o_datatype' => '', + )); + } + elseif ($inco_t['dir'] == 'rev') { + $this->addT(array( + 's' => $lct['new_s'], + 's_type' => preg_match('/^\_\:/', $lct['new_s']) ? 'bnode' : 'uri', + 'p' => $inco_t['p'], + 'o' => $ct['p_s'], + 'o_type' => preg_match('/^\_\:/', $ct['p_s']) ? 'bnode' : 'uri', + 'o_lang' => '', + 'o_datatype' => '', + )); + } + } + } + /* step 13 (12) (result flag) */ + if ($ts_added) return 1; + if ($lct['new_s'] && !preg_match('/^\_\:/', $lct['new_s'])) return 1; + if ($complete_triples) return 1; + return 0; + } + + /* */ + + function getAttributeURIs($n, $ct, $lct, $attr) { + $vals = ($val = $this->v($attr, '', $n['a'])) ? explode(' ', $val) : array(); + $r = array(); + foreach ($vals as $val) { + if(!trim($val)) continue; + if ((list($uri, $sub_v) = $this->xURI(trim($val), $lct['base'], $lct['ns'], $attr, $lct)) && $uri) { + $r[] = $uri; + } + } + return $r; + } + + /* */ + + function getCurrentObjectLiteral($n, $lct, $ct) { + $xml_val = $this->getContent($n); + $plain_val = $this->getPlainContent($n, 0, 0); + if (function_exists('html_entity_decode')) { + $plain_val = html_entity_decode($plain_val, ENT_QUOTES); + } + $dt = $this->v('datatype', '', $n['a']); + list($dt_uri, $sub_v) = $this->xURI($dt, $lct['base'], $lct['ns'], '', $lct); + $dt = $dt ? $dt_uri : $dt; + $r = array('value' => '', 'lang' => $lct['lang'], 'datatype' => $dt); + if (isset($n['a']['content'])) { + $r['value'] = $n['a']['content']; + if (function_exists('html_entity_decode')) { + $r['value'] = html_entity_decode($r['value'], ENT_QUOTES); + } + } + elseif ($xml_val == $plain_val) { + $r['value'] = $plain_val; + } + elseif (!preg_match('/[\<\>]/', $xml_val)) { + $r['value'] = $xml_val; + } + elseif (isset($n['a']['datatype']) && ($dt != 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral')) { + $r['value'] = $plain_val; + } + elseif (!isset($n['a']['datatype']) || ($dt == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral')) { + $r['value'] = $this->injectXMLDeclarations($xml_val, $lct['ns'], $lct['lang']); + $r['datatype'] = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral'; + } + return $r; + } + + function injectXMLDeclarations($val, $ns, $lang) {//@@todo proper node rebuilding */ + $lang_code = $lang ? ' xml:lang="' . $lang . '"' : ''; + /* ns */ + $val = preg_replace('/<([a-z0-9]+)([\>\s])/is', '<\\1 xmlns="http://www.w3.org/1999/xhtml"' . $lang_code . '\\2', $val); + foreach ($ns as $prefix => $uri) { + if ($prefix && ($pos = strpos(' ' . $val, '<' . $prefix . ':'))) { + $val = substr($val, 0, $pos - 1) . preg_replace('/^(<' . $prefix . '\:[^\>\s]+)/', '\\1 xmlns:' . $prefix. '="' . $uri . '"' . $lang_code, substr($val, $pos - 1)); + } + } + /* remove accidentally added xml:lang and xmlns= */ + $val = preg_replace('/(\<[^\>]*)( xml\:lang[^\s\>]+)([^\>]*)(xml\:lang[^\s\>]+)/s', '\\1\\3\\4', $val); + $val = preg_replace('/(\<[^\>]*)( xmlns=[^\s\>]+)([^\>]*)(xmlns=[^\s\>]+)/s', '\\1\\3\\4', $val); + return $val; + } + + /* */ + + function xURI($v, $base, $ns, $attr_type = '', $lct = '') { + if ((list($sub_r, $sub_v) = $this->xBlankCURIE($v, $base, $ns)) && $sub_r) { + return array($sub_r, $sub_v); + } + if ((list($sub_r, $sub_v) = $this->xSafeCURIE($v, $base, $ns, $lct)) && $sub_r) { + return array($sub_r, $sub_v); + } + if ((list($sub_r, $sub_v) = $this->xCURIE($v, $base, $ns)) && $sub_r) { + return array($sub_r, $sub_v); + } + if (preg_match('/^(rel|rev)$/', $attr_type) && preg_match('/^\s*(alternate|appendix|bookmark|cite|chapter|contents|copyright|glossary|help|icon|index|last|license|meta|next|p3pv1|prev|role|section|stylesheet|subsection|start|up)(\s|$)/is', $v, $m)) { + return array('http://www.w3.org/1999/xhtml/vocab#' . strtolower($m[1]), preg_replace('/^\s*' . $m[1]. '/is', '', $v)); + } + if (preg_match('/^(rel|rev)$/', $attr_type) && preg_match('/^[a-z0-9\.]+$/i', $v)) { + return array(0, $v); + } + return array($this->calcURI($v, $base), ''); + } + + function xBlankCURIE($v, $base, $ns) { + if ($sub_r = $this->x('\[\_\:\]', $v)) { + $this->empty_bnode = isset($this->empty_bnode) ? $this->empty_bnode : $this->createBnodeID(); + return array($this->empty_bnode, ''); + } + if ($sub_r = $this->x('\[?(\_\:[a-z0-9\_\-]+)\]?', $v)) { + return array($sub_r[1], ''); + } + return array(0, $v); + } + + function xSafeCURIE($v, $base, $ns, $lct = '') { + /* empty */ + if ($sub_r = $this->x('\[\]', $v)) { + $r = $lct ? $lct['prev_s'] : $base;/* should be current subject value */ + return $sub_r[1] ? array($r, $sub_r[1]) : array($r, ''); + } + if ($sub_r = $this->x('\[([^\:]*)\:([^\]]*)\]', $v)) { + if (!$sub_r[1]) return array('http://www.w3.org/1999/xhtml/vocab#' . $sub_r[2], ''); + if (isset($ns[$sub_r[1]])) { + return array($ns[$sub_r[1]] . $sub_r[2], ''); + } + } + return array(0, $v); + } + + function xCURIE($v, $base, $ns) { + if ($sub_r = $this->x('([a-z0-9\-\_]*)\:([^\s]+)', $v)) { + if (!$sub_r[1]) return array('http://www.w3.org/1999/xhtml/vocab#' . $sub_r[2], ''); + if (isset($ns[$sub_r[1]])) { + return array($ns[$sub_r[1]] . $sub_r[2], ''); + } + } + return array(0, $v); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/extractors/ARC2_TwitterProfilePicExtractor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/extractors/ARC2_TwitterProfilePicExtractor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,45 @@ +a['ns']['foaf'] = 'http://xmlns.com/foaf/0.1/'; + $this->a['ns']['mf'] = 'http://poshrdf.org/ns/mf#'; + } + + /* */ + + function extractRDF() { + $t_vals = array(); + $t = ''; + foreach ($this->nodes as $n) { + if (isset($n['tag']) && ($n['tag'] == 'img') && ($this->v('id', '', $n['a']) == 'profile-image')) { + $t_vals['vcard_id'] = $this->getDocID($n) . '#resource(side/1/2/1)'; + $t .= '?vcard_id mf:photo <' . $n['a']['src'] . '> . '; + break; + } + } + if ($t) { + $doc = $this->getFilledTemplate($t, $t_vals, $n['doc_base']); + $this->addTs(ARC2::getTriplesFromIndex($doc)); + } + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_AtomParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_AtomParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,245 @@ +triples = array(); + $this->target_encoding = ''; + $this->t_count = 0; + $this->added_triples = array(); + $this->skip_dupes = false; + $this->bnode_prefix = $this->v('bnode_prefix', 'arc'.substr(md5(uniqid(rand())), 0, 4).'b', $this->a); + $this->bnode_id = 0; + $this->cache = array(); + $this->allowCDataNodes = 0; + } + + /* */ + + function done() { + $this->extractRDF(); + } + + /* */ + + function setReader(&$reader) { + $this->reader = $reader; + } + + function createBnodeID(){ + $this->bnode_id++; + return '_:' . $this->bnode_prefix . $this->bnode_id; + } + + function addT($t) { + //if (!isset($t['o_datatype'])) + if ($this->skip_dupes) { + //$h = md5(print_r($t, 1)); + $h = md5(serialize($t)); + if (!isset($this->added_triples[$h])) { + $this->triples[$this->t_count] = $t; + $this->t_count++; + $this->added_triples[$h] = true; + } + } + else { + $this->triples[$this->t_count] = $t; + $this->t_count++; + } + } + + function getTriples() { + return $this->v('triples', array()); + } + + function countTriples() { + return $this->t_count; + } + + function getSimpleIndex($flatten_objects = 1, $vals = '') { + return ARC2::getSimpleIndex($this->getTriples(), $flatten_objects, $vals); + } + + /* */ + + function extractRDF() { + $index = $this->getNodeIndex(); + //print_r($index); + $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->atom = 'http://www.w3.org/2005/Atom'; + $this->rss = 'http://purl.org/rss/1.0/'; + $this->dc = 'http://purl.org/dc/elements/1.1/'; + $this->sioc = 'http://rdfs.org/sioc/ns#'; + $this->dct = 'http://purl.org/dc/terms/'; + $this->content = 'http://purl.org/rss/1.0/modules/content/'; + $this->enc = 'http://purl.oclc.org/net/rss_2.0/enc#'; + $this->mappings = array( + 'feed' => $this->rss . 'channel', + 'entry' => $this->rss . 'item', + 'title' => $this->rss . 'title', + 'link' => $this->rss . 'link', + 'summary' => $this->rss . 'description', + 'content' => $this->content . 'encoded', + 'id' => $this->dc . 'identifier', + 'author' => $this->dc . 'creator', + 'category' => $this->dc . 'subject', + 'updated' => $this->dc . 'date', + 'source' => $this->dc . 'source', + ); + $this->dt_props = array( + $this->dc . 'identifier', + $this->rss . 'link' + ); + foreach ($index as $p_id => $nodes) { + foreach ($nodes as $pos => $node) { + $tag = $this->v('tag', '', $node); + if ($tag == 'feed') { + $struct = $this->extractChannel($index[$node['id']]); + $triples = ARC2::getTriplesFromIndex($struct); + foreach ($triples as $t) { + $this->addT($t); + } + } + elseif ($tag == 'entry') { + $struct = $this->extractItem($index[$node['id']]); + $triples = ARC2::getTriplesFromIndex($struct); + foreach ($triples as $t) { + $this->addT($t); + } + } + } + } + } + + function extractChannel($els) { + list($props, $sub_index) = $this->extractProps($els, 'channel'); + $uri = $props[$this->rss . 'link'][0]['value']; + return ARC2::getMergedIndex(array($uri => $props), $sub_index); + } + + function extractItem($els) { + list($props, $sub_index) = $this->extractProps($els, 'item'); + $uri = $props[$this->rss . 'link'][0]['value']; + return ARC2::getMergedIndex(array($uri => $props), $sub_index); + } + + function extractProps($els, $container) { + $r = array($this->rdf . 'type' => array(array('value' => $this->rss . $container, 'type' => 'uri'))); + $sub_index = array(); + foreach ($els as $info) { + /* key */ + $tag = $info['tag']; + if (!preg_match('/^[a-z0-9]+\:/i', $tag)) { + $k = isset($this->mappings[$tag]) ? $this->mappings[$tag] : ''; + } + elseif (isset($this->mappings[$tag])) { + $k = $this->mappings[$tag]; + } + else {/* qname */ + $k = $this->expandPName($tag); + } + //echo $k . "\n"; + if (($container == 'channel') && ($k == $this->rss . 'item')) continue; + /* val */ + $v = trim($info['cdata']); + if (!$v) $v = $this->v('href uri', '', $info['a']); + /* prop */ + if ($k) { + /* content handling */ + if (in_array($k, array($this->rss . 'description', $this->content . 'encoded'))) { + $v = $this->getNodeContent($info); + } + /* source handling */ + elseif ($k == $this->dc . 'source') { + $sub_nodes = $this->node_index[$info['id']]; + foreach ($sub_nodes as $sub_pos => $sub_info) { + if ($sub_info['tag'] == 'id') { + $v = trim($sub_info['cdata']); + } + } + } + /* link handling */ + elseif ($k == $this->rss . 'link') { + if ($link_type = $this->v('type', '', $info['a'])) { + $k2 = $this->dc . 'format'; + if (!isset($sub_index[$v])) $sub_index[$v] = array(); + if (!isset($sub_index[$v][$k2])) $sub_index[$v][$k2] = array(); + $sub_index[$v][$k2][] = array('value' => $link_type, 'type' => 'literal'); + } + } + /* author handling */ + elseif ($k == $this->dc . 'creator') { + $sub_nodes = $this->node_index[$info['id']]; + foreach ($sub_nodes as $sub_pos => $sub_info) { + if ($sub_info['tag'] == 'name') { + $v = trim($sub_info['cdata']); + } + if ($sub_info['tag'] == 'uri') { + $k2 = $this->sioc . 'has_creator'; + $v2 = trim($sub_info['cdata']); + if (!isset($r[$k2])) $r[$k2] = array(); + $r[$k2][] = array('value' => $v2, 'type' => 'uri'); + } + } + } + /* date handling */ + elseif (in_array($k, array($this->dc . 'date', $this->dct . 'modified'))) { + if (!preg_match('/^[0-9]{4}/', $v) && ($sub_v = strtotime($v)) && ($sub_v != -1)) { + $tz = date('Z', $sub_v); /* timezone offset */ + $sub_v -= $tz; /* utc */ + $v = date('Y-m-d\TH:i:s\Z', $sub_v); + } + } + /* tag handling */ + elseif ($k == $this->dc . 'subject') { + $v = $this->v('term', '', $info['a']); + } + /* other attributes in closed tags */ + elseif (!$v && ($info['state'] == 'closed') && $info['a']) { + foreach ($info['a'] as $sub_k => $sub_v) { + if (!preg_match('/(xmlns|\:|type)/', $sub_k)) { + $v = $sub_v; + break; + } + } + } + if (!isset($r[$k])) $r[$k] = array(); + $r[$k][] = array('value' => $v, 'type' => in_array($k, $this->dt_props) || !preg_match('/^[a-z0-9]+\:[^\s]+$/is', $v) ? 'literal' : 'uri'); + } + } + return array($r, $sub_index); + } + + function initXMLParser() { + if (!isset($this->xml_parser)) { + $enc = preg_match('/^(utf\-8|iso\-8859\-1|us\-ascii)$/i', $this->getEncoding(), $m) ? $m[1] : 'UTF-8'; + $parser = xml_parser_create($enc); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_set_element_handler($parser, 'open', 'close'); + xml_set_character_data_handler($parser, 'cData'); + xml_set_start_namespace_decl_handler($parser, 'nsDecl'); + xml_set_object($parser, $this); + $this->xml_parser = $parser; + } + } + + /* */ + + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_CBJSONParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_CBJSONParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,267 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('JSONParser'); + +class ARC2_CBJSONParser extends ARC2_JSONParser { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* reader */ + parent::__init(); + $this->base = 'http://cb.semsol.org/'; + $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->default_ns = $this->base . 'ns#'; + $this->nsp = array($this->rdf => 'rdf'); + } + + /* */ + + function done() { + $this->extractRDF(); + } + + function extractRDF() { + $struct = $this->struct; + if ($type = $this->getStructType($struct)) { + $s = $this->getResourceID($struct, $type); + /* rdf:type */ + $this->addT($s, $this->rdf . 'type', $this->default_ns . $this->camelCase($type), 'uri', 'uri'); + /* explicit triples */ + $this->extractResourceRDF($struct, $s); + } + } + + function getStructType($struct, $rel = '') { + /* url-based */ + if ($url = $this->v('crunchbase_url', '', $struct)) { + return preg_replace('/^.*crunchbase\.com\/([^\/]+)\/.*$/', '\\1', $url); + } + /* rel-based */ + if ($rel == 'person') return 'person'; + if ($rel == 'company') return 'company'; + if ($rel == 'acquiring_company') return 'company'; + if ($rel == 'firm') return 'company'; + if ($rel == 'provider') return 'service-provider'; + /* struct-based */ + if (isset($struct['_type'])) return $struct['_type']; + if (isset($struct['round_code'])) return 'funding_round'; + if (isset($struct['products'])) return 'company'; + if (isset($struct['first_name'])) return 'person'; + if (isset($struct['investments'])) return 'financial-organization'; + if (isset($struct['launched_year'])) return 'product'; + if (isset($struct['providerships']) && is_array($struct['providerships'])) return 'service-provider'; + return ''; + } + + function getResourceID($struct, $type) { + if ($type && isset($struct['permalink'])) { + return $this->base . $type . '/' . $struct['permalink'] . '#self'; + } + return $this->createBnodeID(); + } + + function getPropertyURI($name, $ns = '') { + if (!$ns) $ns = $this->default_ns; + if (preg_match('/^(product|funding_round|investment|acquisition|.+ship|office|milestone|.+embed|.+link|degree|fund)s/', $name, $m)) $name = $m[1]; + if ($name == 'tag_list') $name = 'tag'; + if ($name == 'competitions') $name = 'competitor'; + return $ns . $name; + } + + function createSubURI($s, $k, $pos) { + $s = str_replace('#self', '/', $s); + if (preg_match('/(office|ship|investment|milestone|fund|embed|link)s$/', $k)) $k = substr($k, 0, -1); + return $s . $k . '-' . ($pos + 1) . '#self'; + } + + /* */ + + function extractResourceRDF($struct, $s, $pos = 0) { + $s_type = preg_match('/^\_\:/', $s) ? 'bnode' : 'uri'; + $date_prefixes = array(); + foreach ($struct as $k => $v) { + if ($k == 'acquisition') $k = 'exit'; + if (preg_match('/^(.*)\_(year|month|day)$/', $k, $m)) { + if (!in_array($m[1], $date_prefixes)) $date_prefixes[] = $m[1]; + } + $sub_m = 'extract' . $this->camelCase($k) . 'RDF'; + if (method_exists($this, $sub_m)) { + $this->$sub_m($s, $s_type, $v); + continue; + } + $p = $this->getPropertyURI($k); + if (!$v) continue; + /* simple, single v */ + if (!is_array($v)) { + $o_type = preg_match('/^[a-z]+\:[^\s]+$/is', $v) ? 'uri' : 'literal'; + $v = trim($v); + if (preg_match('/^https?\:\/\/[^\/]+$/', $v)) $v .= '/'; + $this->addT($s, $p, $v, $s_type, $o_type); + /* rdfs:label */ + if ($k == 'name') $this->addT($s, 'http://www.w3.org/2000/01/rdf-schema#label', $v, $s_type, $o_type); + /* dc:identifier */ + //if ($k == 'permalink') $this->addT($s, 'http://purl.org/dc/elements/1.1/identifier', $v, $s_type, $o_type); + } + /* structured, single v */ + elseif (!$this->isFlatArray($v)) { + if ($o_type = $this->getStructType($v, $k)) {/* known type */ + $o = $this->getResourceID($v, $o_type); + $this->addT($s, $p, $o, $s_type, 'uri'); + $this->addT($o, $this->rdf . 'type', $this->default_ns . $this->camelCase($o_type), 'uri', 'uri'); + } + else {/* unknown type */ + $o = $this->createSubURI($s, $k, $pos); + $this->addT($s, $p, $o, $s_type, 'uri'); + $this->extractResourceRDF($v, $o); + } + } + /* value list */ + else { + foreach ($v as $sub_pos => $sub_v) { + $this->extractResourceRDF(array($k => $sub_v), $s, $sub_pos); + } + } + } + /* infer XSD triples */ + foreach ($date_prefixes as $prefix) { + $this->inferDate($prefix, $s, $struct); + } + } + + function isFlatArray($v) { + foreach ($v as $k => $sub_v) { + return is_numeric($k) ? 1 : 0; + } + } + + /* */ + + function extractTagListRDF($s, $s_type, $v) { + if (!$v) return 0; + $tags = preg_split('/\, /', $v); + foreach ($tags as $tag) { + if (!trim($tag)) continue; + $this->addT($s, $this->getPropertyURI('tag'), $tag, $s_type, 'literal'); + } + } + + function extractImageRDF($s, $s_type, $v, $rel = 'image') { + if (!$v) return 1; + $sizes = $v['available_sizes']; + foreach ($sizes as $size) { + $w = $size[0][0]; + $h = $size[0][1]; + $img = 'http://www.crunchbase.com/' . $size[1]; + $this->addT($s, $this->getPropertyURI($rel), $img, $s_type, 'uri'); + $this->addT($img, $this->getPropertyURI('width'), $w, 'uri', 'literal'); + $this->addT($img, $this->getPropertyURI('height'), $h, 'uri', 'literal'); + } + } + + function extractScreenshotsRDF($s, $s_type, $v) { + if (!$v) return 1; + foreach ($v as $sub_v) { + $this->extractImageRDF($s, $s_type, $sub_v, 'screenshot'); + } + } + + function extractProductsRDF($s, $s_type, $v) { + foreach ($v as $sub_v) { + $o = $this->getResourceID($sub_v, 'product'); + $this->addT($s, $this->getPropertyURI('product'), $o, $s_type, 'uri'); + } + } + + function extractCompetitionsRDF($s, $s_type, $v) { + foreach ($v as $sub_v) { + $o = $this->getResourceID($sub_v['competitor'], 'company'); + $this->addT($s, $this->getPropertyURI('competitor'), $o, $s_type, 'uri'); + } + } + + function extractFundingRoundsRDF($s, $s_type, $v) { + foreach ($v as $pos => $sub_v) { + $o = $this->createSubURI($s, 'funding_round', $pos); + $this->addT($s, $this->getPropertyURI('funding_round'), $o, $s_type, 'uri'); + $this->extractResourceRDF($sub_v, $o, $pos); + } + } + + function extractInvestmentsRDF($s, $s_type, $v) { + foreach ($v as $pos => $sub_v) { + /* incoming */ + foreach (array('person' => 'person', 'company' => 'company', 'financial_org' => 'financial-organization') as $k => $type) { + if (isset($sub_v[$k])) $this->addT($s, $this->getPropertyURI('investment'), $this->getResourceID($sub_v[$k], $type), $s_type, 'uri'); + } + /* outgoing */ + if (isset($sub_v['funding_round'])) { + $o = $this->createSubURI($s, 'investment', $pos); + $this->addT($s, $this->getPropertyURI('investment'), $o, $s_type, 'uri'); + $this->extractResourceRDF($sub_v['funding_round'], $o, $pos); + } + } + } + + function extractExternalLinksRDF($s, $s_type, $v) { + foreach ($v as $sub_v) { + $href = $sub_v['external_url']; + if (preg_match('/^https?\:\/\/[^\/]+$/', $href)) $href .= '/'; + $this->addT($s, $this->getPropertyURI('external_link'), $href, $s_type, 'uri'); + $this->addT($href, $this->getPropertyURI('title'), $sub_v['title'], $s_type, 'literal'); + } + } + + function extractWebPresencesRDF($s, $s_type, $v) { + foreach ($v as $sub_v) { + $href = $sub_v['external_url']; + if (preg_match('/^https?\:\/\/[^\/]+$/', $href)) $href .= '/'; + $this->addT($s, $this->getPropertyURI('web_presence'), $href, $s_type, 'uri'); + $this->addT($href, $this->getPropertyURI('title'), $sub_v['title'], $s_type, 'literal'); + } + } + + function extractCreatedAtRDF($s, $s_type, $v) { + $v = $this->getAPIDateXSD($v); + $this->addT($s, $this->getPropertyURI('created_at'), $v, $s_type, 'literal'); + } + + function extractUpdatedAtRDF($s, $s_type, $v) { + $v = $this->getAPIDateXSD($v); + $this->addT($s, $this->getPropertyURI('updated_at'), $v, $s_type, 'literal'); + } + + function getAPIDateXSD($val) { + //Fri Jan 16 21:11:48 UTC 2009 + if (preg_match('/^[a-z]+ ([a-z]+) ([0-9]+) ([0-9]{2}\:[0-9]{2}\:[0-9]{2}) UTC ([0-9]{4})/i', $val, $m)) { + $months = array('Jan' => '01', 'Feb' => '02', 'Mar' =>'03', 'Apr' => '04', 'May' => '05', 'Jun' => '06', 'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12'); + return $m[4] . '-' . $months[$m[1]] . '-' . $m[2] . 'T' . $m[3] . 'Z'; + } + return '2000-01-01'; + } + + /* */ + + function inferDate($prefix, $s, $struct) { + $s_type = preg_match('/^\_\:/', $s) ? 'bnode' : 'uri'; + $r = ''; + foreach (array('year', 'month', 'day') as $suffix) { + $val = $this->v1($prefix . '_' . $suffix, '00', $struct); + $r .= ($r ? '-' : '') . str_pad($val, 2, '0', STR_PAD_LEFT); + } + if ($r != '00-00-00') { + $this->addT($s, $this->getPropertyURI($prefix . '_date'), $r, $s_type, 'literal'); + } + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_JSONParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_JSONParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,165 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('RDFParser'); + +class ARC2_JSONParser extends ARC2_RDFParser { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + } + + /* */ + + function x($re, $v, $options = 'si') { + while (preg_match('/^\s*(\/\*.*\*\/)(.*)$/Usi', $v, $m)) {/* comment removal */ + $v = $m[2]; + } + $this->unparsed_code = (strlen($this->unparsed_code) > strlen($v)) ? $v : $this->unparsed_code; + return ARC2::x($re, $v, $options); + } + + function parse($path, $data = '') { + $this->state = 0; + /* reader */ + if (!$this->v('reader')) { + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->setAcceptHeader('Accept: application/json; q=0.9, */*; q=0.1'); + $this->reader->activate($path, $data); + $this->x_base = isset($this->a['base']) && $this->a['base'] ? $this->a['base'] : $this->reader->base; + /* parse */ + $doc = ''; + while ($d = $this->reader->readStream()) { + $doc .= $d; + } + $this->reader->closeStream(); + unset($this->reader); + $doc = preg_replace('/^[^\{]*(.*\})[^\}]*$/is', '\\1', $doc); + $this->unparsed_code = $doc; + list($this->struct, $rest) = $this->extractObject($doc); + return $this->done(); + } + + /* */ + + function extractObject($v) { + if (function_exists('json_decode')) return array(json_decode($v, 1), ''); + $r = array(); + /* sub-object */ + if ($sub_r = $this->x('\{', $v)) { + $v = $sub_r[1]; + while ((list($sub_r, $v) = $this->extractEntry($v)) && $sub_r) { + $r[$sub_r['key']] = $sub_r['value']; + } + if ($sub_r = $this->x('\}', $v)) $v = $sub_r[1]; + } + /* sub-list */ + elseif ($sub_r = $this->x('\[', $v)) { + $v = $sub_r[1]; + while ((list($sub_r, $v) = $this->extractObject($v)) && $sub_r) { + $r[] = $sub_r; + $v = ltrim($v, ','); + } + if ($sub_r = $this->x('\]', $v)) $v = $sub_r[1]; + } + /* sub-value */ + elseif ((list($sub_r, $v) = $this->extractValue($v)) && ($sub_r !== false)) { + $r = $sub_r; + } + return array($r, $v); + } + + function extractEntry($v) { + if ($r = $this->x('\,', $v)) $v = $r[1]; + /* k */ + if ($r = $this->x('\"([^\"]+)\"\s*\:', $v)) { + $k = $r[1]; + $sub_v = $r[2]; + if (list($sub_r, $sub_v) = $this->extractObject($sub_v)) { + return array( + array('key' => $k, 'value' => $sub_r), + $sub_v + ); + } + } + return array(0, $v); + } + + function extractValue($v) { + if ($r = $this->x('\,', $v)) $v = $r[1]; + if ($sub_r = $this->x('null', $v)) { + return array(null, $sub_r[1]); + } + if ($sub_r = $this->x('(true|false)', $v)) { + return array($sub_r[1], $sub_r[2]); + } + if ($sub_r = $this->x('([\-\+]?[0-9\.]+)', $v)) { + return array($sub_r[1], $sub_r[2]); + } + if ($sub_r = $this->x('\"', $v)) { + $rest = $sub_r[1]; + if (preg_match('/^([^\x5c]*|.*[^\x5c]|.*\x5c{2})\"(.*)$/sU', $rest, $m)) { + $val = $m[1]; + /* unescape chars (single-byte) */ + $val = preg_replace('/\\\u(.{4})/e', 'chr(hexdec("\\1"))', $val); + //$val = preg_replace('/\\\u00(.{2})/e', 'rawurldecode("%\\1")', $val); + /* other escaped chars */ + $from = array('\\\\', '\r', '\t', '\n', '\"', '\b', '\f', '\/'); + $to = array("\\", "\r", "\t", "\n", '"', "\b", "\f", "/"); + $val = str_replace($from, $to, $val); + return array($val, $m[2]); + } + } + return array(false, $v); + } + + /* */ + + function getObject() { + return $this->v('struct', array()); + } + + function getTriples() { + return $this->v('triples', array()); + } + + function countTriples() { + return $this->t_count; + } + + function addT($s = '', $p = '', $o = '', $s_type = '', $o_type = '', $o_dt = '', $o_lang = '') { + $o = $this->toUTF8($o); + //echo str_replace($this->base, '', "-----\n adding $s / $p / $o\n-----\n"); + $t = array('s' => $s, 'p' => $p, 'o' => $o, 's_type' => $s_type, 'o_type' => $o_type, 'o_datatype' => $o_dt, 'o_lang' => $o_lang); + if ($this->skip_dupes) { + $h = md5(serialize($t)); + if (!isset($this->added_triples[$h])) { + $this->triples[$this->t_count] = $t; + $this->t_count++; + $this->added_triples[$h] = true; + } + } + else { + $this->triples[$this->t_count] = $t; + $this->t_count++; + } + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_LegacyXMLParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_LegacyXMLParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,311 @@ +encoding = $this->v('encoding', false, $this->a); + $this->state = 0; + $this->x_base = $this->base; + $this->xml = 'http://www.w3.org/XML/1998/namespace'; + $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->nsp = array($this->xml => 'xml', $this->rdf => 'rdf'); + $this->allowCDataNodes = 1; + $this->target_encoding = ''; + $this->keep_cdata_ws = $this->v('keep_cdata_whitespace', 0, $this->a); + } + + /* */ + + function setReader(&$reader) { + $this->reader = $reader; + } + + function parse($path, $data = '', $iso_fallback = false) { + $this->nodes = array(); + $this->node_count = 0; + $this->level = 0; + /* reader */ + if (!$this->v('reader')) { + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->setAcceptHeader('Accept: application/xml; q=0.9, */*; q=0.1'); + $this->reader->activate($path, $data); + $this->x_base = isset($this->a['base']) && $this->a['base'] ? $this->a['base'] : $this->reader->base; + $this->base = $this->x_base; + $this->doc_url = $this->reader->base; + /* xml parser */ + $this->initXMLParser(); + /* parse */ + $first = true; + while ($d = $this->reader->readStream(1)) { + if ($iso_fallback && $first) { + $d = '' . "\n" . preg_replace('/^\<\?xml [^\>]+\?\>\s*/s', '', $d); + } + if (!xml_parse($this->xml_parser, $d, false)) { + $error_str = xml_error_string(xml_get_error_code($this->xml_parser)); + $line = xml_get_current_line_number($this->xml_parser); + if (!$iso_fallback && preg_match("/Invalid character/i", $error_str)) { + xml_parser_free($this->xml_parser); + unset($this->xml_parser); + $this->reader->closeStream(); + unset($this->reader); + $this->__init(); + $this->encoding = 'ISO-8859-1'; + $this->initXMLParser(); + return $this->parse($path, $data, true); + } + else { + return $this->addError('XML error: "' . $error_str . '" at line ' . $line . ' (parsing as ' . $this->getEncoding() . ')'); + } + } + $first = false; + } + $this->target_encoding = xml_parser_get_option($this->xml_parser, XML_OPTION_TARGET_ENCODING); + xml_parser_free($this->xml_parser); + $this->reader->closeStream(); + unset($this->reader); + return $this->done(); + } + + /* */ + + function getEncoding($src = 'config') { + if ($src == 'parser') { + return $this->target_encoding; + } + elseif (($src == 'config') && $this->encoding) { + return $this->encoding; + } + return $this->reader->getEncoding(); + } + + /* */ + + function done() { + + } + + /* */ + + function getStructure() { + return array('nodes' => $this->v('nodes', array())); + } + + /* */ + + function getNodeIndex(){ + if (!isset($this->node_index)) { + /* index by parent */ + $index = array(); + for ($i = 0, $i_max = count($this->nodes); $i < $i_max; $i++) { + $node = $this->nodes[$i]; + $node['id'] = $i; + $node['doc_base'] = $this->base; + if (isset($this->doc_url)) $node['doc_url'] = $this->doc_url; + $this->updateNode($node); + $p_id = $node['p_id']; + if (!isset($index[$p_id])) { + $index[$p_id] = array(); + } + $index[$p_id][$node['pos']] = $node; + } + $this->node_index = $index; + } + return $this->node_index; + } + + function getNodes() { + return $this->nodes; + } + + function getSubNodes($n) { + return $this->v($n['id'], array(), $this->getNodeIndex()); + } + + function getNodeContent($n, $outer = 0, $trim = 1) { + //echo '
    ' . htmlspecialchars(print_r($n, 1)) . '
    '; + if ($n['tag'] == 'cdata') { + $r = $n['a']['value']; + } + else { + $r = ''; + if ($outer) { + $r .= '<' . $n['tag']; + asort($n['a']); + if (isset($n['a']['xmlns']) && $n['a']['xmlns']['']) { + $r .= ' xmlns="' . $n['a']['xmlns'][''] . '"'; + } + foreach ($n['a'] as $a => $val) { + $r .= preg_match('/^[^\s]+$/', $a) && !is_array($val) ? ' ' . $a . '="' . addslashes($val) . '"' : ''; + } + $r .= $n['empty'] ? '/>' : '>'; + } + if (!$n['empty']) { + $r .= $this->v('cdata', '', $n); + $sub_nodes = $this->getSubNodes($n); + foreach ($sub_nodes as $sub_n) { + $r .= $this->getNodeContent($sub_n, 1, 0); + } + if ($outer) { + $r .= ''; + } + } + } + return ($trim && !$this->keep_cdata_ws) ? trim($r) : $r; + } + + /* */ + + function pushNode($n) { + $n['id'] = $this->node_count; + $this->nodes[$this->node_count] = $n; + $this->node_count++; + } + + function getCurNode($t = '') { + $i = 1; + do { + $r = $this->node_count ? $this->nodes[$this->node_count - $i] : 0; + $found = (!$t || ($r['tag'] == $t)) ? 1 : 0; + $i++; + } while (!$found && isset($this->nodes[$this->node_count - $i])); + return $r; + } + + function updateNode($node) {/* php4-save */ + $this->nodes[$node['id']] = $node; + } + + /* */ + + function initXMLParser() { + if (!isset($this->xml_parser)) { + $enc = preg_match('/^(utf\-8|iso\-8859\-1|us\-ascii)$/i', $this->getEncoding(), $m) ? $m[1] : 'UTF-8'; + $parser = xml_parser_create_ns($enc, ''); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_set_element_handler($parser, 'open', 'close'); + xml_set_character_data_handler($parser, 'cData'); + xml_set_start_namespace_decl_handler($parser, 'nsDecl'); + xml_set_object($parser, $this); + $this->xml_parser = $parser; + } + } + + /* */ + + function open($p, $t, $a) { + $t_exact = $t; + //echo "
    \n".'opening '.$t . ' ' . print_r($a, 1); flush(); + //echo "
    \n".'opening '.$t; flush(); + $t = strpos($t, ':') ? $t : strtolower($t); + /* base check */ + $base = ''; + if (($t == 'base') && isset($a['href'])) { + $this->base = $a['href']; + $base = $a['href']; + } + /* URIs */ + foreach (array('href', 'src', 'id') as $uri_a) { + if (isset($a[$uri_a])) { + $a[$uri_a . ' uri'] = ($uri_a == 'id') ? $this->calcURI('#'.$a[$uri_a]) : $this->calcURI($a[$uri_a]); + } + } + /* ns */ + if ($a) { + foreach ($a as $k => $v) { + if (strpos($k, 'xmlns') === 0) { + $this->nsDecl($p, trim(substr($k, 5), ':'), $v); + } + } + } + /* node */ + $node = array( + 'tag' => $t, + 'tag_exact' => $t_exact, + 'a' => $a, + 'level' => $this->level, + 'pos' => 0, + 'p_id' => $this->node_count-1, + 'state' => 'open', + 'empty' => 0, + 'cdata' =>'' + ); + if ($base) { + $node['base'] = $base; + } + /* parent/sibling */ + if ($this->node_count) { + $l = $this->level; + $prev_node = $this->getCurNode(); + if ($prev_node['level'] == $l) { + $node['p_id'] = $prev_node['p_id']; + $node['pos'] = $prev_node['pos']+1; + } + elseif($prev_node['level'] > $l) { + while($prev_node['level'] > $l) { + if (!isset($this->nodes[$prev_node['p_id']])) { + //$this->addError('nesting mismatch: tag is ' . $t . ', level is ' . $l . ', prev_level is ' . $prev_node['level'] . ', prev_node p_id is ' . $prev_node['p_id']); + break; + } + $prev_node = $this->nodes[$prev_node['p_id']]; + } + $node['p_id'] = $prev_node['p_id']; + $node['pos'] = $prev_node['pos']+1; + } + } + $this->pushNode($node); + $this->level++; + /* cdata */ + $this->cur_cdata=""; + } + + function close($p, $t, $empty = 0) { + //echo "
    \n".'closing '.$t; flush(); + $node = $this->getCurNode($t); + $node['state'] = 'closed'; + $node['empty'] = $empty; + $this->updateNode($node); + $this->level--; + } + + function cData($p, $d) { + //echo trim($d) ? "
    \n".'cdata: ' . $d : ''; flush(); + $node = $this->getCurNode(); + if($node['state'] == 'open') { + $node['cdata'] .= $d; + $this->updateNode($node); + } + else {/* cdata is sibling of node */ + if ($this->allowCDataNodes) { + $this->open($p, 'cdata', array('value' => $d)); + $this->close($p, 'cdata'); + } + } + } + + function nsDecl($p, $prf, $uri) { + if (is_array($uri)) return 1; + $this->ns[$prf] = $uri; + $this->nsp[$uri] = isset($this->nsp[$uri]) ? $this->nsp[$uri] : $prf; + } + + /* */ + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_RDFParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_RDFParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,139 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_RDFParser extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* proxy_host, proxy_port, proxy_skip, http_accept_header, http_user_agent_header, max_redirects, reader, skip_dupes */ + parent::__init(); + $this->a['format'] = $this->v('format', false, $this->a); + $this->keep_time_limit = $this->v('keep_time_limit', 0, $this->a); + $this->triples = array(); + $this->t_count = 0; + $this->added_triples = array(); + $this->skip_dupes = $this->v('skip_dupes', false, $this->a); + $this->bnode_prefix = $this->v('bnode_prefix', 'arc'.substr(md5(uniqid(rand())), 0, 4).'b', $this->a); + $this->bnode_id = 0; + $this->format = ''; + } + + /* */ + + function setReader(&$reader) { + $this->reader = $reader; + } + + function parse($path, $data = '') { + /* reader */ + if (!isset($this->reader)) { + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->activate($path, $data) ; + /* format detection */ + $mappings = array( + 'rdfxml' => 'RDFXML', + 'turtle' => 'Turtle', + 'sparqlxml' => 'SPOG', + 'ntriples' => 'Turtle', + 'html' => 'SemHTML', + 'rss' => 'RSS', + 'atom' => 'Atom', + 'sgajson' => 'SGAJSON', + 'cbjson' => 'CBJSON' + ); + $format = $this->reader->getFormat(); + if (!$format || !isset($mappings[$format])) { + return $this->addError('No parser available for "' . $format . '".'); + } + $this->format = $format; + /* format parser */ + $suffix = $mappings[$format] . 'Parser'; + ARC2::inc($suffix); + $cls = 'ARC2_' . $suffix; + $this->parser = new $cls($this->a, $this); + $this->parser->setReader($this->reader); + return $this->parser->parse($path, $data); + } + + function parseData($data) { + return $this->parse(ARC2::getScriptURI(), $data); + } + + /* */ + + function done() { + } + + /* */ + + function createBnodeID(){ + $this->bnode_id++; + return '_:' . $this->bnode_prefix . $this->bnode_id; + } + + function getTriples() { + return $this->v('parser') ? $this->m('getTriples', false, array(), $this->v('parser')) : array(); + } + + function countTriples() { + return $this->v('parser') ? $this->m('countTriples', false, 0, $this->v('parser')) : 0; + } + + function getSimpleIndex($flatten_objects = 1, $vals = '') { + return ARC2::getSimpleIndex($this->getTriples(), $flatten_objects, $vals); + } + + function reset() { + $this->__init(); + if (isset($this->reader)) unset($this->reader); + if (isset($this->parser)) { + $this->parser->__init(); + unset($this->parser); + } + } + + /* */ + + function extractRDF($formats = '') { + if (method_exists($this->parser, 'extractRDF')) { + return $this->parser->extractRDF($formats); + } + } + + /* */ + + function getEncoding($src = 'config') { + if (method_exists($this->parser, 'getEncoding')) { + return $this->parser->getEncoding($src); + } + } + + /** + * returns the array of namespace prefixes encountered during parsing + * @return array (keys = namespace URI / values = prefix used) + */ + + function getParsedNamespacePrefixes() { + if (isset($this->parser)) { + return $this->v('nsp', array(), $this->parser); + } + return $this->v('nsp', array()); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_RDFXMLParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_RDFXMLParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,640 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 +*/ + +ARC2::inc('RDFParser'); + +class ARC2_RDFXMLParser extends ARC2_RDFParser { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* reader */ + parent::__init(); + $this->encoding = $this->v('encoding', false, $this->a); + $this->state = 0; + $this->x_lang = ''; + $this->x_base = $this->base; + $this->xml = 'http://www.w3.org/XML/1998/namespace'; + $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->nsp = array($this->xml => 'xml', $this->rdf => 'rdf'); + $this->s_stack = array(); + $this->s_count = 0; + $this->target_encoding = ''; + } + + /* */ + + function parse($path, $data = '', $iso_fallback = false) { + /* reader */ + if (!$this->v('reader')) { + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->setAcceptHeader('Accept: application/rdf+xml; q=0.9, */*; q=0.1'); + $this->reader->activate($path, $data); + $this->x_base = isset($this->a['base']) && $this->a['base'] ? $this->a['base'] : $this->reader->base; + /* xml parser */ + $this->initXMLParser(); + /* parse */ + $first = true; + while ($d = $this->reader->readStream()) { + if (!$this->keep_time_limit) @set_time_limit($this->v('time_limit', 60, $this->a)); + if ($iso_fallback && $first) { + $d = '' . "\n" . preg_replace('/^\<\?xml [^\>]+\?\>\s*/s', '', $d); + $first = false; + } + if (!xml_parse($this->xml_parser, $d, false)) { + $error_str = xml_error_string(xml_get_error_code($this->xml_parser)); + $line = xml_get_current_line_number($this->xml_parser); + $this->tmp_error = 'XML error: "' . $error_str . '" at line ' . $line . ' (parsing as ' . $this->getEncoding() . ')'; + if (!$iso_fallback && preg_match("/Invalid character/i", $error_str)) { + xml_parser_free($this->xml_parser); + unset($this->xml_parser); + $this->reader->closeStream(); + $this->__init(); + $this->encoding = 'ISO-8859-1'; + unset($this->xml_parser); + unset($this->reader); + return $this->parse($path, $data, true); + } + else { + return $this->addError($this->tmp_error); + } + } + } + $this->target_encoding = xml_parser_get_option($this->xml_parser, XML_OPTION_TARGET_ENCODING); + xml_parser_free($this->xml_parser); + $this->reader->closeStream(); + unset($this->reader); + return $this->done(); + } + + /* */ + + function initXMLParser() { + if (!isset($this->xml_parser)) { + $enc = preg_match('/^(utf\-8|iso\-8859\-1|us\-ascii)$/i', $this->getEncoding(), $m) ? $m[1] : 'UTF-8'; + $parser = xml_parser_create_ns($enc, ''); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_set_element_handler($parser, 'open', 'close'); + xml_set_character_data_handler($parser, 'cdata'); + xml_set_start_namespace_decl_handler($parser, 'nsDecl'); + xml_set_object($parser, $this); + $this->xml_parser = $parser; + } + } + + /* */ + + function getEncoding($src = 'config') { + if ($src == 'parser') { + return $this->target_encoding; + } + elseif (($src == 'config') && $this->encoding) { + return $this->encoding; + } + return $this->reader->getEncoding(); + } + + /* */ + + function getTriples() { + return $this->v('triples', array()); + } + + function countTriples() { + return $this->t_count; + } + + /* */ + + function pushS(&$s) { + $s['pos'] = $this->s_count; + $this->s_stack[$this->s_count] = $s; + $this->s_count++; + } + + function popS(){/* php 4.0.x-safe */ + $r = array(); + $this->s_count--; + for ($i = 0, $i_max = $this->s_count; $i < $i_max; $i++) { + $r[$i] = $this->s_stack[$i]; + } + $this->s_stack = $r; + } + + function updateS($s) { + $this->s_stack[$s['pos']] = $s; + } + + function getParentS() { + return ($this->s_count && isset($this->s_stack[$this->s_count - 1])) ? $this->s_stack[$this->s_count - 1] : false; + } + + function getParentXBase() { + if ($p = $this->getParentS()) { + return isset($p['p_x_base']) && $p['p_x_base'] ? $p['p_x_base'] : (isset($p['x_base']) ? $p['x_base'] : ''); + } + return $this->x_base; + } + + function getParentXLang() { + if ($p = $this->getParentS()) { + return isset($p['p_x_lang']) && $p['p_x_lang'] ? $p['p_x_lang'] : (isset($p['x_lang']) ? $p['x_lang'] : ''); + } + return $this->x_lang; + } + + /* */ + + function addT($s, $p, $o, $s_type, $o_type, $o_dt = '', $o_lang = '') { + //echo "-----\nadding $s / $p / $o\n-----\n"; + $t = array('s' => $s, 'p' => $p, 'o' => $o, 's_type' => $s_type, 'o_type' => $o_type, 'o_datatype' => $o_dt, 'o_lang' => $o_lang); + if ($this->skip_dupes) { + $h = md5(serialize($t)); + if (!isset($this->added_triples[$h])) { + $this->triples[$this->t_count] = $t; + $this->t_count++; + $this->added_triples[$h] = true; + } + } + else { + $this->triples[$this->t_count] = $t; + $this->t_count++; + } + } + + function reify($t, $s, $p, $o, $s_type, $o_type, $o_dt = '', $o_lang = '') { + $this->addT($t, $this->rdf.'type', $this->rdf.'Statement', 'uri', 'uri'); + $this->addT($t, $this->rdf.'subject', $s, 'uri', $s_type); + $this->addT($t, $this->rdf.'predicate', $p, 'uri', 'uri'); + $this->addT($t, $this->rdf.'object', $o, 'uri', $o_type, $o_dt, $o_lang); + } + + /* */ + + function open($p, $t, $a) { + //echo "state is $this->state\n"; + //echo "opening $t\n"; + switch($this->state) { + case 0: return $this->h0Open($t, $a); + case 1: return $this->h1Open($t, $a); + case 2: return $this->h2Open($t, $a); + case 4: return $this->h4Open($t, $a); + case 5: return $this->h5Open($t, $a); + case 6: return $this->h6Open($t, $a); + default: $this->addError('open() called at state ' . $this->state . ' in '.$t); + } + } + + function close($p, $t) { + //echo "state is $this->state\n"; + //echo "closing $t\n"; + switch($this->state){ + case 1: return $this->h1Close($t); + case 2: return $this->h2Close($t); + case 3: return $this->h3Close($t); + case 4: return $this->h4Close($t); + case 5: return $this->h5Close($t); + case 6: return $this->h6Close($t); + default: $this->addError('close() called at state ' . $this->state . ' in '.$t); + } + } + + function cdata($p, $d) { + //echo "state is $this->state\n"; + //echo "cdata\n"; + switch($this->state){ + case 4: return $this->h4Cdata($d); + case 6: return $this->h6Cdata($d); + default: return false; + } + } + + function nsDecl($p, $prf, $uri) { + $this->nsp[$uri] = isset($this->nsp[$uri]) ? $this->nsp[$uri] : $prf; + } + + /* */ + + function h0Open($t, $a) { + $this->x_lang = $this->v($this->xml.'lang', $this->x_lang, $a); + $this->x_base = $this->calcURI($this->v($this->xml.'base', $this->x_base, $a)); + $this->state = 1; + if ($t !== $this->rdf.'RDF') { + $this->h1Open($t, $a); + } + } + + /* */ + + function h1Open($t, $a) { + $s = array( + 'x_base' => isset($a[$this->xml.'base']) ? $this->calcURI($a[$this->xml.'base']) : $this->getParentXBase(), + 'x_lang' => isset($a[$this->xml.'lang']) ? $a[$this->xml.'lang'] : $this->getParentXLang(), + 'li_count' => 0, + ); + /* ID */ + if (isset($a[$this->rdf.'ID'])) { + $s['type'] = 'uri'; + $s['value'] = $this->calcURI('#'.$a[$this->rdf.'ID'], $s['x_base']); + } + /* about */ + elseif (isset($a[$this->rdf.'about'])) { + $s['type'] = 'uri'; + $s['value'] = $this->calcURI($a[$this->rdf.'about'], $s['x_base']); + } + /* bnode */ + else { + $s['type'] = 'bnode'; + if (isset($a[$this->rdf.'nodeID'])) { + $s['value'] = '_:'.$a[$this->rdf.'nodeID']; + } + else { + $s['value'] = $this->createBnodeID(); + } + } + /* sub-node */ + if ($this->state === 4) { + $sup_s = $this->getParentS(); + /* new collection */ + if (isset($sup_s['o_is_coll']) && $sup_s['o_is_coll']) { + $coll = array('value' => $this->createBnodeID(), 'type' => 'bnode', 'is_coll' => true, 'x_base' => $s['x_base'], 'x_lang' => $s['x_lang']); + $this->addT($sup_s['value'], $sup_s['p'], $coll['value'], $sup_s['type'], $coll['type']); + $this->addT($coll['value'], $this->rdf . 'first', $s['value'], $coll['type'], $s['type']); + $this->pushS($coll); + } + /* new entry in existing coll */ + elseif (isset($sup_s['is_coll']) && $sup_s['is_coll']) { + $coll = array('value' => $this->createBnodeID(), 'type' => 'bnode', 'is_coll' => true, 'x_base' => $s['x_base'], 'x_lang' => $s['x_lang']); + $this->addT($sup_s['value'], $this->rdf . 'rest', $coll['value'], $sup_s['type'], $coll['type']); + $this->addT($coll['value'], $this->rdf . 'first', $s['value'], $coll['type'], $s['type']); + $this->pushS($coll); + } + /* normal sub-node */ + elseif(isset($sup_s['p']) && $sup_s['p']) { + $this->addT($sup_s['value'], $sup_s['p'], $s['value'], $sup_s['type'], $s['type']); + } + } + /* typed node */ + if ($t !== $this->rdf.'Description') { + $this->addT($s['value'], $this->rdf.'type', $t, $s['type'], 'uri'); + } + /* (additional) typing attr */ + if (isset($a[$this->rdf.'type'])) { + $this->addT($s['value'], $this->rdf.'type', $a[$this->rdf.'type'], $s['type'], 'uri'); + } + /* Seq|Bag|Alt */ + if (in_array($t, array($this->rdf.'Seq', $this->rdf.'Bag', $this->rdf.'Alt'))) { + $s['is_con'] = true; + } + /* any other attrs (skip rdf and xml, except rdf:_, rdf:value, rdf:Seq) */ + foreach($a as $k => $v) { + if (((strpos($k, $this->xml) === false) && (strpos($k, $this->rdf) === false)) || preg_match('/(\_[0-9]+|value|Seq|Bag|Alt|Statement|Property|List)$/', $k)) { + if (strpos($k, ':')) { + $this->addT($s['value'], $k, $v, $s['type'], 'literal', '', $s['x_lang']); + } + } + } + $this->pushS($s); + $this->state = 2; + } + + /* */ + + function h2Open($t, $a) { + $s = $this->getParentS(); + foreach (array('p_x_base', 'p_x_lang', 'p_id', 'o_is_coll') as $k) { + unset($s[$k]); + } + /* base */ + if (isset($a[$this->xml.'base'])) { + $s['p_x_base'] = $this->calcURI($a[$this->xml.'base'], $s['x_base']); + } + $b = isset($s['p_x_base']) && $s['p_x_base'] ? $s['p_x_base'] : $s['x_base']; + /* lang */ + if (isset($a[$this->xml.'lang'])) { + $s['p_x_lang'] = $a[$this->xml.'lang']; + } + $l = isset($s['p_x_lang']) && $s['p_x_lang'] ? $s['p_x_lang'] : $s['x_lang']; + /* adjust li */ + if ($t === $this->rdf.'li') { + $s['li_count']++; + $t = $this->rdf.'_'.$s['li_count']; + } + /* set p */ + $s['p'] = $t; + /* reification */ + if (isset($a[$this->rdf.'ID'])) { + $s['p_id'] = $a[$this->rdf.'ID']; + } + $o = array('value' => '', 'type' => '', 'x_base' => $b, 'x_lang' => $l); + /* resource/rdf:resource */ + if (isset($a['resource'])) { + $a[$this->rdf . 'resource'] = $a['resource']; + unset($a['resource']); + } + if (isset($a[$this->rdf.'resource'])) { + $o['value'] = $this->calcURI($a[$this->rdf.'resource'], $b); + $o['type'] = 'uri'; + $this->addT($s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + /* type */ + if (isset($a[$this->rdf.'type'])) { + $this->addT($o['value'], $this->rdf.'type', $a[$this->rdf.'type'], 'uri', 'uri'); + } + /* reification */ + if (isset($s['p_id'])) { + $this->reify($this->calcURI('#'.$s['p_id'], $b), $s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + unset($s['p_id']); + } + $this->state = 3; + } + /* named bnode */ + elseif (isset($a[$this->rdf.'nodeID'])) { + $o['value'] = '_:' . $a[$this->rdf.'nodeID']; + $o['type'] = 'bnode'; + $this->addT($s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + $this->state = 3; + /* reification */ + if (isset($s['p_id'])) { + $this->reify($this->calcURI('#'.$s['p_id'], $b), $s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + } + } + /* parseType */ + elseif (isset($a[$this->rdf.'parseType'])) { + if ($a[$this->rdf.'parseType'] === 'Literal') { + $s['o_xml_level'] = 0; + $s['o_xml_data'] = ''; + $s['p_xml_literal_level'] = 0; + $s['ns'] = array(); + $this->state = 6; + } + elseif ($a[$this->rdf.'parseType'] === 'Resource') { + $o['value'] = $this->createBnodeID(); + $o['type'] = 'bnode'; + $o['has_closing_tag'] = 0; + $this->addT($s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + $this->pushS($o); + /* reification */ + if (isset($s['p_id'])) { + $this->reify($this->calcURI('#'.$s['p_id'], $b), $s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + unset($s['p_id']); + } + $this->state = 2; + } + elseif ($a[$this->rdf.'parseType'] === 'Collection') { + $s['o_is_coll'] = true; + $this->state = 4; + } + } + /* sub-node or literal */ + else { + $s['o_cdata'] = ''; + if (isset($a[$this->rdf.'datatype'])) { + $s['o_datatype'] = $a[$this->rdf.'datatype']; + } + $this->state = 4; + } + /* any other attrs (skip rdf and xml) */ + foreach($a as $k => $v) { + if (((strpos($k, $this->xml) === false) && (strpos($k, $this->rdf) === false)) || preg_match('/(\_[0-9]+|value)$/', $k)) { + if (strpos($k, ':')) { + if (!$o['value']) { + $o['value'] = $this->createBnodeID(); + $o['type'] = 'bnode'; + $this->addT($s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + } + /* reification */ + if (isset($s['p_id'])) { + $this->reify($this->calcURI('#'.$s['p_id'], $b), $s['value'], $s['p'], $o['value'], $s['type'], $o['type']); + unset($s['p_id']); + } + $this->addT($o['value'], $k, $v, $o['type'], 'literal'); + $this->state = 3; + } + } + } + $this->updateS($s); + } + + /* */ + + function h4Open($t, $a) { + return $this->h1Open($t, $a); + } + + /* */ + + function h5Open($t, $a) { + $this->state = 4; + return $this->h4Open($t, $a); + } + + /* */ + + function h6Open($t, $a) { + $s = $this->getParentS(); + $data = isset($s['o_xml_data']) ? $s['o_xml_data'] : ''; + $ns = isset($s['ns']) ? $s['ns'] : array(); + $parts = $this->splitURI($t); + if ((count($parts) === 1) || empty($parts[1])) { + $data .= '<'.$t; + } + else { + $ns_uri = $parts[0]; + $name = $parts[1]; + if (!isset($this->nsp[$ns_uri])) { + foreach ($this->nsp as $tmp1 => $tmp2) { + if (strpos($t, $tmp1) === 0) { + $ns_uri = $tmp1; + $name = substr($t, strlen($tmp1)); + break; + } + } + } + $nsp = $this->nsp[$ns_uri]; + $data .= $nsp ? '<' . $nsp . ':' . $name : '<' . $name; + /* ns */ + if (!isset($ns[$nsp.'='.$ns_uri]) || !$ns[$nsp.'='.$ns_uri]) { + $data .= $nsp ? ' xmlns:'.$nsp.'="'.$ns_uri.'"' : ' xmlns="'.$ns_uri.'"'; + $ns[$nsp.'='.$ns_uri] = true; + $s['ns'] = $ns; + } + } + foreach ($a as $k => $v) { + $parts = $this->splitURI($k); + if (count($parts) === 1) { + $data .= ' '.$k.'="'.$v.'"'; + } + else { + $ns_uri = $parts[0]; + $name = $parts[1]; + $nsp = $this->v($ns_uri, '', $this->nsp); + $data .= $nsp ? ' '.$nsp.':'.$name.'="'.$v.'"' : ' '.$name.'="'.$v.'"' ; + } + } + $data .= '>'; + $s['o_xml_data'] = $data; + $s['o_xml_level'] = isset($s['o_xml_level']) ? $s['o_xml_level'] + 1 : 1; + if ($t == $s['p']) {/* xml container prop */ + $s['p_xml_literal_level'] = isset($s['p_xml_literal_level']) ? $s['p_xml_literal_level'] + 1 : 1; + } + $this->updateS($s); + } + + /* */ + + function h1Close($t) {/* end of doc */ + $this->state = 0; + } + + /* */ + + function h2Close($t) {/* expecting a prop, getting a close */ + if ($s = $this->getParentS()) { + $has_closing_tag = (isset($s['has_closing_tag']) && !$s['has_closing_tag']) ? 0 : 1; + $this->popS(); + $this->state = 5; + if ($s = $this->getParentS()) {/* new s */ + if (!isset($s['p']) || !$s['p']) {/* p close after collection|parseType=Resource|node close after p close */ + $this->state = $this->s_count ? 4 : 1; + if (!$has_closing_tag) { + $this->state = 2; + } + } + elseif (!$has_closing_tag) { + $this->state = 2; + } + } + } + } + + /* */ + + function h3Close($t) {/* p close */ + $this->state = 2; + } + + /* */ + + function h4Close($t) {/* empty p | pClose after cdata | pClose after collection */ + if ($s = $this->getParentS()) { + $b = isset($s['p_x_base']) && $s['p_x_base'] ? $s['p_x_base'] : (isset($s['x_base']) ? $s['x_base'] : ''); + if (isset($s['is_coll']) && $s['is_coll']) { + $this->addT($s['value'], $this->rdf . 'rest', $this->rdf . 'nil', $s['type'], 'uri'); + /* back to collection start */ + while ((!isset($s['p']) || ($s['p'] != $t))) { + $sub_s = $s; + $this->popS(); + $s = $this->getParentS(); + } + /* reification */ + if (isset($s['p_id']) && $s['p_id']) { + $this->reify($this->calcURI('#'.$s['p_id'], $b), $s['value'], $s['p'], $sub_s['value'], $s['type'], $sub_s['type']); + } + unset($s['p']); + $this->updateS($s); + } + else { + $dt = isset($s['o_datatype']) ? $s['o_datatype'] : ''; + $l = isset($s['p_x_lang']) && $s['p_x_lang'] ? $s['p_x_lang'] : (isset($s['x_lang']) ? $s['x_lang'] : ''); + $o = array('type' => 'literal', 'value' => $s['o_cdata']); + $this->addT($s['value'], $s['p'], $o['value'], $s['type'], $o['type'], $dt, $l); + /* reification */ + if (isset($s['p_id']) && $s['p_id']) { + $this->reify($this->calcURI('#'.$s['p_id'], $b), $s['value'], $s['p'], $o['value'], $s['type'], $o['type'], $dt, $l); + } + unset($s['o_cdata']); + unset($s['o_datatype']); + unset($s['p']); + $this->updateS($s); + } + $this->state = 2; + } + } + + /* */ + + function h5Close($t) {/* p close */ + if ($s = $this->getParentS()) { + unset($s['p']); + $this->updateS($s); + $this->state = 2; + } + } + + /* */ + + function h6Close($t) { + if ($s = $this->getParentS()) { + $l = isset($s['p_x_lang']) && $s['p_x_lang'] ? $s['p_x_lang'] : (isset($s['x_lang']) ? $s['x_lang'] : ''); + $data = $s['o_xml_data']; + $level = $s['o_xml_level']; + if ($level === 0) {/* pClose */ + $this->addT($s['value'], $s['p'], trim($data, ' '), $s['type'], 'literal', $this->rdf.'XMLLiteral', $l); + unset($s['o_xml_data']); + $this->state = 2; + } + else { + $parts = $this->splitURI($t); + if ((count($parts) === 1) || empty($parts[1])) { + $data .= ''; + } + else { + $ns_uri = $parts[0]; + $name = $parts[1]; + if (!isset($this->nsp[$ns_uri])) { + foreach ($this->nsp as $tmp1 => $tmp2) { + if (strpos($t, $tmp1) === 0) { + $ns_uri = $tmp1; + $name = substr($t, strlen($tmp1)); + break; + } + } + } + $nsp = $this->nsp[$ns_uri]; + $data .= $nsp ? '' : ''; + } + $s['o_xml_data'] = $data; + $s['o_xml_level'] = $level - 1; + if ($t == $s['p']) {/* xml container prop */ + $s['p_xml_literal_level']--; + } + } + $this->updateS($s); + } + } + + /* */ + + function h4Cdata($d) { + if ($s = $this->getParentS()) { + $s['o_cdata'] = isset($s['o_cdata']) ? $s['o_cdata'] . $d : $d; + $this->updateS($s); + } + } + + /* */ + + function h6Cdata($d) { + if ($s = $this->getParentS()) { + if (isset($s['o_xml_data']) || preg_match("/[\n\r]/", $d) || trim($d)) { + $d = htmlspecialchars($d, ENT_NOQUOTES); + $s['o_xml_data'] = isset($s['o_xml_data']) ? $s['o_xml_data'] . $d : $d; + } + $this->updateS($s); + } + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_RSSParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_RSSParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,185 @@ +triples = array(); + $this->target_encoding = ''; + $this->t_count = 0; + $this->added_triples = array(); + $this->skip_dupes = false; + $this->bnode_prefix = $this->v('bnode_prefix', 'arc'.substr(md5(uniqid(rand())), 0, 4).'b', $this->a); + $this->bnode_id = 0; + $this->cache = array(); + $this->allowCDataNodes = 0; + } + + /* */ + + function done() { + $this->extractRDF(); + } + + /* */ + + function setReader(&$reader) { + $this->reader = $reader; + } + + function createBnodeID(){ + $this->bnode_id++; + return '_:' . $this->bnode_prefix . $this->bnode_id; + } + + function addT($t) { + //if (!isset($t['o_datatype'])) + if ($this->skip_dupes) { + $h = md5(serialize($t)); + if (!isset($this->added_triples[$h])) { + $this->triples[$this->t_count] = $t; + $this->t_count++; + $this->added_triples[$h] = true; + } + } + else { + $this->triples[$this->t_count] = $t; + $this->t_count++; + } + } + + function getTriples() { + return $this->v('triples', array()); + } + + function countTriples() { + return $this->t_count; + } + + function getSimpleIndex($flatten_objects = 1, $vals = '') { + return ARC2::getSimpleIndex($this->getTriples(), $flatten_objects, $vals); + } + + /* */ + + function extractRDF() { + $index = $this->getNodeIndex(); + $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->rss = 'http://purl.org/rss/1.0/'; + $this->dc = 'http://purl.org/dc/elements/1.1/'; + $this->dct = 'http://purl.org/dc/terms/'; + $this->content = 'http://purl.org/rss/1.0/modules/content/'; + $this->enc = 'http://purl.oclc.org/net/rss_2.0/enc#'; + $this->mappings = array( + 'channel' => $this->rss . 'channel', + 'item' => $this->rss . 'item', + 'title' => $this->rss . 'title', + 'link' => $this->rss . 'link', + 'description' => $this->rss . 'description', + 'guid' => $this->dc . 'identifier', + 'author' => $this->dc . 'creator', + 'category' => $this->dc . 'subject', + 'pubDate' => $this->dc . 'date', + 'pubdate' => $this->dc . 'date', + 'source' => $this->dc . 'source', + 'enclosure' => $this->enc . 'enclosure', + ); + $this->dt_props = array( + $this->dc . 'identifier', + $this->rss . 'link' + ); + foreach ($index as $p_id => $nodes) { + foreach ($nodes as $pos => $node) { + $tag = $this->v('tag', '', $node); + if ($tag == 'channel') { + $struct = $this->extractChannel($index[$node['id']]); + $triples = ARC2::getTriplesFromIndex($struct); + foreach ($triples as $t) { + $this->addT($t); + } + } + elseif ($tag == 'item') { + $struct = $this->extractItem($index[$node['id']]); + $triples = ARC2::getTriplesFromIndex($struct); + foreach ($triples as $t) { + $this->addT($t); + } + } + } + } + } + + function extractChannel($els) { + $res = array($this->rdf . 'type' => array(array('value' => $this->rss . 'channel', 'type' => 'uri'))); + $res = array_merge($res, $this->extractProps($els, 'channel')); + return array($res[$this->rss . 'link'][0]['value'] => $res); + } + + function extractItem($els) { + $res = array($this->rdf . 'type' => array(array('value' => $this->rss . 'item', 'type' => 'uri'))); + $res = array_merge($res, $this->extractProps($els, 'item')); + if (isset($res[$this->rss . 'link'])) return array($res[$this->rss . 'link'][0]['value'] => $res); + if (isset($res[$this->dc . 'identifier'])) return array($res[$this->dc . 'identifier'][0]['value'] => $res); + } + + function extractProps($els, $container) { + $res = array(); + foreach ($els as $info) { + /* key */ + $tag = $info['tag']; + if (!preg_match('/^[a-z0-9]+\:/i', $tag)) { + $k = isset($this->mappings[$tag]) ? $this->mappings[$tag] : ''; + } + else { + $k = $tag; + } + if (($container == 'channel') && ($k == $this->rss . 'item')) continue; + /* val */ + $v = $info['cdata']; + if (!$v) $v = $this->v('url', '', $info['a']); + if (!$v) $v = $this->v('href', '', $info['a']); + /* prop */ + if ($k) { + /* enclosure handling */ + if ($k == $this->enc . 'enclosure') { + $sub_res = array(); + foreach (array('length', 'type') as $attr) { + if ($attr_v = $this->v($attr, 0, $info['a'])) { + $sub_res[$this->enc . $attr] = array(array('value' => $attr_v, 'type' => 'literal')); + } + } + $struct[$v] = $sub_res; + } + /* date handling */ + if (in_array($k, array($this->dc . 'date', $this->dct . 'modified'))) { + if (!preg_match('/^[0-9]{4}/', $v) && ($sub_v = strtotime($v)) && ($sub_v != -1)) { + $tz = date('Z', $sub_v); /* timezone offset */ + $sub_v -= $tz; /* utc */ + $v = date('Y-m-d\TH:i:s\Z', $sub_v); + } + } + if (!isset($res[$k])) $res[$k] = array(); + $res[$k][] = array('value' => $v, 'type' => in_array($k, $this->dt_props) || !preg_match('/^[a-z0-9]+\:[^\s]+$/is', $v) ? 'literal' : 'uri'); + } + } + return $res; + } + + /* */ + + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_SGAJSONParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_SGAJSONParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,63 @@ +rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->nsp = array($this->rdf => 'rdf'); + } + + /* */ + + function done() { + $this->extractRDF(); + } + + function extractRDF() { + $s = $this->getContext(); + $os = $this->getURLs($this->struct); + foreach ($os as $o) { + if ($o != $s) $this->addT($s, 'http://www.w3.org/2000/01/rdf-schema#seeAlso', $o, 'uri', 'uri'); + } + } + + function getContext() { + if (!isset($this->struct['canonical_mapping'])) return ''; + foreach ($this->struct['canonical_mapping'] as $k => $v) return $v; + } + + function getURLs($struct) { + $r =array(); + if (is_array($struct)) { + foreach ($struct as $k => $v) { + if (preg_match('/^http:\/\//', $k) && !in_array($k, $r)) $r[] = $k; + $sub_r = $this->getURLs($v); + foreach ($sub_r as $sub_v) { + if (!in_array($sub_v, $r)) $r[] = $sub_v; + } + } + } + elseif (preg_match('/^http:\/\//', $struct) && !in_array($struct, $r)) { + $r[] = $struct; + } + return $r; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,777 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('TurtleParser'); + +class ARC2_SPARQLParser extends ARC2_TurtleParser { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->bnode_prefix = $this->v('bnode_prefix', 'arc'.substr(md5(uniqid(rand())), 0, 4).'b', $this->a); + $this->bnode_id = 0; + $this->bnode_pattern_index = array('patterns' => array(), 'bnodes' => array()); + } + + /* */ + + function parse($q, $src = '', $iso_fallback = 'ignore') { + $this->setDefaultPrefixes(); + $this->base = $src ? $this->calcBase($src) : ARC2::getRequestURI(); + $this->r = array( + 'base' => '', + 'vars' => array(), + 'prefixes' => array() + ); + $this->unparsed_code = $q; + list($r, $v) = $this->xQuery($q); + if ($r) { + $this->r['query'] = $r; + $this->unparsed_code = trim($v); + } + elseif (!$this->getErrors() && !$this->unparsed_code) { + $this->addError('Query not properly closed'); + } + $this->r['prefixes'] = $this->prefixes; + $this->r['base'] = $this->base; + /* remove trailing comments */ + while (preg_match('/^\s*(\#[^\xd\xa]*)(.*)$/si', $this->unparsed_code, $m)) $this->unparsed_code = $m[2]; + if ($this->unparsed_code && !$this->getErrors()) { + $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($this->unparsed_code, 0, 30)); + $msg = trim($rest) ? 'Could not properly handle "' . $rest . '"' : 'Syntax error, probably an incomplete pattern'; + $this->addError($msg); + } + } + + function getQueryInfos() { + return $this->v('r', array()); + } + + /* 1 */ + + function xQuery($v) { + list($r, $v) = $this->xPrologue($v); + foreach (array('Select', 'Construct', 'Describe', 'Ask') as $type) { + $m = 'x' . $type . 'Query'; + if ((list($r, $v) = $this->$m($v)) && $r) { + return array($r, $v); + } + } + return array(0, $v); + } + + /* 2 */ + + function xPrologue($v) { + $r = 0; + if ((list($sub_r, $v) = $this->xBaseDecl($v)) && $sub_r) { + $this->base = $sub_r; + $r = 1; + } + while ((list($sub_r, $v) = $this->xPrefixDecl($v)) && $sub_r) { + $this->prefixes[$sub_r['prefix']] = $sub_r['uri']; + $r = 1; + } + return array($r, $v); + } + + /* 5.. */ + + function xSelectQuery($v) { + if ($sub_r = $this->x('SELECT\s+', $v)) { + $r = array( + 'type' => 'select', + 'result_vars' => array(), + 'dataset' => array(), + ); + $all_vars = 0; + $sub_v = $sub_r[1]; + /* distinct, reduced */ + if ($sub_r = $this->x('(DISTINCT|REDUCED)\s+', $sub_v)) { + $r[strtolower($sub_r[1])] = 1; + $sub_v = $sub_r[2]; + } + /* result vars */ + if ($sub_r = $this->x('\*\s+', $sub_v)) { + $all_vars = 1; + $sub_v = $sub_r[1]; + } + else { + while ((list($sub_r, $sub_v) = $this->xResultVar($sub_v)) && $sub_r) { + $r['result_vars'][] = $sub_r; + } + } + if (!$all_vars && !count($r['result_vars'])) { + $this->addError('No result bindings specified.'); + } + /* dataset */ + while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) { + $r['dataset'][] = $sub_r; + } + /* where */ + if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) { + $r['pattern'] = $sub_r; + } + else { + return array(0, $v); + } + /* solution modifier */ + if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) { + $r = array_merge($r, $sub_r); + } + /* all vars */ + if ($all_vars) { + foreach ($this->r['vars'] as $var) { + $r['result_vars'][] = array('var' => $var, 'aggregate' => 0, 'alias' => ''); + } + if (!$r['result_vars']) { + $r['result_vars'][] = '*'; + } + } + return array($r, $sub_v); + } + return array(0, $v); + } + + function xResultVar($v) { + return $this->xVar($v); + } + + /* 6.. */ + + function xConstructQuery($v) { + if ($sub_r = $this->x('CONSTRUCT\s*', $v)) { + $r = array( + 'type' => 'construct', + 'dataset' => array(), + ); + $sub_v = $sub_r[1]; + /* construct template */ + if ((list($sub_r, $sub_v) = $this->xConstructTemplate($sub_v)) && is_array($sub_r)) { + $r['construct_triples'] = $sub_r; + } + else { + $this->addError('Construct Template not found'); + return array(0, $v); + } + /* dataset */ + while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) { + $r['dataset'][] = $sub_r; + } + /* where */ + if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) { + $r['pattern'] = $sub_r; + } + else { + return array(0, $v); + } + /* solution modifier */ + if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) { + $r = array_merge($r, $sub_r); + } + return array($r, $sub_v); + } + return array(0, $v); + } + + /* 7.. */ + + function xDescribeQuery($v) { + if ($sub_r = $this->x('DESCRIBE\s+', $v)) { + $r = array( + 'type' => 'describe', + 'result_vars' => array(), + 'result_uris' => array(), + 'dataset' => array(), + ); + $sub_v = $sub_r[1]; + $all_vars = 0; + /* result vars/uris */ + if ($sub_r = $this->x('\*\s+', $sub_v)) { + $all_vars = 1; + $sub_v = $sub_r[1]; + } + else { + do { + $proceed = 0; + if ((list($sub_r, $sub_v) = $this->xResultVar($sub_v)) && $sub_r) { + $r['result_vars'][] = $sub_r; + $proceed = 1; + } + if ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) { + $r['result_uris'][] = $sub_r; + $proceed =1; + } + } while ($proceed); + } + if (!$all_vars && !count($r['result_vars']) && !count($r['result_uris'])) { + $this->addError('No result bindings specified.'); + } + /* dataset */ + while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) { + $r['dataset'][] = $sub_r; + } + /* where */ + if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) { + $r['pattern'] = $sub_r; + } + /* solution modifier */ + if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) { + $r = array_merge($r, $sub_r); + } + /* all vars */ + if ($all_vars) { + foreach ($this->r['vars'] as $var) { + $r['result_vars'][] = array('var' => $var, 'aggregate' => 0, 'alias' => ''); + } + } + return array($r, $sub_v); + } + return array(0, $v); + } + + /* 8.. */ + + function xAskQuery($v) { + if ($sub_r = $this->x('ASK\s+', $v)) { + $r = array( + 'type' => 'ask', + 'dataset' => array(), + ); + $sub_v = $sub_r[1]; + /* dataset */ + while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) { + $r['dataset'][] = $sub_r; + } + /* where */ + if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) { + $r['pattern'] = $sub_r; + return array($r, $sub_v); + } + else { + $this->addError('Missing or invalid WHERE clause.'); + } + } + return array(0, $v); + } + + /* 9, 10, 11, 12 */ + + function xDatasetClause($v) { + if ($r = $this->x('FROM(\s+NAMED)?\s+', $v)) { + $named = $r[1] ? 1 : 0; + if ((list($r, $sub_v) = $this->xIRIref($r[2])) && $r) { + return array(array('graph' => $r, 'named' => $named), $sub_v); + } + } + return array(0, $v); + } + + /* 13 */ + + function xWhereClause($v) { + if ($r = $this->x('(WHERE)?', $v)) { + $v = $r[2]; + } + if ((list($r, $v) = $this->xGroupGraphPattern($v)) && $r) { + return array($r, $v); + } + return array(0, $v); + } + + /* 14, 15 */ + + function xSolutionModifier($v) { + $r = array(); + if ((list($sub_r, $sub_v) = $this->xOrderClause($v)) && $sub_r) { + $r['order_infos'] = $sub_r; + } + while ((list($sub_r, $sub_v) = $this->xLimitOrOffsetClause($sub_v)) && $sub_r) { + $r = array_merge($r, $sub_r); + } + return ($v == $sub_v) ? array(0, $v) : array($r, $sub_v); + } + + /* 18, 19 */ + + function xLimitOrOffsetClause($v) { + if ($sub_r = $this->x('(LIMIT|OFFSET)', $v)) { + $key = strtolower($sub_r[1]); + $sub_v = $sub_r[2]; + if ((list($sub_r, $sub_v) = $this->xINTEGER($sub_v)) && ($sub_r !== false)) { + return array(array($key =>$sub_r), $sub_v); + } + if ((list($sub_r, $sub_v) = $this->xPlaceholder($sub_v)) && ($sub_r !== false)) { + return array(array($key =>$sub_r), $sub_v); + } + } + return array(0, $v); + } + + /* 16 */ + + function xOrderClause($v) { + if ($sub_r = $this->x('ORDER BY\s+', $v)) { + $sub_v = $sub_r[1]; + $r = array(); + while ((list($sub_r, $sub_v) = $this->xOrderCondition($sub_v)) && $sub_r) { + $r[] = $sub_r; + } + if (count($r)) { + return array($r, $sub_v); + } + else { + $this->addError('No order conditions specified.'); + } + } + return array(0, $v); + } + + /* 17, 27 */ + + function xOrderCondition($v) { + if ($sub_r = $this->x('(ASC|DESC)', $v)) { + $dir = strtolower($sub_r[1]); + $sub_v = $sub_r[2]; + if ((list($sub_r, $sub_v) = $this->xBrackettedExpression($sub_v)) && $sub_r) { + $sub_r['direction'] = $dir; + return array($sub_r, $sub_v); + } + } + elseif ((list($sub_r, $sub_v) = $this->xVar($v)) && $sub_r) { + $sub_r['direction'] = 'asc'; + return array($sub_r, $sub_v); + } + elseif ((list($sub_r, $sub_v) = $this->xBrackettedExpression($v)) && $sub_r) { + return array($sub_r, $sub_v); + } + elseif ((list($sub_r, $sub_v) = $this->xBuiltInCall($v)) && $sub_r) { + $sub_r['direction'] = 'asc'; + return array($sub_r, $sub_v); + } + elseif ((list($sub_r, $sub_v) = $this->xFunctionCall($v)) && $sub_r) { + $sub_r['direction'] = 'asc'; + return array($sub_r, $sub_v); + } + return array(0, $v); + } + + /* 20 */ + + function xGroupGraphPattern($v) { + $pattern_id = substr(md5(uniqid(rand())), 0, 4); + if ($sub_r = $this->x('\{', $v)) { + $r = array('type' => 'group', 'patterns' => array()); + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_v)) && $sub_r) { + $this->indexBnodes($sub_r, $pattern_id); + $r['patterns'][] = array('type' => 'triples', 'patterns' => $sub_r); + } + do { + $proceed = 0; + if ((list($sub_r, $sub_v) = $this->xGraphPatternNotTriples($sub_v)) && $sub_r) { + $r['patterns'][] = $sub_r; + $pattern_id = substr(md5(uniqid(rand())), 0, 4); + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xFilter($sub_v)) && $sub_r) { + $r['patterns'][] = array('type' => 'filter', 'constraint' => $sub_r); + $proceed = 1; + } + if ($sub_r = $this->x('\.', $sub_v)) { + $sub_v = $sub_r[1]; + } + if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_v)) && $sub_r) { + $this->indexBnodes($sub_r, $pattern_id); + $r['patterns'][] = array('type' => 'triples', 'patterns' => $sub_r); + $proceed = 1; + } + if ((list($sub_r, $sub_v) = $this->xPlaceholder($sub_v)) && $sub_r) { + $r['patterns'][] = $sub_r; + $proceed = 1; + } + } while ($proceed); + if ($sub_r = $this->x('\}', $sub_v)) { + $sub_v = $sub_r[1]; + return array($r, $sub_v); + } + $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($sub_v, 0, 30)); + $this->addError('Incomplete or invalid Group Graph pattern. Could not handle "' . $rest . '"'); + } + return array(0, $v); + } + + function indexBnodes($triples, $pattern_id) { + $index_id = count($this->bnode_pattern_index['patterns']); + $index_id = $pattern_id; + $this->bnode_pattern_index['patterns'][] = $triples; + foreach ($triples as $t) { + foreach (array('s', 'p', 'o') as $term) { + if ($t[$term . '_type'] == 'bnode') { + $val = $t[$term]; + if (isset($this->bnode_pattern_index['bnodes'][$val]) && ($this->bnode_pattern_index['bnodes'][$val] != $index_id)) { + $this->addError('Re-used bnode label "' .$val. '" across graph patterns'); + } + else { + $this->bnode_pattern_index['bnodes'][$val] = $index_id; + } + } + } + } + } + + /* 22.., 25.. */ + + function xGraphPatternNotTriples($v) { + if ((list($sub_r, $sub_v) = $this->xOptionalGraphPattern($v)) && $sub_r) { + return array($sub_r, $sub_v); + } + if ((list($sub_r, $sub_v) = $this->xGraphGraphPattern($v)) && $sub_r) { + return array($sub_r, $sub_v); + } + $r = array('type' => 'union', 'patterns' => array()); + $sub_v = $v; + do { + $proceed = 0; + if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) { + $r['patterns'][] = $sub_r; + if ($sub_r = $this->x('UNION', $sub_v)) { + $sub_v = $sub_r[1]; + $proceed = 1; + } + } + } while ($proceed); + $pc = count($r['patterns']); + if ($pc == 1) { + return array($r['patterns'][0], $sub_v); + } + elseif ($pc > 1) { + return array($r, $sub_v); + } + return array(0, $v); + } + + /* 23 */ + + function xOptionalGraphPattern($v) { + if ($sub_r = $this->x('OPTIONAL', $v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) { + return array(array('type' => 'optional', 'patterns' => $sub_r['patterns']), $sub_v); + } + $this->addError('Missing or invalid Group Graph Pattern after OPTIONAL'); + } + return array(0, $v); + } + + /* 24.. */ + + function xGraphGraphPattern($v) { + if ($sub_r = $this->x('GRAPH', $v)) { + $sub_v = $sub_r[1]; + $r = array('type' => 'graph', 'var' => '', 'uri' => '', 'patterns' => array()); + if ((list($sub_r, $sub_v) = $this->xVar($sub_v)) && $sub_r) { + $r['var'] = $sub_r; + } + elseif ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) { + $r['uri'] = $sub_r; + } + if ($r['var'] || $r['uri']) { + if ((list($sub_r, $sub_v) = $this->xGroupGraphPattern($sub_v)) && $sub_r) { + $r['patterns'][] = $sub_r; + return array($r, $sub_v); + } + $this->addError('Missing or invalid Graph Pattern'); + } + } + return array(0, $v); + } + + /* 26.., 27.. */ + + function xFilter($v) { + if ($r = $this->x('FILTER', $v)) { + $sub_v = $r[1]; + if ((list($r, $sub_v) = $this->xBrackettedExpression($sub_v)) && $r) { + return array($r, $sub_v); + } + if ((list($r, $sub_v) = $this->xBuiltInCall($sub_v)) && $r) { + return array($r, $sub_v); + } + if ((list($r, $sub_v) = $this->xFunctionCall($sub_v)) && $r) { + return array($r, $sub_v); + } + $this->addError('Incomplete FILTER'); + } + return array(0, $v); + } + + /* 28.. */ + + function xFunctionCall($v) { + if ((list($r, $sub_v) = $this->xIRIref($v)) && $r) { + if ((list($sub_r, $sub_v) = $this->xArgList($sub_v)) && $sub_r) { + return array(array('type' => 'function_call', 'uri' => $r, 'args' => $sub_r), $sub_v); + } + } + return array(0, $v); + } + + /* 29 */ + + function xArgList($v) { + $r = array(); + $sub_v = $v; + $closed = 0; + if ($sub_r = $this->x('\(', $sub_v)) { + $sub_v = $sub_r[1]; + do { + $proceed = 0; + if ((list($sub_r, $sub_v) = $this->xExpression($sub_v)) && $sub_r) { + $r[] = $sub_r; + if ($sub_r = $this->x('\,', $sub_v)) { + $sub_v = $sub_r[1]; + $proceed = 1; + } + } + if ($sub_r = $this->x('\)', $sub_v)) { + $sub_v = $sub_r[1]; + $closed = 1; + $proceed = 0; + } + } while ($proceed); + } + return $closed ? array($r, $sub_v) : array(0, $v); + } + + /* 30, 31 */ + + function xConstructTemplate($v) { + if ($sub_r = $this->x('\{', $v)) { + $r = array(); + if ((list($sub_r, $sub_v) = $this->xTriplesBlock($sub_r[1])) && is_array($sub_r)) { + $r = $sub_r; + } + if ($sub_r = $this->x('\}', $sub_v)) { + return array($r, $sub_r[1]); + } + } + return array(0, $v); + } + + /* 46, 47 */ + + function xExpression($v) { + if ((list($sub_r, $sub_v) = $this->xConditionalAndExpression($v)) && $sub_r) { + $r = array('type' => 'expression', 'sub_type' => 'or', 'patterns' => array($sub_r)); + do { + $proceed = 0; + if ($sub_r = $this->x('\|\|', $sub_v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xConditionalAndExpression($sub_v)) && $sub_r) { + $r['patterns'][] = $sub_r; + $proceed = 1; + } + } + } while ($proceed); + return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v); + } + return array(0, $v); + } + + /* 48.., 49.. */ + + function xConditionalAndExpression($v) { + if ((list($sub_r, $sub_v) = $this->xRelationalExpression($v)) && $sub_r) { + $r = array('type' => 'expression', 'sub_type' => 'and', 'patterns' => array($sub_r)); + do { + $proceed = 0; + if ($sub_r = $this->x('\&\&', $sub_v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xRelationalExpression($sub_v)) && $sub_r) { + $r['patterns'][] = $sub_r; + $proceed = 1; + } + } + } while ($proceed); + return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v); + } + return array(0, $v); + } + + /* 50, 51 */ + + function xRelationalExpression($v) { + if ((list($sub_r, $sub_v) = $this->xAdditiveExpression($v)) && $sub_r) { + $r = array('type' => 'expression', 'sub_type' => 'relational', 'patterns' => array($sub_r)); + do { + $proceed = 0; + /* don't mistake '<' + uriref with '<'-operator ("longest token" rule) */ + if ((list($sub_r, $sub_v) = $this->xIRI_REF($sub_v)) && $sub_r) { + $this->addError('Expected operator, found IRIref: "'.$sub_r.'".'); + } + if ($sub_r = $this->x('(\!\=|\=\=|\=|\<\=|\>\=|\<|\>)', $sub_v)) { + $op = $sub_r[1]; + $sub_v = $sub_r[2]; + $r['operator'] = $op; + if ((list($sub_r, $sub_v) = $this->xAdditiveExpression($sub_v)) && $sub_r) { + //$sub_r['operator'] = $op; + $r['patterns'][] = $sub_r; + $proceed = 1; + } + } + } while ($proceed); + return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v); + } + return array(0, $v); + } + + /* 52 */ + + function xAdditiveExpression($v) { + if ((list($sub_r, $sub_v) = $this->xMultiplicativeExpression($v)) && $sub_r) { + $r = array('type' => 'expression', 'sub_type' => 'additive', 'patterns' => array($sub_r)); + do { + $proceed = 0; + if ($sub_r = $this->x('(\+|\-)', $sub_v)) { + $op = $sub_r[1]; + $sub_v = $sub_r[2]; + if ((list($sub_r, $sub_v) = $this->xMultiplicativeExpression($sub_v)) && $sub_r) { + $sub_r['operator'] = $op; + $r['patterns'][] = $sub_r; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xNumericLiteral($sub_v)) && $sub_r) { + $r['patterns'][] = array('type' => 'numeric', 'operator' => $op, 'value' => $sub_r); + $proceed = 1; + } + } + } while ($proceed); + //return array($r, $sub_v); + return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v); + } + return array(0, $v); + } + + /* 53 */ + + function xMultiplicativeExpression($v) { + if ((list($sub_r, $sub_v) = $this->xUnaryExpression($v)) && $sub_r) { + $r = array('type' => 'expression', 'sub_type' => 'multiplicative', 'patterns' => array($sub_r)); + do { + $proceed = 0; + if ($sub_r = $this->x('(\*|\/)', $sub_v)) { + $op = $sub_r[1]; + $sub_v = $sub_r[2]; + if ((list($sub_r, $sub_v) = $this->xUnaryExpression($sub_v)) && $sub_r) { + $sub_r['operator'] = $op; + $r['patterns'][] = $sub_r; + $proceed = 1; + } + } + } while ($proceed); + return count($r['patterns']) == 1 ? array($r['patterns'][0], $sub_v) : array($r, $sub_v); + } + return array(0, $v); + } + + /* 54 */ + + function xUnaryExpression($v) { + $sub_v = $v; + $op = ''; + if ($sub_r = $this->x('(\!|\+|\-)', $sub_v)) { + $op = $sub_r[1]; + $sub_v = $sub_r[2]; + } + if ((list($sub_r, $sub_v) = $this->xPrimaryExpression($sub_v)) && $sub_r) { + if (!is_array($sub_r)) { + $sub_r = array('type' => 'unary', 'expression' => $sub_r); + } + elseif ($sub_op = $this->v1('operator', '', $sub_r)) { + $ops = array('!!' => '', '++' => '+', '--' => '+', '+-' => '-', '-+' => '-'); + $op = isset($ops[$op . $sub_op]) ? $ops[$op . $sub_op] : $op . $sub_op; + } + $sub_r['operator'] = $op; + return array($sub_r, $sub_v); + } + return array(0, $v); + } + + /* 55 */ + + function xPrimaryExpression($v) { + foreach (array('BrackettedExpression', 'BuiltInCall', 'IRIrefOrFunction', 'RDFLiteral', 'NumericLiteral', 'BooleanLiteral', 'Var', 'Placeholder') as $type) { + $m = 'x' . $type; + if ((list($sub_r, $sub_v) = $this->$m($v)) && $sub_r) { + return array($sub_r, $sub_v); + } + } + return array(0, $v); + } + + /* 56 */ + + function xBrackettedExpression($v) { + if ($r = $this->x('\(', $v)) { + if ((list($r, $sub_v) = $this->xExpression($r[1])) && $r) { + if ($sub_r = $this->x('\)', $sub_v)) { + return array($r, $sub_r[1]); + } + } + } + return array(0, $v); + } + + /* 57.., 58.. */ + + function xBuiltInCall($v) { + if ($sub_r = $this->x('(str|lang|langmatches|datatype|bound|sameterm|isiri|isuri|isblank|isliteral|regex)\s*\(', $v)) { + $r = array('type' => 'built_in_call', 'call' => strtolower($sub_r[1])); + if ((list($sub_r, $sub_v) = $this->xArgList('(' . $sub_r[2])) && is_array($sub_r)) { + $r['args'] = $sub_r; + return array($r, $sub_v); + } + } + return array(0, $v); + } + + /* 59.. */ + + function xIRIrefOrFunction($v) { + if ((list($r, $v) = $this->xIRIref($v)) && $r) { + if ((list($sub_r, $sub_v) = $this->xArgList($v)) && is_array($sub_r)) { + return array(array('type' => 'function', 'uri' => $r, 'args' => $sub_r), $sub_v); + } + return array(array('type' => 'uri', 'uri' => $r), $sub_v); + } + } + + /* 70.. @@sync with TurtleParser */ + + function xIRI_REF($v) { + if (($r = $this->x('\<(\$\{[^\>]*\})\>', $v)) && ($sub_r = $this->xPlaceholder($r[1]))) { + return array($r[1], $r[2]); + } + elseif ($r = $this->x('\<([^\<\>\s\"\|\^`]*)\>', $v)) { + return array($r[1] ? $r[1] : true, $r[2]); + } + /* allow reserved chars in obvious IRIs */ + elseif ($r = $this->x('\<(https?\:[^\s][^\<\>]*)\>', $v)) { + return array($r[1] ? $r[1] : true, $r[2]); + } + return array(0, $v); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLPlusParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLPlusParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,210 @@ +xPrologue($v); + foreach (array('Select', 'Construct', 'Describe', 'Ask', 'Insert', 'Delete', 'Load') as $type) { + $m = 'x' . $type . 'Query'; + if ((list($r, $v) = $this->$m($v)) && $r) { + return array($r, $v); + } + } + return array(0, $v); + } + + /* +3 */ + + function xResultVar($v) { + $aggregate = ''; + /* aggregate */ + if ($sub_r = $this->x('\(?(AVG|COUNT|MAX|MIN|SUM)\s*\(\s*([^\)]+)\)\s+AS\s+([^\s\)]+)\)?', $v)) { + $aggregate = $sub_r[1]; + $result_var = $sub_r[3]; + $v = $sub_r[2] . $sub_r[4]; + } + if ($sub_r && (list($sub_r, $sub_v) = $this->xVar($result_var)) && $sub_r) { + $result_var = $sub_r['value']; + } + /* * or var */ + if ((list($sub_r, $sub_v) = $this->x('\*', $v)) && $sub_r) { + return array(array('var' => $sub_r['value'], 'aggregate' => $aggregate, 'alias' => $aggregate ? $result_var : ''), $sub_v); + } + if ((list($sub_r, $sub_v) = $this->xVar($v)) && $sub_r) { + return array(array('var' => $sub_r['value'], 'aggregate' => $aggregate, 'alias' => $aggregate ? $result_var : ''), $sub_v); + } + return array(0, $v); + } + + /* +4 */ + + function xLoadQuery($v) { + if ($sub_r = $this->x('LOAD\s+', $v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) { + $r = array('type' => 'load', 'url' => $sub_r, 'target_graph' => ''); + if ($sub_r = $this->x('INTO\s+', $sub_v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) { + $r['target_graph'] = $sub_r; + } + } + return array($r, $sub_v); + } + } + return array(0, $v); + } + + /* +5 */ + + function xInsertQuery($v) { + if ($sub_r = $this->x('INSERT\s+', $v)) { + $r = array( + 'type' => 'insert', + 'dataset' => array(), + ); + $sub_v = $sub_r[1]; + /* target */ + if ($sub_r = $this->x('INTO\s+', $sub_v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) { + $r['target_graph'] = $sub_r; + /* CONSTRUCT keyword, optional */ + if ($sub_r = $this->x('CONSTRUCT\s+', $sub_v)) { + $sub_v = $sub_r[1]; + } + /* construct template */ + if ((list($sub_r, $sub_v) = $this->xConstructTemplate($sub_v)) && is_array($sub_r)) { + $r['construct_triples'] = $sub_r; + } + else { + $this->addError('Construct Template not found'); + return array(0, $v); + } + /* dataset */ + while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) { + $r['dataset'][] = $sub_r; + } + /* where */ + if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) { + $r['pattern'] = $sub_r; + } + /* solution modifier */ + if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) { + $r = array_merge($r, $sub_r); + } + return array($r, $sub_v); + } + } + } + return array(0, $v); + } + + /* +6 */ + + function xDeleteQuery($v) { + if ($sub_r = $this->x('DELETE\s+', $v)) { + $r = array( + 'type' => 'delete', + 'target_graphs' => array() + ); + $sub_v = $sub_r[1]; + /* target */ + do { + $proceed = false; + if ($sub_r = $this->x('FROM\s+', $sub_v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xIRIref($sub_v)) && $sub_r) { + $r['target_graphs'][] = $sub_r; + $proceed = 1; + } + } + } while ($proceed); + /* CONSTRUCT keyword, optional */ + if ($sub_r = $this->x('CONSTRUCT\s+', $sub_v)) { + $sub_v = $sub_r[1]; + } + /* construct template */ + if ((list($sub_r, $sub_v) = $this->xConstructTemplate($sub_v)) && is_array($sub_r)) { + $r['construct_triples'] = $sub_r; + /* dataset */ + while ((list($sub_r, $sub_v) = $this->xDatasetClause($sub_v)) && $sub_r) { + $r['dataset'][] = $sub_r; + } + /* where */ + if ((list($sub_r, $sub_v) = $this->xWhereClause($sub_v)) && $sub_r) { + $r['pattern'] = $sub_r; + } + /* solution modifier */ + if ((list($sub_r, $sub_v) = $this->xSolutionModifier($sub_v)) && $sub_r) { + $r = array_merge($r, $sub_r); + } + } + return array($r, $sub_v); + } + return array(0, $v); + } + + /* +7 */ + + function xSolutionModifier($v) { + $r = array(); + if ((list($sub_r, $sub_v) = $this->xGroupClause($v)) && $sub_r) { + $r['group_infos'] = $sub_r; + } + if ((list($sub_r, $sub_v) = $this->xOrderClause($sub_v)) && $sub_r) { + $r['order_infos'] = $sub_r; + } + while ((list($sub_r, $sub_v) = $this->xLimitOrOffsetClause($sub_v)) && $sub_r) { + $r = array_merge($r, $sub_r); + } + return ($v == $sub_v) ? array(0, $v) : array($r, $sub_v); + } + + /* +8 */ + + function xGroupClause($v) { + if ($sub_r = $this->x('GROUP BY\s+', $v)) { + $sub_v = $sub_r[1]; + $r = array(); + do { + $proceed = 0; + if ((list($sub_r, $sub_v) = $this->xVar($sub_v)) && $sub_r) { + $r[] = $sub_r; + $proceed = 1; + if ($sub_r = $this->x('\,', $sub_v)) { + $sub_v = $sub_r[1]; + } + } + } while ($proceed); + if (count($r)) { + return array($r, $sub_v); + } + else { + $this->addError('No columns specified in GROUP BY clause.'); + } + } + return array(0, $v); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLXMLResultParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_SPARQLXMLResultParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,112 @@ +srx = 'http://www.w3.org/2005/sparql-results#'; + $this->nsp[$this->srx] = 'srx'; + $this->allowCDataNodes = 0; + } + + /* */ + + function done() { + } + + /* */ + + function getVariables() { + $r = array(); + foreach ($this->nodes as $node) { + if ($node['tag'] == $this->srx . 'variable') { + $r[] = $node['a']['name']; + } + } + return $r; + } + + function getRows() { + $r = array(); + $index = $this->getNodeIndex(); + foreach ($this->nodes as $node) { + if ($node['tag'] == $this->srx . 'result') { + $row = array(); + $row_id = $node['id']; + $bindings = isset($index[$row_id])? $index[$row_id] : array(); + foreach ($bindings as $binding) { + $row = array_merge($row, $this->getBinding($binding)); + } + if ($row) { + $r[] = $row; + } + } + } + return $r; + } + + function getBinding($node) { + $r = array(); + $index = $this->getNodeIndex(); + $var = $node['a']['name']; + $term = $index[$node['id']][0]; + $r[$var . ' type'] = preg_replace('/^uri$/', 'uri', substr($term['tag'], strlen($this->srx))); + $r[$var] = ($r[$var . ' type'] == 'bnode') ? '_:' . $term['cdata'] : $term['cdata']; + if (isset($term['a']['datatype'])) { + $r[$var . ' datatype'] = $term['a']['datatype']; + } + elseif (isset($term['a'][$this->xml . 'lang'])) { + $r[$var . ' lang'] = $term['a'][$this->xml . 'lang']; + } + return $r; + } + + function getBooleanInsertedDeleted() { + foreach ($this->nodes as $node) { + if ($node['tag'] == $this->srx . 'boolean') { + return ($node['cdata'] == 'true') ? array('boolean' => true) : array('boolean' => false); + } + elseif ($node['tag'] == $this->srx . 'inserted') { + return array('inserted' => $node['cdata']); + } + elseif ($node['tag'] == $this->srx . 'deleted') { + return array('deleted' => $node['cdata']); + } + elseif ($node['tag'] == $this->srx . 'results') { + return ''; + } + } + return ''; + } + + /* */ + + function getStructure() { + $r = array('variables' => $this->getVariables(), 'rows' => $this->getRows()); + /* boolean|inserted|deleted */ + if ($sub_r = $this->getBooleanInsertedDeleted()) { + foreach ($sub_r as $k => $v) { + $r[$k] = $v; + } + } + return $r; + } + + /* */ + + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_SPOGParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_SPOGParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,188 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('RDFParser'); + +class ARC2_SPOGParser extends ARC2_RDFParser { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* reader */ + parent::__init(); + $this->encoding = $this->v('encoding', false, $this->a); + $this->xml = 'http://www.w3.org/XML/1998/namespace'; + $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->nsp = array($this->xml => 'xml', $this->rdf => 'rdf'); + $this->target_encoding = ''; + } + + /* */ + + function parse($path, $data = '', $iso_fallback = false) { + $this->state = 0; + /* reader */ + if (!$this->v('reader')) { + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->setAcceptHeader('Accept: sparql-results+xml; q=0.9, */*; q=0.1'); + $this->reader->activate($path, $data); + $this->x_base = isset($this->a['base']) && $this->a['base'] ? $this->a['base'] : $this->reader->base; + /* xml parser */ + $this->initXMLParser(); + /* parse */ + $first = true; + while ($d = $this->reader->readStream()) { + if ($iso_fallback && $first) { + $d = '' . "\n" . preg_replace('/^\<\?xml [^\>]+\?\>\s*/s', '', $d); + $first = false; + } + if (!xml_parse($this->xml_parser, $d, false)) { + $error_str = xml_error_string(xml_get_error_code($this->xml_parser)); + $line = xml_get_current_line_number($this->xml_parser); + $this->tmp_error = 'XML error: "' . $error_str . '" at line ' . $line . ' (parsing as ' . $this->getEncoding() . ')'; + $this->tmp_error .= $d . urlencode($d); + if (0 && !$iso_fallback && preg_match("/Invalid character/i", $error_str)) { + xml_parser_free($this->xml_parser); + unset($this->xml_parser); + $this->reader->closeStream(); + $this->__init(); + $this->encoding = 'ISO-8859-1'; + unset($this->xml_parser); + unset($this->reader); + return $this->parse($path, $data, true); + } + else { + return $this->addError($this->tmp_error); + } + } + } + $this->target_encoding = xml_parser_get_option($this->xml_parser, XML_OPTION_TARGET_ENCODING); + xml_parser_free($this->xml_parser); + $this->reader->closeStream(); + unset($this->reader); + return $this->done(); + } + + /* */ + + function initXMLParser() { + if (!isset($this->xml_parser)) { + $enc = preg_match('/^(utf\-8|iso\-8859\-1|us\-ascii)$/i', $this->getEncoding(), $m) ? $m[1] : 'UTF-8'; + $parser = xml_parser_create($enc); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 0); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_set_element_handler($parser, 'open', 'close'); + xml_set_character_data_handler($parser, 'cdata'); + xml_set_start_namespace_decl_handler($parser, 'nsDecl'); + xml_set_object($parser, $this); + $this->xml_parser = $parser; + } + } + + /* */ + + function getEncoding($src = 'config') { + if ($src == 'parser') { + return $this->target_encoding; + } + elseif (($src == 'config') && $this->encoding) { + return $this->encoding; + } + return $this->reader->getEncoding(); + return 'UTF-8'; + } + + /* */ + + function getTriples() { + return $this->v('triples', array()); + } + + function countTriples() { + return $this->t_count; + } + + function addT($s = '', $p = '', $o = '', $s_type = '', $o_type = '', $o_dt = '', $o_lang = '', $g = '') { + if (!($s && $p && $o)) return 0; + //echo "-----\nadding $s / $p / $o\n-----\n"; + $t = array('s' => $s, 'p' => $p, 'o' => $o, 's_type' => $s_type, 'o_type' => $o_type, 'o_datatype' => $o_dt, 'o_lang' => $o_lang, 'g' => $g); + if ($this->skip_dupes) { + $h = md5(serialize($t)); + if (!isset($this->added_triples[$h])) { + $this->triples[$this->t_count] = $t; + $this->t_count++; + $this->added_triples[$h] = true; + } + } + else { + $this->triples[$this->t_count] = $t; + $this->t_count++; + } + } + + /* */ + + function open($p, $t, $a) { + $this->state = $t; + if ($t == 'result') { + $this->t = array(); + } + elseif ($t == 'binding') { + $this->binding = $a['name']; + $this->t[$this->binding] = ''; + } + elseif ($t == 'literal') { + $this->t[$this->binding . '_dt'] = $this->v('datatype', '', $a); + $this->t[$this->binding . '_lang'] = $this->v('xml:lang', '', $a); + $this->t[$this->binding . '_type'] = 'literal'; + } + elseif ($t == 'uri') { + $this->t[$this->binding . '_type'] = 'uri'; + } + elseif ($t == 'bnode') { + $this->t[$this->binding . '_type'] = 'bnode'; + $this->t[$this->binding] = '_:'; + } + } + + function close($p, $t) { + $this->prev_state = $this->state; + $this->state = ''; + if ($t == 'result') { + $this->addT( + $this->v('s', '', $this->t), + $this->v('p', '', $this->t), + $this->v('o', '', $this->t), + $this->v('s_type', '', $this->t), + $this->v('o_type', '', $this->t), + $this->v('o_dt', '', $this->t), + $this->v('o_lang', '', $this->t), + $this->v('g', '', $this->t) + ); + } + } + + function cData($p, $d) { + if (in_array($this->state, array('uri', 'bnode', 'literal'))) { + $this->t[$this->binding] .= $d; + } + } + + function nsDecl($p, $prf, $uri) { + $this->nsp[$uri] = isset($this->nsp[$uri]) ? $this->nsp[$uri] : $prf; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_SemHTMLParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_SemHTMLParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ +default_sem_html_formats = 'dc openid erdf rdfa posh-rdf microformats'; + $this->triples = array(); + $this->target_encoding = ''; + $this->t_count = 0; + $this->added_triples = array(); + $this->skip_dupes = false; + $this->bnode_prefix = $this->v('bnode_prefix', 'arc'.substr(md5(uniqid(rand())), 0, 4).'b', $this->a); + $this->bnode_id = 0; + $this->auto_extract = $this->v('auto_extract', 1, $this->a); + $this->extracted_formats = array(); + $this->cache = array(); + $this->detected_formats = array(); + $this->keep_cdata_ws = $this->v('keep_cdata_whitespace', 0, $this->a); + } + + /* */ + + function x($re, $v, $options = 'si', $keep_ws = 0) { + list($ws, $v) = preg_match('/^(\s*)(.*)$/s', $v, $m) ? array($m[1], $m[2]) : array('', $v); + if (preg_match("/^" . $re . "(.*)$/" . $options, $v, $m)) { + if ($keep_ws) $m[1] = $ws . $m[1]; + return $m; + } + return false; + } + + /* */ + + function setReader(&$reader) { + $this->reader = $reader; + } + + function createBnodeID(){ + $this->bnode_id++; + return '_:' . $this->bnode_prefix . $this->bnode_id; + } + + function addT($t) { + if (function_exists('html_entity_decode')) { + $t['o'] = html_entity_decode($t['o']); + } + if ($this->skip_dupes) { + $h = md5(serialize($t)); + if (!isset($this->added_triples[$h])) { + $this->triples[$this->t_count] = $t; + $this->t_count++; + $this->added_triples[$h] = true; + } + } + else { + $this->triples[$this->t_count] = $t; + $this->t_count++; + } + } + + function getTriples() { + return $this->v('triples', array()); + } + + function countTriples() { + return $this->t_count; + } + + function getSimpleIndex($flatten_objects = 1, $vals = '') { + return ARC2::getSimpleIndex($this->getTriples(), $flatten_objects, $vals); + } + + /* */ + + function parse($path, $data = '', $iso_fallback = 'ignore') { + $this->nodes = array(); + $this->node_count = 0; + $this->level = 0; + /* reader */ + if (!$this->v('reader')) { + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->setAcceptHeader('Accept: text/html, application/xhtml, */*; q=0.9'); + $this->reader->activate($path, $data); + $this->target_encoding = $this->reader->getEncoding(false); + $this->x_base = isset($this->a['base']) && $this->a['base'] ? $this->a['base'] : $this->reader->base; + $this->base = $this->x_base; + $this->doc_url = $this->reader->base; + /* parse */ + $rest = ''; + $this->cur_tag = ''; + while ($d = $this->reader->readStream(1)) { + $rest = $this->processData($rest . $d); + } + $this->reader->closeStream(); + unset($this->reader); + return $this->done(); + } + + /* */ + + function getEncoding($src = 'ignore') { + return $this->target_encoding; + } + + /* */ + + function done() { + if ($this->auto_extract) { + $this->extractRDF(); + } + } + + /* */ + + function processData($v) { + $sub_v = $v; + do { + $proceed = 1; + if ((list($sub_r, $sub_v) = $this->xComment($sub_v)) && $sub_r) { + $this->open(0, 'comment', array('value' => $sub_r)); + $this->close(0, 'comment'); + continue; + } + if ((list($sub_r, $sub_v) = $this->xDoctype($sub_v)) && $sub_r) { + $this->open(0, 'doctype', array('value' => $sub_r)); + $this->close(0, 'doctype'); + /* RDFa detection */ + if (preg_match('/rdfa /i', $sub_r)) $this->detected_formats['rdfa'] = 1; + continue; + } + if ($this->level && ((list($sub_r, $sub_v) = $this->xWS($sub_v)) && $sub_r)) { + $this->cData(0, $sub_r); + } + elseif ((list($sub_r, $sub_v) = $this->xOpen($sub_v)) && $sub_r) { + $this->open(0, $sub_r['tag'], $sub_r['a']); + $this->cur_tag = $sub_r['tag']; + if ($sub_r['empty']) { + $this->close(0, $sub_r['tag'], 1); + $this->cur_tag = ''; + } + /* eRDF detection */ + if (!isset($this->detected_formats['erdf']) && isset($sub_r['a']['profile m']) && in_array('http://purl.org/NET/erdf/profile', $sub_r['a']['profile m'])) $this->detected_formats['erdf'] = 1; + /* poshRDF detection */ + if (!isset($this->detected_formats['posh-rdf']) && isset($sub_r['a']['class m']) && in_array('rdf-p', $sub_r['a']['class m'])) $this->detected_formats['posh-rdf'] = 1; + /* RDFa detection */ + if (!isset($this->detected_formats['rdfa']) && ($this->cur_tag == 'html') && isset($sub_r['a']['version m']) && in_array('XHTML+RDFa', $sub_r['a']['version m'])) $this->detected_formats['rdfa'] = 1; + if (!isset($this->detected_formats['rdfa']) && isset($sub_r['a']['xmlns']) && $sub_r['a']['xmlns'] && $this->isRDFNSDecl($sub_r['a']['xmlns'])) $this->detected_formats['rdfa'] = 1; + if (!isset($this->detected_formats['rdfa']) && array_intersect(array('about', 'typeof', 'property'), array_keys($sub_r['a']))) $this->detected_formats['rdfa'] = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xClose($sub_v)) && $sub_r) { + if (preg_match('/^(area|base|br|col|frame|hr|input|img|link|xmeta|param)$/', $sub_r['tag'])) { + /* already implicitly closed */ + } + else { + $this->close(0, $sub_r['tag']); + $this->cur_tag = ''; + } + } + elseif ((list($sub_r, $sub_v) = $this->xCData($sub_v)) && $sub_r) { + $this->cData(0, $sub_r); + } + else { + $proceed = 0; + } + } while ($proceed); + return $sub_v; + } + + /* */ + + function isRDFNSDecl($ns) { + foreach ($ns as $k => $v) { + if ($k) return 1; + } + return 0; + } + + /* */ + + function xComment($v) { + if ($r = $this->x('\<\!\-\-', $v)) { + if ($sub_r = $this->x('(.*)\-\-\>', $r[1], 'Us')) { + return array($sub_r[1], $sub_r[2]); + } + } + return array(0, $v); + } + + function xDoctype($v) { + if ($r = $this->x('\<\!DOCTYPE', $v)) { + if ($sub_r = $this->x('([^\>]+)\>', $r[1])) { + return array($sub_r[1], $sub_r[2]); + } + } + return array(0, $v); + } + + function xWS($v) { + if ($r = ARC2::x('(\s+)', $v)) { + return array($r[1], $r[2]); + } + return array(0, $v); + } + + /* */ + + function xOpen($v) { + if ($r = $this->x('\<([^\s\/\>]+)([^\>]*)\>', $v)) { + list($sub_r, $sub_v) = $this->xAttributes($r[2]); + return array(array('tag' => strtolower($r[1]), 'a' => $sub_r, 'empty' => $this->isEmpty($r[1], $r[2])), $r[3]); + } + return array(0, $v); + } + + /* */ + + function xAttributes($v) { + $r = array(); + while ((list($sub_r, $v) = $this->xAttribute($v)) && $sub_r) { + if ($sub_sub_r = $this->x('xmlns\:?(.*)', $sub_r['k'])) { + $this->nsDecl(0, $sub_sub_r[1], $sub_r['value']); + $r['xmlns'][$sub_sub_r[1]] = $sub_r['value']; + } + else { + $r[$sub_r['k']] = $sub_r['value']; + $r[$sub_r['k'] . ' m'] = $sub_r['values']; + } + } + return array($r, $v); + } + + /* */ + + function xAttribute($v) { + if ($r = $this->x('([^\s\=]+)\s*(\=)?\s*([\'\"]?)', $v)) { + if (!$r[2]) {/* no '=' */ + if ($r[1] == '/') { + return array(0, $r[4]); + } + return array(array('k' => $r[1], 'value' => 1, 'values' => array(1)), $r[4]); + } + if (!$r[3]) {/* no quots */ + if ($sub_r = $this->x('([^\s]+)', $r[4])) { + return array(array('k' => $r[1], 'value' => $sub_r[1], 'values' => array($sub_r[1])), $sub_r[2]); + } + return array(array('k' => $r[1], 'value' => '', 'values' => array()), $r[4]); + } + $val = ''; + $multi = 0; + $sub_v = $r[4]; + while ($sub_v && (!$sub_r = $this->x('(\x5c\\' .$r[3]. '|\\' .$r[3]. ')', $sub_v))) { + $val .= substr($sub_v, 0, 1); + $sub_v = substr($sub_v, 1); + } + $sub_v = $sub_v ? $sub_r[2] : $sub_v; + $vals = preg_split('/ /', $val); + return array(array('k' => $r[1], 'value' => $val, 'values' => $vals), $sub_v); + } + return array(0, $v); + } + + /* */ + + function isEmpty($t, $v) { + if (preg_match('/^(area|base|br|col|frame|hr|input|img|link|xmeta|param)$/', $t)) { + return 1; + } + if (preg_match('/\/$/', $v)) { + return 1; + } + return 0; + } + + /* */ + + function xClose($v) { + if ($r = $this->x('\<\/([^\s\>]+)\>', $v)) { + return array(array('tag' => strtolower($r[1])), $r[2]); + } + return array(0, $v); + } + + /* */ + + function xCData($v) { + if (preg_match('/(script|style)/i', $this->cur_tag)) { + if ($r = $this->x('(.+)(\<\/' . $this->cur_tag . '\>)', $v, 'Uis')) { + return array($r[1], $r[2] . $r[3]); + } + } + elseif ($r = $this->x('([^\<]+)', $v, 'si', $this->keep_cdata_ws)) { + return array($r[1], $r[2]); + } + return array(0, $v); + } + + /* */ + + function extractRDF($formats = '') { + $this->node_index = $this->getNodeIndex(); + $formats = !$formats ? $this->v('sem_html_formats', $this->default_sem_html_formats, $this->a) : $formats; + $formats = preg_split('/ /', $formats); + foreach ($formats as $format) { + if (!in_array($format, $this->extracted_formats)) { + $comp = $this->camelCase($format) . 'Extractor'; + if (ARC2::inc($comp)) { + $cls = 'ARC2_' . $comp; + $e = new $cls($this->a, $this); + $e->extractRDF(); + } + $this->extracted_formats[] = $format; + } + } + } + + function getNode($id) { + return isset($this->nodes[$id]) ? $this->nodes[$id] : 0; + } + + /* */ + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/parsers/ARC2_TurtleParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/parsers/ARC2_TurtleParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,886 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('RDFParser'); + +class ARC2_TurtleParser extends ARC2_RDFParser { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* reader */ + parent::__init(); + $this->state = 0; + $this->xml = 'http://www.w3.org/XML/1998/namespace'; + $this->rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $this->xsd = 'http://www.w3.org/2001/XMLSchema#'; + $this->nsp = array($this->xml => 'xml', $this->rdf => 'rdf', $this->xsd => 'xsd'); + $this->unparsed_code = ''; + $this->max_parsing_loops = $this->v('turtle_max_parsing_loops', 500, $this->a); + } + + /* */ + + function x($re, $v, $options = 'si') { + $v = preg_replace('/^[\xA0\xC2]+/', ' ', $v); + while (preg_match('/^\s*(\#[^\xd\xa]*)(.*)$/si', $v, $m)) {/* comment removal */ + $v = $m[2]; + } + return ARC2::x($re, $v, $options); + //$this->unparsed_code = ($sub_r && count($sub_r)) ? $sub_r[count($sub_r) - 1] : ''; + } + + function createBnodeID(){ + $this->bnode_id++; + return '_:' . $this->bnode_prefix . $this->bnode_id; + } + + /* */ + + function addT($t) { + if ($this->skip_dupes) { + $h = md5(serialize($t)); + if (!isset($this->added_triples[$h])) { + $this->triples[$this->t_count] = $t; + $this->t_count++; + $this->added_triples[$h] = true; + } + } + else { + $this->triples[$this->t_count] = $t; + $this->t_count++; + } + } + + /* */ + + function getTriples() { + return $this->v('triples', array()); + } + + function countTriples() { + return $this->t_count; + } + + /* */ + + function getUnparsedCode() { + return $this->v('unparsed_code', ''); + } + + /* */ + + function setDefaultPrefixes() { + $this->prefixes = array( + 'rdf:' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rdfs:' => 'http://www.w3.org/2000/01/rdf-schema#', + 'owl:' => 'http://www.w3.org/2002/07/owl#', + 'xsd:' => 'http://www.w3.org/2001/XMLSchema#', + ); + if ($ns = $this->v('ns', array(), $this->a)) { + foreach ($ns as $p => $u) $this->prefixes[$p . ':'] = $u; + } + } + + + function parse($path, $data = '', $iso_fallback = false) { + $this->setDefaultPrefixes(); + /* reader */ + if (!$this->v('reader')) { + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->setAcceptHeader('Accept: application/x-turtle; q=0.9, */*; q=0.1'); + $this->reader->activate($path, $data); + $this->base = $this->v1('base', $this->reader->base, $this->a); + $this->r = array('vars' => array()); + /* parse */ + $buffer = ''; + $more_triples = array(); + $sub_v = ''; + $sub_v2 = ''; + $loops = 0; + $prologue_done = 0; + while ($d = $this->reader->readStream(0, 8192)) { + $buffer .= $d; + $sub_v = $buffer; + do { + $proceed = 0; + if (!$prologue_done) { + $proceed = 1; + if ((list($sub_r, $sub_v) = $this->xPrologue($sub_v)) && $sub_r) { + $loops = 0; + $sub_v .= $this->reader->readStream(0, 128); + /* we might have missed the final DOT in the previous prologue loop */ + if ($sub_r = $this->x('\.', $sub_v)) $sub_v = $sub_r[1]; + if ($this->x("\@?(base|prefix)", $sub_v)) {/* more prologue to come, use outer loop */ + $proceed = 0; + } + } + else { + $prologue_done = 1; + } + } + if ($prologue_done && (list($sub_r, $sub_v, $more_triples, $sub_v2) = $this->xTriplesBlock($sub_v)) && is_array($sub_r)) { + $proceed = 1; + $loops = 0; + foreach ($sub_r as $t) { + $this->addT($t); + } + } + } while ($proceed); + $loops++; + $buffer = $sub_v; + if ($loops > $this->max_parsing_loops) {/* most probably a parser or code bug, might also be a huge object value, though */ + $this->addError('too many loops: ' . $loops . '. Could not parse "' . substr($buffer, 0, 200) . '..."'); + break; + } + } + foreach ($more_triples as $t) { + $this->addT($t); + } + $sub_v = count($more_triples) ? $sub_v2 : $sub_v; + $buffer = $sub_v; + $this->unparsed_code = $buffer; + $this->reader->closeStream(); + unset($this->reader); + /* remove trailing comments */ + while (preg_match('/^\s*(\#[^\xd\xa]*)(.*)$/si', $this->unparsed_code, $m)) $this->unparsed_code = $m[2]; + if ($this->unparsed_code && !$this->getErrors()) { + $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($this->unparsed_code, 0, 30)); + if (trim($rest)) $this->addError('Could not parse "' . $rest . '"'); + } + return $this->done(); + } + + function xPrologue($v) { + $r = 0; + if (!$this->t_count) { + if ((list($sub_r, $v) = $this->xBaseDecl($v)) && $sub_r) { + $this->base = $sub_r; + $r = 1; + } + while ((list($sub_r, $v) = $this->xPrefixDecl($v)) && $sub_r) { + $this->prefixes[$sub_r['prefix']] = $sub_r['uri']; + $r = 1; + } + } + return array($r, $v); + } + + /* 3 */ + + function xBaseDecl($v) { + if ($r = $this->x("\@?base\s+", $v)) { + if ((list($r, $sub_v) = $this->xIRI_REF($r[1])) && $r) { + if ($sub_r = $this->x('\.', $sub_v)) { + $sub_v = $sub_r[1]; + } + return array($r, $sub_v); + } + } + return array(0, $v); + } + + /* 4 */ + + function xPrefixDecl($v) { + if ($r = $this->x("\@?prefix\s+", $v)) { + if ((list($r, $sub_v) = $this->xPNAME_NS($r[1])) && $r) { + $prefix = $r; + if((list($r, $sub_v) = $this->xIRI_REF($sub_v)) && $r) { + $uri = $this->calcURI($r, $this->base); + if ($sub_r = $this->x('\.', $sub_v)) { + $sub_v = $sub_r[1]; + } + return array(array('prefix' => $prefix, 'uri_ref' => $r, 'uri' => $uri), $sub_v); + } + } + } + return array(0, $v); + } + + /* 21.., 32.. */ + + function xTriplesBlock($v) { + $pre_r = array(); + $r = array(); + $state = 1; + $sub_v = $v; + $buffer = $sub_v; + do { + $proceed = 0; + if ($state == 1) {/* expecting subject */ + $t = array('type' => 'triple', 's' => '', 'p' => '', 'o' => '', 's_type' => '', 'p_type' => '', 'o_type' => '', 'o_datatype' => '', 'o_lang' => ''); + if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) { + $t['s'] = $sub_r['value']; + $t['s_type'] = $sub_r['type']; + $state = 2; + $proceed = 1; + if ($sub_r = $this->x('(\}|\.)', $sub_v)) { + if ($t['s_type'] == 'placeholder') { + $state = 4; + } + else { + $this->addError('"' . $sub_r[1]. '" after subject found.'); + } + } + } + elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) { + $t['s'] = $sub_r['id']; + $t['s_type'] = $sub_r['type']; + $pre_r = array_merge($pre_r, $sub_r['triples']); + $state = 2; + $proceed = 1; + if ($sub_r = $this->x('\.', $sub_v)) { + $this->addError('DOT after subject found.'); + } + } + elseif ((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) { + $t['s'] = $sub_r['id']; + $t['s_type'] = $sub_r['type']; + $pre_r = array_merge($pre_r, $sub_r['triples']); + $state = 2; + $proceed = 1; + } + elseif ($sub_r = $this->x('\.', $sub_v)) { + $this->addError('Subject expected, DOT found.' . $sub_v); + } + } + if ($state == 2) {/* expecting predicate */ + if ($sub_r = $this->x('a\s+', $sub_v)) { + $sub_v = $sub_r[1]; + $t['p'] = $this->rdf . 'type'; + $t['p_type'] = 'uri'; + $state = 3; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) { + if ($sub_r['type'] == 'bnode') { + $this->addError('Blank node used as triple predicate'); + } + $t['p'] = $sub_r['value']; + $t['p_type'] = $sub_r['type']; + $state = 3; + $proceed = 1; + } + elseif ($sub_r = $this->x('\.', $sub_v)) { + $state = 4; + } + elseif ($sub_r = $this->x('\}', $sub_v)) { + $buffer = $sub_v; + $r = array_merge($r, $pre_r); + $pre_r = array(); + $proceed = 0; + } + } + if ($state == 3) {/* expecting object */ + if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) { + $t['o'] = $sub_r['value']; + $t['o_type'] = $sub_r['type']; + $t['o_lang'] = $this->v('lang', '', $sub_r); + $t['o_datatype'] = $this->v('datatype', '', $sub_r); + $pre_r[] = $t; + $state = 4; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) { + $t['o'] = $sub_r['id']; + $t['o_type'] = $sub_r['type']; + $t['o_datatype'] = ''; + $pre_r = array_merge($pre_r, array($t), $sub_r['triples']); + $state = 4; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) { + $t['o'] = $sub_r['id']; + $t['o_type'] = $sub_r['type']; + $t['o_datatype'] = ''; + $pre_r = array_merge($pre_r, array($t), $sub_r['triples']); + $state = 4; + $proceed = 1; + } + } + if ($state == 4) {/* expecting . or ; or , or } */ + if ($sub_r = $this->x('\.', $sub_v)) { + $sub_v = $sub_r[1]; + $buffer = $sub_v; + $r = array_merge($r, $pre_r); + $pre_r = array(); + $state = 1; + $proceed = 1; + } + elseif ($sub_r = $this->x('\;', $sub_v)) { + $sub_v = $sub_r[1]; + $state = 2; + $proceed = 1; + } + elseif ($sub_r = $this->x('\,', $sub_v)) { + $sub_v = $sub_r[1]; + $state = 3; + $proceed = 1; + if ($sub_r = $this->x('\}', $sub_v)) { + $this->addError('Object expected, } found.'); + } + } + if ($sub_r = $this->x('(\}|\{|OPTIONAL|FILTER|GRAPH)', $sub_v)) { + $buffer = $sub_v; + $r = array_merge($r, $pre_r); + $pre_r = array(); + $proceed = 0; + } + } + } while ($proceed); + return count($r) ? array($r, $buffer, $pre_r, $sub_v) : array(0, $buffer, $pre_r, $sub_v); + } + + /* 39.. */ + + function xBlankNodePropertyList($v) { + if ($sub_r = $this->x('\[', $v)) { + $sub_v = $sub_r[1]; + $s = $this->createBnodeID(); + $r = array('id' => $s, 'type' => 'bnode', 'triples' => array()); + $t = array('type' => 'triple', 's' => $s, 'p' => '', 'o' => '', 's_type' => 'bnode', 'p_type' => '', 'o_type' => '', 'o_datatype' => '', 'o_lang' => ''); + $state = 2; + $closed = 0; + do { + $proceed = 0; + if ($state == 2) {/* expecting predicate */ + if ($sub_r = $this->x('a\s+', $sub_v)) { + $sub_v = $sub_r[1]; + $t['p'] = $this->rdf . 'type'; + $t['p_type'] = 'uri'; + $state = 3; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) { + $t['p'] = $sub_r['value']; + $t['p_type'] = $sub_r['type']; + $state = 3; + $proceed = 1; + } + } + if ($state == 3) {/* expecting object */ + if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) { + $t['o'] = $sub_r['value']; + $t['o_type'] = $sub_r['type']; + $t['o_lang'] = $this->v('lang', '', $sub_r); + $t['o_datatype'] = $this->v('datatype', '', $sub_r); + $r['triples'][] = $t; + $state = 4; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) { + $t['o'] = $sub_r['id']; + $t['o_type'] = $sub_r['type']; + $t['o_datatype'] = ''; + $r['triples'] = array_merge($r['triples'], array($t), $sub_r['triples']); + $state = 4; + $proceed = 1; + } + elseif((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) { + $t['o'] = $sub_r['id']; + $t['o_type'] = $sub_r['type']; + $t['o_datatype'] = ''; + $r['triples'] = array_merge($r['triples'], array($t), $sub_r['triples']); + $state = 4; + $proceed = 1; + } + } + if ($state == 4) {/* expecting . or ; or , or ] */ + if ($sub_r = $this->x('\.', $sub_v)) { + $sub_v = $sub_r[1]; + $state = 1; + $proceed = 1; + } + if ($sub_r = $this->x('\;', $sub_v)) { + $sub_v = $sub_r[1]; + $state = 2; + $proceed = 1; + } + if ($sub_r = $this->x('\,', $sub_v)) { + $sub_v = $sub_r[1]; + $state = 3; + $proceed = 1; + } + if ($sub_r = $this->x('\]', $sub_v)) { + $sub_v = $sub_r[1]; + $proceed = 0; + $closed = 1; + } + } + } while ($proceed); + if ($closed) { + return array($r, $sub_v); + } + return array(0, $v); + } + return array(0, $v); + } + + /* 40.. */ + + function xCollection($v) { + if ($sub_r = $this->x('\(', $v)) { + $sub_v = $sub_r[1]; + $s = $this->createBnodeID(); + $r = array('id' => $s, 'type' => 'bnode', 'triples' => array()); + $closed = 0; + do { + $proceed = 0; + if ((list($sub_r, $sub_v) = $this->xVarOrTerm($sub_v)) && $sub_r) { + $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'first', 'o' => $sub_r['value'], 's_type' => 'bnode', 'p_type' => 'uri', 'o_type' => $sub_r['type'], 'o_lang' => $this->v('lang', '', $sub_r), 'o_datatype' => $this->v('datatype', '', $sub_r)); + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xCollection($sub_v)) && $sub_r) { + $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'first', 'o' => $sub_r['id'], 's_type' => 'bnode', 'p_type' => 'uri', 'o_type' => $sub_r['type'], 'o_lang' => '', 'o_datatype' => ''); + $r['triples'] = array_merge($r['triples'], $sub_r['triples']); + $proceed = 1; + } + elseif((list($sub_r, $sub_v) = $this->xBlankNodePropertyList($sub_v)) && $sub_r) { + $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'first', 'o' => $sub_r['id'], 's_type' => 'bnode', 'p_type' => 'uri', 'o_type' => $sub_r['type'], 'o_lang' => '', 'o_datatype' => ''); + $r['triples'] = array_merge($r['triples'], $sub_r['triples']); + $proceed = 1; + } + if ($proceed) { + if ($sub_r = $this->x('\)', $sub_v)) { + $sub_v = $sub_r[1]; + $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'rest', 'o' => $this->rdf . 'nil', 's_type' => 'bnode', 'p_type' => 'uri', 'o_type' => 'uri', 'o_lang' => '', 'o_datatype' => ''); + $closed = 1; + $proceed = 0; + } + else { + $next_s = $this->createBnodeID(); + $r['triples'][] = array('type' => 'triple', 's' => $s, 'p' => $this->rdf . 'rest', 'o' => $next_s, 's_type' => 'bnode', 'p_type' => 'uri', 'o_type' => 'bnode', 'o_lang' => '', 'o_datatype' => ''); + $s = $next_s; + } + } + } while ($proceed); + if ($closed) { + return array($r, $sub_v); + } + } + return array (0, $v); + } + + /* 42 */ + + function xVarOrTerm($v) { + if ((list($sub_r, $sub_v) = $this->xVar($v)) && $sub_r) { + return array($sub_r, $sub_v); + } + elseif ((list($sub_r, $sub_v) = $this->xGraphTerm($v)) && $sub_r) { + return array($sub_r, $sub_v); + } + return array(0, $v); + } + + /* 44, 74.., 75.. */ + + function xVar($v) { + if ($r = $this->x('(\?|\$)([^\s]+)', $v)) { + if ((list($sub_r, $sub_v) = $this->xVARNAME($r[2])) && $sub_r) { + if (!in_array($sub_r, $this->r['vars'])) { + $this->r['vars'][] = $sub_r; + } + return array(array('value' => $sub_r, 'type' => 'var'), $sub_v . $r[3]); + } + } + return array(0, $v); + } + + /* 45 */ + + function xGraphTerm($v) { + foreach (array( + 'IRIref' => 'uri', + 'RDFLiteral' => 'literal', + 'NumericLiteral' => 'literal', + 'BooleanLiteral' => 'literal', + 'BlankNode' => 'bnode', + 'NIL' => 'uri', + 'Placeholder' => 'placeholder' + ) as $term => $type) { + $m = 'x' . $term; + if ((list($sub_r, $sub_v) = $this->$m($v)) && $sub_r) { + if (!is_array($sub_r)) { + $sub_r = array('value' => $sub_r); + } + $sub_r['type'] = $this->v1('type', $type, $sub_r); + return array($sub_r, $sub_v); + } + } + return array(0, $v); + } + + /* 60 */ + + function xRDFLiteral($v) { + if ((list($sub_r, $sub_v) = $this->xString($v)) && $sub_r) { + $sub_r['value'] = $this->unescapeNtripleUTF($sub_r['value']); + $r = $sub_r; + if ((list($sub_r, $sub_v) = $this->xLANGTAG($sub_v)) && $sub_r) { + $r['lang'] = $sub_r; + } + elseif (!$this->x('\s', $sub_v) && ($sub_r = $this->x('\^\^', $sub_v)) && (list($sub_r, $sub_v) = $this->xIRIref($sub_r[1])) && $sub_r[1]) { + $r['datatype'] = $sub_r; + } + return array($r, $sub_v); + } + return array(0, $v); + } + + /* 61.., 62.., 63.., 64.. */ + + function xNumericLiteral($v) { + $sub_r = $this->x('(\-|\+)?', $v); + $prefix = $sub_r[1]; + $sub_v = $sub_r[2]; + foreach (array('DOUBLE' => 'double', 'DECIMAL' => 'decimal', 'INTEGER' => 'integer') as $type => $xsd) { + $m = 'x' . $type; + if ((list($sub_r, $sub_v) = $this->$m($sub_v)) && ($sub_r !== false)) { + $r = array('value' => $prefix . $sub_r, 'type' => 'literal', 'datatype' => $this->xsd . $xsd); + return array($r, $sub_v); + } + } + return array(0, $v); + } + + /* 65.. */ + + function xBooleanLiteral($v) { + if ($r = $this->x('(true|false)', $v)) { + return array($r[1], $r[2]); + } + return array(0, $v); + } + + /* 66.., 87.., 88.., 89.., 90.., 91.. */ + + function xString($v) {/* largely simplified, may need some tweaks in following revisions */ + $sub_v = $v; + if (!preg_match('/^\s*([\']{3}|\'|[\"]{3}|\")(.*)$/s', $sub_v, $m)) return array(0, $v); + $delim = $m[1]; + $rest = $m[2]; + $sub_types = array("'''" => 'literal_long1', '"""' => 'literal_long2', "'" => 'literal1', '"' => 'literal2'); + $sub_type = $sub_types[$delim]; + $pos = 0; + $r = false; + do { + $proceed = 0; + $delim_pos = strpos($rest, $delim, $pos); + if ($delim_pos === false) break; + $new_rest = substr($rest, $delim_pos + strlen($delim)); + $r = substr($rest, 0, $delim_pos); + if (!preg_match('/([\x5c]+)$/s', $r, $m) || !(strlen($m[1]) % 2)) { + $rest = $new_rest; + } + else { + $r = false; + $pos = $delim_pos + 1; + $proceed = 1; + } + } while ($proceed); + if ($r !== false) { + return array(array('value' => $this->toUTF8($r) , 'type' => 'literal', 'sub_type' => $sub_type), $rest); + } + return array(0, $v); + } + + /* 67 */ + + function xIRIref($v) { + if ((list($r, $v) = $this->xIRI_REF($v)) && $r) { + return array($this->calcURI($r, $this->base), $v); + } + elseif ((list($r, $v) = $this->xPrefixedName($v)) && $r) { + return array($r, $v); + } + return array(0, $v); + } + + /* 68 */ + + function xPrefixedName($v) { + if ((list($r, $v) = $this->xPNAME_LN($v)) && $r) { + return array($r, $v); + } + elseif ((list($r, $sub_v) = $this->xPNAME_NS($v)) && $r) { + return isset($this->prefixes[$r]) ? array($this->prefixes[$r], $sub_v) : array(0, $v); + } + return array(0, $v); + } + + /* 69.., 73.., 93, 94.. */ + + function xBlankNode($v) { + if (($r = $this->x('\_\:', $v)) && (list($r, $sub_v) = $this->xPN_LOCAL($r[1])) && $r) { + return array(array('type' => 'bnode', 'value' => '_:' . $r), $sub_v); + } + if ($r = $this->x('\[[\x20\x9\xd\xa]*\]', $v)) { + return array(array('type' => 'bnode', 'value' => $this->createBnodeID()), $r[1]); + } + return array(0, $v); + } + + /* 70.. @@sync with SPARQLParser */ + + function xIRI_REF($v) { + //if ($r = $this->x('\<([^\<\>\"\{\}\|\^\'[:space:]]*)\>', $v)) { + if (($r = $this->x('\<(\$\{[^\>]*\})\>', $v)) && ($sub_r = $this->xPlaceholder($r[1]))) { + return array($r[1], $r[2]); + } + elseif ($r = $this->x('\<\>', $v)) { + return array(true, $r[1]); + } + elseif ($r = $this->x('\<([^\s][^\<\>]*)\>', $v)) { + return array($r[1] ? $r[1] : true, $r[2]); + } + return array(0, $v); + } + + /* 71 */ + + function xPNAME_NS($v) { + list($r, $sub_v) = $this->xPN_PREFIX($v); + $prefix = $r ? $r : ''; + return ($r = $this->x("\:", $sub_v)) ? array($prefix . ':', $r[1]) : array(0, $v); + } + + /* 72 */ + + function xPNAME_LN($v) { + if ((list($r, $sub_v) = $this->xPNAME_NS($v)) && $r) { + if (!$this->x('\s', $sub_v) && (list($sub_r, $sub_v) = $this->xPN_LOCAL($sub_v)) && $sub_r) { + if (!isset($this->prefixes[$r])) { + return array(0, $v); + } + return array($this->prefixes[$r] . $sub_r, $sub_v); + } + } + return array(0, $v); + } + + /* 76 */ + + function xLANGTAG($v) { + if (!$this->x('\s', $v) && ($r = $this->x('\@([a-z]+(\-[a-z0-9]+)*)', $v))) { + return array($r[1], $r[3]); + } + return array(0, $v); + } + + /* 77.. */ + + function xINTEGER($v) { + if ($r = $this->x('([0-9]+)', $v)) { + return array($r[1], $r[2]); + } + return array(false, $v); + } + + /* 78.. */ + + function xDECIMAL($v) { + if ($r = $this->x('([0-9]+\.[0-9]*)', $v)) { + return array($r[1], $r[2]); + } + if ($r = $this->x('(\.[0-9]+)', $v)) { + return array($r[1], $r[2]); + } + return array(false, $v); + } + + /* 79.., 86.. */ + + function xDOUBLE($v) { + if ($r = $this->x('([0-9]+\.[0-9]*E[\+\-]?[0-9]+)', $v)) { + return array($r[1], $r[2]); + } + if ($r = $this->x('(\.[0-9]+E[\+\-]?[0-9]+)', $v)) { + return array($r[1], $r[2]); + } + if ($r = $this->x('([0-9]+E[\+\-]?[0-9]+)', $v)) { + return array($r[1], $r[2]); + } + return array(false, $v); + } + + /* 92 */ + + function xNIL($v) { + if ($r = $this->x('\([\x20\x9\xd\xa]*\)', $v)) { + return array(array('type' => 'uri', 'value' => $this->rdf . 'nil'), $r[1]); + } + return array(0, $v); + } + + /* 95.. */ + + function xPN_CHARS_BASE($v) { + if ($r = $this->x("([a-z]+|\\\u[0-9a-f]{1,4})", $v)) { + return array($r[1], $r[2]); + } + return array(0, $v); + } + + /* 96 */ + + function xPN_CHARS_U($v) { + if ((list($r, $sub_v) = $this->xPN_CHARS_BASE($v)) && $r) { + return array($r, $sub_v); + } + elseif ($r = $this->x("(_)", $v)) { + return array($r[1], $r[2]); + } + return array(0, $v); + } + + /* 97.. */ + + function xVARNAME($v) { + $r = ''; + do { + $proceed = 0; + if ($sub_r = $this->x('([0-9]+)', $v)) { + $r .= $sub_r[1]; + $v = $sub_r[2]; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xPN_CHARS_U($v)) && $sub_r) { + $r .= $sub_r; + $v = $sub_v; + $proceed = 1; + } + elseif ($r && ($sub_r = $this->x('([\xb7\x300-\x36f]+)', $v))) { + $r .= $sub_r[1]; + $v = $sub_r[2]; + $proceed = 1; + } + } while ($proceed); + return array($r, $v); + } + + /* 98.. */ + + function xPN_CHARS($v) { + if ((list($r, $sub_v) = $this->xPN_CHARS_U($v)) && $r) { + return array($r, $sub_v); + } + elseif ($r = $this->x('([\-0-9\xb7\x300-\x36f])', $v)) { + return array($r[1], $r[2]); + } + return array(false, $v); + } + + /* 99 */ + + function xPN_PREFIX($v) { + if ($sub_r = $this->x("([^\s\:\(\)\{\}\;\,]+)", $v, 's')) {/* accelerator */ + return array($sub_r[1], $sub_r[2]);/* @@testing */ + } + if ((list($r, $sub_v) = $this->xPN_CHARS_BASE($v)) && $r) { + do { + $proceed = 0; + list($sub_r, $sub_v) = $this->xPN_CHARS($sub_v); + if ($sub_r !== false) { + $r .= $sub_r; + $proceed = 1; + } + elseif ($sub_r = $this->x("\.", $sub_v)) { + $r .= '.'; + $sub_v = $sub_r[1]; + $proceed = 1; + } + } while ($proceed); + list($sub_r, $sub_v) = $this->xPN_CHARS($sub_v); + $r .= $sub_r ? $sub_r : ''; + } + return array($r, $sub_v); + } + + /* 100 */ + + function xPN_LOCAL($v) { + if (($sub_r = $this->x("([^\s\(\)\{\}\[\]\;\,\.]+)", $v, 's')) && !preg_match('/^\./', $sub_r[2])) {/* accelerator */ + return array($sub_r[1], $sub_r[2]);/* @@testing */ + } + $r = ''; + $sub_v = $v; + do { + $proceed = 0; + if ($this->x('\s', $sub_v)) { + return array($r, $sub_v); + } + if ($sub_r = $this->x('([0-9])', $sub_v)) { + $r .= $sub_r[1]; + $sub_v = $sub_r[2]; + $proceed = 1; + } + elseif ((list($sub_r, $sub_v) = $this->xPN_CHARS_U($sub_v)) && $sub_r) { + $r .= $sub_r; + $proceed = 1; + } + elseif ($r) { + if (($sub_r = $this->x('(\.)', $sub_v)) && !preg_match('/^[\s\}]/s', $sub_r[2])) { + $r .= $sub_r[1]; + $sub_v = $sub_r[2]; + } + if ((list($sub_r, $sub_v) = $this->xPN_CHARS($sub_v)) && $sub_r) { + $r .= $sub_r; + $proceed = 1; + } + } + } while ($proceed); + return array($r, $sub_v); + } + + /* */ + + function unescapeNtripleUTF($v) { + if (strpos($v, '\\') === false) return $v; + $mappings = array('t' => "\t", 'n' => "\n", 'r' => "\r", '\"' => '"', '\'' => "'"); + foreach ($mappings as $in => $out) { + $v = preg_replace('/\x5c([' . $in . '])/', $out, $v); + } + if (strpos(strtolower($v), '\u') === false) return $v; + while (preg_match('/\\\(U)([0-9A-F]{8})/', $v, $m) || preg_match('/\\\(u)([0-9A-F]{4})/', $v, $m)) { + $no = hexdec($m[2]); + if ($no < 128) $char = chr($no); + else if ($no < 2048) $char = chr(($no >> 6) + 192) . chr(($no & 63) + 128); + else if ($no < 65536) $char = chr(($no >> 12) + 224) . chr((($no >> 6) & 63) + 128) . chr(($no & 63) + 128); + else if ($no < 2097152) $char = chr(($no >> 18) + 240) . chr((($no >> 12) & 63) + 128) . chr((($no >> 6) & 63) + 128) . chr(($no & 63) + 128); + else $char= ''; + $v = str_replace('\\' . $m[1] . $m[2], $char, $v); + } + return $v; + } + + /* */ + + function xPlaceholder($v) { + //if ($r = $this->x('(\?|\$)\{([^\}]+)\}', $v)) { + if ($r = $this->x('(\?|\$)', $v)) { + if (preg_match('/(\{(?:[^{}]+|(?R))*\})/', $r[2], $m) && strpos(trim($r[2]), $m[1]) === 0) { + $ph = substr($m[1], 1, -1); + $rest = substr(trim($r[2]), strlen($m[1])); + if (!isset($this->r['placeholders'])) $this->r['placeholders'] = array(); + if (!in_array($ph, $this->r['placeholders'])) $this->r['placeholders'][] = $ph; + return array(array('value' => $ph, 'type' => 'placeholder'), $rest); + } + } + return array(0, $v); + } + + /* */ +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_LegacyHTMLSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_LegacyHTMLSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,111 @@ +content_header = 'text/html'; + } + + /* */ + + function getSerializedArray($struct, $root = 1, $ind = ' ') { + $n = "\n"; + $r = ''; + $is_flat = $this->isAssociativeArray($struct) ? 0 : 1; + foreach ($struct as $k => $v) { + if (!$is_flat) $r .= $n . $ind . $ind . '
    ' . $k . '
    '; + $r .= $n . $ind . $ind . '
    ' . (is_array($v) ? $this->getSerializedArray($v, 0, $ind . $ind . $ind) . $n . $ind . $ind : htmlspecialchars($v)) . '
    '; + } + return $n . $ind . '
    ' . $r . $n . $ind . '
    '; + } + + /* */ + + function isAssociativeArray($v) { + foreach (array_keys($v) as $k => $val) { + if ($k !== $val) return 1; + } + return 0; + } + + /* */ + + function getSerializedNode($index, $node, $level = 0, $raw = 0) { + $r = ''; + $tag = $this->v('tag', '', $node); + if (preg_match('/^(comment|script)$/', $tag)) { + } + elseif ($tag == 'cdata') { + $r .= $this->v('cdata', '', $node); + $r .= $this->v('value', '', $node['a']); + } + else { + /* open tag */ + if (preg_match('/^(div|form|p|section)$/', $tag)) { + $r .= str_pad("\n", $level + 1, " "); + } + $r .= '<' . $tag; + $attrs = $this->v('a', array(), $node); + foreach ($attrs as $k => $v) { + /* use uri, if detected */ + if ($k != 'id') { + $v = $this->v($k . ' uri', $v, $attrs); + } + /* skip arrays and other derived attrs */ + if (preg_match('/\s/s', $k)) continue; + $r .= ' ' . $k . '="' . $v . '"'; + } + if ($node['empty']) { + $r .= '/>'; + } + else { + $r .= '>'; + /* cdata */ + $r .= $this->v('cdata', '', $node); + /* sub-nodes */ + $sub_nodes = $this->v($node['id'], array(), $index); + foreach ($sub_nodes as $sub_node) { + $r .= $this->getSerializedNode($index, $sub_node, $level + 1, 1); + } + /* close tag */ + //$r .= str_pad("\n", $level + 1, " ") . ''; + $r .= ''; + if (preg_match('/^(div|form|p|section)$/', $tag)) { + $r .= str_pad("\n", $level + 1, " "); + } + } + } + /* doc envelope, in case of sub-structure serializing */ + if (!$raw && ($level == 0) && ($node['level'] > 1)) { + $r = ' + + + + + + ' . $r . ' + + + '; + } + return $r; + } + + /* */ +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_LegacyJSONSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_LegacyJSONSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,53 @@ +content_header = 'application/json'; + } + + /* */ + + function getSerializedArray($struct, $ind = '') { + $n = "\n"; + if (function_exists('json_encode')) return str_replace('","', '",' . $n . '"', str_replace("\/","/",json_encode($struct))); + $r = ''; + $from = array("\\", "\r", "\t", "\n", '"', "\b", "\f"); + $to = array('\\\\', '\r', '\t', '\n', '\"', '\b', '\f'); + $is_flat = $this->isAssociativeArray($struct) ? 0 : 1; + foreach ($struct as $k => $v) { + $r .= $r ? ',' . $n . $ind . $ind : $ind . $ind; + $r .= $is_flat ? '' : '"' . $k . '": '; + $r .= is_array($v) ? $this->getSerializedArray($v, $ind . ' ') : '"' . str_replace($from, $to, $v) . '"'; + } + return $is_flat ? $ind . '[' . $n . $r . $n . $ind . ']' : $ind . '{' . $n . $r . $n . $ind . '}'; + } + + /* */ + + function isAssociativeArray($v) { + foreach (array_keys($v) as $k => $val) { + if ($k !== $val) return 1; + } + return 0; + } + + /* */ + +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_LegacyXMLSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_LegacyXMLSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ +content_header = 'text/xml'; + } + + /* */ + + function getSerializedArray($struct, $root = 1, $ind = ' ') { + $n = "\n"; + $r = ''; + $is_flat = $this->isAssociativeArray($struct) ? 0 : 1; + foreach ($struct as $k => $v) { + $tag = $is_flat ? 'item' : preg_replace('/[\s]/s', '_', $k); + $tag = preg_replace('/^.*([a-z0-9\-\_]+)$/Uis', '\\1', $tag); + $r .= $n . $ind . '<' . $tag . '>' . (is_array($v) ? $this->getSerializedArray($v, 0, $ind . ' ') . $n . $ind : htmlspecialchars($v)) . ''; + } + if ($root) $r = $this->getHead() . $r . $this->getFooter(); + return $r; + } + + /* */ + + function getHead() { + $n = "\n"; + $r = ''; + $r .= $n . ''; + return $r; + } + + function getFooter() { + $n = "\n"; + $r = $n . ''; + return $r; + } + + /* */ + + function isAssociativeArray($v) { + foreach (array_keys($v) as $k => $val) { + if ($k !== $val) return 1; + } + return 0; + } + + /* */ + +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_MicroRDFSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_MicroRDFSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,142 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('RDFSerializer'); + +class ARC2_MicroRDFSerializer extends ARC2_RDFSerializer { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->content_header = 'text/html'; + $this->label_store = $this->v('label_store', '', $this->a); + } + + /* */ + + function getLabel($res, $ps = '') { + if (!$ps) $ps = array(); + foreach ($ps as $p => $os) { + if (preg_match('/[\/\#](name|label|summary|title|fn)$/i', $p)) { + return $os[0]['value']; + } + } + if (preg_match('/^\_\:/', $res)) return "An unnamed resource"; + return $this->extractTermLabel($res); + return preg_replace("/^(.*[\/\#])([^\/\#]+)$/", '\\2', str_replace('_', ' ', $res)); + } + + function getSerializedIndex($index, $res = '') { + $r = ''; + $n = "\n"; + if ($res) $index = array($res => $index[$res]); + //return Trice::dump($index); + $types = $this->v($this->expandPName('rdf:type'), array(), $index); + $main_type = $types ? $types[0]['value'] : ''; + foreach ($index as $s => $ps) { + /* node */ + $r .= ' +
    mdAttrs($s, $main_type) . '> +

    ' . ucfirst($this->getLabel($s, $ps)) . '

    + '; + /* arcs */ + foreach ($ps as $p => $os) { + $p_cls = strtolower($this->getPName($p)); + $p_cls = str_replace(':', '-', $p_cls); + $r .= ' +
    + ' . ucfirst($this->getLabel($p)) . ': +
      + '; + $oc = count($os); + foreach ($os as $i => $o) { + $val = $this->getObjectValue($o, $p); + $cls = ''; + if ($i == 0) $cls .= ($cls ? ' ' : '') . 'first'; + if ($i == $oc - 1) $cls .= ($cls ? ' ' : '') . 'last'; + $r .= $n . '' . $val . ''; + } + $r .= ' +
    +
    +
    + '; + } + /* /node */ + $r .= ' +
    +
    + '; + } + return $r; + } + + function getObjectValue($o, $p) { + if ($o['type'] == 'uri') { + if (preg_match('/(jpe?g|gif|png)$/i', $o['value'])) { + return $this->getImageObjectValue($o, $p); + } + return $this->getURIObjectValue($o, $p); + } + if ($o['type'] == "bnode") { + return $this->getBNodeObjectValue($o, $p); + } + return $this->getLiteralObjectValue($o, $p); + } + + function getImageObjectValue($o, $p) { + return 'img'; + } + + function getURIObjectValue($o, $p) { + $id = htmlspecialchars($o['value']); + $label = $this->getObjectLabel($o['value']); + /* differing href */ + $href = htmlspecialchars($this->v('href', $o['value'], $o)); + if ($id != $href) { + return '' . $label . ''; + } + return '' . $label . ''; + //$label = $o['value']; + //$label = preg_replace('/^https?\:\/\/(www\.)?/', '', $label); + } + + function getBNodeObjectValue($o, $p) { + return '
    ' . $o['value'] . '
    '; + return '
    An unnamed resource
    '; + } + + function getLiteralObjectValue($o, $p) { + return '
    ' . $o['value'] . '
    '; + } + + /* */ + + function getObjectLabel($id) { + $r = $this->extractTermLabel($id); + if (!$this->label_store) return $r; + $q = ' + SELECT ?val WHERE { + <' . $id . '> ?p ?val . + FILTER(REGEX(str(?p), "(label|title|name|summary)$")) + } LIMIT 1 + '; + $row = $this->label_store->query($q, 'row'); + return $row ? $row['val'] : $r; + } + + /* */ + +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_NTriplesSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_NTriplesSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,192 @@ + + * @homepage + * @package ARC2 +*/ + +ARC2::inc('RDFSerializer'); + +class ARC2_NTriplesSerializer extends ARC2_RDFSerializer { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->esc_chars = array(); + $this->raw = 0; + } + + /* */ + + function getTerm($v, $term = '') { + // type detection + if (!is_array($v) || empty($v['type'])) { + // bnode + if (preg_match('/^\_\:/', $v)) { + return $this->getTerm(array('value' => $v, 'type' => 'bnode')); + } + // uri + if (preg_match('/^[a-z0-9]+\:[^\s\"]*$/is' . ($this->has_pcre_unicode ? 'u' : ''), $v)) { + return $this->getTerm(array('value' => $v, 'type' => 'uri')); + } + // fallback for non-unicode environments: subjects and predicates can't be literals. + if (in_array($term, array('s', 'p'))) { + return $this->getTerm(array('value' => $v, 'type' => 'uri')); + } + // assume literal + return $this->getTerm(array('type' => 'literal', 'value' => $v)); + } + if ($v['type'] == 'bnode') { + return $v['value']; + } + elseif ($v['type'] == 'uri') { + return '<' . $this->escape($v['value']) . '>'; + } + // something went wrong + elseif ($v['type'] != 'literal') { + return $this->getTerm($v['value']); + } + /* literal */ + $quot = '"'; + if ($this->raw && preg_match('/\"/', $v['value'])) { + $quot = "'"; + if (preg_match('/\'/', $v['value'])) { + $quot = '"""'; + if (preg_match('/\"\"\"/', $v['value']) || preg_match('/\"$/', $v['value']) || preg_match('/^\"/', $v['value'])) { + $quot = "'''"; + $v['value'] = preg_replace("/'$/", "' ", $v['value']); + $v['value'] = preg_replace("/^'/", " '", $v['value']); + $v['value'] = str_replace("'''", '\\\'\\\'\\\'', $v['value']); + } + } + } + if ($this->raw && (strlen($quot) == 1) && preg_match('/[\x0d\x0a]/', $v['value'])) { + $quot = $quot . $quot . $quot; + } + $suffix = isset($v['lang']) && $v['lang'] ? '@' . $v['lang'] : ''; + $suffix = isset($v['datatype']) && $v['datatype'] ? '^^' . $this->getTerm($v['datatype']) : $suffix; + //return $quot . "object" . utf8_encode($v['value']) . $quot . $suffix; + return $quot . $this->escape($v['value']) . $quot . $suffix; + } + + function getSerializedIndex($index, $raw = 0) { + $this->raw = $raw; + $r = ''; + $nl = "\n"; + foreach ($index as $s => $ps) { + $s = $this->getTerm($s, 's'); + foreach ($ps as $p => $os) { + $p = $this->getTerm($p, 'p'); + if (!is_array($os)) {/* single literal o */ + $os = array(array('value' => $os, 'type' => 'literal')); + } + foreach ($os as $o) { + $o = $this->getTerm($o, 'o‚'); + $r .= $r ? $nl : ''; + $r .= $s . ' ' . $p . ' ' . $o . ' .'; + } + } + } + return $r . $nl; + } + + /* */ + + function escape($v) { + $r = ''; + // decode, if possible + $v = (strpos(utf8_decode(str_replace('?', '', $v)), '?') === false) ? utf8_decode($v) : $v; + if ($this->raw) return $v;// no further escaping wanted + // escape tabs and linefeeds + $v = str_replace(array("\t", "\r", "\n"), array('\t', '\r', '\n'), $v); + // escape non-ascii-chars + $v = preg_replace_callback('/([^a-zA-Z0-9 \!\#\$\%\&\(\)\*\+\,\-\.\/\:\;\=\?\@\^\_\{\|\}]+)/', array($this, 'escapeChars'), $v); + return $v; + } + + function escapeChars($matches) { + $v = $matches[1]; + $r = ''; + // loop through mb chars + if (function_exists('mb_strlen')) { + for ($i = 0, $i_max = mb_strlen($v, 'UTF-8'); $i < $i_max; $i++) { + $c = mb_substr($v, $i, 1, 'UTF-8'); + if (!isset($this->esc_chars[$c])) { + $this->esc_chars[$c] = $this->getEscapedChar($c, $this->getCharNo($c, 1)); + } + $r .= $this->esc_chars[$c]; + } + } + // fall back to built-in JSON functionality, if available + else if (function_exists('json_encode')) { + $r = json_encode($v); + if ($r == 'null') $r = json_encode (utf8_encode($v)); + // remove boundary quotes + if (substr($r, 0, 1) == '"') $r = substr($r, 1); + if (substr($r, -1) == '"') $r = substr($r, 0, -1); + // uppercase hex chars + $r = preg_replace('/(\\\u)([0-9a-f]{4})/e', "'\\1' . strtoupper('\\2')", $r); + $r = preg_replace('/(\\\U)([0-9a-f]{8})/e', "'\\1' . strtoupper('\\2')", $r); + } + // escape byte-wise (may be wrong for mb chars and newer php versions) + else { + for ($i = 0, $i_max = strlen($v); $i < $i_max; $i++) { + $c = $v[$i]; + if (!isset($this->esc_chars[$c])) { + $this->esc_chars[$c] = $this->getEscapedChar($c, $this->getCharNo($c)); + } + $r .= $this->esc_chars[$c]; + } + } + return $r; + } + + /* */ + + function getCharNo($c, $is_encoded = false) { + $c_utf = $is_encoded ? $c : utf8_encode($c); + $bl = strlen($c_utf);/* binary length */ + $r = 0; + switch ($bl) { + case 1:/* 0####### (0-127) */ + $r = ord($c_utf); + break; + case 2:/* 110##### 10###### = 192+x 128+x */ + $r = ((ord($c_utf[0]) - 192) * 64) + (ord($c_utf[1]) - 128); + break; + case 3:/* 1110#### 10###### 10###### = 224+x 128+x 128+x */ + $r = ((ord($c_utf[0]) - 224) * 4096) + ((ord($c_utf[1]) - 128) * 64) + (ord($c_utf[2]) - 128); + break; + case 4:/* 1111#### 10###### 10###### 10###### = 240+x 128+x 128+x 128+x */ + $r = ((ord($c_utf[0]) - 240) * 262144) + ((ord($c_utf[1]) - 128) * 4096) + ((ord($c_utf[2]) - 128) * 64) + (ord($c_utf[3]) - 128); + break; + } + return $r; + } + + function getEscapedChar($c, $no) {/*see http://www.w3.org/TR/rdf-testcases/#ntrip_strings */ + if ($no < 9) return "\\u" . sprintf('%04X', $no); /* #x0-#x8 (0-8) */ + if ($no == 9) return '\t'; /* #x9 (9) */ + if ($no == 10) return '\n'; /* #xA (10) */ + if ($no < 13) return "\\u" . sprintf('%04X', $no); /* #xB-#xC (11-12) */ + if ($no == 13) return '\r'; /* #xD (13) */ + if ($no < 32) return "\\u" . sprintf('%04X', $no); /* #xE-#x1F (14-31) */ + if ($no < 34) return $c; /* #x20-#x21 (32-33) */ + if ($no == 34) return '\"'; /* #x22 (34) */ + if ($no < 92) return $c; /* #x23-#x5B (35-91) */ + if ($no == 92) return '\\'; /* #x5C (92) */ + if ($no < 127) return $c; /* #x5D-#x7E (93-126) */ + if ($no < 65536) return "\\u" . sprintf('%04X', $no); /* #x7F-#xFFFF (128-65535) */ + if ($no < 1114112) return "\\U" . sprintf('%08X', $no); /* #x10000-#x10FFFF (65536-1114111) */ + return ''; /* not defined => ignore */ + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_POSHRDFSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_POSHRDFSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,105 @@ +content_header = 'text/html'; + } + + /* */ + + function getLabel($res, $ps = '') { + if (!$ps) $ps = array(); + foreach ($ps as $p => $os) { + if (preg_match('/[\/\#](name|label|summary|title|fn)$/i', $p)) { + return $os[0]['value']; + } + } + if (preg_match('/^\_\:/', $res)) return "An unnamed resource"; + return preg_replace("/^(.*[\/\#])([^\/\#]+)$/", '\\2', str_replace('_', ' ', $res)); + } + + function getSerializedIndex($index, $res = '') { + $r = ''; + $n = "\n"; + if ($res) $index = array($res => $index[$res]); + //return Trice::dump($index); + foreach ($index as $s => $ps) { + /* node */ + $r .= ' +
    +

    ' . $this->getLabel($s, $ps) . '

    + '; + /* arcs */ + foreach ($ps as $p => $os) { + $r .= ' +
    + ' . ucfirst($this->getLabel($p)) . ' + '; + foreach ($os as $o) { + $r .= $n . $this->getObjectValue($o); + } + $r .= ' +
    + '; + } + /* node */ + $r .= ' +
    +
    + '; + } + return $r; + } + + function getObjectValue($o) { + if ($o['type'] == 'uri') { + if (preg_match('/(jpe?g|gif|png)$/i', $o['value'])) { + return $this->getImageObjectValue($o); + } + return $this->getURIObjectValue($o); + } + if ($o['type'] == "bnode") { + return $this->getBNodeObjectValue($o); + } + return $this->getLiteralObjectValue($o); + } + + function getImageObjectValue($o) { + return 'img'; + } + + function getURIObjectValue($o) { + $href = htmlspecialchars($o['value']); + $label = $o['value']; + $label = preg_replace('/^https?\:\/\/(www\.)?/', '', $label); + return '' . $label . ''; + } + + function getBNodeObjectValue($o) { + return '
    An unnamed resource
    '; + } + + function getLiteralObjectValue($o) { + return '
    ' . $o['value'] . '
    '; + } + + /* */ + +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_RDFJSONSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_RDFJSONSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,89 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 +*/ + +ARC2::inc('RDFSerializer'); + +class ARC2_RDFJSONSerializer extends ARC2_RDFSerializer { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->content_header = 'application/json'; + } + + /* */ + + function getTerm($v, $term = 's') { + if (!is_array($v)) { + if (preg_match('/^\_\:/', $v)) { + return ($term == 'o') ? $this->getTerm(array('value' => $v, 'type' => 'bnode'), 'o') : '"' . $v . '"'; + } + return ($term == 'o') ? $this->getTerm(array('value' => $v, 'type' => 'uri'), 'o') : '"' . $v . '"'; + } + if (!isset($v['type']) || ($v['type'] != 'literal')) { + if ($term != 'o') { + return $this->getTerm($v['value'], $term); + } + if (preg_match('/^\_\:/', $v['value'])) { + return '{ "value" : "' . $this->jsonEscape($v['value']) . '", "type" : "bnode" }'; + } + return '{ "value" : "' . $this->jsonEscape($v['value']) . '", "type" : "uri" }'; + } + /* literal */ + $r = '{ "value" : "' . $this->jsonEscape($v['value']) . '", "type" : "literal"'; + $suffix = isset($v['datatype']) ? ', "datatype" : "' . $v['datatype'] . '"' : ''; + $suffix = isset($v['lang']) ? ', "lang" : "' . $v['lang'] . '"' : $suffix; + $r .= $suffix . ' }'; + return $r; + } + + function jsonEscape($v) { + if (function_exists('json_encode')) { + return preg_replace('/^"(.*)"$/', '\\1', str_replace("\/","/",json_encode($v))); + } + $from = array("\\", "\r", "\t", "\n", '"', "\b", "\f"); + $to = array('\\\\', '\r', '\t', '\n', '\"', '\b', '\f'); + return str_replace($from, $to, $v); + } + + function getSerializedIndex($index, $raw = 0) { + $r = ''; + $nl = "\n"; + foreach ($index as $s => $ps) { + $r .= $r ? ',' . $nl . $nl : ''; + $r .= ' ' . $this->getTerm($s). ' : {'; + $first_p = 1; + foreach ($ps as $p => $os) { + $r .= $first_p ? $nl : ',' . $nl; + $r .= ' ' . $this->getTerm($p). ' : ['; + $first_o = 1; + if (!is_array($os)) {/* single literal o */ + $os = array(array('value' => $os, 'type' => 'literal')); + } + foreach ($os as $o) { + $r .= $first_o ? $nl : ',' . $nl; + $r .= ' ' . $this->getTerm($o, 'o'); + $first_o = 0; + } + $first_p = 0; + $r .= $nl . ' ]'; + } + $r .= $nl . ' }'; + } + $r .= $r ? ' ' : ''; + return '{' . $nl . $r . $nl . '}'; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_RDFSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_RDFSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,53 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_RDFSerializer extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + foreach ($this->ns as $k => $v) { + $this->nsp[$v] = $k; + } + } + + /* */ + + function xgetPName($v) {/* moved to merged getPName in ARC2_CLass */ + if (preg_match('/^([a-z0-9\_\-]+)\:([a-z\_][a-z0-9\_\-]*)$/i', $v, $m) && isset($this->ns[$m[1]])) { + $this->used_ns = !in_array($this->ns[$m[1]], $this->used_ns) ? array_merge($this->used_ns, array($this->ns[$m[1]])) : $this->used_ns; + return $v; + } + if (preg_match('/^(.*[\/\#])([a-z\_][a-z0-9\-\_]*)$/i', $v, $m)) { + return $this->getPrefix($m[1]) . ':' . $m[2]; + } + return 0; + } + + /* */ + + function getSerializedTriples($triples, $raw = 0) { + $index = ARC2::getSimpleIndex($triples, 0); + return $this->getSerializedIndex($index, $raw); + } + + function getSerializedIndex($index, $raw = 0) { + return ''; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_RDFXMLSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_RDFXMLSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,198 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('RDFSerializer'); + +class ARC2_RDFXMLSerializer extends ARC2_RDFSerializer { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->content_header = 'application/rdf+xml'; + $this->pp_containers = $this->v('serializer_prettyprint_containers', 0, $this->a); + $this->default_ns = $this->v('serializer_default_ns', '', $this->a); + $this->type_nodes = $this->v('serializer_type_nodes', 0, $this->a); + } + + /* */ + + function getTerm($v, $type) { + if (!is_array($v)) {/* uri or bnode */ + if (preg_match('/^\_\:(.*)$/', $v, $m)) { + return ' rdf:nodeID="' . $m[1] . '"'; + } + if ($type == 's') { + return ' rdf:about="' . htmlspecialchars($v) . '"'; + } + if ($type == 'p') { + $pn = $this->getPName($v); + return $pn ? $pn : 0; + } + if ($type == 'o') { + $v = $this->expandPName($v); + if (!preg_match('/^[a-z0-9]{2,}\:[^\s]+$/is', $v)) return $this->getTerm(array('value' => $v, 'type' => 'literal'), $type); + return ' rdf:resource="' . htmlspecialchars($v) . '"'; + } + if ($type == 'datatype') { + $v = $this->expandPName($v); + return ' rdf:datatype="' . htmlspecialchars($v) . '"'; + } + if ($type == 'lang') { + return ' xml:lang="' . htmlspecialchars($v) . '"'; + } + } + if ($this->v('type', '', $v) != 'literal') { + return $this->getTerm($v['value'], 'o'); + } + /* literal */ + $dt = isset($v['datatype']) ? $v['datatype'] : ''; + $lang = isset($v['lang']) ? $v['lang'] : ''; + if ($dt == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral') { + return ' rdf:parseType="Literal">' . $v['value']; + } + elseif ($dt) { + return $this->getTerm($dt, 'datatype') . '>' . htmlspecialchars($v['value']); + } + elseif ($lang) { + return $this->getTerm($lang, 'lang') . '>' . htmlspecialchars($v['value']); + } + return '>' . htmlspecialchars($this->v('value', '', $v)); + } + + function getPName($v, $connector = ':') { + if ($this->default_ns && (strpos($v, $this->default_ns) === 0)) { + $pname = substr($v, strlen($this->default_ns)); + if (!preg_match('/\//', $pname)) return $pname; + } + return parent::getPName($v, $connector); + } + + function getHead() { + $r = ''; + $nl = "\n"; + $r .= ''; + $r .= $nl . 'used_ns as $v) { + $r .= $first_ns ? ' ' : $nl . ' '; + foreach ($this->ns as $prefix => $ns) { + if ($ns != $v) continue; + $r .= 'xmlns:' . $prefix . '="' .$v. '"'; + break; + } + $first_ns = 0; + } + if ($this->default_ns) { + $r .= $first_ns ? ' ' : $nl . ' '; + $r .= 'xmlns="' . $this->default_ns . '"'; + } + $r .= '>'; + return $r; + } + + function getFooter() { + $r = ''; + $nl = "\n"; + $r .= $nl . $nl . ''; + return $r; + } + + function getSerializedIndex($index, $raw = 0) { + $r = ''; + $nl = "\n"; + foreach ($index as $raw_s => $ps) { + $r .= $r ? $nl . $nl : ''; + $s = $this->getTerm($raw_s, 's'); + $tag = 'rdf:Description'; + list($tag, $ps) = $this->getNodeTag($ps); + $sub_ps = 0; + /* pretty containers */ + if ($this->pp_containers && ($ctag = $this->getContainerTag($ps))) { + $tag = 'rdf:' . $ctag; + list($ps, $sub_ps) = $this->splitContainerEntries($ps); + } + $r .= ' <' . $tag . '' .$s . '>'; + $first_p = 1; + foreach ($ps as $p => $os) { + if (!$os) continue; + $p = $this->getTerm($p, 'p'); + if ($p) { + $r .= $nl . str_pad('', 4); + $first_o = 1; + if (!is_array($os)) {/* single literal o */ + $os = array(array('value' => $os, 'type' => 'literal')); + } + foreach ($os as $o) { + $o = $this->getTerm($o, 'o'); + $r .= $first_o ? '' : $nl . ' '; + $r .= '<' . $p; + $r .= $o; + $r .= preg_match('/\>/', $o) ? '' : '/>'; + $first_o = 0; + } + $first_p = 0; + } + } + $r .= $r ? $nl . ' ' : ''; + if ($sub_ps) $r .= $nl . $nl . $this->getSerializedIndex(array($raw_s => $sub_ps), 1); + } + if ($raw) { + return $r; + } + return $this->getHead() . $nl . $nl . $r . $this->getFooter(); + } + + function getNodeTag($ps) { + if (!$this->type_nodes) return array('rdf:Description', $ps); + $rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $types = $this->v($rdf . 'type', array(), $ps); + if (!$types) return array('rdf:Description', $ps); + $type = array_shift($types); + $ps[$rdf . 'type'] = $types; + if (!is_array($type)) $type = array('value' => $type); + return array($this->getPName($type['value']), $ps); + } + + /* */ + + function getContainerTag($ps) { + $rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + if (!isset($ps[$rdf . 'type'])) return ''; + $types = $ps[$rdf . 'type']; + foreach ($types as $type) { + if (!in_array($type['value'], array($rdf . 'Bag', $rdf . 'Seq', $rdf . 'Alt'))) return ''; + return str_replace($rdf, '', $type['value']); + } + } + + function splitContainerEntries($ps) { + $rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $items = array(); + $rest = array(); + foreach ($ps as $p => $os) { + $p_short = str_replace($rdf, '', $p); + if ($p_short === 'type') continue; + if (preg_match('/^\_([0-9]+)$/', $p_short, $m)) { + $items = array_merge($items, $os); + } + else { + $rest[$p] = $os; + } + } + if ($items) return array(array($rdf . 'li' => $items), $rest); + return array($rest, 0); + } + + /* */ +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_RSS10Serializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_RSS10Serializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,30 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('RDFXMLSerializer'); + +class ARC2_RSS10Serializer extends ARC2_RDFXMLSerializer { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->content_header = 'application/rss+xml'; + $this->default_ns = 'http://purl.org/rss/1.0/'; + $this->type_nodes = true; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/serializers/ARC2_TurtleSerializer.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/serializers/ARC2_TurtleSerializer.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,121 @@ + + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('RDFSerializer'); + +class ARC2_TurtleSerializer extends ARC2_RDFSerializer { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->content_header = 'application/x-turtle'; + } + + /* */ + + function getTerm($v, $term = '', $qualifier = '') { + if (!is_array($v)) { + if (preg_match('/^\_\:/', $v)) { + return $v; + } + if (($term === 'p') && ($pn = $this->getPName($v))) { + return $pn; + } + if ( + ($term === 'o') && + in_array($qualifier, array('rdf:type', 'rdfs:domain', 'rdfs:range', 'rdfs:subClassOf')) && + ($pn = $this->getPName($v)) + ) { + return $pn; + } + if (preg_match('/^[a-z0-9]+\:[^\s]*$/is' . ($this->has_pcre_unicode ? 'u' : ''), $v)) { + return '<' .$v. '>'; + } + return $this->getTerm(array('type' => 'literal', 'value' => $v), $term, $qualifier); + } + if (!isset($v['type']) || ($v['type'] != 'literal')) { + return $this->getTerm($v['value'], $term, $qualifier); + } + /* literal */ + $quot = '"'; + if (preg_match('/\"/', $v['value'])) { + $quot = "'"; + if (preg_match('/\'/', $v['value']) || preg_match('/[\x0d\x0a]/', $v['value'])) { + $quot = '"""'; + if (preg_match('/\"\"\"/', $v['value']) || preg_match('/\"$/', $v['value']) || preg_match('/^\"/', $v['value'])) { + $quot = "'''"; + $v['value'] = preg_replace("/'$/", "' ", $v['value']); + $v['value'] = preg_replace("/^'/", " '", $v['value']); + $v['value'] = str_replace("'''", '\\\'\\\'\\\'', $v['value']); + } + } + } + if ((strlen($quot) == 1) && preg_match('/[\x0d\x0a]/', $v['value'])) { + $quot = $quot . $quot . $quot; + } + $suffix = isset($v['lang']) && $v['lang'] ? '@' . $v['lang'] : ''; + $suffix = isset($v['datatype']) && $v['datatype'] ? '^^' . $this->getTerm($v['datatype'], 'dt') : $suffix; + return $quot . $v['value'] . $quot . $suffix; + } + + function getHead() { + $r = ''; + $nl = "\n"; + foreach ($this->used_ns as $v) { + $r .= $r ? $nl : ''; + foreach ($this->ns as $prefix => $ns) { + if ($ns != $v) continue; + $r .= '@prefix ' . $prefix . ': <' .$v. '> .'; + break; + } + } + return $r; + } + + function getSerializedIndex($index, $raw = 0) { + $r = ''; + $nl = "\n"; + foreach ($index as $s => $ps) { + $r .= $r ? ' .' . $nl . $nl : ''; + $s = $this->getTerm($s, 's'); + $r .= $s; + $first_p = 1; + foreach ($ps as $p => $os) { + if (!$os) continue; + $p = $this->getTerm($p, 'p'); + $r .= $first_p ? ' ' : ' ;' . $nl . str_pad('', strlen($s) + 1); + $r .= $p; + $first_o = 1; + if (!is_array($os)) {/* single literal o */ + $os = array(array('value' => $os, 'type' => 'literal')); + } + foreach ($os as $o) { + $r .= $first_o ? ' ' : ' ,' . $nl . str_pad('', strlen($s) + strlen($p) + 2); + $o = $this->getTerm($o, 'o', $p); + $r .= $o; + $first_o = 0; + } + $first_p = 0; + } + } + $r .= $r ? ' .' : ''; + if ($raw) { + return $r; + } + return $r ? $this->getHead() . $nl . $nl . $r : ''; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/sparqlscript/ARC2_SPARQLScriptParser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/sparqlscript/ARC2_SPARQLScriptParser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,280 @@ +setDefaultPrefixes(); + $this->base = $src ? $this->calcBase($src) : ARC2::getScriptURI(); + $this->blocks = array(); + $this->r = array('base' => '', 'vars' => array(), 'prefixes' => $this->prefixes); + do { + $proceed = 0; + if ((list($r, $v) = $this->xScriptBlock($v)) && $r) { + $this->blocks[] = $r; + $proceed = 1; + } + $this->unparsed_code = trim($v); + } while ($proceed); + if (trim($this->unparsed_code) && !$this->getErrors()) { + $rest = preg_replace('/[\x0a|\x0d]/i', ' ', substr($this->unparsed_code, 0, 30)); + $msg = trim($rest) ? 'Could not properly handle "' . $rest . '"' : 'Syntax Error'; + $this->addError($msg); + } + } + + function getScriptBlocks() { + return $this->v('blocks', array()); + } + + /* */ + + function xScriptBlock($v) { + /* comment removal */ + while (preg_match('/^\s*(\#[^\xd\xa]*)(.*)$/si', $v, $m)) $v = $m[2]; + /* BaseDecl */ + if ((list($sub_r, $v) = $this->xBaseDecl($v)) && $sub_r) { + $this->base = $sub_r; + } + /* PrefixDecl */ + while ((list($r, $v) = $this->xPrefixDecl($v)) && $r) { + $this->prefixes[$r['prefix']] = $r['uri']; + } + /* EndpointDecl */ + if ((list($r, $v) = $this->xEndpointDecl($v)) && $r) { + return array($r, $v); + } + /* Return */ + if ((list($r, $v) = $this->xReturn($v)) && $r) { + return array($r, $v); + } + /* Assignment */ + if ((list($r, $v) = $this->xAssignment($v)) && $r) { + return array($r, $v); + } + /* IFBlock */ + if ((list($r, $v) = $this->xIFBlock($v)) && $r) { + return array($r, $v); + } + /* FORBlock */ + if ((list($r, $v) = $this->xFORBlock($v)) && $r) { + return array($r, $v); + } + /* String */ + if ((list($r, $v) = $this->xString($v)) && $r) { + return array($r, $v); + } + /* FunctionCall */ + if ((list($r, $v) = $this->xFunctionCall($v)) && $r) { + return array($r, ltrim($v, ';')); + } + /* Query */ + $prev_r = $this->r; + $this->r = array('base' => '', 'vars' => array(), 'prefixes' => $this->prefixes); + if ((list($r, $rest) = $this->xQuery($v)) && $r) { + $q = $rest ? trim(substr($v, 0, -strlen($rest))) : trim($v); + $v = $rest; + $r = array_merge($this->r, array( + 'type' => 'query', + 'query_type' => $r['type'], + 'query' => $q, + //'prefixes' => $this->prefixes, + 'base' => $this->base, + //'infos' => $r + )); + return array($r, $v); + } + else { + $this->r = $prev_r; + } + return array(0, $v); + } + + function xBlockSet($v) { + if (!$r = $this->x("\{", $v)) return array(0, $v); + $blocks = array(); + $sub_v = $r[1]; + while ((list($sub_r, $sub_v) = $this->xScriptBlock($sub_v)) && $sub_r) { + $blocks[] = $sub_r; + } + if (!$sub_r = $this->x("\}", $sub_v)) return array(0, $v); + $sub_v = $sub_r[1]; + return array(array('type' => 'block_set', 'blocks' => $blocks), $sub_v); + } + + /* s2 */ + + function xEndpointDecl($v) { + if ($r = $this->x("ENDPOINT\s+", $v)) { + if ((list($r, $sub_v) = $this->xIRI_REF($r[1])) && $r) { + $r = $this->calcURI($r, $this->base); + if ($sub_r = $this->x('\.', $sub_v)) { + $sub_v = $sub_r[1]; + } + return array( + array('type' => 'endpoint_decl', 'endpoint' => $r), + $sub_v + ); + } + } + return array(0, $v); + } + + /* s3 */ + + function xAssignment($v) { + /* Var */ + list($r, $sub_v) = $this->xVar($v); + if (!$r) return array(0, $v); + $var = $r; + /* := | = */ + if (!$sub_r = $this->x("\:?\=", $sub_v)) return array(0, $v); + $sub_v = $sub_r[1]; + /* try String */ + list($r, $sub_v) = $this->xString($sub_v); + if ($r) return array(array('type' => 'assignment', 'var' => $var, 'sub_type' => 'string', 'string' => $r), ltrim($sub_v, '; ')); + /* try VarMerge */ + list($r, $sub_v) = $this->xVarMerge($sub_v); + if ($r) return array(array('type' => 'assignment', 'var' => $var, 'sub_type' => 'var_merge', 'var2' => $r[0], 'var3' => $r[1]), ltrim($sub_v, '; ')); + /* try Var */ + list($r, $sub_v) = $this->xVar($sub_v); + if ($r) return array(array('type' => 'assignment', 'var' => $var, 'sub_type' => 'var', 'var2' => $r), ltrim($sub_v, '; ')); + /* try function */ + list($r, $sub_v) = $this->xFunctionCall($sub_v); + if ($r) return array(array('type' => 'assignment', 'var' => $var, 'sub_type' => 'function_call', 'function_call' => $r), ltrim($sub_v, '; ')); + /* try Placeholder */ + list($r, $sub_v) = $this->xPlaceholder($sub_v); + if ($r) return array(array('type' => 'assignment', 'var' => $var, 'sub_type' => 'placeholder', 'placeholder' => $r), ltrim($sub_v, '; ')); + /* try query */ + $prev_r = $this->r; + $this->r = array('base' => '', 'vars' => array(), 'prefixes' => $this->prefixes); + list($r, $rest) = $this->xQuery($sub_v); + if (!$r) { + $this->r = $prev_r; + return array(0, $v); + } + else { + $q = $rest ? trim(substr($sub_v, 0, -strlen($rest))) : trim($sub_v); + return array( + array( + 'type' => 'assignment', + 'var' => $var, + 'sub_type' => 'query', + 'query' => array_merge($this->r, array( + 'type' => 'query', + 'query_type' => $r['type'], + 'query' => $q, + 'base' => $this->base, + )), + ), + ltrim($rest, '; ') + ); + } + } + + function xReturn($v) { + if ($r = $this->x("return\s+", $v)) { + /* fake assignment which accepts same right-hand values */ + $sub_v = '$__return_value__ := ' . $r[1]; + if ((list($r, $sub_v) = $this->xAssignment($sub_v)) && $r) { + $r['type'] = 'return'; + return array($r, $sub_v); + } + } + return array(0, $v); + } + + /* s4 'IF' BrackettedExpression '{' Script '}' ( 'ELSE' '{' Script '}')? */ + + function xIFBlock($v) { + if ($r = $this->x("IF\s*", $v)) { + if ((list($sub_r, $sub_v) = $this->xBrackettedExpression($r[1])) && $sub_r) { + $cond = $sub_r; + if ((list($sub_r, $sub_v) = $this->xBlockSet($sub_v)) && $sub_r) { + $blocks = $sub_r['blocks']; + /* else */ + $else_blocks = array(); + $rest = $sub_v; + if ($sub_r = $this->x("ELSE\s*", $sub_v)) { + if ((list($sub_r, $sub_v) = $this->xBlockSet($sub_r[1])) && $sub_r) { + $else_blocks = $sub_r['blocks']; + } + else { + $sub_v = $rest; + } + } + return array( + array( + 'type' => 'ifblock', + 'condition' => $cond, + 'blocks' => $blocks, + 'else_blocks' => $else_blocks, + ), + $sub_v + ); + } + } + } + return array(0, $v); + } + + /* s5 'FOR' '(' Var 'IN' Var ')' '{' Script '}' */ + + function xFORBlock($v) { + if ($r = $this->x("FOR\s*\(\s*[\$\?]([^\s]+)\s+IN\s+[\$\?]([^\s]+)\s*\)", $v)) {/* @@todo split into sub-patterns? */ + $iterator = $r[1]; + $set_var = $r[2]; + $sub_v = $r[3]; + if ((list($sub_r, $sub_v) = $this->xBlockSet($sub_v)) && $sub_r) { + return array( + array( + 'type' => 'forblock', + 'set' => $set_var, + 'iterator' => $iterator, + 'blocks' => $sub_r['blocks'] + ), + $sub_v + ); + } + } + return array(0, $v); + } + + /* s6 Var '+' Var */ + + function xVarMerge($v) { + if ((list($sub_r, $sub_v) = $this->xVar($v)) && $sub_r) { + $var1 = $sub_r; + if ($sub_r = $this->x("\+", $sub_v)) { + $sub_v = $sub_r[1]; + if ((list($sub_r, $sub_v) = $this->xVar($sub_v)) && $sub_r) { + return array( + array($var1, $sub_r), + $sub_v + ); + } + } + } + return array(0, $v); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/sparqlscript/ARC2_SPARQLScriptProcessor.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/sparqlscript/ARC2_SPARQLScriptProcessor.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,592 @@ + + * @license http://arc.semsol.org/license + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_SPARQLScriptProcessor extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->max_operations = $this->v('sparqlscript_max_operations', 0, $this->a); + $this->max_queries = $this->v('sparqlscript_max_queries', 0, $this->a); + $this->return = 0; + $this->script_hash = ''; + $this->env = array( + 'endpoint' => '', + 'vars' => array(), + 'output' => '', + 'operation_count' => 0, + 'query_count' => 0, + 'query_log' => array() + ); + } + + function reset() { + $this->__init(); + } + + /* */ + + function processScript($s) { + $this->script_hash = abs(crc32($s)); + $parser = $this->getParser(); + $parser->parse($s); + $blocks = $parser->getScriptBlocks(); + if ($parser->getErrors()) return 0; + foreach ($blocks as $block) { + $this->processBlock($block); + if ($this->return) return 0; + if ($this->getErrors()) return 0; + } + } + + function getResult() { + if ($this->return) { + return $this->getVarValue('__return_value__'); + } + else { + return $this->env['output']; + } + } + + /* */ + + function getParser() { + ARC2::inc('SPARQLScriptParser'); + return new ARC2_SPARQLScriptParser($this->a, $this); + } + + /* */ + + function setVar($name, $val, $type = 'literal', $meta = '') { + /* types: literal, var, rows, bool, doc, http_response, undefined, ? */ + $this->env['vars'][$name] = array( + 'value_type' => $type, + 'value' => $val, + 'meta' => $meta ? $meta : array() + ); + } + + function getVar($name) { + return isset($this->env['vars'][$name]) ? $this->env['vars'][$name] : ''; + } + + function getVarValue($name) { + return ($v = $this->getVar($name)) ? (isset($v['value']) ? $v['value'] : $v ) : ''; + } + + /* */ + + function replacePlaceholders($val, $context = '', $return_string = 1, $loop = 0) { + do { + $old_val = $val; + if (preg_match_all('/(\{(?:[^{}]+|(?R))*\})/', $val, $m)) { + foreach ($m[1] as $match) { + if (strpos($val, '$' . $match) === false) {/* just some container brackets, recurse */ + $val = str_replace($match, '{' . $this->replacePlaceholders(substr($match, 1, -1), $context, $return_string, $loop + 1) . '}', $val); + } + else { + $ph = substr($match, 1, -1); + $sub_val = $this->getPlaceholderValue($ph); + if (is_array($sub_val)) { + $sub_val = $this->getArraySerialization($sub_val, $context); + } + $val = str_replace('${' . $ph . '}', $sub_val, $val); + } + } + } + } while (($old_val != $val) && ($loop < 10)); + return $val; + } + + function getPlaceholderValue($ph) { + /* simple vars */ + if (isset($this->env['vars'][$ph])) { + return $this->v('value', $this->env['vars'][$ph], $this->env['vars'][$ph]); + } + /* GET/POST */ + if (preg_match('/^(GET|POST)\.([^\.]+)(.*)$/', $ph, $m)) { + $vals = strtoupper($m[1]) == 'GET' ? $_GET : $POST; + $r = isset($vals[$m[2]]) ? $vals[$m[2]] : ''; + return $m[3] ? $this->getPropertyValue(array('value' => $r, 'value_type' => '?'), ltrim($m[3], '.')) : $r; + } + /* NOW */ + if (preg_match('/^NOW(.*)$/', $ph, $m)) { + $rest = $m[1]; + /* may have sub-phs */ + $rest = $this->replacePlaceholders($rest); + $r_struct = array( + 'y' => date('Y'), + 'mo' => date('m'), + 'd' => date('d'), + 'h' => date('H'), + 'mi' => date('i'), + 's' => date('s') + ); + if (preg_match('/(\+|\-)\s*([0-9]+)(y|mo|d|h|mi|s)[a-z]*(.*)/is', trim($rest), $m2)) { + eval('$r_struct[$m2[3]] ' . $m2[1] . '= (int)' . $m2[2] . ';'); + $rest = $m2[4]; + } + $uts = mktime($r_struct['h'], $r_struct['mi'], $r_struct['s'], $r_struct['mo'], $r_struct['d'], $r_struct['y']); + $uts -= date('Z', $uts); /* timezone offset */ + $r = date('Y-m-d\TH:i:s\Z', $uts); + if (preg_match('/^\.(.+)$/', $rest, $m)) { + return $this->getPropertyValue(array('value' => $r), $m[1]); + } + return $r; + } + /* property */ + if (preg_match('/^([^\.]+)\.(.+)$/', $ph, $m)) { + list($var, $path) = array($m[1], $m[2]); + if (isset($this->env['vars'][$var])) { + return $this->getPropertyValue($this->env['vars'][$var], $path); + } + } + return ''; + } + + function getPropertyValue($obj, $path) { + $val = isset($obj['value']) ? $obj['value'] : $obj; + $path = $this->replacePlaceholders($path, 'property_value', 0); + /* reserved */ + if ($path == 'size') { + if ($obj['value_type'] == 'rows') return count($val); + if ($obj['value_type'] == 'literal') return strlen($val); + } + if (preg_match('/^replace\([\'\"](\/.*\/[a-z]*)[\'\"],\s*[\'\"](.*)[\'\"]\)$/is', $path, $m)) { + return @preg_replace($m[1], str_replace('$', '\\', $m[2]), $val); + } + if (preg_match('/^match\([\'\"](\/.*\/[a-z]*)[\'\"]\)$/is', $path, $m)) { + return @preg_match($m[1], $val, $m) ? $m : ''; + } + if (preg_match('/^urlencode\([\'\"]?(get|post|.*)[\'\"]?\)$/is', $path, $m)) { + return (strtolower($m[1]) == 'post') ? rawurlencode($val) : urlencode($val); + } + if (preg_match('/^toDataURI\([^\)]*\)$/is', $path, $m)) { + return 'data:text/plain;charset=utf-8,' . rawurlencode($val); + } + if (preg_match('/^fromDataURI\([^\)]*\)$/is', $path, $m)) { + return rawurldecode(str_replace('data:text/plain;charset=utf-8,', '', $val)); + } + if (preg_match('/^toPrettyDate\([^\)]*\)$/is', $path, $m)) { + $uts = strtotime(preg_replace('/(T|\+00\:00)/', ' ', $val)); + return date('D j M H:i', $uts); + } + if (preg_match('/^render\(([^\)]*)\)$/is', $path, $m)) { + $src_format = trim($m[1], '"\''); + return $this->render($val, $src_format); + } + /* struct */ + if (is_array($val)) { + if (isset($val[$path])) return $val[$path]; + $exp_path = $this->expandPName($path); + if (isset($val[$exp_path])) return $val[$exp_path]; + if (preg_match('/^([^\.]+)\.(.+)$/', $path, $m)) { + list($var, $path) = array($m[1], $m[2]); + if (isset($val[$var])) { + return $this->getPropertyValue(array('value' => $val[$var]), $path); + } + /* qname */ + $exp_var = $this->expandPName($var); + if (isset($val[$exp_var])) { + return $this->getPropertyValue(array('value' => $val[$exp_var]), $path); + } + return ''; + } + } + /* meta */ + if (preg_match('/^\_/', $path) && isset($obj['meta']) && isset($obj['meta'][substr($path, 1)])) { + return $obj['meta'][substr($path, 1)]; + } + return ''; + } + + function render($val, $src_format = '') { + if ($src_format) { + $mthd = 'render' . $this->camelCase($src_format); + if (method_exists($this, $mthd)) { + return $this->$mthd($val); + } + else { + return 'No rendering method found for "' . $src_format. '"'; + } + } + /* try RDF */ + return $this->getArraySerialization($val); + } + + function renderObjects($os) { + $r = ''; + foreach ($os as $o) { + $r .= $r ? ', ' : ''; + $r .= $o['value']; + } + return $r; + } + + /* */ + + function getArraySerialization($v, $context) { + $v_type = ARC2::getStructType($v);/* string|array|triples|index */ + $pf = ARC2::getPreferredFormat(); + /* string */ + if ($v_type == 'string') return $v; + /* simple array (e.g. from SELECT) */ + if ($v_type == 'array') { + return join(', ', $v); + $m = method_exists($this, 'toLegacy' . $pf) ? 'toLegacy' . $pf : 'toLegacyXML'; + } + /* rdf */ + if (($v_type == 'triples') || ($v_type == 'index')) { + $m = method_exists($this, 'to' . $pf) ? 'to' . $pf : ($context == 'query' ? 'toNTriples' : 'toRDFXML'); + } + /* else */ + return $this->$m($v); + } + + /* */ + + function processBlock($block) { + if ($this->max_operations && ($this->env['operation_count'] >= $this->max_operations)) return $this->addError('Number of ' . $this->max_operations . ' allowed operations exceeded.'); + if ($this->return) return 0; + $this->env['operation_count']++; + $type = $block['type']; + $m = 'process' . $this->camelCase($type) . 'Block'; + if (method_exists($this, $m)) { + return $this->$m($block); + } + return $this->addError('Unsupported block type "' . $type . '"'); + } + + /* */ + + function processEndpointDeclBlock($block) { + $this->env['endpoint'] = $block['endpoint']; + return $this->env; + } + + /* */ + + function processQueryBlock($block) { + if ($this->max_queries && ($this->env['query_count'] >= $this->max_queries)) return $this->addError('Number of ' . $this->max_queries . ' allowed queries exceeded.'); + $this->env['query_count']++; + $ep_uri = $this->replacePlaceholders($this->env['endpoint'], 'endpoint'); + /* q */ + $prologue = 'BASE <' . $block['base']. '>'; + $q = $this->replacePlaceholders($block['query'], 'query'); + /* prefixes */ + $ns = isset($this->a['ns']) ? array_merge($this->a['ns'], $block['prefixes']) : $block['prefixes']; + $q = $prologue . "\n" . $this->completeQuery($q, $ns); + $this->env['query_log'][] = '(' . $ep_uri . ') ' . $q; + if ($store = $this->getStore($ep_uri)) { + $sub_r = $this->v('is_remote', '', $store) ? $store->query($q, '', $ep_uri) : $store->query($q); + /* ignore socket errors */ + if (($errs = $this->getErrors()) && preg_match('/socket/', $errs[0])) { + $this->warnings[] = $errs[0]; + $this->errors = array(); + $sub_r = array(); + } + return $sub_r; + } + else { + return $this->addError("no store (" . $ep_uri . ")"); + } + } + + function getStore($ep_uri) { + /* local store */ + if ((!$ep_uri || $ep_uri == ARC2::getScriptURI()) && ($this->v('sparqlscript_default_endpoint', '', $this->a) == 'local')) { + if (!isset($this->local_store)) $this->local_store = ARC2::getStore($this->a);/* @@todo error checking */ + return $this->local_store; + } + elseif ($ep_uri) { + ARC2::inc('RemoteStore'); + $conf = array_merge($this->a, array('remote_store_endpoint' => $ep_uri, 'reader_timeout' => 10)); + return new ARC2_RemoteStore($conf, $this); + } + return 0; + } + + /* */ + + function processAssignmentBlock($block) { + $sub_type = $block['sub_type']; + $m = 'process' . $this->camelCase($sub_type) . 'AssignmentBlock'; + if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"'); + return $this->$m($block); + } + + function processQueryAssignmentBlock($block) { + $qr = $this->processQueryBlock($block['query']); + if ($this->getErrors() || !isset($qr['query_type'])) return 0; + $qt = $qr['query_type']; + $vts = array('ask' => 'bool', 'select' => 'rows', 'desribe' => 'doc', 'construct' => 'doc'); + $r = array( + 'value_type' => isset($vts[$qt]) ? $vts[$qt] : $qt . ' result', + 'value' => ($qt == 'select') ? $this->v('rows', array(), $qr['result']) : $qr['result'], + ); + $this->env['vars'][$block['var']['value']] = $r; + } + + function processStringAssignmentBlock($block) { + $r = array('value_type' => 'literal', 'value' => $this->replacePlaceholders($block['string']['value'])); + $this->env['vars'][$block['var']['value']] = $r; + } + + function processVarAssignmentBlock($block) { + if (isset($this->env['vars'][$block['var2']['value']])) { + $this->env['vars'][$block['var']['value']] = $this->env['vars'][$block['var2']['value']]; + } + else { + $this->env['vars'][$block['var']['value']] = array('value_type' => 'undefined', 'value' => ''); + } + } + + function processPlaceholderAssignmentBlock($block) { + $ph_val = $this->getPlaceholderValue($block['placeholder']['value']); + $this->env['vars'][$block['var']['value']] = array('value_type' => 'undefined', 'value' => $ph_val); + } + + function processVarMergeAssignmentBlock($block) { + $val1 = isset($this->env['vars'][$block['var2']['value']]) ? $this->env['vars'][$block['var2']['value']] : array('value_type' => 'undefined', 'value' => ''); + $val2 = isset($this->env['vars'][$block['var3']['value']]) ? $this->env['vars'][$block['var3']['value']] : array('value_type' => 'undefined', 'value' => ''); + if (is_array($val1) && is_array($val2)) { + $this->env['vars'][$block['var']['value']] = array('value_type' => $val2['value_type'], 'value' => array_merge($val1['value'], $val2['value'])); + } + elseif (is_numeric($val1) && is_numeric($val2)) { + $this->env['vars'][$block['var']['value']] = $val1 + $val2; + } + } + + function processFunctionCallAssignmentBlock($block) { + $sub_r = $this->processFunctionCallBlock($block['function_call']); + if ($this->getErrors()) return 0; + $this->env['vars'][$block['var']['value']] = $sub_r; + } + + /* */ + + function processReturnBlock($block) { + $sub_type = $block['sub_type']; + $m = 'process' . $this->camelCase($sub_type) . 'AssignmentBlock'; + if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"'); + $sub_r = $this->$m($block); + $this->return = 1; + return $sub_r; + } + + /* */ + + function processIfblockBlock($block) { + if ($this->testCondition($block['condition'])) { + $blocks = $block['blocks']; + } + else { + $blocks = $block['else_blocks']; + } + foreach ($blocks as $block) { + $sub_r = $this->processBlock($block); + if ($this->getErrors()) return 0; + } + } + + function testCondition($cond) { + $m = 'test' . $this->camelCase($cond['type']) . 'Condition'; + if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"'); + return $this->$m($cond); + } + + function testVarCondition($cond) { + $r = 0; + $vn = $cond['value']; + if (isset($this->env['vars'][$vn])) $r = $this->env['vars'][$vn]['value']; + $op = $this->v('operator', '', $cond); + if ($op == '!') $r = !$r; + return $r ? true : false; + } + + function testPlaceholderCondition($cond) { + $val = $this->getPlaceholderValue($cond['value']); + $r = $val ? true : false; + $op = $this->v('operator', '', $cond); + if ($op == '!') $r = !$r; + return $r; + } + + function testExpressionCondition($cond) { + $m = 'test' . $this->camelCase($cond['sub_type']) . 'ExpressionCondition'; + if (!method_exists($this, $m)) return $this->addError('Unknown method "' . $m . '"'); + return $this->$m($cond); + } + + function testRelationalExpressionCondition($cond) { + $op = $cond['operator']; + if ($op == '=') $op = '=='; + $val1 = $this->getPatternValue($cond['patterns'][0]); + $val2 = $this->getPatternValue($cond['patterns'][1]); + eval('$result = ($val1 ' . $op . ' $val2) ? 1 : 0;'); + return $result; + } + + function testAndExpressionCondition($cond) { + foreach ($cond['patterns'] as $pattern) { + if (!$this->testCondition($pattern)) return false; + } + return true; + } + + function getPatternValue($pattern) { + $m = 'get' . $this->camelCase($pattern['type']) . 'PatternValue'; + if (!method_exists($this, $m)) return ''; + return $this->$m($pattern); + } + + function getLiteralPatternValue($pattern) { + return $pattern['value']; + } + + function getPlaceholderPatternValue($pattern) { + return $this->getPlaceholderValue($pattern['value']); + } + + /* */ + + function processForblockBlock($block) { + $set = $this->v($block['set'], array('value' => array()), $this->env['vars']); + $entries = isset($set['value']) ? $set['value'] : $set; + $iterator = $block['iterator']; + $blocks = $block['blocks']; + if (!is_array($entries)) return 0; + $rc = count($entries); + foreach ($entries as $i => $entry) { + $val_type = $this->v('value_type', 'set', $set) . ' entry'; + $this->env['vars'][$iterator] = array( + 'value' => $entry, + 'value_type' => $val_type, + 'meta' => array( + 'pos' => $i, + 'odd_even' => ($i % 2) ? 'even' : 'odd' + ) + ); + foreach ($blocks as $block) { + $this->processBlock($block); + if ($this->getErrors()) return 0; + } + } + } + + /* */ + + function processLiteralBlock($block) { + $this->env['output'] .= $this->replacePlaceholders($block['value'], 'output'); + } + + /* */ + + function processFunctionCallBlock($block) { + $uri = $this->replacePlaceholders($block['uri'], 'function_call'); + /* built-ins */ + if (strpos($uri, $this->a['ns']['sps']) === 0) { + return $this->processBuiltinFunctionCallBlock($block); + } + /* remote functions */ + } + + function processBuiltinFunctionCallBlock($block) { + $fnc_uri = $this->replacePlaceholders($block['uri'], 'function_call'); + $fnc_name = substr($fnc_uri, strlen($this->a['ns']['sps'])); + if (preg_match('/^(get|post)$/i', $fnc_name, $m)) { + return $this->processHTTPCall($block, strtoupper($m[1])); + } + if ($fnc_name == 'eval') { + return $this->processEvalCall($block); + } + } + + function processEvalCall($block) { + if (!$block['args']) return 0; + $arg = $block['args'][0]; + $script = ''; + if ($arg['type'] == 'placeholder') $script = $this->getPlaceholderValue($arg['value']); + if ($arg['type'] == 'literal') $script = $arg['value']; + if ($arg['type'] == 'var') $script = $this->getVarValue($arg['value']); + //echo "\n" . $script . $arg['type']; + $this->processScript($script); + } + + function processHTTPCall($block, $mthd = 'GET') { + ARC2::inc('Reader'); + $reader = new ARC2_Reader($this->a, $this); + $url = $this->replacePlaceholders($block['args'][0]['value'], 'function_call'); + if ($mthd != 'GET') { + $reader->setHTTPMethod($mthd); + $reader->setCustomHeaders("Content-Type: application/x-www-form-urlencoded"); + } + $to = $this->v('remote_call_timeout', 0, $this->a); + $reader->activate($url, '', 0, $to); + $format = $reader->getFormat(); + $resp = ''; + while ($d = $reader->readStream()) { + $resp .= $d; + } + $reader->closeStream(); + unset($this->reader); + return array('value_type' => 'http_response', 'value' => $resp); + } + + /* */ + + function extractVars($pattern, $input = '') { + $vars = array(); + /* replace PHs, track ()s */ + $regex = $pattern; + $vars = array(); + if (preg_match_all('/([\?\$]\{([^\}]+)\}|\([^\)]+\))/', $regex, $m)) { + $matches = $m[1]; + $pre_vars = $m[2]; + foreach ($matches as $i => $match) { + $vars[] = $pre_vars[$i]; + if ($pre_vars[$i]) {/* placeholder */ + $regex = str_replace($match, '(.+)', $regex); + } + else {/* parentheses, but may contain placeholders */ + $sub_regex = $match; + while (preg_match('/([\?\$]\{([^\}]+)\})/', $sub_regex, $m)) { + $sub_regex = str_replace($m[1], '(.+)', $sub_regex); + $vars[] = $m[2]; + } + $regex = str_replace($match, $sub_regex, $regex); + } + } + /* eval regex */ + if (@preg_match('/' . $regex . '/is', $input, $m)) { + $vals = $m; + } + else { + return 0; + } + for ($i = 0; $i < count($vars); $i++) { + if ($vars[$i]) { + $this->setVar($vars[$i], isset($vals[$i + 1]) ? $vals[$i + 1] : ''); + } + } + return 1; + } + /* no placeholders */ + return ($pattern == $input) ? 1 : 0; + } + + /* */ + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_MemStore.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_MemStore.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,194 @@ + + * @license http://arc.semsol.org/license + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_MemStore extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + $this->is_mem = 1; + } + + function __init() { + parent::__init(); + $this->data = array(); + } + + /* */ + + function isSetUp() { + return 1; + } + + function setUp() {} + + /* */ + + function reset() { + $this->data = array(); + } + + function drop() { + $this->reset(); + } + + /* */ + + function insert($doc, $g = 'http://localhost/') { + $index = $this->v($g, array(), $this->data); + $this->data[$g] = ARC2::getMergedIndex($index, $this->toIndex($doc)); + } + + /* */ + /* */ + + + function delete($doc, $g = 'http://localhost/') { + $index = $this->v($g, array(), $this->data); + $this->data[$g] = ARC2::getCleanedIndex($index, $this->toIndex($doc)); + } + + function replace($doc, $g, $doc_2) { + return array($this->delete($doc, $g), $this->insert($doc_2, $g)); + } + + /* */ + + function query($q, $result_format = '', $src = '', $keep_bnode_ids = 0, $log_query = 0) { + if ($log_query) $this->logQuery($q); + ARC2::inc('SPARQLPlusParser'); + $p = new ARC2_SPARQLPlusParser($this->a, $this); + $p->parse($q, $src); + $infos = $p->getQueryInfos(); + $t1 = ARC2::mtime(); + if (!$errs = $p->getErrors()) { + $qt = $infos['query']['type']; + $r = array('query_type' => $qt, 'result' => $this->runQuery($q, $qt)); + } + else { + $r = array('result' => ''); + } + $t2 = ARC2::mtime(); + $r['query_time'] = $t2 - $t1; + /* query result */ + if ($result_format == 'raw') { + return $r['result']; + } + if ($result_format == 'rows') { + return $this->v('rows', array(), $r['result']); + } + if ($result_format == 'row') { + return $r['result']['rows'] ? $r['result']['rows'][0] : array(); + } + return $r; + } + + function runQuery($q, $qt = '') { + /* ep */ + $ep = $this->v('remote_store_endpoint', 0, $this->a); + if (!$ep) return false; + /* prefixes */ + $ns = isset($this->a['ns']) ? $this->a['ns'] : array(); + $added_prefixes = array(); + $prologue = ''; + foreach ($ns as $k => $v) { + $k = rtrim($k, ':'); + if (in_array($k, $added_prefixes)) continue; + if (preg_match('/(^|\s)' . $k . ':/s', $q) && !preg_match('/PREFIX\s+' . $k . '\:/is', $q)) { + $prologue .= "\n" . 'PREFIX ' . $k . ': <' . $v . '>'; + } + $added_prefixes[] = $k; + } + $q = $prologue . "\n" . $q; + /* http verb */ + $mthd = in_array($qt, array('load', 'insert', 'delete')) ? 'POST' : 'GET'; + /* reader */ + ARC2::inc('Reader'); + $reader = new ARC2_Reader($this->a, $this); + $reader->setAcceptHeader('Accept: application/sparql-results+xml; q=0.9, application/rdf+xml; q=0.9, */*; q=0.1'); + if ($mthd == 'GET') { + $url = $ep; + $url .= strpos($ep, '?') ? '&' : '?'; + $url .= 'query=' . urlencode($q); + if ($k = $this->v('store_read_key', '', $this->a)) $url .= '&key=' . urlencode($k); + } + else { + $url = $ep; + $reader->setHTTPMethod($mthd); + $reader->setCustomHeaders("Content-Type: application/x-www-form-urlencoded"); + $suffix = ($k = $this->v('store_write_key', '', $this->a)) ? '&key=' . rawurlencode($k) : ''; + $reader->setMessageBody('query=' . rawurlencode($q) . $suffix); + } + $to = $this->v('remote_store_timeout', 0, $this->a); + $reader->activate($url, '', 0, $to); + $format = $reader->getFormat(); + $resp = ''; + while ($d = $reader->readStream()) { + $resp .= $d; + } + $reader->closeStream(); + $ers = $reader->getErrors(); + unset($this->reader); + if ($ers) return array('errors' => $ers); + $mappings = array('rdfxml' => 'RDFXML', 'sparqlxml' => 'SPARQLXMLResult', 'turtle' => 'Turtle'); + if (!$format || !isset($mappings[$format])) { + return $resp; + //return $this->addError('No parser available for "' . $format . '" SPARQL result'); + } + /* format parser */ + $suffix = $mappings[$format] . 'Parser'; + ARC2::inc($suffix); + $cls = 'ARC2_' . $suffix; + $parser = new $cls($this->a, $this); + $parser->parse($ep, $resp); + /* ask|load|insert|delete */ + if (in_array($qt, array('ask', 'load', 'insert', 'delete'))) { + $bid = $parser->getBooleanInsertedDeleted(); + switch ($qt) { + case 'ask': return $bid['boolean']; + default: return $bid; + } + } + /* select */ + if (($qt == 'select') && !method_exists($parser, 'getRows')) return $resp; + if ($qt == 'select') return array('rows' => $parser->getRows(), 'variables' => $parser->getVariables()); + /* any other */ + return $parser->getSimpleIndex(0); + } + + /* */ + + function optimizeTables() {} + + /* */ + + function getResourceLabel($res, $unnamed_label = 'An unnamed resource') { + if (!isset($this->resource_labels)) $this->resource_labels = array(); + if (isset($this->resource_labels[$res])) return $this->resource_labels[$res]; + if (!preg_match('/^[a-z0-9\_]+\:[^\s]+$/si', $res)) return $res;/* literal */ + $r = ''; + if (preg_match('/^\_\:/', $res)) { + return $unnamed_label; + } + $row = $this->query('SELECT ?o WHERE { <' . $res . '> ?p ?o . FILTER(REGEX(str(?p), "(label|name)$", "i"))}', 'row'); + if ($row) { + $r = $row['o']; + } + else { + $r = preg_replace("/^(.*[\/\#])([^\/\#]+)$/", '\\2', str_replace('#self', '', $res)); + $r = str_replace('_', ' ', $r); + $r = preg_replace('/([a-z])([A-Z])/e', '"\\1 " . strtolower("\\2")', $r); + } + $this->resource_labels[$res] = $r; + return $r; + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_RemoteStore.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_RemoteStore.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,204 @@ + + * @license http://arc.semsol.org/license + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_RemoteStore extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + $this->is_remote = 1; + } + + function __init() { + parent::__init(); + } + + /* */ + + function isSetUp() { + return 1; + } + + function setUp() {} + + function killDBProcesses() {} + + /* */ + + function reset() {} + + function drop() {} + + function insert($doc, $g, $keep_bnode_ids = 0) { + return $this->query('INSERT INTO <' . $g . '> { ' . $this->toNTriples($doc, '', 1) . ' }'); + } + + function delete($doc, $g) { + if (!$doc) { + return $this->query('DELETE FROM <' . $g . '>'); + } + else { + return $this->query('DELETE FROM <' . $g . '> { ' . $this->toNTriples($doc, '', 1) . ' }'); + } + } + + function replace($doc, $g, $doc_2) { + return array($this->delete($doc, $g), $this->insert($doc_2, $g)); + } + + /* */ + + function query($q, $result_format = '', $src = '', $keep_bnode_ids = 0, $log_query = 0) { + if ($log_query) $this->logQuery($q); + ARC2::inc('SPARQLPlusParser'); + $p = new ARC2_SPARQLPlusParser($this->a, $this); + $p->parse($q, $src); + $infos = $p->getQueryInfos(); + $t1 = ARC2::mtime(); + if (!$errs = $p->getErrors()) { + $qt = $infos['query']['type']; + $r = array('query_type' => $qt, 'result' => $this->runQuery($q, $qt, $infos)); + } + else { + $r = array('result' => ''); + } + $t2 = ARC2::mtime(); + $r['query_time'] = $t2 - $t1; + /* query result */ + if ($result_format == 'raw') { + return $r['result']; + } + if ($result_format == 'rows') { + return $this->v('rows', array(), $r['result']); + } + if ($result_format == 'row') { + if (!isset($r['result']['rows'])) return array(); + return $r['result']['rows'] ? $r['result']['rows'][0] : array(); + } + return $r; + } + + function runQuery($q, $qt = '', $infos = '') { + /* ep */ + $ep = $this->v('remote_store_endpoint', 0, $this->a); + if (!$ep) return false; + /* prefixes */ + $q = $this->completeQuery($q); + /* custom handling */ + $mthd = 'run' . $this->camelCase($qt) . 'Query'; + if (method_exists($this, $mthd)) { + return $this->$mthd($q, $infos); + } + /* http verb */ + $mthd = in_array($qt, array('load', 'insert', 'delete')) ? 'POST' : 'GET'; + /* reader */ + ARC2::inc('Reader'); + $reader = new ARC2_Reader($this->a, $this); + $reader->setAcceptHeader('Accept: application/sparql-results+xml; q=0.9, application/rdf+xml; q=0.9, */*; q=0.1'); + if ($mthd == 'GET') { + $url = $ep; + $url .= strpos($ep, '?') ? '&' : '?'; + $url .= 'query=' . urlencode($q); + if ($k = $this->v('store_read_key', '', $this->a)) $url .= '&key=' . urlencode($k); + } + else { + $url = $ep; + $reader->setHTTPMethod($mthd); + $reader->setCustomHeaders("Content-Type: application/x-www-form-urlencoded"); + $suffix = ($k = $this->v('store_write_key', '', $this->a)) ? '&key=' . rawurlencode($k) : ''; + $reader->setMessageBody('query=' . rawurlencode($q) . $suffix); + } + $to = $this->v('remote_store_timeout', 0, $this->a); + $reader->activate($url, '', 0, $to); + $format = $reader->getFormat(); + $resp = ''; + while ($d = $reader->readStream()) { + $resp .= $this->toUTF8($d); + } + $reader->closeStream(); + $ers = $reader->getErrors(); + $this->a['reader_auth_infos'] = $reader->getAuthInfos(); + unset($this->reader); + if ($ers) return array('errors' => $ers); + $mappings = array('rdfxml' => 'RDFXML', 'sparqlxml' => 'SPARQLXMLResult', 'turtle' => 'Turtle'); + if (!$format || !isset($mappings[$format])) { + return $resp; + //return $this->addError('No parser available for "' . $format . '" SPARQL result'); + } + /* format parser */ + $suffix = $mappings[$format] . 'Parser'; + ARC2::inc($suffix); + $cls = 'ARC2_' . $suffix; + $parser = new $cls($this->a, $this); + $parser->parse($ep, $resp); + /* ask|load|insert|delete */ + if (in_array($qt, array('ask', 'load', 'insert', 'delete'))) { + $bid = $parser->getBooleanInsertedDeleted(); + if ($qt == 'ask') { + $r = $bid['boolean']; + } + else { + $r = $bid; + } + } + /* select */ + elseif (($qt == 'select') && !method_exists($parser, 'getRows')) { + $r = $resp; + } + elseif ($qt == 'select') { + $r = array('rows' => $parser->getRows(), 'variables' => $parser->getVariables()); + } + /* any other */ + else { + $r = $parser->getSimpleIndex(0); + } + unset($parser); + return $r; + } + + /* */ + + function optimizeTables() {} + + /* */ + + function getResourceLabel($res, $unnamed_label = 'An unnamed resource') { + if (!isset($this->resource_labels)) $this->resource_labels = array(); + if (isset($this->resource_labels[$res])) return $this->resource_labels[$res]; + if (!preg_match('/^[a-z0-9\_]+\:[^\s]+$/si', $res)) return $res;/* literal */ + $r = ''; + if (preg_match('/^\_\:/', $res)) { + return $unnamed_label; + } + $row = $this->query('SELECT ?o WHERE { <' . $res . '> ?p ?o . FILTER(REGEX(str(?p), "(label|name)$", "i"))}', 'row'); + if ($row) { + $r = $row['o']; + } + else { + $r = preg_replace("/^(.*[\/\#])([^\/\#]+)$/", '\\2', str_replace('#self', '', $res)); + $r = str_replace('_', ' ', $r); + $r = preg_replace('/([a-z])([A-Z])/e', '"\\1 " . strtolower("\\2")', $r); + } + $this->resource_labels[$res] = $r; + return $r; + } + + function getDomains($p) { + $r = array(); + foreach($this->query('SELECT DISTINCT ?type WHERE {?s <' . $p . '> ?o ; a ?type . }', 'rows') as $row) { + $r[] = $row['type']; + } + return $r; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_Store.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_Store.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,711 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 +*/ + +ARC2::inc('Class'); + +class ARC2_Store extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* db_con */ + parent::__init(); + $this->table_lock = 0; + $this->triggers = $this->v('store_triggers', array(), $this->a); + $this->queue_queries = $this->v('store_queue_queries', 0, $this->a); + $this->is_win = (strtolower(substr(PHP_OS, 0, 3)) == 'win') ? true : false; + $this->max_split_tables = $this->v('store_max_split_tables', 10, $this->a); + $this->split_predicates = $this->v('store_split_predicates', array(), $this->a); + } + + /* */ + + function getName() { + return $this->v('store_name', 'arc', $this->a); + } + + function getTablePrefix() { + if (!isset($this->tbl_prefix)) { + $r = $this->v('db_table_prefix', '', $this->a); + $r .= $r ? '_' : ''; + $r .= $this->getName() . '_'; + $this->tbl_prefix = $r; + } + return $this->tbl_prefix;; + } + + /* */ + + function createDBCon() { + foreach (array('db_host' => 'localhost', 'db_user' => '', 'db_pwd' => '', 'db_name' => '') as $k => $v) { + $this->a[$k] = $this->v($k, $v, $this->a); + } + if (!$db_con = mysql_connect($this->a['db_host'], $this->a['db_user'], $this->a['db_pwd'])) { + return $this->addError(mysql_error()); + } + $this->a['db_con'] = $db_con; + if (!mysql_select_db($this->a['db_name'], $db_con)) { + $fixed = 0; + /* try to create it */ + if ($this->a['db_name']) { + $this->queryDB(" + CREATE DATABASE IF NOT EXISTS " . $this->a['db_name'] . " + DEFAULT CHARACTER SET utf8 + DEFAULT COLLATE utf8_general_ci + ", $db_con, 1 + ); + if (mysql_select_db($this->a['db_name'], $db_con)) { + $this->queryDB("SET NAMES 'utf8'", $db_con); + $fixed = 1; + } + } + if (!$fixed) { + return $this->addError(mysql_error($db_con)); + } + } + if (preg_match('/^utf8/', $this->getCollation())) { + $this->queryDB("SET NAMES 'utf8'", $db_con); + } + // This is RDF, we may need many JOINs... + $this->queryDB("SET SESSION SQL_BIG_SELECTS=1", $db_con); + return true; + } + + function getDBCon($force = 0) { + if ($force || !isset($this->a['db_con'])) { + if (!$this->createDBCon()) { + return false; + } + } + if (!$force && !@mysql_thread_id($this->a['db_con'])) return $this->getDBCon(1); + return $this->a['db_con']; + } + + function closeDBCon() { + if ($this->v('db_con', false, $this->a)) { + @mysql_close($this->a['db_con']); + } + unset($this->a['db_con']); + } + + function getDBVersion() { + if (!$this->v('db_version')) { + $this->db_version = preg_match("/^([0-9]+)\.([0-9]+)\.([0-9]+)/", mysql_get_server_info($this->getDBCon()), $m) ? sprintf("%02d-%02d-%02d", $m[1], $m[2], $m[3]) : '00-00-00'; + } + return $this->db_version; + } + + /* */ + + function getCollation() { + $rs = $this->queryDB('SHOW TABLE STATUS LIKE "' . $this->getTablePrefix(). 'setting"', $this->getDBCon()); + return ($rs && ($row = mysql_fetch_array($rs)) && isset($row['Collation'])) ? $row['Collation'] : ''; + } + + function getColumnType() { + if (!$this->v('column_type')) { + $tbl = $this->getTablePrefix() . 'g2t'; + $rs = $this->queryDB('SHOW COLUMNS FROM ' . $tbl . ' LIKE "t"', $this->getDBCon()); + $row = $rs ? mysql_fetch_array($rs) : array('Type' => 'mediumint'); + $this->column_type = preg_match('/mediumint/', $row['Type']) ? 'mediumint' : 'int'; + } + return $this->column_type; + } + + /* */ + + function hasHashColumn($tbl) { + $var_name = 'has_hash_column_' . $tbl; + if (!isset($this->$var_name)) { + $tbl = $this->getTablePrefix() . $tbl; + $rs = $this->queryDB('SHOW COLUMNS FROM ' . $tbl . ' LIKE "val_hash"', $this->getDBCon()); + $this->$var_name = ($rs && mysql_fetch_array($rs)); + } + return $this->$var_name; + } + + /* */ + + function hasFulltextIndex() { + if (!isset($this->has_fulltext_index)) { + $this->has_fulltext_index = 0; + $tbl = $this->getTablePrefix() . 'o2val'; + $rs = $this->queryDB('SHOW INDEX FROM ' . $tbl, $this->getDBCon()); + while ($row = mysql_fetch_array($rs)) { + if ($row['Column_name'] != 'val') continue; + if ($row['Index_type'] != 'FULLTEXT') continue; + $this->has_fulltext_index = 1; + break; + } + } + return $this->has_fulltext_index; + } + + function enableFulltextSearch() { + if ($this->hasFulltextIndex()) return 1; + $tbl = $this->getTablePrefix() . 'o2val'; + $this->queryDB('CREATE FULLTEXT INDEX vft ON ' . $tbl . '(val(128))', $this->getDBCon(), 1); + } + + function disableFulltextSearch() { + if (!$this->hasFulltextIndex()) return 1; + $tbl = $this->getTablePrefix() . 'o2val'; + $this->queryDB('DROP INDEX vft ON ' . $tbl, $this->getDBCon()); + } + + /* */ + + function countDBProcesses() { + return ($rs = $this->queryDB('SHOW PROCESSLIST', $this->getDBCon())) ? mysql_num_rows($rs) : 0; + } + + function killDBProcesses($needle = '', $runtime = 30) { + $dbcon = $this->getDBCon(); + /* make sure needle is sql */ + if (preg_match('/\?.+ WHERE/i', $needle, $m)) { + $needle = $this->query($needle, 'sql'); + } + $rs = $this->queryDB('SHOW FULL PROCESSLIST', $dbcon); + $ref_tbl = $this->getTablePrefix() . 'triple'; + while ($row = mysql_fetch_array($rs)) { + if ($row['Time'] < $runtime) continue; + if (!preg_match('/^\s*(INSERT|SELECT) /s', $row['Info'])) continue; /* only basic queries */ + if (!strpos($row['Info'], $ref_tbl . ' ')) continue; /* only from this store */ + $kill = 0; + if ($needle && (strpos($row['Info'], $needle) !== false)) $kill = 1; + if (!$needle) $kill = 1; + if (!$kill) continue; + $this->queryDB('KILL ' . $row['Id'], $dbcon); + } + } + + /* */ + + function getTables() { + return array('triple', 'g2t', 'id2val', 's2val', 'o2val', 'setting'); + } + + /* */ + + function isSetUp() { + if (($con = $this->getDBCon())) { + $tbl = $this->getTablePrefix() . 'setting'; + return $this->queryDB("SELECT 1 FROM " . $tbl . " LIMIT 0", $con) ? 1 : 0; + } + } + + function setUp($force = 0) { + if (($force || !$this->isSetUp()) && ($con = $this->getDBCon())) { + if ($this->getDBVersion() < '04-00-04') { + /* UPDATE + JOINs */ + return $this->addError('MySQL version not supported. ARC requires version 4.0.4 or higher.'); + } + ARC2::inc('StoreTableManager'); + $mgr = new ARC2_StoreTableManager($this->a, $this); + $mgr->createTables(); + } + } + + function extendColumns() { + ARC2::inc('StoreTableManager'); + $mgr = new ARC2_StoreTableManager($this->a, $this); + $mgr->extendColumns(); + $this->column_type = 'int'; + } + + function splitTables() { + ARC2::inc('StoreTableManager'); + $mgr = new ARC2_StoreTableManager($this->a, $this); + $mgr->splitTables(); + } + + /* */ + + function hasSetting($k) { + $tbl = $this->getTablePrefix() . 'setting'; + $sql = "SELECT val FROM " . $tbl . " WHERE k = '" .md5($k). "'"; + $rs = $this->queryDB($sql, $this->getDBCon()); + return ($rs && ($row = mysql_fetch_array($rs))) ? 1 : 0; + } + + function getSetting($k, $default = 0) { + $tbl = $this->getTablePrefix() . 'setting'; + $sql = "SELECT val FROM " . $tbl . " WHERE k = '" .md5($k). "'"; + $rs = $this->queryDB($sql, $this->getDBCon()); + if ($rs && ($row = mysql_fetch_array($rs))) { + return unserialize($row['val']); + } + return $default; + } + + function setSetting($k, $v) { + $con = $this->getDBCon(); + $tbl = $this->getTablePrefix() . 'setting'; + if ($this->hasSetting($k)) { + $sql = "UPDATE " .$tbl . " SET val = '" . mysql_real_escape_string(serialize($v), $con) . "' WHERE k = '" . md5($k) . "'"; + } + else { + $sql = "INSERT INTO " . $tbl . " (k, val) VALUES ('" . md5($k) . "', '" . mysql_real_escape_string(serialize($v), $con) . "')"; + } + return $this->queryDB($sql, $con); + } + + function removeSetting($k) { + $tbl = $this->getTablePrefix() . 'setting'; + return $this->queryDB("DELETE FROM " . $tbl . " WHERE k = '" . md5($k) . "'", $this->getDBCon()); + } + + function getQueueTicket() { + if (!$this->queue_queries) return 1; + $t = 'ticket_' . substr(md5(uniqid(rand())), 0, 10); + $con = $this->getDBCon(); + /* lock */ + $rs = $this->queryDB('LOCK TABLES ' . $this->getTablePrefix() . 'setting WRITE', $con); + /* queue */ + $queue = $this->getSetting('query_queue', array()); + $queue[] = $t; + $this->setSetting('query_queue', $queue); + $this->queryDB('UNLOCK TABLES', $con); + /* loop */ + $lc = 0; + $queue = $this->getSetting('query_queue', array()); + while ($queue && ($queue[0] != $t) && ($lc < 30)) { + if ($this->is_win) { + sleep(1); + $lc++; + } + else { + usleep(100000); + $lc += 0.1; + } + $queue = $this->getSetting('query_queue', array()); + } + return ($lc < 30) ? $t : 0; + } + + function removeQueueTicket($t) { + if (!$this->queue_queries) return 1; + $con = $this->getDBCon(); + /* lock */ + $this->queryDB('LOCK TABLES ' . $this->getTablePrefix() . 'setting WRITE', $con); + /* queue */ + $vals = $this->getSetting('query_queue', array()); + $pos = array_search($t, $vals); + $queue = ($pos < (count($vals) - 1)) ? array_slice($vals, $pos + 1) : array(); + $this->setSetting('query_queue', $queue); + $this->queryDB('UNLOCK TABLES', $con); + } + + /* */ + + function reset($keep_settings = 0) { + $con = $this->getDBCon(); + $tbls = $this->getTables(); + $prefix = $this->getTablePrefix(); + /* remove split tables */ + $ps = $this->getSetting('split_predicates', array()); + foreach ($ps as $p) { + $tbl = 'triple_' . abs(crc32($p)); + $this->queryDB('DROP TABLE ' . $prefix . $tbl, $con); + } + $this->removeSetting('split_predicates'); + /* truncate tables */ + foreach ($tbls as $tbl) { + if ($keep_settings && ($tbl == 'setting')) { + continue; + } + $this->queryDB('TRUNCATE ' . $prefix . $tbl, $con); + } + } + + function drop() { + $con = $this->getDBCon(); + $tbls = $this->getTables(); + $prefix = $this->getTablePrefix(); + foreach ($tbls as $tbl) { + $this->queryDB('DROP TABLE ' . $prefix . $tbl, $con); + } + } + + function insert($doc, $g, $keep_bnode_ids = 0) { + $doc = is_array($doc) ? $this->toTurtle($doc) : $doc; + $infos = array('query' => array('url' => $g, 'target_graph' => $g)); + ARC2::inc('StoreLoadQueryHandler'); + $h = new ARC2_StoreLoadQueryHandler($this->a, $this); + $r = $h->runQuery($infos, $doc, $keep_bnode_ids); + $this->processTriggers('insert', $infos); + return $r; + } + + function delete($doc, $g) { + if (!$doc) { + $infos = array('query' => array('target_graphs' => array($g))); + ARC2::inc('StoreDeleteQueryHandler'); + $h = new ARC2_StoreDeleteQueryHandler($this->a, $this); + $r = $h->runQuery($infos); + $this->processTriggers('delete', $infos); + return $r; + } + } + + function replace($doc, $g, $doc_2) { + return array($this->delete($doc, $g), $this->insert($doc_2, $g)); + } + + function dump() { + ARC2::inc('StoreDumper'); + $d = new ARC2_StoreDumper($this->a, $this); + $d->dumpSPOG(); + } + + function createBackup($path, $q = '') { + ARC2::inc('StoreDumper'); + $d = new ARC2_StoreDumper($this->a, $this); + $d->saveSPOG($path, $q); + } + + function renameTo($name) { + $con = $this->getDBCon(); + $tbls = $this->getTables(); + $old_prefix = $this->getTablePrefix(); + $new_prefix = $this->v('db_table_prefix', '', $this->a); + $new_prefix .= $new_prefix ? '_' : ''; + $new_prefix .= $name . '_'; + foreach ($tbls as $tbl) { + $rs = $this->queryDB('RENAME TABLE ' . $old_prefix . $tbl .' TO ' . $new_prefix . $tbl, $con); + $err = mysql_error($con); + if ($err) { + return $this->addError($err); + } + } + $this->a['store_name'] = $name; + unset($this->tbl_prefix); + } + + function replicateTo($name) { + $conf = array_merge($this->a, array('store_name' => $name)); + $new_store = ARC2::getStore($conf); + $new_store->setUp(); + $new_store->reset(); + $con = $this->getDBCon(); + $tbls = $this->getTables(); + $old_prefix = $this->getTablePrefix(); + $new_prefix = $new_store->getTablePrefix(); + foreach ($tbls as $tbl) { + $rs = $this->queryDB('INSERT IGNORE INTO ' . $new_prefix . $tbl .' SELECT * FROM ' . $old_prefix . $tbl, $con); + $err = mysql_error($con); + if ($err) { + return $this->addError($err); + } + } + return $new_store->query('SELECT COUNT(*) AS t_count WHERE { ?s ?p ?o}', 'row'); + } + + /* */ + + function query($q, $result_format = '', $src = '', $keep_bnode_ids = 0, $log_query = 0) { + if ($log_query) $this->logQuery($q); + $con = $this->getDBCon(); + if (preg_match('/^dump/i', $q)) { + $infos = array('query' => array('type' => 'dump')); + } + else { + ARC2::inc('SPARQLPlusParser'); + $p = new ARC2_SPARQLPlusParser($this->a, $this); + $p->parse($q, $src); + $infos = $p->getQueryInfos(); + } + if ($result_format == 'infos') return $infos; + $infos['result_format'] = $result_format; + if (!isset($p) || !$p->getErrors()) { + $qt = $infos['query']['type']; + if (!in_array($qt, array('select', 'ask', 'describe', 'construct', 'load', 'insert', 'delete', 'dump'))) { + return $this->addError('Unsupported query type "'.$qt.'"'); + } + $t1 = ARC2::mtime(); + $r = array('query_type' => $qt, 'result' => $this->runQuery($infos, $qt, $keep_bnode_ids, $q)); + $t2 = ARC2::mtime(); + $r['query_time'] = $t2 - $t1; + /* query result */ + if ($result_format == 'raw') { + return $r['result']; + } + if ($result_format == 'rows') { + return $r['result']['rows'] ? $r['result']['rows'] : array(); + } + if ($result_format == 'row') { + return $r['result']['rows'] ? $r['result']['rows'][0] : array(); + } + return $r; + } + return 0; + } + + function runQuery($infos, $type, $keep_bnode_ids = 0, $q = '') { + ARC2::inc('Store' . ucfirst($type) . 'QueryHandler'); + $cls = 'ARC2_Store' . ucfirst($type) . 'QueryHandler'; + $h = new $cls($this->a, $this); + $ticket = 1; + $r = array(); + if ($q && ($type == 'select')) $ticket = $this->getQueueTicket($q); + if ($ticket) { + if ($type == 'load') {/* the LoadQH supports raw data as 2nd parameter */ + $r = $h->runQuery($infos, '', $keep_bnode_ids); + } + else { + $r = $h->runQuery($infos, $keep_bnode_ids); + } + } + if ($q && ($type == 'select')) $this->removeQueueTicket($ticket); + $trigger_r = $this->processTriggers($type, $infos); + return $r; + } + + function processTriggers($type, $infos) { + $r = array(); + $trigger_defs = $this->triggers; + $this->triggers = array(); + $triggers = $this->v($type, array(), $trigger_defs); + if ($triggers) { + $r['trigger_results'] = array(); + $triggers = is_array($triggers) ? $triggers : array($triggers); + $trigger_inc_path = $this->v('store_triggers_path', '', $this->a); + foreach ($triggers as $trigger) { + $trigger .= !preg_match('/Trigger$/', $trigger) ? 'Trigger' : ''; + if (ARC2::inc(ucfirst($trigger), $trigger_inc_path)) { + $cls = 'ARC2_' . ucfirst($trigger); + $config = array_merge($this->a, array('query_infos' => $infos)); + $trigger_obj = new $cls($config, $this); + if (method_exists($trigger_obj, 'go')) { + $r['trigger_results'][] = $trigger_obj->go(); + } + } + } + } + $this->triggers = $trigger_defs; + return $r; + } + + /* */ + + function getValueHash($val) { + return abs(crc32($val)); + } + + function getTermID($val, $term = '') { + /* mem cache */ + if (!isset($this->term_id_cache) || (count(array_keys($this->term_id_cache)) > 100)) { + $this->term_id_cache = array(); + } + if (!isset($this->term_id_cache[$term])) { + $this->term_id_cache[$term] = array(); + } + $tbl = preg_match('/^(s|o)$/', $term) ? $term . '2val' : 'id2val'; + /* cached? */ + if ((strlen($val) < 100) && isset($this->term_id_cache[$term][$val])) { + return $this->term_id_cache[$term][$val]; + } + $con = $this->getDBCon(); + $r = 0; + /* via hash */ + if (preg_match('/^(s2val|o2val)$/', $tbl) && $this->hasHashColumn($tbl)) { + $sql = "SELECT id, val FROM " . $this->getTablePrefix() . $tbl . " WHERE val_hash = '" . $this->getValueHash($val) . "'"; + if (($rs = $this->queryDB($sql, $con)) && mysql_num_rows($rs)) { + while ($row = mysql_fetch_array($rs)) { + if ($row['val'] == $val) { + $r = $row['id']; + break; + } + } + } + } + /* exact match */ + else { + $sql = "SELECT id FROM " . $this->getTablePrefix() . $tbl . " WHERE val = BINARY '" . mysql_real_escape_string($val, $con) . "' LIMIT 1"; + if (($rs = $this->queryDB($sql, $con)) && mysql_num_rows($rs) && ($row = mysql_fetch_array($rs))) { + $r = $row['id']; + } + } + if ($r && (strlen($val) < 100)) { + $this->term_id_cache[$term][$val] = $r; + } + return $r; + } + + function getIDValue($id, $term = '') { + $tbl = preg_match('/^(s|o)$/', $term) ? $term . '2val' : 'id2val'; + $con = $this->getDBCon(); + $sql = "SELECT val FROM " . $this->getTablePrefix() . $tbl . " WHERE id = " . mysql_real_escape_string($id, $con) . " LIMIT 1"; + if (($rs = $this->queryDB($sql, $con)) && mysql_num_rows($rs) && ($row = mysql_fetch_array($rs))) { + return $row['val']; + } + return 0; + } + + /* */ + + function getLock($t_out = 10, $t_out_init = '') { + if (!$t_out_init) $t_out_init = $t_out; + $con = $this->getDBCon(); + $l_name = $this->a['db_name'] . '.' . $this->getTablePrefix() . '.write_lock'; + $rs = $this->queryDB('SELECT IS_FREE_LOCK("' . $l_name. '") AS success', $con); + if ($rs) { + $row = mysql_fetch_array($rs); + if (!$row['success']) { + if ($t_out) { + sleep(1); + return $this->getLock($t_out - 1, $t_out_init); + } + } + else { + $rs = $this->queryDB('SELECT GET_LOCK("' . $l_name. '", ' . $t_out_init. ') AS success', $con); + if ($rs) { + $row = mysql_fetch_array($rs); + return $row['success']; + } + } + } + return 0; + } + + function releaseLock() { + $con = $this->getDBCon(); + return $this->queryDB('DO RELEASE_LOCK("' . $this->a['db_name'] . '.' . $this->getTablePrefix() . '.write_lock")', $con); + } + + /* */ + + function processTables($level = 2, $operation = 'optimize') {/* 1: triple + g2t, 2: triple + *2val, 3: all tables */ + $con = $this->getDBCon(); + $pre = $this->getTablePrefix(); + $tbls = $this->getTables(); + $sql = ''; + foreach ($tbls as $tbl) { + if (($level < 3) && preg_match('/(backup|setting)$/', $tbl)) continue; + if (($level < 2) && preg_match('/(val)$/', $tbl)) continue; + $sql .= $sql ? ', ' : strtoupper($operation) . ' TABLE '; + $sql .= $pre . $tbl; + } + $this->queryDB($sql, $con); + $err = mysql_error($con); + if ($err) { + $this->addError($err . ' in ' . $sql); + } + } + + function optimizeTables($level = 2) { + if ($this->v('ignore_optimization')) return 1; + return $this->processTables($level, 'optimize'); + } + + function checkTables($level = 2) { + return $this->processTables($level, 'check'); + } + + function repairTables($level = 2) { + return $this->processTables($level, 'repair'); + } + + /* */ + + function changeNamespaceURI($old_uri, $new_uri) { + ARC2::inc('StoreHelper'); + $c = new ARC2_StoreHelper($this->a, $this); + return $c->changeNamespaceURI($old_uri, $new_uri); + } + + /* */ + + function getResourceLabel($res, $unnamed_label = 'An unnamed resource') { + if (!isset($this->resource_labels)) $this->resource_labels = array(); + if (isset($this->resource_labels[$res])) return $this->resource_labels[$res]; + if (!preg_match('/^[a-z0-9\_]+\:[^\s]+$/si', $res)) return $res;/* literal */ + $ps = $this->getLabelProps(); + if ($this->getSetting('store_label_properties', '-') != md5(serialize($ps))) { + $this->inferLabelProps($ps); + } + //$sub_q .= $sub_q ? ' || ' : ''; + //$sub_q .= 'REGEX(str(?p), "(last_name|name|fn|title|label)$", "i")'; + $q = 'SELECT ?label WHERE { <' . $res . '> ?p ?label . ?p a } LIMIT 3'; + $r = ''; + $rows = $this->query($q, 'rows'); + foreach ($rows as $row) { + $r = strlen($row['label']) > strlen($r) ? $row['label'] : $r; + } + if (!$r && preg_match('/^\_\:/', $res)) { + return $unnamed_label; + } + $r = $r ? $r : preg_replace("/^(.*[\/\#])([^\/\#]+)$/", '\\2', str_replace('#self', '', $res)); + $r = str_replace('_', ' ', $r); + $r = preg_replace('/([a-z])([A-Z])/e', '"\\1 " . strtolower("\\2")', $r); + $this->resource_labels[$res] = $r; + return $r; + } + + function getLabelProps() { + return array_merge( + $this->v('rdf_label_properties' , array(), $this->a), + array( + 'http://www.w3.org/2000/01/rdf-schema#label', + 'http://xmlns.com/foaf/0.1/name', + 'http://purl.org/dc/elements/1.1/title', + 'http://purl.org/rss/1.0/title', + 'http://www.w3.org/2004/02/skos/core#prefLabel', + 'http://xmlns.com/foaf/0.1/nick', + ) + ); + } + + function inferLabelProps($ps) { + $this->query('DELETE FROM '); + $sub_q = ''; + foreach ($ps as $p) { + $sub_q .= ' <' . $p . '> a . '; + } + $this->query('INSERT INTO { ' . $sub_q. ' }'); + $this->setSetting('store_label_properties', md5(serialize($ps))); + } + + /* */ + + function getResourcePredicates($res) { + $r = array(); + $rows = $this->query('SELECT DISTINCT ?p WHERE { <' . $res . '> ?p ?o . }', 'rows'); + foreach ($rows as $row) { + $r[$row['p']] = array(); + } + return $r; + } + + function getDomains($p) { + $r = array(); + foreach($this->query('SELECT DISTINCT ?type WHERE {?s <' . $p . '> ?o ; a ?type . }', 'rows') as $row) { + $r[] = $row['type']; + } + return $r; + } + + function getPredicateRange($p) { + $row = $this->query('SELECT ?val WHERE {<' . $p . '> rdfs:range ?val . } LIMIT 1', 'row'); + return $row ? $row['val'] : ''; + } + + /* */ + + function logQuery($q) { + $fp = @fopen("arc_query_log.txt", "a"); + @fwrite($fp, date('Y-m-d\TH:i:s\Z', time()) . ' : ' . $q . '' . "\n\n"); + @fclose($fp); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreAskQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreAskQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,53 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('StoreSelectQueryHandler'); + +class ARC2_StoreAskQueryHandler extends ARC2_StoreSelectQueryHandler { + + function __construct($a, &$caller) {/* caller has to be a store */ + parent::__construct($a, $caller); + } + + function __init() {/* db_con */ + parent::__init(); + $this->store = $this->caller; + } + + /* */ + + function runQuery($infos) { + $infos['query']['limit'] = 1; + $this->infos = $infos; + $this->buildResultVars(); + return parent::runQuery($this->infos); + } + + /* */ + + function buildResultVars() { + $this->infos['query']['result_vars'][] = array('var' => '1', 'aggregate' => '', 'alias' => 'success'); + } + + /* */ + + function getFinalQueryResult($q_sql, $tmp_tbl) { + $con = $this->store->getDBCon(); + $rs = mysql_query('SELECT success FROM ' . $tmp_tbl, $con); + $r = ($row = mysql_fetch_array($rs)) ? $row['success'] : 0; + return $r ? true : false; + } + + /* */ + +} + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreAtomLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreAtomLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,32 @@ +caller->addT($t['s'], $t['p'], $t['o'], $t['s_type'], $t['o_type'], $t['o_datatype'], $t['o_lang']); + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreCBJSONLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreCBJSONLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,38 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('CBJSONParser'); + +class ARC2_StoreCBJSONLoader extends ARC2_CBJSONParser { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + } + + /* */ + + function done() { + $this->extractRDF(); + } + + function addT($s, $p, $o, $s_type, $o_type, $o_dt = '', $o_lang = '') { + $o = $this->toUTF8($o); + $this->caller->addT($s, $p, $o, $s_type, $o_type, $o_dt, $o_lang); + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreConstructQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreConstructQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,115 @@ +store = $this->caller; + } + + /* */ + + function runQuery($infos) { + $this->infos = $infos; + $this->buildResultVars(); + $this->infos['query']['distinct'] = 1; + $sub_r = parent::runQuery($this->infos); + $rf = $this->v('result_format', '', $infos); + if (in_array($rf, array('sql', 'structure', 'index'))) { + return $sub_r; + } + return $this->getResultIndex($sub_r); + } + + /* */ + + function buildResultVars() { + $r = array(); + foreach ($this->infos['query']['construct_triples'] as $t) { + foreach (array('s', 'p', 'o') as $term) { + if ($t[$term . '_type'] == 'var') { + if (!in_array($t[$term], $r)) { + $r[] = array('var' => $t[$term], 'aggregate' => '', 'alias' => ''); + } + } + } + } + $this->infos['query']['result_vars'] = $r; + } + + /* */ + + function getResultIndex($qr) { + $r = array(); + $added = array(); + $rows = $this->v('rows', array(), $qr); + $cts = $this->infos['query']['construct_triples']; + $bnc = 0; + foreach ($rows as $row) { + $bnc++; + foreach ($cts as $ct) { + $skip_t = 0; + $t = array(); + foreach (array('s', 'p', 'o') as $term) { + $val = $ct[$term]; + $type = $ct[$term . '_type']; + $val = ($type == 'bnode') ? $val . $bnc : $val; + if ($type == 'var') { + $skip_t = !isset($row[$val]) ? 1 : $skip_t; + $type = !$skip_t ? $row[$val . ' type'] : ''; + $val = (!$skip_t) ? $row[$val] : ''; + } + $t[$term] = $val; + $t[$term . '_type'] = $type; + if (isset($row[$ct[$term] . ' lang'])) { + $t[$term . '_lang'] = $row[$ct[$term] . ' lang']; + } + if (isset($row[$ct[$term] . ' datatype'])) { + $t[$term . '_datatype'] = $row[$ct[$term] . ' datatype']; + } + } + if (!$skip_t) { + $s = $t['s']; + $p = $t['p']; + $o = $t['o']; + if (!isset($r[$s])) { + $r[$s] = array(); + } + if (!isset($r[$s][$p])) { + $r[$s][$p] = array(); + } + $o = array('value' => $o); + foreach (array('lang', 'type', 'datatype') as $suffix) { + if (isset($t['o_' . $suffix]) && $t['o_' . $suffix]) { + $o[$suffix] = $t['o_' . $suffix]; + } + } + if (!isset($added[md5($s . ' ' . $p . ' ' . serialize($o))])) { + $r[$s][$p][] = $o; + $added[md5($s . ' ' . $p . ' ' . serialize($o))] = 1; + } + } + } + } + return $r; + } + + /* */ + +} + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreDeleteQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreDeleteQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,233 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 +*/ + +ARC2::inc('StoreQueryHandler'); + +class ARC2_StoreDeleteQueryHandler extends ARC2_StoreQueryHandler { + + function __construct($a, &$caller) {/* caller has to be a store */ + parent::__construct($a, $caller); + } + + function __init() {/* db_con */ + parent::__init(); + $this->store = $this->caller; + $this->handler_type = 'delete'; + } + + /* */ + + function runQuery($infos) { + $this->infos = $infos; + $con = $this->store->getDBCon(); + $t1 = ARC2::mtime(); + /* delete */ + $this->refs_deleted = false; + /* graph(s) only */ + if (!$this->v('construct_triples', array(), $this->infos['query'])) { + $tc = $this->deleteTargetGraphs(); + } + /* graph(s) + explicit triples */ + elseif (!$this->v('pattern', array(), $this->infos['query'])) { + $tc = $this->deleteTriples(); + } + /* graph(s) + constructed triples */ + else { + $tc = $this->deleteConstructedGraph(); + } + $t2 = ARC2::mtime(); + /* clean up */ + if ($tc && ($this->refs_deleted || (rand(1, 100) == 1))) $this->cleanTableReferences(); + if ($tc && (rand(1, 100) == 1)) $this->store->optimizeTables(); + if ($tc && (rand(1, 500) == 1)) $this->cleanValueTables(); + $t3 = ARC2::mtime(); + $index_dur = round($t3 - $t2, 4); + $dur = round($t3 - $t1, 4); + return array( + 't_count' => $tc, + 'delete_time' => $dur, + 'index_update_time' => $index_dur, + ); + } + + /* */ + + function deleteTargetGraphs() { + $tbl_prefix = $this->store->getTablePrefix(); + $r = 0; + $con = $this->store->getDBCon(); + foreach ($this->infos['query']['target_graphs'] as $g) { + if ($g_id = $this->getTermID($g, 'g')) { + $rs = mysql_query('DELETE FROM ' . $tbl_prefix . 'g2t WHERE g = ' .$g_id, $con); + $r += mysql_affected_rows($con); + } + } + $this->refs_deleted = $r ? 1 : 0; + return $r; + } + + /* */ + + function deleteTriples() { + $r = 0; + $dbv = $this->store->getDBVersion(); + $tbl_prefix = $this->store->getTablePrefix(); + $con = $this->store->getDBCon(); + /* graph restriction */ + $tgs = $this->infos['query']['target_graphs']; + $gq = ''; + foreach ($tgs as $g) { + if ($g_id = $this->getTermID($g, 'g')) { + $gq .= $gq ? ', ' . $g_id : $g_id; + } + } + $gq = $gq ? ' AND G.g IN (' . $gq . ')' : ''; + /* triples */ + foreach ($this->infos['query']['construct_triples'] as $t) { + $q = ''; + $skip = 0; + foreach (array('s', 'p', 'o') as $term) { + if (isset($t[$term . '_type']) && preg_match('/(var)/', $t[$term . '_type'])) { + //$skip = 1; + } + else { + $term_id = $this->getTermID($t[$term], $term); + $q .= ($q ? ' AND ' : '') . 'T.' . $term . '=' . $term_id; + /* explicit lang/dt restricts the matching */ + if ($term == 'o') { + $o_lang = $this->v1('o_lang', '', $t); + $o_lang_dt = $this->v1('o_datatype', $o_lang, $t); + if ($o_lang_dt) { + $q .= ($q ? ' AND ' : '') . 'T.o_lang_dt=' . $this->getTermID($o_lang_dt, 'lang_dt'); + } + } + } + } + if ($skip) { + continue; + } + if ($gq) { + $sql = ($dbv < '04-01') ? 'DELETE ' . $tbl_prefix . 'g2t' : 'DELETE G'; + $sql .= ' + FROM ' . $tbl_prefix . 'g2t G + JOIN ' . $this->getTripleTable() . ' T ON (T.t = G.t' . $gq . ') + WHERE ' . $q . ' + '; + $this->refs_deleted = 1; + } + else {/* triples only */ + $sql = ($dbv < '04-01') ? 'DELETE ' . $this->getTripleTable() : 'DELETE T'; + $sql .= ' FROM ' . $this->getTripleTable() . ' T WHERE ' . $q; + } + //$rs = mysql_query($sql, $con); + $rs = $this->queryDB($sql, $con); + if ($er = mysql_error($con)) { + $this->addError($er .' in ' . $sql); + } + $r += mysql_affected_rows($con); + } + return $r; + } + + /* */ + + function deleteConstructedGraph() { + ARC2::inc('StoreConstructQueryHandler'); + $h = new ARC2_StoreConstructQueryHandler($this->a, $this->store); + $sub_r = $h->runQuery($this->infos); + $triples = ARC2::getTriplesFromIndex($sub_r); + $tgs = $this->infos['query']['target_graphs']; + $this->infos = array('query' => array('construct_triples' => $triples, 'target_graphs' => $tgs)); + return $this->deleteTriples(); + } + + /* */ + + function cleanTableReferences() { + /* lock */ + if (!$this->store->getLock()) return $this->addError('Could not get lock in "cleanTableReferences"'); + $con = $this->store->getDBCon(); + $tbl_prefix = $this->store->getTablePrefix(); + $dbv = $this->store->getDBVersion(); + /* check for unconnected triples */ + $sql = ' + SELECT T.t FROM '. $tbl_prefix . 'triple T LEFT JOIN '. $tbl_prefix . 'g2t G ON ( G.t = T.t ) + WHERE G.t IS NULL LIMIT 1 + '; + if (($rs = mysql_query($sql, $con)) && mysql_num_rows($rs)) { + /* delete unconnected triples */ + $sql = ($dbv < '04-01') ? 'DELETE ' . $tbl_prefix . 'triple' : 'DELETE T'; + $sql .= ' + FROM ' . $tbl_prefix . 'triple T + LEFT JOIN ' . $tbl_prefix . 'g2t G ON (G.t = T.t) + WHERE G.t IS NULL + '; + mysql_query($sql, $con); + } + /* check for unconnected graph refs */ + if ((rand(1, 10) == 1)) { + $sql = ' + SELECT G.g FROM '. $tbl_prefix . 'g2t G LEFT JOIN '. $tbl_prefix . 'triple T ON ( T.t = G.t ) + WHERE T.t IS NULL LIMIT 1 + '; + if (($rs = mysql_query($sql, $con)) && mysql_num_rows($rs)) { + /* delete unconnected graph refs */ + $sql = ($dbv < '04-01') ? 'DELETE ' . $tbl_prefix . 'g2t' : 'DELETE G'; + $sql .= ' + FROM ' . $tbl_prefix . 'g2t G + LEFT JOIN ' . $tbl_prefix . 'triple T ON (T.t = G.t) + WHERE T.t IS NULL + '; + mysql_query($sql, $con); + } + } + /* release lock */ + $this->store->releaseLock(); + } + + /* */ + + function cleanValueTables() { + /* lock */ + if (!$this->store->getLock()) return $this->addError('Could not get lock in "cleanValueTables"'); + $con = $this->store->getDBCon(); + $tbl_prefix = $this->store->getTablePrefix(); + $dbv = $this->store->getDBVersion(); + /* o2val */ + $sql = ($dbv < '04-01') ? 'DELETE ' . $tbl_prefix . 'o2val' : 'DELETE V'; + $sql .= ' + FROM ' . $tbl_prefix . 'o2val V + LEFT JOIN ' . $tbl_prefix . 'triple T ON (T.o = V.id) + WHERE T.t IS NULL + '; + mysql_query($sql, $con); + /* s2val */ + $sql = ($dbv < '04-01') ? 'DELETE ' . $tbl_prefix . 's2val' : 'DELETE V'; + $sql .= ' + FROM ' . $tbl_prefix . 's2val V + LEFT JOIN ' . $tbl_prefix . 'triple T ON (T.s = V.id) + WHERE T.t IS NULL + '; + mysql_query($sql, $con); + /* id2val */ + $sql = ($dbv < '04-01') ? 'DELETE ' . $tbl_prefix . 'id2val' : 'DELETE V'; + $sql .= ' + FROM ' . $tbl_prefix . 'id2val V + LEFT JOIN ' . $tbl_prefix . 'g2t G ON (G.g = V.id) + LEFT JOIN ' . $tbl_prefix . 'triple T1 ON (T1.p = V.id) + LEFT JOIN ' . $tbl_prefix . 'triple T2 ON (T2.o_lang_dt = V.id) + WHERE G.g IS NULL AND T1.t IS NULL AND T2.t IS NULL + '; + //mysql_query($sql, $con); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreDescribeQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreDescribeQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,119 @@ +store = $this->caller; + $this->detect_labels = $this->v('detect_describe_query_labels', 0, $this->a); + } + + /* */ + + function runQuery($infos) { + $ids = $infos['query']['result_uris']; + if ($vars = $infos['query']['result_vars']) { + $sub_r = parent::runQuery($infos); + $rf = $this->v('result_format', '', $infos); + if (in_array($rf, array('sql', 'structure', 'index'))) { + return $sub_r; + } + $rows = $this->v('rows', array(), $sub_r); + foreach ($rows as $row) { + foreach ($vars as $info) { + $val = isset($row[$info['var']]) ? $row[$info['var']] : ''; + if ($val && ($row[$info['var'] . ' type'] != 'literal') && !in_array($val, $ids)) { + $ids[] = $val; + } + } + } + } + $this->r = array(); + $this->described_ids = array(); + $this->ids = $ids; + $this->added_triples = array(); + $is_sub_describe = 0; + while ($this->ids) { + $id = $this->ids[0]; + $this->described_ids[] = $id; + if ($this->detect_labels) { + $q = ' + CONSTRUCT { + <' . $id . '> ?p ?o . + ?o ?label_p ?o_label . + ?o ?o_label . + } WHERE { + <' . $id . '> ?p ?o . + OPTIONAL { + ?o ?label_p ?o_label . + FILTER REGEX(str(?label_p), "(name|label|title|summary|nick|fn)$", "i") + } + } + '; + } + else { + $q = ' + CONSTRUCT { + <' . $id . '> ?p ?o . + } WHERE { + <' . $id . '> ?p ?o . + } + '; + } + $sub_r = $this->store->query($q); + $sub_index = is_array($sub_r['result']) ? $sub_r['result'] : array(); + $this->mergeSubResults($sub_index, $is_sub_describe); + $is_sub_describe = 1; + } + return $this->r; + } + + /* */ + + function mergeSubResults($index, $is_sub_describe = 1) { + foreach ($index as $s => $ps) { + if (!isset($this->r[$s])) $this->r[$s] = array(); + foreach ($ps as $p => $os) { + if (!isset($this->r[$s][$p])) $this->r[$s][$p] = array(); + foreach ($os as $o) { + $id = md5($s . ' ' . $p . ' ' . serialize($o)); + if (!isset($this->added_triples[$id])) { + if (1 || !$is_sub_describe) { + $this->r[$s][$p][] = $o; + if (is_array($o) && ($o['type'] == 'bnode') && !in_array($o['value'], $this->ids)) $this->ids[] = $o['value']; + } + elseif (!is_array($o) || ($o['type'] != 'bnode')) { + $this->r[$s][$p][] = $o; + } + $this->added_triples[$id] = 1; + } + } + } + } + /* adjust ids */ + $ids = $this->ids; + $this->ids = array(); + foreach ($ids as $id) { + if (!in_array($id, $this->described_ids)) $this->ids[] = $id; + } + } + + /* */ + +} + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreDumpQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreDumpQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,37 @@ +store = $this->caller; + } + + /* */ + + function runQuery($infos, $keep_bnode_ids = 0) { + $this->infos = $infos; + $con = $this->store->getDBCon(); + ARC2::inc('StoreDumper'); + $d = new ARC2_StoreDumper($this->a, $this->store); + $d->dumpSPOG(); + return 1; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreDumper.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreDumper.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,222 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_StoreDumper extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->store = $this->caller; + $this->keep_time_limit = $this->v('keep_time_limit', 0, $this->a); + $this->limit = 100000; + } + + /* */ + + function dumpSPOG() { + header('Content-Type: application/sparql-results+xml'); + if ($this->v('store_use_dump_dir', 0, $this->a)) { + $path = $this->v('store_dump_dir', 'dumps', $this->a); + /* default: monthly dumps */ + $path_suffix = $this->v('store_dump_suffix', date('Y_m'), $this->a); + $path .= '/dump_' . $path_suffix . '.spog'; + if (!file_exists($path)) { + $this->saveSPOG($path); + } + readfile($path); + exit; + } + echo $this->getHeader(); + $offset = 0; + do { + $proceed = 0; + $rs = $this->getRecordset($offset); + if (!$rs) break; + while ($row = mysql_fetch_array($rs)) { + echo $this->getEntry($row); + $proceed = 1; + } + $offset += $this->limit; + } while ($proceed); + echo $this->getFooter(); + } + + /* */ + + function saveSPOG($path, $q = '') { + if ($q) return $this->saveCustomSPOG($path, $q); + if (!$fp = @fopen($path, 'w')) return $this->addError('Could not create backup file at ' . realpath($path)); + fwrite($fp, $this->getHeader()); + $offset = 0; + do { + $proceed = 0; + $rs = $this->getRecordset($offset); + if (!$rs) break; + while ($row = mysql_fetch_array($rs)) { + fwrite($fp, $this->getEntry($row)); + $proceed = 1; + } + $offset += $this->limit; + } while ($proceed); + fwrite($fp, $this->getFooter()); + @fclose($fp); + return 1; + } + + /* */ + + function saveCustomSPOG($path, $q) { + if (!$fp = @fopen($path, 'w')) return $this->addError('Could not create backup file at ' . realpath($path)); + fwrite($fp, $this->getHeader()); + $rows = $this->store->query($q, 'rows'); + foreach ($rows as $row) { + fwrite($fp, $this->getEntry($row)); + } + fwrite($fp, $this->getFooter()); + @fclose($fp); + } + + /* */ + + function getRecordset($offset) { + $prefix = $this->store->getTablePrefix(); + $con = $this->store->getDBCon(); + $sql = ' + SELECT + VS.val AS s, + T.s_type AS `s type`, + VP.val AS p, + 0 AS `p type`, + VO.val AS o, + T.o_type AS `o type`, + VLDT.val as `o lang_dt`, + VG.val as g, + 0 AS `g type` + FROM + ' . $prefix . 'triple T + JOIN ' . $prefix . 's2val VS ON (T.s = VS.id) + JOIN ' . $prefix . 'id2val VP ON (T.p = VP.id) + JOIN ' . $prefix . 'o2val VO ON (T.o = VO.id) + JOIN ' . $prefix . 'id2val VLDT ON (T.o_lang_dt = VLDT.id) + JOIN ' . $prefix . 'g2t G2T ON (T.t = G2T.t) + JOIN ' . $prefix . 'id2val VG ON (G2T.g = VG.id) + '; + if ($this->limit) $sql .= ' LIMIT ' . $this->limit; + if ($offset) $sql .= ' OFFSET ' . $offset; + $rs = mysql_unbuffered_query($sql, $con); + if (($err = mysql_error($con))) { + return $this->addError($err); + } + return $rs; + } + + /* */ + + function getHeader() { + $n = "\n"; + return '' . + '' . + $n . '' . + $n . ' ' . + $n . ' ' . + $n . ' ' . + $n . ' ' . + $n . ' ' . + $n . ' ' . + $n . ' ' . + ''; + } + + function getEntry($row) { + if (!$this->keep_time_limit) @set_time_limit($this->v('time_limit', 1200, $this->a)); + $n = "\n"; + $r = ''; + $r .= $n . ' '; + foreach (array('s', 'p', 'o', 'g') as $var) { + if (isset($row[$var])) { + $type = (string) $row[$var . ' type']; + $r .= $n . ' '; + $val = $this->toUTF8($row[$var]); + if (($type == '0') || ($type == 'uri')) { + $r .= $n . ' ' . $this->getSafeValue($val) . ''; + } + elseif (($type == '1') || ($type == 'bnode')) { + $r .= $n . ' ' . substr($val, 2) . ''; + } + else { + $lang_dt = ''; + foreach (array('o lang_dt', 'o lang', 'o datatype') as $k) { + if (($var == 'o') && isset($row[$k]) && $row[$k]) $lang_dt = $row[$k]; + } + $is_lang = preg_match('/^([a-z]+(\-[a-z0-9]+)*)$/i', $lang_dt); + list($lang, $dt) = $is_lang ? array($lang_dt, '') : array('', $lang_dt); + $lang = $lang ? ' xml:lang="' . $lang . '"' : ''; + $dt = $dt ? ' datatype="' . htmlspecialchars($dt) . '"' : ''; + $r .= $n . ' ' . $this->getSafeValue($val) . ''; + } + $r .= $n . ' '; + } + } + $r .= $n . ' '; + return $r; + } + + function getSafeValue($val) {/* mainly for fixing json_decode bugs */ + $mappings = array( + '%00' => '', + '%01' => '', + '%02' => '', + '%03' => '', + '%04' => '', + '%05' => '', + '%06' => '', + '%07' => '', + '%08' => '', + '%09' => '', + '%0B' => '', + '%0C' => '', + '%0E' => '', + '%0F' => '', + '%15' => '', + '%17' => 'ė', + '%1A' => ',', + '%1F' => '', + ); + $froms = array_keys($mappings); + $tos = array_values($mappings); + foreach ($froms as $i => $from) $froms[$i] = urldecode($from); + $val = str_replace($froms, $tos, $val); + if (strpos($val, '\n"; + } + else { + $val = htmlspecialchars($val); + } + return $val; + } + + function getFooter() { + $n = "\n"; + return '' . + $n . ' ' . + $n . '' . + $n . + ''; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreEndpoint.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreEndpoint.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1080 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Store'); + +class ARC2_StoreEndpoint extends ARC2_Store { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() { + parent::__init(); + $this->headers = array('http' => 'HTTP/1.1 200 OK', 'vary' => 'Vary: Accept'); + $this->read_key = $this->v('endpoint_read_key', '', $this->a); + $this->write_key = $this->v('endpoint_write_key', '', $this->a); + $this->timeout = $this->v('endpoint_timeout', 0, $this->a); + $this->a['store_allow_extension_functions'] = $this->v('store_allow_extension_functions', 0, $this->a); + $this->allow_sql = $this->v('endpoint_enable_sql_output', 0, $this->a); + $this->result = ''; + } + + /* */ + + function getQueryString($mthd = '') { + $r = ''; + if (!$mthd || ($mthd == 'post')) { + $r = @file_get_contents('php://input'); + } + $r = !$r ?$this->v1('QUERY_STRING', '', $_SERVER) : $r; + return $r; + } + + function p($name='', $mthd = '', $multi = '', $default = '') { + $mthd = strtolower($mthd); + if($multi){ + $qs = $this->getQueryString($mthd); + if (preg_match_all('/\&' . $name . '=([^\&]+)/', $qs, $m)){ + foreach ($m[1] as $i => $val) { + $m[1][$i] = stripslashes($val); + } + return $m[1]; + } + return $default ? $default : array(); + } + $args = array_merge($_GET, $_POST); + $r = isset($args[$name]) ? $args[$name] : $default; + return is_array($r) ? $r : stripslashes($r); + } + + /* */ + + function getFeatures() { + return $this->v1('endpoint_features', array(), $this->a); + } + + function setHeader($k, $v) { + $this->headers[$k] = $v; + } + + function sendHeaders() { + if (!isset($this->is_dump) || !$this->is_dump) { + $this->setHeader('content-length', 'Content-Length: ' . strlen($this->getResult())); + foreach ($this->headers as $k => $v) { + header($v); + } + } + } + + function getResult() { + return $this->result; + } + + /* */ + + function handleRequest($auto_setup = 0) { + if (!$this->isSetUp()) { + if ($auto_setup) { + $this->setUp(); + return $this->handleRequest(0); + } + else { + $this->setHeader('http', 'HTTP/1.1 400 Bad Request'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Missing configuration or the endpoint store was not set up yet.'; + } + } + elseif (($img = $this->p('img'))) { + $this->handleImgRequest($img); + } + elseif (($q = $this->p('query'))) { + $this->checkProcesses(); + $this->handleQueryRequest($q); + if ($this->p('show_inline')) { + $this->query_result = ' +
    + ' . ($this->p('output') != 'htmltab' ? '
    ' . htmlspecialchars($this->getResult()) . '
    ' : $this->getResult()) . ' +
    + '; + $this->handleEmptyRequest(1); + } + } + else { + $this->handleEmptyRequest(); + } + } + + function go($auto_setup = 0) { + $this->handleRequest($auto_setup); + $this->sendHeaders(); + echo $this->getResult(); + } + + /* */ + + function handleImgRequest($img) { + $this->setHeader('content-type', 'Content-type: image/gif'); + $imgs = array( + 'bg_body' => base64_decode('R0lGODlhAQBkAMQAAPf39/Hx8erq6vPz8/Ly8u/v7+np6fT09Ovr6/b29u3t7ejo6Pz8/Pv7+/39/fr6+vj4+P7+/vn5+f///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAAAAAAALAAAAAABAGQAAAUp4GIIiFIExHAkAAC9cAxJdG3TT67vTe//jKBQ6Cgaj5GkcpmcOJ/QZwgAOw=='), + ); + $this->result = isset($imgs[$img]) ? $imgs[$img] : ''; + $this->sendHeaders(); + echo $this->getResult(); + exit; + } + + /* */ + + function handleEmptyRequest($force = 0) { + /* service description */ + $formats = array( + 'rdfxml' => 'RDFXML', 'rdf+xml' => 'RDFXML', 'html' => 'HTML' + ); + if (!$force && $this->getResultFormat($formats, 'html') != 'HTML') { + $this->handleServiceDescriptionRequest(); + } + else { + $this->setHeader('content-type', 'Content-type: text/html; charset=utf-8'); + $this->result = $this->getHTMLFormDoc(); + } + } + + /* */ + + function handleServiceDescriptionRequest() { + $q = ' + PREFIX void: + CONSTRUCT { + <> void:sparqlEndpoint <> . + } + WHERE { + ?s ?p ?o . + } LIMIT 1 + '; + $this->handleQueryRequest($q); + } + + /* */ + + function checkProcesses() { + if (method_exists($this->caller, 'checkSPARQLEndpointProcesses')) { + $sub_r = $this->caller->checkSPARQLEndpointProcesses(); + } + elseif ($this->timeout) { + $this->killDBProcesses('', $this->timeout); + } + } + + /* */ + + function handleQueryRequest($q) { + if (preg_match('/^dump/i', $q)) { + $infos = array('query' => array('type' => 'dump')); + $this->is_dump = 1; + } + else { + ARC2::inc('SPARQLPlusParser'); + $p = new ARC2_SPARQLPlusParser($this->a, $this); + $p->parse($q); + $infos = $p->getQueryInfos(); + } + /* errors? */ + if ($errors = $this->getErrors()) { + $this->setHeader('http', 'HTTP/1.1 400 Bad Request'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = htmlspecialchars(join("\n", $errors)); + return true; + } + $qt = $infos['query']['type']; + /* wrong read key? */ + if ($this->read_key && ($this->p('key') != $this->read_key) && preg_match('/^(select|ask|construct|describe|dump)$/', $qt)) { + $this->setHeader('http', 'HTTP/1.1 401 Access denied'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Access denied. Missing or wrong "key" parameter.'; + return true; + } + /* wrong write key? */ + if ($this->write_key && ($this->p('key') != $this->write_key) && preg_match('/^(load|insert|delete|update)$/', $qt)) { + $this->setHeader('http', 'HTTP/1.1 401 Access denied'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Access denied. Missing or wrong "key" parameter.'; + return true; + } + /* non-allowed query type? */ + if (!in_array($qt, $this->getFeatures())) { + $this->setHeader('http', 'HTTP/1.1 401 Access denied'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Access denied for "' .$qt. '" query'; + return true; + } + /* load/insert/delete via GET */ + if (in_array($qt, array('load', 'insert', 'delete')) && isset($_GET['query'])) { + $this->setHeader('http', 'HTTP/1.1 501 Not Implemented'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Query type "' .$qt. '" not supported via GET'; + return true; + } + /* unsupported query type */ + if (!in_array($qt, array('select', 'ask', 'describe', 'construct', 'load', 'insert', 'delete', 'dump'))) { + $this->setHeader('http', 'HTTP/1.1 501 Not Implemented'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Unsupported query type "' .$qt. '"'; + return true; + } + /* adjust infos */ + $infos = $this->adjustQueryInfos($infos); + $t1 = ARC2::mtime(); + $r = array('result' => $this->runQuery($infos, $qt)); + $t2 = ARC2::mtime(); + $r['query_time'] = $t2 - $t1; + /* query errors? */ + if ($errors = $this->getErrors()) { + $this->setHeader('http', 'HTTP/1.1 400 Bad Request'); + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Error: ' . join("\n", $errors); + return true; + } + /* result */ + $m = 'get' . ucfirst($qt) . 'ResultDoc'; + if (method_exists($this, $m)) { + $this->result = $this->$m($r); + } + else { + $this->setHeader('content-type', 'Content-type: text/plain; charset=utf-8'); + $this->result = 'Result serializer not available, dumping raw data:' . "\n" . print_r($r, 1); + } + } + + /* */ + + function adjustQueryInfos($infos) { + /* limit */ + if ($max_l = $this->v('endpoint_max_limit', 0, $this->a)) { + if ($this->v('limit', $max_l + 1, $infos['query']) > $max_l) { + $infos['query']['limit'] = $max_l; + } + } + /* default-graph-uri / named-graph-uri */ + $dgs = $this->p('default-graph-uri', '', 1); + $ngs = $this->p('named-graph-uri', '', 1); + if (count(array_merge($dgs, $ngs))) { + $ds = array(); + foreach ($dgs as $g) { + $ds[] = array('graph' => $this->calcURI($g), 'named' => 0); + } + foreach ($ngs as $g) { + $ds[] = array('graph' => $this->calcURI($g), 'named' => 1); + } + $infos['query']['dataset'] = $ds; + } + /* infos result format */ + if (($this->p('format') == 'infos') || ($this->p('output') == 'infos')) { + $infos['result_format'] = 'structure'; + } + /* sql result format */ + if (($this->p('format') == 'sql') || ($this->p('output') == 'sql')) { + $infos['result_format'] = 'sql'; + } + return $infos; + } + + /* */ + + function getResultFormat($formats, $default) { + $prefs = array(); + /* arg */ + if (($v = $this->p('format')) || ($v = $this->p('output'))) { + $prefs[] = $v; + } + /* accept header */ + $vals = explode(',', $_SERVER['HTTP_ACCEPT']); + if ($vals) { + $o_vals = array(); + foreach ($vals as $val) { + if (preg_match('/(rdf\+n3|x\-turtle|rdf\+xml|sparql\-results\+xml|sparql\-results\+json|json)/', $val, $m)) { + $o_vals[$m[1]] = 1; + if (preg_match('/\;q\=([0-9\.]+)/', $val, $sub_m)) { + $o_vals[$m[1]] = 1 * $sub_m[1]; + } + } + } + arsort($o_vals); + foreach ($o_vals as $val => $prio) { + $prefs[] = $val; + } + } + /* default */ + $prefs[] = $default; + foreach ($prefs as $pref) { + if (isset($formats[$pref])) { + return $formats[$pref]; + } + } + } + + /* SELECT */ + + function getSelectResultDoc($r) { + $formats = array( + 'xml' => 'SPARQLXML', 'sparql-results+xml' => 'SPARQLXML', + 'json' => 'SPARQLJSON', 'sparql-results+json' => 'SPARQLJSON', + 'php_ser' => 'PHPSER', 'plain' => 'Plain', + 'sql' => ($this->allow_sql ? 'Plain' : 'xSQL'), + 'infos' => 'Plain', + 'htmltab' => 'HTMLTable', + 'tsv' => 'TSV', + ); + if ($f = $this->getResultFormat($formats, 'xml')) { + $m = 'get' . $f . 'SelectResultDoc'; + return method_exists($this, $m) ? $this->$m($r) : 'not implemented'; + } + return ''; + } + + function getSPARQLXMLSelectResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+xml'); + $vars = $r['result']['variables']; + $rows = $r['result']['rows']; + $dur = $r['query_time']; + $nl = "\n"; + /* doc */ + $r = '' . + '' . + $nl . '' . + ''; + /* head */ + $r .= $nl . ' '; + $r .= $nl . ' '; + if (is_array($vars)) { + foreach ($vars as $var) { + $r .= $nl . ' '; + } + } + $r .= $nl . ' '; + /* results */ + $r .= $nl . ' '; + if (is_array($rows)) { + foreach ($rows as $row) { + $r .= $nl . ' '; + foreach ($vars as $var) { + if (isset($row[$var])) { + $r .= $nl . ' '; + if ($row[$var . ' type'] == 'uri') { + $r .= $nl . ' ' .htmlspecialchars($row[$var]). ''; + } + elseif ($row[$var . ' type'] == 'bnode') { + $r .= $nl . ' ' .substr($row[$var], 2). ''; + } + else { + $dt = isset($row[$var . ' datatype']) ? ' datatype="' .htmlspecialchars($row[$var . ' datatype']). '"' : ''; + $lang = isset($row[$var . ' lang']) ? ' xml:lang="' .htmlspecialchars($row[$var . ' lang']). '"' : ''; + $r .= $nl . ' ' .htmlspecialchars($row[$var]). ''; + } + $r .= $nl . ' '; + } + } + $r .= $nl . ' '; + } + } + $r .= $nl . ' '; + /* /doc */ + $r .= $nl . ''; + return $r; + } + + function getSPARQLJSONSelectResultDoc($r) { + $con = $this->getDBCon(); + $this->setHeader('content-type', 'Content-Type: application/sparql-results+json'); + $vars = $r['result']['variables']; + $rows = $r['result']['rows']; + $dur = $r['query_time']; + $nl = "\n"; + /* doc */ + $r = '{'; + /* head */ + $r .= $nl . ' "head": {'; + $r .= $nl . ' "vars": ['; + $first_var = 1; + foreach ($vars as $var) { + $r .= $first_var ? $nl : ',' . $nl; + $r .= ' "' .$var. '"'; + $first_var = 0; + } + $r .= $nl . ' ]'; + $r .= $nl . ' },'; + /* results */ + $r .= $nl . ' "results": {'; + $r .= $nl . ' "bindings": ['; + $first_row = 1; + foreach ($rows as $row) { + $r .= $first_row ? $nl : ',' . $nl; + $r .= ' {'; + $first_var = 1; + foreach ($vars as $var) { + if (isset($row[$var])) { + $r .= $first_var ? $nl : ',' . $nl . $nl; + $r .= ' "' .$var. '": {'; + if ($row[$var . ' type'] == 'uri') { + $r .= $nl . ' "type": "uri",'; + $r .= $nl . ' "value": "' .mysql_real_escape_string($row[$var], $con). '"'; + } + elseif ($row[$var . ' type'] == 'bnode') { + $r .= $nl . ' "type": "bnode",'; + $r .= $nl . ' "value": "' . substr($row[$var], 2) . '"'; + } + else { + $dt = isset($row[$var . ' datatype']) ? ',' . $nl .' "datatype": "' .mysql_real_escape_string($row[$var . ' datatype'], $con). '"' : ''; + $lang = isset($row[$var . ' lang']) ? ',' . $nl .' "xml:lang": "' .mysql_real_escape_string($row[$var . ' lang'], $con). '"' : ''; + $type = $dt ? 'typed-literal' : 'literal'; + $r .= $nl . ' "type": "' . $type . '",'; + $r .= $nl . ' "value": "' . $this->jsonEscape($row[$var]) . '"'; + $r .= $dt . $lang; + } + $r .= $nl . ' }'; + $first_var = 0; + } + } + $r .= $nl . ' }'; + $first_row = 0; + } + $r .= $nl . ' ]'; + $r .= $nl . ' }'; + /* /doc */ + $r .= $nl . '}'; + if (($v = $this->p('jsonp')) || ($v = $this->p('callback'))) { + $r = $v . '(' . $r . ')'; + } + return $r; + } + + function getPHPSERSelectResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return serialize($r); + } + + function getPlainSelectResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return print_r($r['result'], 1); + } + + function getHTMLTableSelectResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/html; charset=utf-8'); + $vars = $r['result']['variables']; + $rows = $r['result']['rows']; + $dur = $r['query_time']; + if ($this->p('show_inline')) return '' . $this->getHTMLTableRows($rows, $vars) . '
    '; + return ' + + ' .$this->getHTMLDocHead() . ' + + + ' . $this->getHTMLTableRows($rows, $vars) . ' +
    + + + '; + } + + function getHTMLTableRows($rows, $vars) { + $r = ''; + foreach ($rows as $row) { + $hr = ''; + $rr = ''; + foreach ($vars as $var) { + $hr .= $r ? '' : '' . htmlspecialchars($var) . ''; + $rr .= '' . @htmlspecialchars($row[$var]) . ''; + } + $r .= $hr ? '' . $hr . '' : ''; + $r .= '' . $rr . ''; + } + return $r ? $r : 'No results found'; + } + + function getTSVSelectResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain; charset=utf-8'); + $vars = $r['result']['variables']; + $rows = $r['result']['rows']; + $dur = $r['query_time']; + return $this->getTSVRows($rows, $vars); + } + + function getTSVRows($rows, $vars) { + $r = ''; + $delim = "\t"; + $esc_delim = "\\t"; + foreach ($rows as $row) { + $hr = ''; + $rr = ''; + foreach ($vars as $var) { + $hr .= $r ? '' : ($hr ? $delim . $var : $var); + $val = isset($row[$var]) ? str_replace($delim, $esc_delim, $row[$var]) : ''; + $rr .= $rr ? $delim . $val : $val; + } + $r .= $hr . "\n" . $rr; + } + return $r ? $r : 'No results found'; + } + + /* ASK */ + + function getAskResultDoc($r) { + $formats = array( + 'xml' => 'SPARQLXML', 'sparql-results+xml' => 'SPARQLXML', + 'json' => 'SPARQLJSON', 'sparql-results+json' => 'SPARQLJSON', + 'plain' => 'Plain', + 'php_ser' => 'PHPSER', + 'sql' => ($this->allow_sql ? 'Plain' : 'xSQL'), + 'infos' => 'Plain', + ); + if ($f = $this->getResultFormat($formats, 'xml')) { + $m = 'get' . $f . 'AskResultDoc'; + return method_exists($this, $m) ? $this->$m($r) : 'not implemented'; + } + return ''; + } + + function getSPARQLXMLAskResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+xml'); + $r_val = $r['result'] ? 'true' : 'false'; + $dur = $r['query_time']; + $nl = "\n"; + return '' . + '' . + $nl . '' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' .$r_val. '' . + $nl . '' . + ''; + } + + function getSPARQLJSONAskResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+json'); + $r_val = $r['result'] ? 'true' : 'false'; + $dur = $r['query_time']; + $nl = "\n"; + $r = '' . + $nl . '{' . + $nl . ' "head": {' . + $nl . ' },' . + $nl . ' "boolean" : ' . $r_val . + $nl . '}' . + ''; + if (($v = $this->p('jsonp')) || ($v = $this->p('callback'))) { + $r = $v . '(' . $r . ')'; + } + return $r; + } + + function getPHPSERAskResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return serialize($r); + } + + function getPlainAskResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return $r['result'] ? 'true' : 'false'; + } + + /* CONSTRUCT */ + + function getConstructResultDoc($r) { + $formats = array( + 'rdfxml' => 'RDFXML', 'rdf+xml' => 'RDFXML', + 'json' => 'RDFJSON', 'rdf+json' => 'RDFJSON', + 'turtle' => 'Turtle', 'x-turtle' => 'Turtle', 'rdf+n3' => 'Turtle', + 'php_ser' => 'PHPSER', + 'sql' => ($this->allow_sql ? 'Plain' : 'xSQL'), + 'infos' => 'Plain', + ); + if ($f = $this->getResultFormat($formats, 'rdfxml')) { + $m = 'get' . $f . 'ConstructResultDoc'; + return method_exists($this, $m) ? $this->$m($r) : 'not implemented'; + } + return ''; + } + + function getRDFXMLConstructResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/rdf+xml'); + $index = $r['result']; + $ser = ARC2::getRDFXMLSerializer($this->a); + $dur = $r['query_time']; + return $ser->getSerializedIndex($index) . "\n" . ''; + } + + function getTurtleConstructResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/x-turtle'); + $index = $r['result']; + $ser = ARC2::getTurtleSerializer($this->a); + $dur = $r['query_time']; + return '# query time: ' . $dur . "\n" . $ser->getSerializedIndex($index); + } + + function getRDFJSONConstructResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/json'); + $index = $r['result']; + $ser = ARC2::getRDFJSONSerializer($this->a); + $dur = $r['query_time']; + $r = $ser->getSerializedIndex($index); + if (($v = $this->p('jsonp')) || ($v = $this->p('callback'))) { + $r = $v . '(' . $r . ')'; + } + return $r; + } + + function getPHPSERConstructResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return serialize($r); + } + + function getPlainConstructResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return print_r($r['result'], 1); + } + + /* DESCRIBE */ + + function getDescribeResultDoc($r) { + $formats = array( + 'rdfxml' => 'RDFXML', 'rdf+xml' => 'RDFXML', + 'json' => 'RDFJSON', 'rdf+json' => 'RDFJSON', + 'turtle' => 'Turtle', 'x-turtle' => 'Turtle', 'rdf+n3' => 'Turtle', + 'php_ser' => 'PHPSER', + 'sql' => ($this->allow_sql ? 'Plain' : 'xSQL'), + 'infos' => 'Plain' + ); + if ($f = $this->getResultFormat($formats, 'rdfxml')) { + $m = 'get' . $f . 'DescribeResultDoc'; + return method_exists($this, $m) ? $this->$m($r) : 'not implemented'; + } + return ''; + } + + function getRDFXMLDescribeResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/rdf+xml'); + $index = $r['result']; + $ser = ARC2::getRDFXMLSerializer($this->a); + $dur = $r['query_time']; + return $ser->getSerializedIndex($index) . "\n" . ''; + } + + function getTurtleDescribeResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/x-turtle'); + $index = $r['result']; + $ser = ARC2::getTurtleSerializer($this->a); + $dur = $r['query_time']; + return '# query time: ' . $dur . "\n" . $ser->getSerializedIndex($index); + } + + function getRDFJSONDescribeResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/json'); + $index = $r['result']; + $ser = ARC2::getRDFJSONSerializer($this->a); + $dur = $r['query_time']; + $r = $ser->getSerializedIndex($index); + if (($v = $this->p('jsonp')) || ($v = $this->p('callback'))) { + $r = $v . '(' . $r . ')'; + } + return $r; + } + + function getPHPSERDescribeResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return serialize($r); + } + + function getPlainDescribeResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return print_r($r['result'], 1); + } + + /* DUMP */ + + function getDumpResultDoc() { + $this->headers = array(); + return ''; + } + + /* LOAD */ + + function getLoadResultDoc($r) { + $formats = array( + 'xml' => 'SPARQLXML', 'sparql-results+xml' => 'SPARQLXML', + 'json' => 'SPARQLJSON', 'sparql-results+json' => 'SPARQLJSON', + 'plain' => 'Plain', + 'php_ser' => 'PHPSER', + 'sql' => ($this->allow_sql ? 'Plain' : 'xSQL'), + 'infos' => 'Plain', + ); + if ($f = $this->getResultFormat($formats, 'xml')) { + $m = 'get' . $f . 'LoadResultDoc'; + return method_exists($this, $m) ? $this->$m($r) : 'not implemented'; + } + return ''; + } + + function getSPARQLXMLLoadResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+xml'); + $r_val = $r['result']['t_count']; + $dur = $r['query_time']; + $nl = "\n"; + return '' . + '' . + $nl . '' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' .$r_val. '' . + $nl . '' . + ''; + } + + function getSPARQLJSONLoadResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+json'); + $r_val = $r['result']['t_count']; + $dur = $r['query_time']; + $nl = "\n"; + $r = '' . + $nl . '{' . + $nl . ' "head": {' . + $nl . ' },' . + $nl . ' "inserted" : ' . $r_val . + $nl . '}' . + ''; + if (($v = $this->p('jsonp')) || ($v = $this->p('callback'))) { + $r = $v . '(' . $r . ')'; + } + return $r; + } + + function getPHPSERLoadResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return serialize($r); + } + + function getPlainLoadResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return print_r($r['result'], 1); + } + + /* DELETE */ + + function getDeleteResultDoc($r) { + $formats = array( + 'xml' => 'SPARQLXML', 'sparql-results+xml' => 'SPARQLXML', + 'json' => 'SPARQLJSON', 'sparql-results+json' => 'SPARQLJSON', + 'plain' => 'Plain', + 'php_ser' => 'PHPSER' + ); + if ($f = $this->getResultFormat($formats, 'xml')) { + $m = 'get' . $f . 'DeleteResultDoc'; + return method_exists($this, $m) ? $this->$m($r) : 'not implemented'; + } + return ''; + } + + function getSPARQLXMLDeleteResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+xml'); + $r_val = $r['result']['t_count']; + $dur = $r['query_time']; + $nl = "\n"; + return '' . + '' . + $nl . '' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' .$r_val. '' . + $nl . '' . + ''; + } + + function getSPARQLJSONDeleteResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+json'); + $r_val = $r['result']['t_count']; + $dur = $r['query_time']; + $nl = "\n"; + $r = '' . + $nl . '{' . + $nl . ' "head": {' . + $nl . ' },' . + $nl . ' "deleted" : ' . $r_val . + $nl . '}' . + ''; + if (($v = $this->p('jsonp')) || ($v = $this->p('callback'))) { + $r = $v . '(' . $r . ')'; + } + return $r; + } + + function getPHPSERDeleteResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return serialize($r); + } + + function getPlainDeleteResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return print_r($r['result'], 1); + } + + /* INSERT */ + + function getInsertResultDoc($r) { + $formats = array( + 'xml' => 'SPARQLXML', 'sparql-results+xml' => 'SPARQLXML', + 'json' => 'SPARQLJSON', 'sparql-results+json' => 'SPARQLJSON', + 'plain' => 'Plain', + 'php_ser' => 'PHPSER' + ); + if ($f = $this->getResultFormat($formats, 'xml')) { + $m = 'get' . $f . 'InsertResultDoc'; + return method_exists($this, $m) ? $this->$m($r) : 'not implemented'; + } + return ''; + } + + function getSPARQLXMLInsertResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+xml'); + $r_val = $r['result']['t_count']; + $dur = $r['query_time']; + $nl = "\n"; + return '' . + '' . + $nl . '' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' . + $nl . ' ' .$r_val. '' . + $nl . '' . + ''; + } + + function getSPARQLJSONInsertResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: application/sparql-results+json'); + $r_val = $r['result']['t_count']; + $dur = $r['query_time']; + $nl = "\n"; + $r = '' . + $nl . '{' . + $nl . ' "head": {' . + $nl . ' },' . + $nl . ' "inserted" : ' . $r_val . + $nl . '}' . + ''; + if (($v = $this->p('jsonp')) || ($v = $this->p('callback'))) { + $r = $v . '(' . $r . ')'; + } + return $r; + } + + function getPHPSERInsertResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return serialize($r); + } + + function getPlainInsertResultDoc($r) { + $this->setHeader('content-type', 'Content-Type: text/plain'); + return print_r($r['result'], 1); + } + + /* */ + + function jsonEscape($v) { + if (function_exists('json_encode')) return trim(json_encode($v), '"'); + $from = array("\\", "\r", "\t", "\n", '"', "\b", "\f", "/"); + $to = array('\\\\', '\r', '\t', '\n', '\"', '\b', '\f', '\/'); + return str_replace($from, $to, $v); + } + + /* */ + + function getHTMLFormDoc() { + return ' + + ' . $this->getHTMLDocHead() . ' + ' . $this->getHTMLDocBody() . ' + + '; + } + + function getHTMLDocHead() { + return ' + + ' . $this->getHTMLDocTitle() . ' + + + '; + } + + function getHTMLDocTitle() { + return $this->v('endpoint_title', 'ARC SPARQL+ Endpoint', $this->a); + } + + function getHTMLDocHeading() { + return $this->v('endpoint_heading', 'ARC SPARQL+ Endpoint (v' . ARC2::getVersion() . ')', $this->a); + } + + function getHTMLDocCSS() { + $default = ' + body { + font-size: 14px; + font-family: Trebuchet MS, Verdana, Geneva, sans-serif; + background: #fff url(?img=bg_body) top center repeat-x; + padding: 5px 20px 20px 20px; + color: #666; + } + h1 { font-size: 1.6em; font-weight: normal; } + a { color: #c00000; } + th, td { + border: 1px dotted #eee; + padding: 2px 4px; + } + #sparql-form { + margin-bottom: 30px; + } + #query { + float: left; + width: 60%; + display: block; + height: 265px; + margin-bottom: 10px; + } + .options { + float: right; + font-size: 0.9em; + width: 35%; + border-top: 1px solid #ccc; + } + .options h3 { + margin: 5px; + } + .options dl{ + margin: 0px; + padding: 0px 10px 5px 20px; + } + .options dl dt { + border-top: 1px dotted #ddd; + padding-top: 10px; + } + .options dl dt.first { + border: none; + } + .options dl dd { + padding: 5px 0px 7px 0px; + } + .options-2 { + clear: both; + margin: 10px 0px; + } + .form-buttons { + } + .results { + border: 1px solid #eee; + padding: 5px; + background-color: #fcfcfc; + } + '; + return $this->v('endpoint_css', $default, $this->a); + } + + function getHTMLDocBody() { + return ' + +

    ' . $this->getHTMLDocHeading() . '

    +
    +

    + This interface implements + SPARQL and + SPARQL+ via HTTP Bindings. +

    +

    + Enabled operations: ' . join(', ', $this->getFeatures()) . ' +

    +

    + Max. number of results : ' . $this->v('endpoint_max_limit', 'unrestricted', $this->a) . ' +

    +
    + ' . $this->getHTMLDocForm() .' + ' . ($this->p('show_inline') ? $this->query_result : '') . ' + + '; + } + + function getHTMLDocForm() { + $q = $this->p('query') ? htmlspecialchars($this->p('query')) : "SELECT * WHERE {\n GRAPH ?g { ?s ?p ?o . }\n}\nLIMIT 10"; + return ' +
    + + ' . $this->getHTMLDocOptions() . ' +
    + + +
    +
    + '; + } + + function getHTMLDocOptions() { + $sel = $this->p('output'); + $sel_code = ' selected="selected"'; + return ' +
    +

    Options

    +
    +
    Output format (if supported by query type):
    +
    + +
    + +
    jsonp/callback (for JSON results)
    +
    + +
    + +
    API key (if required)
    +
    + +
    + +
    Show results inline:
    +
    + p('show_inline') ? ' checked="checked"' : '') . ' /> +
    + +
    +
    +
    + Change HTTP method: + GET + POST +
    + '; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreHelper.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreHelper.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ +store = $this->caller; + } + + /* */ + + function changeNamespaceURI($old_uri, $new_uri) { + $id_changes = 0; + $t_changes = 0; + /* table lock */ + if ($this->store->getLock()) { + $con = $this->store->getDBCon(); + foreach (array('id', 's', 'o') as $id_col) { + $tbl = $this->store->getTablePrefix() . $id_col . '2val'; + $sql = 'SELECT id, val FROM ' . $tbl . ' WHERE val LIKE "' . mysql_real_escape_string($old_uri, $con). '%"'; + $rs = mysql_query($sql, $con); + if (!$rs) continue; + while ($row = mysql_fetch_array($rs)) { + $new_val = str_replace($old_uri, $new_uri, $row['val']); + $new_id = $this->store->getTermID($new_val, $id_col); + if (!$new_id) {/* unknown ns uri, overwrite current id value */ + $sub_sql = "UPDATE " . $tbl . " SET val = '" . mysql_real_escape_string($new_val, $con) . "' WHERE id = " . $row['id']; + $sub_r = mysql_query($sub_sql, $con); + $id_changes++; + } + else {/* replace ids */ + $t_tbls = $this->store->getTables(); + foreach ($t_tbls as $t_tbl) { + if (preg_match('/^triple/', $t_tbl)) { + foreach (array('s', 'p', 'o', 'o_lang_dt') as $t_col) { + $sub_sql = "UPDATE " . $this->store->getTablePrefix() . $t_tbl . " SET " . $t_col . " = " . $new_id . " WHERE " . $t_col . " = " . $row['id']; + $sub_r = mysql_query($sub_sql, $con); + $t_changes += mysql_affected_rows($con); + } + } + } + } + } + } + $this->store->releaseLock(); + } + return array('id_replacements' => $id_changes, 'triple_updates' => $t_changes); + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreInsertQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreInsertQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,54 @@ + + * @license http://arc.semsol.org/license + * @homepage + * @package ARC2 +*/ + +ARC2::inc('StoreQueryHandler'); + +class ARC2_StoreInsertQueryHandler extends ARC2_StoreQueryHandler { + + function __construct($a, &$caller) {/* caller has to be a store */ + parent::__construct($a, $caller); + } + + function __init() {/* db_con */ + parent::__init(); + $this->store = $this->caller; + } + + /* */ + + function runQuery($infos, $keep_bnode_ids = 0) { + $this->infos = $infos; + $con = $this->store->getDBCon(); + /* insert */ + if (!$this->v('pattern', array(), $this->infos['query'])) { + $triples = $this->infos['query']['construct_triples']; + /* don't execute empty INSERTs as they trigger a LOAD on the graph URI */ + if ($triples) { + return $this->store->insert($triples, $this->infos['query']['target_graph'], $keep_bnode_ids); + } + else { + return array('t_count' => 0, 'load_time' => 0); + } + } + else { + $keep_bnode_ids = 1; + ARC2::inc('StoreConstructQueryHandler'); + $h = new ARC2_StoreConstructQueryHandler($this->a, $this->store); + $sub_r = $h->runQuery($this->infos); + if ($sub_r) { + return $this->store->insert($sub_r, $this->infos['query']['target_graph'], $keep_bnode_ids); + } + return array('t_count' => 0, 'load_time' => 0); + } + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreLoadQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreLoadQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,464 @@ + + * @homepage + * @package ARC2 +*/ + +ARC2::inc('StoreQueryHandler'); + +class ARC2_StoreLoadQueryHandler extends ARC2_StoreQueryHandler { + + function __construct($a, &$caller) {/* caller has to be a store */ + parent::__construct($a, $caller); + } + + function __init() {/* db_con, store_log_inserts */ + parent::__init(); + $this->store = $this->caller; + $this->write_buffer_size = $this->v('store_write_buffer', 2500, $this->a); + $this->split_threshold = $this->v('store_split_threshold', 0, $this->a); + $this->strip_mb_comp_str = $this->v('store_strip_mb_comp_str', 0, $this->a); + } + + /* */ + + function runQuery($infos, $data = '', $keep_bnode_ids = 0) { + $url = $infos['query']['url']; + $graph = $infos['query']['target_graph']; + $this->target_graph = $graph ? $this->calcURI($graph) : $this->calcURI($url); + $this->fixed_target_graph = $graph ? $this->target_graph : ''; + $this->keep_bnode_ids = $keep_bnode_ids; + /* reader */ + ARC2::inc('Reader'); + $reader = new ARC2_Reader($this->a, $this); + $reader->activate($url, $data); + /* format detection */ + $mappings = array( + 'rdfxml' => 'RDFXML', + 'sparqlxml' => 'SPOG', + 'turtle' => 'Turtle', + 'ntriples' => 'Turtle', + 'rss' => 'RSS', + 'atom' => 'Atom', + 'n3' => 'Turtle', + 'html' => 'SemHTML', + 'sgajson' => 'SGAJSON', + 'cbjson' => 'CBJSON' + ); + $format = $reader->getFormat(); + if (!$format || !isset($mappings[$format])) { + return $this->addError('No loader available for "' .$url. '": ' . $format); + } + /* format loader */ + $suffix = 'Store' . $mappings[$format] . 'Loader'; + ARC2::inc($suffix); + $cls = 'ARC2_' . $suffix; + $loader = new $cls($this->a, $this); + $loader->setReader($reader); + /* lock */ + if (!$this->store->getLock()) { + $l_name = $this->a['db_name'] . '.' . $this->store->getTablePrefix() . '.write_lock'; + return $this->addError('Could not get lock in "runQuery" (' . $l_name . ')'); + } + $this->has_lock = 1; + /* logging */ + $this->t_count = 0; + $this->t_start = ARC2::mtime(); + $this->log_inserts = $this->v('store_log_inserts', 0, $this->a); + if ($this->log_inserts) { + @unlink("arc_insert_log.txt"); + $this->inserts = array(); + $this->insert_times = array(); + $this->t_prev = $this->t_start; + $this->t_count_prev = 0 ; + } + /* load and parse */ + $this->max_term_id = $this->getMaxTermID(); + $this->max_triple_id = $this->getMaxTripleID(); + $this->column_type = $this->store->getColumnType(); + //$this->createMergeTable(); + $this->term_ids = array(); + $this->triple_ids = array(); + $this->sql_buffers = array(); + $r = $loader->parse($url, $data); + /* done */ + $this->checkSQLBuffers(1); + if ($this->log_inserts) { + $this->logInserts(); + } + $this->store->releaseLock(); + //$this->dropMergeTable(); + if ((rand(1, 100) == 1)) $this->store->optimizeTables(); + $t2 = ARC2::mtime(); + $dur = round($t2 - $this->t_start, 4); + $r = array( + 't_count' => $this->t_count, + 'load_time' => $dur, + ); + if ($this->log_inserts) { + $r['inserts'] = $this->inserts; + $r['insert_times'] = $this->insert_times; + } + return $r; + } + + /* */ + + function addT($s, $p, $o, $s_type, $o_type, $o_dt = '', $o_lang = '') { + if (!$this->has_lock) return 0; + $type_ids = array ('uri' => '0', 'bnode' => '1' , 'literal' => '2'); + $g = $this->getStoredTermID($this->target_graph, '0', 'id'); + $s = (($s_type == 'bnode') && !$this->keep_bnode_ids) ? '_:b' . abs(crc32($g . $s)) . '_' . (strlen($s) > 12 ? substr(substr($s, 2) , -10) : substr($s, 2)) : $s; + $o = (($o_type == 'bnode') && !$this->keep_bnode_ids) ? '_:b' . abs(crc32($g . $o)) . '_' . (strlen($o) > 12 ? substr(substr($o, 2), -10) : substr($o, 2)) : $o; + /* triple */ + $t = array( + 's' => $this->getStoredTermID($s, $type_ids[$s_type], 's'), + 'p' => $this->getStoredTermID($p, '0', 'id'), + 'o' => $this->getStoredTermID($o, $type_ids[$o_type], 'o'), + 'o_lang_dt' => $this->getStoredTermID($o_dt . $o_lang, $o_dt ? '0' : '2', 'id'), + 'o_comp' => $this->getOComp($o), + 's_type' => $type_ids[$s_type], + 'o_type' => $type_ids[$o_type], + ); + $t['t'] = $this->getTripleID($t); + if (is_array($t['t'])) {/* t exists already */ + $t['t'] = $t['t'][0]; + } + else { + $this->bufferTripleSQL($t); + } + /* g2t */ + $g2t = array('g' => $g, 't' => $t['t']); + $this->bufferGraphSQL($g2t); + $this->t_count++; + /* check buffers */ + if (($this->t_count % $this->write_buffer_size) == 0) { + $force_write = 1; + $reset_buffers = (($this->t_count % ($this->write_buffer_size * 2)) == 0); + $refresh_lock = (($this->t_count % 25000) == 0); + $split_tables = (($this->t_count % ($this->write_buffer_size * 10)) == 0); + if ($this->log_inserts) $this->logInserts(); + $this->checkSQLBuffers($force_write, $reset_buffers, $refresh_lock, $split_tables); + } + } + + /* */ + + function getMaxTermID() { + $con = $this->store->getDBCon(); + $sql = ''; + foreach (array('id2val', 's2val', 'o2val') as $tbl) { + $sql .= $sql ? ' UNION ' : ''; + $sql .= "(SELECT MAX(id) as `id` FROM " . $this->store->getTablePrefix() . $tbl . ')'; + } + $r = 0; + if (($rs = $this->queryDB($sql, $con)) && mysql_num_rows($rs)) { + while ($row = mysql_fetch_array($rs)) { + $r = ($r < $row['id']) ? $row['id'] : $r; + } + } + return $r + 1; + } + + function getMaxTripleID() { + $con = $this->store->getDBCon(); + $sql = "SELECT MAX(t) AS `id` FROM " . $this->store->getTablePrefix() . "triple"; + if (($rs = $this->queryDB($sql, $con)) && mysql_num_rows($rs) && ($row = mysql_fetch_array($rs))) { + return $row['id'] + 1; + } + return 1; + } + + function getStoredTermID($val, $type_id, $tbl) { + $con = $this->store->getDBCon(); + /* buffered */ + if (isset($this->term_ids[$val])) { + if (!isset($this->term_ids[$val][$tbl])) { + foreach (array('id', 's', 'o') as $other_tbl) { + if (isset($this->term_ids[$val][$other_tbl])) { + $this->term_ids[$val][$tbl] = $this->term_ids[$val][$other_tbl]; + $this->bufferIDSQL($tbl, $this->term_ids[$val][$tbl], $val, $type_id); + break; + } + } + } + return $this->term_ids[$val][$tbl]; + } + /* db */ + $tbl_prefix = $this->store->getTablePrefix(); + $sub_tbls = ($tbl == 'id') ? array('id2val', 's2val', 'o2val') : ($tbl == 's' ? array('s2val', 'id2val', 'o2val') : array('o2val', 'id2val', 's2val')); + foreach ($sub_tbls as $sub_tbl) { + $id = 0; + //$sql = "SELECT id AS `id`, '" . $sub_tbl . "' AS `tbl` FROM " . $tbl_prefix . $sub_tbl . " WHERE val = BINARY '" . mysql_real_escape_string($val, $con) . "'"; + /* via hash */ + if (preg_match('/^(s2val|o2val)$/', $sub_tbl) && $this->hasHashColumn($sub_tbl)) { + $sql = "SELECT id AS `id`, val AS `val` FROM " . $tbl_prefix . $sub_tbl . " WHERE val_hash = BINARY '" . $this->getValueHash($val) . "'"; + if (($rs = $this->queryDB($sql, $con)) && mysql_num_rows($rs)) { + while ($row = mysql_fetch_array($rs)) { + if ($row['val'] == $val) { + $id = $row['id']; + break; + } + } + } + } + else { + $sql = "SELECT id AS `id` FROM " . $tbl_prefix . $sub_tbl . " WHERE val = BINARY '" . mysql_real_escape_string($val, $con) . "'"; + if (($rs = $this->queryDB($sql . ' LIMIT 1', $con)) && mysql_num_rows($rs)) { + $row = mysql_fetch_array($rs); + $id = $row['id']; + } + } + if ($id) { + $this->term_ids[$val] = array($tbl => $id); + if ($sub_tbl != $tbl . '2val') { + $this->bufferIDSQL($tbl, $id, $val, $type_id); + } + break; + } + } + /* new */ + if (!isset($this->term_ids[$val])) { + $this->term_ids[$val] = array($tbl => $this->max_term_id); + $this->bufferIDSQL($tbl, $this->max_term_id, $val, $type_id); + $this->max_term_id++; + /* upgrade tables ? */ + if (($this->column_type == 'mediumint') && ($this->max_term_id >= 16750000)) { + $this->store->extendColumns(); + $this->column_type = 'int'; + } + } + return $this->term_ids[$val][$tbl]; + } + + function getTripleID($t) { + $con = $this->store->getDBCon(); + $val = serialize($t); + /* buffered */ + if (isset($this->triple_ids[$val])) { + return array($this->triple_ids[$val]);/* hack for "don't insert this triple" */ + } + /* db */ + $sql = "SELECT t FROM " . $this->store->getTablePrefix() . "triple WHERE + s = " . $t['s'] . " AND p = " . $t['p'] . " AND o = " . $t['o'] . " AND o_lang_dt = " . $t['o_lang_dt'] . " AND s_type = " . $t['s_type'] . " AND o_type = " . $t['o_type'] . " + LIMIT 1 + "; + if (($rs = $this->queryDB($sql, $con)) && mysql_num_rows($rs) && ($row = mysql_fetch_array($rs))) { + $this->triple_ids[$val] = $row['t'];/* hack for "don't insert this triple" */ + return array($row['t']);/* hack for "don't insert this triple" */ + } + /* new */ + else { + $this->triple_ids[$val] = $this->max_triple_id; + $this->max_triple_id++; + /* split tables ? */ + if (0 && $this->split_threshold && !($this->max_triple_id % $this->split_threshold)) { + $this->store->splitTables(); + $this->dropMergeTable(); + $this->createMergeTable(); + } + /* upgrade tables ? // Thanks to patch by Mark Fichtner (https://github.com/Knurg) */ + if (($this->column_type == 'mediumint') && ($this->max_triple_id >= 16750000)) { + $this->store->extendColumns(); + $this->column_type = 'int'; + } + return $this->triple_ids[$val]; + } + } + + function getOComp($val) { + /* try date (e.g. 21 August 2007) */ + if (preg_match('/^[0-9]{1,2}\s+[a-z]+\s+[0-9]{4}/i', $val) && ($uts = strtotime($val)) && ($uts !== -1)) { + return date("Y-m-d\TH:i:s", $uts); + } + /* xsd date (e.g. 2009-05-28T18:03:38+09:00 2009-05-28T18:03:38GMT) */ + if (preg_match('/^([0-9]{4}\-[0-9]{2}\-[0-9]{2}\T)([0-9\:]+)?([0-9\+\-\:\Z]+)?(\s*[a-z]{2,3})?$/si', $val, $m)) { + /* yyyy-mm-dd */ + $val = $m[1]; + /* hh:ss */ + if ($m[2]) { + $val .= $m[2]; + /* timezone offset */ + if (isset($m[3]) && ($m[3] != 'Z')) { + $uts = strtotime(str_replace('T', ' ', $val)); + if (preg_match('/([\+\-])([0-9]{2})\:?([0-9]{2})$/', $m[3], $sub_m)) { + $diff_mins = (3600 * ltrim($sub_m[2], '0')) + ltrim($sub_m[3], '0'); + $uts = ($sub_m[1] == '-') ? $uts + $diff_mins : $uts - $diff_mins; + $val = date('Y-m-d\TH:i:s\Z', $uts); + } + } + else { + $val .= 'Z'; + } + } + return $val; + } + /* fallback & backup w/o UTC calculation, to be removed in later revision */ + if (preg_match('/^[0-9]{4}[0-9\-\:\T\Z\+]+([a-z]{2,3})?$/i', $val)) { + return $val; + } + if (is_numeric($val)) { + $val = sprintf("%f", $val); + if (preg_match("/([\-\+])([0-9]*)\.([0-9]*)/", $val, $m)) { + return $m[1] . sprintf("%018s", $m[2]) . "." . sprintf("%-015s", $m[3]); + } + if (preg_match("/([0-9]*)\.([0-9]*)/", $val, $m)) { + return "+" . sprintf("%018s", $m[1]) . "." . sprintf("%-015s", $m[2]); + } + return $val; + } + /* any other string: remove tags, linebreaks etc., but keep MB-chars */ + //$val = substr(trim(preg_replace('/[\W\s]+/is', '-', strip_tags($val))), 0, 35); + // [\PL\s]+ ( = non-Letters) kills digits + $re = $this->has_pcre_unicode ? '/[\PL\s]+/isu' : '/[\s\'\"\´\`]+/is'; + $re = '/[\s\'\"\´\`]+/is'; + $val = trim(preg_replace($re, '-', strip_tags($val))); + if (strlen($val) > 35) { + $fnc = function_exists("mb_substr") ? 'mb_substr' : 'substr'; + $val = $fnc($val, 0, 17) . '-' . $fnc($val, -17); + } + if ($this->strip_mb_comp_str) { + $val = urldecode(preg_replace('/\%[0-9A-F]{2}/', '', urlencode($val))); + } + return $this->toUTF8($val); + } + + /* */ + + function bufferTripleSQL($t) { + $con = $this->store->getDBCon(); + $tbl = 'triple'; + $sql = ", "; + if (!isset($this->sql_buffers[$tbl])) { + $this->sql_buffers[$tbl] = "INSERT IGNORE INTO " . $this->store->getTablePrefix() . $tbl . " (t, s, p, o, o_lang_dt, o_comp, s_type, o_type) VALUES"; + $sql = " "; + } + $this->sql_buffers[$tbl] .= $sql . "(" . $t['t'] . ", " . $t['s'] . ", " . $t['p'] . ", " . $t['o'] . ", " . $t['o_lang_dt'] . ", '" . mysql_real_escape_string($t['o_comp'], $con) . "', " . $t['s_type'] . ", " . $t['o_type'] . ")"; + } + + function bufferGraphSQL($g2t) { + $tbl = 'g2t'; + $sql = ", "; + if (!isset($this->sql_buffers[$tbl])) { + $this->sql_buffers[$tbl] = "INSERT IGNORE INTO " . $this->store->getTablePrefix() . $tbl . " (g, t) VALUES"; + $sql = " "; + } + $this->sql_buffers[$tbl] .= $sql . "(" . $g2t['g'] . ", " . $g2t['t'] . ")"; + } + + function bufferIDSQL($tbl, $id, $val, $val_type) { + $con = $this->store->getDBCon(); + $tbl = $tbl . '2val'; + if ($tbl == 'id2val') { + $cols = "id, val, val_type"; + $vals = "(" . $id . ", '" . mysql_real_escape_string($val, $con) . "', " . $val_type . ")"; + } + elseif (preg_match('/^(s2val|o2val)$/', $tbl) && $this->hasHashColumn($tbl)) { + $cols = "id, val_hash, val"; + $vals = "(" . $id . ", '" . $this->getValueHash($val). "', '" . mysql_real_escape_string($val, $con) . "')"; + } + else { + $cols = "id, val"; + $vals = "(" . $id . ", '" . mysql_real_escape_string($val, $con) . "')"; + } + if (!isset($this->sql_buffers[$tbl])) { + $this->sql_buffers[$tbl] = ''; + $sql = "INSERT IGNORE INTO " . $this->store->getTablePrefix() . $tbl . "(" . $cols . ") VALUES "; + } + else { + $sql = ", "; + } + $sql .= $vals; + $this->sql_buffers[$tbl] .= $sql; + } + + /* */ + + function checkSQLBuffers($force_write = 0, $reset_id_buffers = 0, $refresh_lock = 0, $split_tables = 0) { + $con = $this->store->getDBCon(); + if (!$this->keep_time_limit) @set_time_limit($this->v('time_limit', 60, $this->a)); + foreach (array('triple', 'g2t', 'id2val', 's2val', 'o2val') as $tbl) { + $buffer_size = isset($this->sql_buffers[$tbl]) ? 1 : 0; + if ($buffer_size && $force_write) { + $t1 = ARC2::mtime(); + $this->queryDB($this->sql_buffers[$tbl], $con); + /* table error */ + if ($er = mysql_error($con)) { + $this->autoRepairTable($er, $con, $this->sql_buffers[$tbl]); + } + unset($this->sql_buffers[$tbl]); + if ($this->log_inserts) { + $t2 = ARC2::mtime(); + $this->inserts[$tbl] = $this->v($tbl, 0, $this->inserts) + max(0, mysql_affected_rows($con)); + $dur = round($t2 - $t1, 4); + $this->insert_times[$tbl] = isset($this->insert_times[$tbl]) ? $this->insert_times[$tbl] : array('min' => $dur, 'max' => $dur, 'sum' => $dur); + $this->insert_times[$tbl] = array('min' => min($dur, $this->insert_times[$tbl]['min']), 'max' => max($dur, $this->insert_times[$tbl]['max']), 'sum' => $dur + $this->insert_times[$tbl]['sum']); + } + /* reset term id buffers */ + if ($reset_id_buffers) { + $this->term_ids = array(); + $this->triple_ids = array(); + } + /* refresh lock */ + if ($refresh_lock) { + $this->store->releaseLock(); + $this->has_lock = 0; + sleep(1); + if (!$this->store->getLock(5)) return $this->addError('Could not re-obtain lock in "checkSQLBuffers"'); + $this->has_lock = 1; + } + } + } + return 1; + } + + function autoRepairTable($er, $con, $sql = '') { + $this->addError('MySQL error: ' . $er . ' (' . $sql . ')'); + if (preg_match('/Table \'[^\']+\/([a-z0-9\_\-]+)\' .*(crashed|repair)/i', $er, $m)) { + $rs = $this->queryDB('REPAIR TABLE ' . rawurlencode($m[1]), $con); + $msg = $rs ? mysql_fetch_array($rs) : array(); + if ($this->v('Msg_type', 'error', $msg) == 'error') { + /* auto-reset */ + if ($this->v('store_reset_on_table_crash', 0, $this->a)) { + $this->store->drop(); + $this->store->setUp(); + } + else { + $er = $this->v('Msg_text', 'unknown error', $msg); + $this->addError('Auto-repair failed on ' . rawurlencode($m[1]) . ': ' . $er); + } + //die("Fatal errors: \n" . print_r($this->getErrors(), 1)); + } + } + } + + /* speed log */ + + function logInserts() { + $t_start = $this->t_start; + $t_prev = $this->t_prev; + $t_now = ARC2::mtime(); + $tc_prev = $this->t_count_prev; + $tc_now = $this->t_count; + $tc_diff = $tc_now - $tc_prev; + + $dur_full = $t_now - $t_start; + $dur_diff = $t_now - $t_prev; + + $speed_full = round($tc_now / $dur_full); + $speed_now = round($tc_diff / $dur_diff); + + $r = $tc_diff . ' in ' . round($dur_diff, 5) . ' = ' . $speed_now . ' t/s (' .$tc_now. ' in ' . round($dur_full, 5). ' = ' . $speed_full . ' t/s )'; + $fp = @fopen("arc_insert_log.txt", "a"); + @fwrite($fp, $r . "\r\n"); + @fclose($fp); + + $this->t_prev = $t_now; + $this->t_count_prev = $tc_now; + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,94 @@ + + * @homepage + * @package ARC2 + * @version 2010-11-16 +*/ + +ARC2::inc('Class'); + +class ARC2_StoreQueryHandler extends ARC2_Class { + + function __construct($a, &$caller) { + parent::__construct($a, $caller); + } + + function __init() {/* db_con */ + parent::__init(); + $this->xsd = 'http://www.w3.org/2001/XMLSchema#'; + $this->allow_extension_functions = $this->v('store_allow_extension_functions', 1, $this->a); + $this->keep_time_limit = $this->v('keep_time_limit', 0, $this->a); + $this->handler_type = ''; + } + + /* */ + + function getTermID($val, $term = '') { + return $this->store->getTermID($val, $term); + } + + function hasHashColumn($tbl) { + return $this->store->hasHashColumn($tbl); + } + + function getValueHash($val) { + return $this->store->getValueHash($val); + } + + /* */ + + function getTripleTable() { + $r = $this->store->getTablePrefix() . 'triple'; + return $r; + } + + /* */ + + function createMergeTable() { + $split_ps = $this->store->getSetting('split_predicates', array()); + if (!$split_ps) return 1; + $this->mrg_table_id = 'MRG_' . $this->store->getTablePrefix() . crc32(uniqid(rand())); + $con = $this->store->getDBCon(); + $this->queryDB("FLUSH TABLES", $con); + $indexes = $this->v('store_indexes', array('sp (s,p)', 'os (o,s)', 'po (p,o)'), $this->a); + $index_code = $indexes ? 'KEY ' . join(', KEY ', $indexes) . ', ' : ''; + $prefix = $this->store->getTablePrefix(); + $sql = " + CREATE TEMPORARY TABLE IF NOT EXISTS " . $prefix . "triple_all ( + t mediumint UNSIGNED NOT NULL, + s mediumint UNSIGNED NOT NULL, + p mediumint UNSIGNED NOT NULL, + o mediumint UNSIGNED NOT NULL, + o_lang_dt mediumint UNSIGNED NOT NULL, + o_comp char(35) NOT NULL, /* normalized value for ORDER BY operations */ + s_type tinyint(1) NOT NULL default 0, /* uri/bnode => 0/1 */ + o_type tinyint(1) NOT NULL default 0, /* uri/bnode/literal => 0/1/2 */ + misc tinyint(1) NOT NULL default 0, /* temporary flags */ + UNIQUE KEY (t), " . $index_code . " KEY (misc) + ) + "; + $v = $this->store->getDBVersion(); + $sql .= (($v < '04-01-00') && ($v >= '04-00-18')) ? 'ENGINE' : (($v >= '04-01-02') ? 'ENGINE' : 'TYPE'); + $sql .= "=MERGE UNION=(" . $prefix . "triple" ; + foreach ($split_ps as $pos => $p) { + $sql .= ',' . $prefix . 'triple_' . abs(crc32($p)); + } + $sql .= ")"; + //$sql .= ($v >= '04-00-00') ? " CHARACTER SET utf8" : ""; + //$sql .= ($v >= '04-01-00') ? " COLLATE utf8_unicode_ci" : ""; + //echo $sql; + return $this->queryDB($sql, $con); + } + + function dropMergeTable() { + return 1; + $sql = "DROP TABLE IF EXISTS " . $this->store->getTablePrefix() . "triple_all"; + //echo $sql; + return $this->queryDB($sql, $this->store->getDBCon()); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreRDFXMLLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreRDFXMLLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,32 @@ +caller->addT($s, $p, $o, $s_type, $o_type, $o_dt, $o_lang); + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreRSSLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreRSSLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,32 @@ +caller->addT($t['s'], $t['p'], $t['o'], $t['s_type'], $t['o_type'], $t['o_datatype'], $t['o_lang']); + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreSGAJSONLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreSGAJSONLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,36 @@ +extractRDF(); + } + + function addT($s, $p, $o, $s_type, $o_type, $o_dt = '', $o_lang = '') { + $this->caller->addT($s, $p, $o, $s_type, $o_type, $o_dt, $o_lang); + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreSPOGLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreSPOGLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,38 @@ +caller->target_graph; + if ($this->caller->fixed_target_graph) $g = $this->caller->fixed_target_graph; + $prev_g = $this->caller->target_graph; + $this->caller->target_graph = $g; + $this->caller->addT($s, $p, $o, $s_type, $o_type, $o_dt, $o_lang); + $this->caller->target_graph = $prev_g; + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreSelectQueryHandler.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreSelectQueryHandler.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1799 @@ + + * @package ARC2 + * @version 2010-11-16 + * +*/ + +ARC2::inc('StoreQueryHandler'); + +class ARC2_StoreSelectQueryHandler extends ARC2_StoreQueryHandler { + + function __construct($a, &$caller) {/* caller has to be a store */ + parent::__construct($a, $caller); + } + + function __init() {/* db_con */ + parent::__init(); + $this->store = $this->caller; + $con = $this->store->getDBCon(); + $this->handler_type = 'select'; + $this->engine_type = $this->v('store_engine_type', 'MyISAM', $this->a); + $this->cache_results = $this->v('store_cache_results', 0, $this->a); + } + + /* */ + + function runQuery($infos) { + $con = $this->store->getDBCon(); + $rf = $this->v('result_format', '', $infos); + $this->infos = $infos; + $this->infos['null_vars'] = array(); + $this->indexes = array(); + $this->pattern_order_offset = 0; + $q_sql = $this->getSQL(); + + /* debug result formats */ + if ($rf == 'sql') return $q_sql; + if ($rf == 'structure') return $this->infos; + if ($rf == 'index') return $this->indexes; + /* create intermediate results (ID-based) */ + $tmp_tbl = $this->createTempTable($q_sql); + /* join values */ + $r = $this->getFinalQueryResult($q_sql, $tmp_tbl); + /* remove intermediate results */ + if (!$this->cache_results) { + $this->queryDB('DROP TABLE IF EXISTS ' . $tmp_tbl, $con); + } + return $r; + } + + function getSQL() { + $r = ''; + $nl = "\n"; + $this->buildInitialIndexes(); + foreach ($this->indexes as $i => $index) { + $this->index = array_merge($this->getEmptyIndex(), $index); + $this->analyzeIndex($this->getPattern('0')); + $sub_r = $this->getQuerySQL(); + $r .= $r ? $nl . 'UNION' . $this->getDistinctSQL() . $nl : ''; + $r .= $this->is_union_query ? '(' . $sub_r . ')' : $sub_r; + $this->indexes[$i] = $this->index; + } + $r .= $this->is_union_query ? $this->getLIMITSQL() : ''; + if ($this->v('order_infos', 0, $this->infos['query'])) { + $r = preg_replace('/SELECT(\s+DISTINCT)?\s*/', 'SELECT\\1 NULL AS `_pos_`, ', $r); + } + $pd_count = $this->problematicDependencies(); + if ($pd_count) { + /* re-arranging the patterns sometimes reduces the LEFT JOIN dependencies */ + $set_sql = 0; + if (!$this->pattern_order_offset) $set_sql = 1; + if (!$set_sql && ($pd_count < $this->opt_sql_pd_count)) $set_sql = 1; + if (!$set_sql && ($pd_count == $this->opt_sql_pd_count) && (strlen($r) < strlen($this->opt_sql))) $set_sql = 1; + if ($set_sql) { + $this->opt_sql = $r; + $this->opt_sql_pd_count = $pd_count; + } + $this->pattern_order_offset++; + if ($this->pattern_order_offset > 5) { + return $this->opt_sql; + } + return $this->getSQL(); + } + return $r; + } + + function buildInitialIndexes() { + $this->dependency_log = array(); + $this->index = $this->getEmptyIndex(); + $this->buildIndex($this->infos['query']['pattern'], 0); + $tmp = $this->index; + $this->analyzeIndex($this->getPattern('0')); + $this->initial_index = $this->index; + $this->index = $tmp; + $this->is_union_query = $this->index['union_branches'] ? 1 : 0; + $this->indexes = $this->is_union_query ? $this->getUnionIndexes($this->index) : array($this->index); + } + + function createTempTable($q_sql) { + $con = $this->store->getDBCon(); + $v = $this->store->getDBVersion(); + if ($this->cache_results) { + $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql); + } + else { + $tbl = $this->store->getTablePrefix() . 'Q' . md5($q_sql . time() . uniqid(rand())); + } + if (strlen($tbl) > 64) $tbl = 'Q' . md5($tbl); + $tmp_sql = 'CREATE TEMPORARY TABLE ' . $tbl . ' ( ' . $this->getTempTableDef($tbl, $q_sql) . ') '; + $tmp_sql .= (($v < '04-01-00') && ($v >= '04-00-18')) ? 'ENGINE' : (($v >= '04-01-02') ? 'ENGINE' : 'TYPE'); + $tmp_sql .= '=' . $this->engine_type;/* HEAP doesn't support AUTO_INCREMENT, and MySQL breaks on MEMORY sometimes */ + if (!$this->queryDB($tmp_sql, $con) && !$this->queryDB(str_replace('CREATE TEMPORARY', 'CREATE', $tmp_sql), $con)) { + return $this->addError(mysql_error($con)); + } + mysql_unbuffered_query('INSERT INTO ' . $tbl . ' ' . "\n" . $q_sql, $con); + if ($er = mysql_error($con)) $this->addError($er); + return $tbl; + } + + function getEmptyIndex() { + return array( + 'from' => array(), + 'join' => array(), + 'left_join' => array(), + 'vars' => array(), 'graph_vars' => array(), 'graph_uris' => array(), + 'bnodes' => array(), + 'triple_patterns' => array(), + 'sub_joins' => array(), + 'constraints' => array(), + 'union_branches'=> array(), + 'patterns' => array(), + 'havings' => array() + ); + } + + function getTempTableDef($tmp_tbl, $q_sql) { + $col_part = preg_replace('/^SELECT\s*(DISTINCT)?(.*)FROM.*$/s', '\\2', $q_sql); + $parts = explode(',', $col_part); + $has_order_infos = $this->v('order_infos', 0, $this->infos['query']); + $r = ''; + $added = array(); + foreach ($parts as $part) { + if (preg_match('/\.?(.+)\s+AS\s+`(.+)`/U', trim($part), $m) && !isset($added[$m[2]])) { + $col = $m[1]; + $alias = $m[2]; + if ($alias == '_pos_') continue; + $r .= $r ? ',' : ''; + $r .= "\n `" . $alias . "` int UNSIGNED"; + $added[$alias] = 1; + } + } + if ($has_order_infos) { + $r = "\n" . '`_pos_` mediumint NOT NULL AUTO_INCREMENT PRIMARY KEY, ' . $r; + } + return $r ? $r . "\n" : ''; + } + + function getFinalQueryResult($q_sql, $tmp_tbl) { + /* var names */ + $vars = array(); + $aggregate_vars = array(); + foreach ($this->infos['query']['result_vars'] as $entry) { + if ($entry['aggregate']) { + $vars[] = $entry['alias']; + $aggregate_vars[] = $entry['alias']; + } + else { + $vars[] = $entry['var']; + } + } + /* result */ + $r = array('variables' => $vars); + $v_sql = $this->getValueSQL($tmp_tbl, $q_sql); + //echo "\n\n" . $v_sql; + $t1 = ARC2::mtime(); + $con = $this->store->getDBCon(); + $rs = mysql_unbuffered_query($v_sql, $con); + if ($er = mysql_error($con)) { + $this->addError($er); + } + $t2 = ARC2::mtime(); + $rows = array(); + $types = array(0 => 'uri', 1 => 'bnode', 2 => 'literal'); + if ($rs) { + while ($pre_row = mysql_fetch_array($rs)) { + $row = array(); + foreach ($vars as $var) { + if (isset($pre_row[$var])) { + $row[$var] = $pre_row[$var]; + $row[$var . ' type'] = isset($pre_row[$var . ' type']) ? $types[$pre_row[$var . ' type']] : (in_array($var, $aggregate_vars) ? 'literal' : 'uri'); + if (isset($pre_row[$var . ' lang_dt']) && ($lang_dt = $pre_row[$var . ' lang_dt'])) { + if (preg_match('/^([a-z]+(\-[a-z0-9]+)*)$/i', $lang_dt)) { + $row[$var . ' lang'] = $lang_dt; + } + else { + $row[$var . ' datatype'] = $lang_dt; + } + } + } + } + if ($row || !$vars) { + $rows[] = $row; + } + } + } + $r['rows'] = $rows; + return $r; + } + + /* */ + + function buildIndex($pattern, $id) { + $pattern['id'] = $id; + $type = $this->v('type', '', $pattern); + if (($type == 'filter') && $this->v('constraint', 0, $pattern)) { + $sub_pattern = $pattern['constraint']; + $sub_pattern['parent_id'] = $id; + $sub_id = $id . '_0'; + $this->buildIndex($sub_pattern, $sub_id); + $pattern['constraint'] = $sub_id; + } + else { + $sub_patterns = $this->v('patterns', array(), $pattern); + $keys = array_keys($sub_patterns); + $spc = count($sub_patterns); + if (($spc > 4) && $this->pattern_order_offset) { + $keys = array(); + for ($i = 0 ; $i < $spc; $i++) { + $keys[$i] = $i + $this->pattern_order_offset; + while ($keys[$i] >= $spc) $keys[$i] -= $spc; + } + } + foreach ($keys as $i => $key) { + $sub_pattern = $sub_patterns[$key]; + $sub_pattern['parent_id'] = $id; + $sub_id = $id . '_' . $key; + $this->buildIndex($sub_pattern, $sub_id); + $pattern['patterns'][$i] = $sub_id; + if ($type == 'union') { + $this->index['union_branches'][] = $sub_id; + } + } + } + $this->index['patterns'][$id] = $pattern; + } + + /* */ + + function analyzeIndex($pattern) { + $type = $this->v('type', '', $pattern); + if (!$type) { + //echo ''; + return false; + } + $type = $pattern['type']; + $id = $pattern['id']; + /* triple */ + if ($type == 'triple') { + foreach (array('s', 'p', 'o') as $term) { + if ($pattern[$term . '_type'] == 'var') { + $val = $pattern[$term]; + $this->index['vars'][$val] = array_merge($this->v($val, array(), $this->index['vars']), array(array('table' => $pattern['id'], 'col' =>$term))); + } + if ($pattern[$term . '_type'] == 'bnode') { + $val = $pattern[$term]; + $this->index['bnodes'][$val] = array_merge($this->v($val, array(), $this->index['bnodes']), array(array('table' => $pattern['id'], 'col' =>$term))); + } + } + $this->index['triple_patterns'][] = $pattern['id']; + /* joins */ + if ($this->isOptionalPattern($id)) { + $this->index['left_join'][] = $id; + } + elseif (!$this->index['from']) { + $this->index['from'][] = $id; + } + elseif (!$this->getJoinInfos($id)) { + $this->index['from'][] = $id; + } + else { + $this->index['join'][] = $id; + } + /* graph infos, graph vars */ + $this->index['patterns'][$id]['graph_infos'] = $this->getGraphInfos($id); + foreach ($this->index['patterns'][$id]['graph_infos'] as $info) { + if ($info['type'] == 'graph') { + if ($info['var']) { + $val = $info['var']['value']; + $this->index['graph_vars'][$val] = array_merge($this->v($val, array(), $this->index['graph_vars']), array(array('table' => $id))); + } + elseif ($info['uri']) { + $val = $info['uri']; + $this->index['graph_uris'][$val] = array_merge($this->v($val, array(), $this->index['graph_uris']), array(array('table' => $id))); + } + } + } + } + $sub_ids = $this->v('patterns', array(), $pattern); + foreach ($sub_ids as $sub_id) { + $this->analyzeIndex($this->getPattern($sub_id)); + } + } + + /* */ + + function getGraphInfos($id) { + $r = array(); + if ($id) { + $pattern = $this->index['patterns'][$id]; + $type = $pattern['type']; + /* graph */ + if ($type == 'graph') { + $r[] = array('type' => 'graph', 'var' => $pattern['var'], 'uri' => $pattern['uri']); + } + $p_pattern = $this->index['patterns'][$pattern['parent_id']]; + if (isset($p_pattern['graph_infos'])) { + return array_merge($p_pattern['graph_infos'], $r); + } + return array_merge($this->getGraphInfos($pattern['parent_id']), $r); + } + /* FROM / FROM NAMED */ + else { + if (isset($this->infos['query']['dataset'])) { + foreach ($this->infos['query']['dataset'] as $set) { + $r[] = array_merge(array('type' => 'dataset'), $set); + } + } + } + return $r; + } + + /* */ + + function getPattern($id) { + if (is_array($id)) { + return $id; + } + return $this->v($id, array(), $this->index['patterns']); + } + + function getInitialPattern($id) { + return $this->v($id, array(), $this->initial_index['patterns']); + } + + /* */ + + function getUnionIndexes($pre_index) { + $r = array(); + $branches = array(); + $min_depth = 1000; + /* only process branches with minimum depth */ + foreach ($pre_index['union_branches'] as $id) { + $branches[$id] = count(preg_split('/\_/', $id)); + $min_depth = min($min_depth, $branches[$id]); + } + foreach ($branches as $branch_id => $depth) { + if ($depth == $min_depth) { + $union_id = preg_replace('/\_[0-9]+$/', '', $branch_id); + $index = array('keeping' => $branch_id, 'union_branches' => array(), 'patterns' => $pre_index['patterns']); + $old_branches = $index['patterns'][$union_id]['patterns']; + $skip_id = ($old_branches[0] == $branch_id) ? $old_branches[1] : $old_branches[0]; + $index['patterns'][$union_id]['type'] = 'group'; + $index['patterns'][$union_id]['patterns'] = array($branch_id); + $has_sub_unions = 0; + foreach ($index['patterns'] as $pattern_id => $pattern) { + if (preg_match('/^' .$skip_id. '/', $pattern_id)) { + unset($index['patterns'][$pattern_id]); + } + elseif ($pattern['type'] == 'union') { + foreach ($pattern['patterns'] as $sub_union_branch_id) { + $index['union_branches'][] = $sub_union_branch_id; + } + } + } + if ($index['union_branches']) { + $r = array_merge($r, $this->getUnionIndexes($index)); + } + else { + $r[] = $index; + } + } + } + return $r; + } + + /* */ + + function isOptionalPattern($id) { + $pattern = $this->getPattern($id); + if ($this->v('type', '', $pattern) == 'optional') { + return 1; + } + if ($this->v('parent_id', '0', $pattern) == '0') { + return 0; + } + return $this->isOptionalPattern($pattern['parent_id']); + } + + function getOptionalPattern($id) { + $pn = $this->getPattern($id); + do { + $pn = $this->getPattern($pn['parent_id']); + } while ($pn['parent_id'] && ($pn['type'] != 'optional')); + return $pn['id']; + } + + function sameOptional($id, $id2) { + return $this->getOptionalPattern($id) == $this->getOptionalPattern($id2); + } + + /* */ + + function isUnionPattern($id) { + $pattern = $this->getPattern($id); + if ($this->v('type', '', $pattern) == 'union') { + return 1; + } + if ($this->v('parent_id', '0', $pattern) == '0') { + return 0; + } + return $this->isUnionPattern($pattern['parent_id']); + } + + /* */ + + function getValueTable($col) { + return $this->store->getTablePrefix() . (preg_match('/^(s|o)$/', $col) ? $col . '2val' : 'id2val'); + } + + function getGraphTable() { + return $this->store->getTablePrefix() . 'g2t'; + } + + /* */ + + function getQuerySQL() { + $nl = "\n"; + $where_sql = $this->getWHERESQL(); /* pre-fills $index['sub_joins'] $index['constraints'] */ + $order_sql = $this->getORDERSQL(); /* pre-fills $index['sub_joins'] $index['constraints'] */ + return '' . + ($this->is_union_query ? 'SELECT' : 'SELECT' . $this->getDistinctSQL()) . $nl . + $this->getResultVarsSQL() . $nl . /* fills $index['sub_joins'] */ + $this->getFROMSQL() . + $this->getAllJoinsSQL() . + $this->getWHERESQL() . + $this->getGROUPSQL() . + $this->getORDERSQL() . + ($this->is_union_query ? '' : $this->getLIMITSQL()) . + $nl . + ''; + } + + /* */ + + function getDistinctSQL() { + if ($this->is_union_query) { + return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? '' : ' ALL'; + } + return ($this->v('distinct', 0, $this->infos['query']) || $this->v('reduced', 0, $this->infos['query'])) ? ' DISTINCT' : ''; + } + + /* */ + + function getResultVarsSQL() { + $r = ''; + $vars = $this->infos['query']['result_vars']; + $nl = "\n"; + $added = array(); + foreach ($vars as $var) { + $var_name = $var['var']; + $tbl_alias = ''; + if ($tbl_infos = $this->getVarTableInfos($var_name, 0)) { + $tbl = $tbl_infos['table']; + $col = $tbl_infos['col']; + $tbl_alias = $tbl_infos['table_alias']; + } + elseif ($var_name == 1) {/* ASK query */ + $r .= '1 AS `success`'; + } + else { + $this->addError('Result variable "' .$var_name. '" not used in query.'); + } + if ($tbl_alias) { + /* aggregate */ + if ($var['aggregate']) { + $conv_code = ''; + if (strtolower($var['aggregate']) != 'count') { + $tbl_alias = 'V_' . $tbl . '_' . $col . '.val'; + $conv_code = '0 + '; + } + if (!isset($added[$var['alias']])) { + $r .= $r ? ',' . $nl . ' ' : ' '; + $distinct_code = (strtolower($var['aggregate']) == 'count') && $this->v('distinct', 0, $this->infos['query']) ? 'DISTINCT ' : ''; + $r .= $var['aggregate'] . '(' . $conv_code . $distinct_code . $tbl_alias. ') AS `' . $var['alias'] . '`'; + $added[$var['alias']] = 1; + } + } + /* normal var */ + else { + if (!isset($added[$var_name])) { + $r .= $r ? ',' . $nl . ' ' : ' '; + $r .= $tbl_alias . ' AS `' . $var_name . '`'; + $is_s = ($col == 's'); + $is_p = ($col == 'p'); + $is_o = ($col == 'o'); + if ($tbl_alias == 'NULL') { + /* type / add in UNION queries? */ + if ($is_s || $is_o) { + $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' type`'; + } + /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */ + if ($is_o || $this->is_union_query) { + $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' lang_dt`'; + } + } + else { + /* type */ + if ($is_s || $is_o) { + $r .= ', ' . $nl . ' ' .$tbl_alias . '_type AS `' . $var_name . ' type`'; + } + /* lang_dt / always add it in UNION queries, the var may be used as s/p/o */ + if ($is_o) { + $r .= ', ' . $nl . ' ' .$tbl_alias . '_lang_dt AS `' . $var_name . ' lang_dt`'; + } + elseif ($this->is_union_query) { + $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' lang_dt`'; + } + } + $added[$var_name] = 1; + } + } + if (!in_array($tbl_alias, $this->index['sub_joins'])) { + $this->index['sub_joins'][] = $tbl_alias; + } + } + } + return $r ? $r : '1 AS `success`'; + } + + function getVarTableInfos($var, $ignore_initial_index = 1) { + if ($var == '*') { + return array('table' => '', 'col' => '', 'table_alias' => '*'); + } + if ($infos = $this->v($var, 0, $this->index['vars'])) { + $infos[0]['table_alias'] = 'T_' . $infos[0]['table'] . '.' . $infos[0]['col']; + return $infos[0]; + } + if ($infos = $this->v($var, 0, $this->index['graph_vars'])) { + $infos[0]['col'] = 'g'; + $infos[0]['table_alias'] = 'G_' . $infos[0]['table'] . '.' . $infos[0]['col']; + return $infos[0]; + } + if ($this->is_union_query && !$ignore_initial_index) { + if (($infos = $this->v($var, 0, $this->initial_index['vars'])) || ($infos = $this->v($var, 0, $this->initial_index['graph_vars']))) { + if (!in_array($var, $this->infos['null_vars'])) { + $this->infos['null_vars'][] = $var; + } + $infos[0]['table_alias'] = 'NULL'; + $infos[0]['col'] = !isset($infos[0]['col']) ? '' : $infos[0]['col']; + return $infos[0]; + } + } + return 0; + } + + /* */ + + function getFROMSQL() { + $from_ids = $this->index['from']; + $r = ''; + foreach ($from_ids as $from_id) { + $r .= $r ? ', ' : ''; + $r .= $this->getTripleTable($from_id) . ' T_' . $from_id; + } + /* MySQL 5 requires parentheses in case of multiple tables */ + /* MySQL >5.5 (?) does not allow parentheses in case of a single table anymore! */ + $r = (count($from_ids) > 1) ? '(' . $r . ')' : $r; + return $r ? 'FROM ' . $r : ''; + } + + /* */ + + function getOrderedJoinIDs() { + return array_merge($this->index['from'], $this->index['join'], $this->index['left_join']); + } + + function getJoinInfos($id) { + $r = array(); + $tbl_ids = $this->getOrderedJoinIDs(); + $pattern = $this->getPattern($id); + foreach ($tbl_ids as $tbl_id) { + $tbl_pattern = $this->getPattern($tbl_id); + if ($tbl_id != $id) { + foreach (array('s', 'p', 'o') as $tbl_term) { + foreach (array('var', 'bnode', 'uri') as $term_type) { + if ($tbl_pattern[$tbl_term . '_type'] == $term_type) { + foreach (array('s', 'p', 'o') as $term) { + if (($pattern[$term . '_type'] == $term_type) && ($tbl_pattern[$tbl_term] == $pattern[$term])) { + $r[] = array('term' => $term, 'join_tbl' => $tbl_id, 'join_term' => $tbl_term); + } + } + } + } + } + } + } + return $r; + } + + function getAllJoinsSQL() { + $js = $this->getJoins(); + $ljs = $this->getLeftJoins(); + $entries = array_merge($js, $ljs); + $id2code = array(); + foreach ($entries as $entry) { + if (preg_match('/([^\s]+) ON (.*)/s', $entry, $m)) { + $id2code[$m[1]] = $entry; + } + } + $deps = array(); + foreach ($id2code as $id => $code) { + $deps[$id]['rank'] = 0; + foreach ($id2code as $other_id => $other_code) { + $deps[$id]['rank'] += ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0; + $deps[$id][$other_id] = ($id != $other_id) && preg_match('/' . $other_id . '/', $code) ? 1 : 0; + } + } + $r = ''; + do { + /* get next 0-rank */ + $next_id = 0; + foreach ($deps as $id => $infos) { + if ($infos['rank'] == 0) { + $next_id = $id; + break; + } + } + if ($next_id) { + $r .= "\n" . $id2code[$next_id]; + unset($deps[$next_id]); + foreach ($deps as $id => $infos) { + $deps[$id]['rank'] = 0; + unset($deps[$id][$next_id]); + foreach ($infos as $k => $v) { + if (!in_array($k, array('rank', $next_id))) { + $deps[$id]['rank'] += $v; + $deps[$id][$k] = $v; + } + } + } + } + } + while ($next_id); + if ($deps) { + $this->addError('Not all patterns could be rewritten to SQL JOINs'); + } + return $r; + } + + function getJoins() { + $r = array(); + $nl = "\n"; + foreach ($this->index['join'] as $id) { + $sub_r = $this->getJoinConditionSQL($id); + $r[] = 'JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')'; + } + foreach (array_merge($this->index['from'], $this->index['join']) as $id) { + if ($sub_r = $this->getRequiredSubJoinSQL($id)) { + $r[] = $sub_r; + } + } + return $r; + } + + function getLeftJoins() { + $r = array(); + $nl = "\n"; + foreach ($this->index['left_join'] as $id) { + $sub_r = $this->getJoinConditionSQL($id); + $r[] = 'LEFT JOIN ' . $this->getTripleTable($id) . ' T_' . $id . ' ON (' . $sub_r . $nl . ')'; + } + foreach ($this->index['left_join'] as $id) { + if ($sub_r = $this->getRequiredSubJoinSQL($id, 'LEFT')) { + $r[] = $sub_r; + } + } + return $r; + } + + function getJoinConditionSQL($id) { + $r = ''; + $nl = "\n"; + $infos = $this->getJoinInfos($id); + $pattern = $this->getPattern($id); + + $tbl = 'T_' . $id; + /* core dependency */ + $d_tbls = $this->getDependentJoins($id); + foreach ($d_tbls as $d_tbl) { + if (preg_match('/^T_([0-9\_]+)\.[spo]+/', $d_tbl, $m) && ($m[1] != $id)) { + if ($this->isJoinedBefore($m[1], $id) && !in_array($m[1], array_merge($this->index['from'], $this->index['join']))) { + $r .= $r ? $nl . ' AND ' : $nl . ' '; + $r .= '(' . $d_tbl . ' IS NOT NULL)'; + } + $this->logDependency($id, $d_tbl); + } + } + /* triple-based join info */ + foreach ($infos as $info) { + if ($this->isJoinedBefore($info['join_tbl'], $id) && $this->joinDependsOn($id, $info['join_tbl'])) { + $r .= $r ? $nl . ' AND ' : $nl . ' '; + $r .= '(' . $tbl . '.' . $info['term'] . ' = T_' . $info['join_tbl'] . '.' . $info['join_term'] . ')'; + } + } + /* filters etc */ + if ($sub_r = $this->getPatternSQL($pattern, 'join__T_' . $id)) { + $r .= $r ? $nl . ' AND ' . $sub_r : $nl . ' ' . '(' . $sub_r . ')'; + } + return $r; + } + + /** + * A log of identified table join dependencies in getJoinConditionSQL + * + */ + + function logDependency($id, $tbl) { + if (!isset($this->dependency_log[$id])) $this->dependency_log[$id] = array(); + if (!in_array($tbl, $this->dependency_log[$id])) { + $this->dependency_log[$id][] = $tbl; + } + } + + /** + * checks whether entries in the dependecy log could perhaps be optimized + * (triggers re-ordering of patterns + */ + + function problematicDependencies() { + foreach ($this->dependency_log as $id => $tbls) { + if (count($tbls) > 1) return count($tbls); + } + return 0; + } + + function isJoinedBefore($tbl_1, $tbl_2) { + $tbl_ids = $this->getOrderedJoinIDs(); + foreach ($tbl_ids as $id) { + if ($id == $tbl_1) { + return 1; + } + if ($id == $tbl_2) { + return 0; + } + } + } + + function joinDependsOn($id, $id2) { + if (in_array($id2, array_merge($this->index['from'], $this->index['join']))) { + return 1; + } + $d_tbls = $this->getDependentJoins($id2); + //echo $id . ' :: ' . $id2 . '=>' . print_r($d_tbls, 1); + foreach ($d_tbls as $d_tbl) { + if (preg_match('/^T_' .$id. '\./', $d_tbl)) { + return 1; + } + } + return 0; + } + + function getDependentJoins($id) { + $r = array(); + /* sub joins */ + foreach ($this->index['sub_joins'] as $alias) { + if (preg_match('/^(T|V|G)_' . $id . '/', $alias)) { + $r[] = $alias; + } + } + /* siblings in shared optional */ + $o_id = $this->getOptionalPattern($id); + foreach ($this->index['sub_joins'] as $alias) { + if (preg_match('/^(T|V|G)_' . $o_id . '/', $alias) && !in_array($alias, $r)) { + $r[] = $alias; + } + } + foreach ($this->index['left_join'] as $alias) { + if (preg_match('/^' . $o_id . '/', $alias) && !in_array($alias, $r)) { + $r[] = 'T_' . $alias . '.s'; + } + } + return $r; + } + + /* */ + + function getRequiredSubJoinSQL($id, $prefix = '') {/* id is a triple pattern id. Optional FILTERS and GRAPHs are getting added to the join directly */ + $nl = "\n"; + $r = ''; + foreach ($this->index['sub_joins'] as $alias) { + if (preg_match('/^V_' . $id . '_([a-z\_]+)\.val$/', $alias, $m)) { + $col = $m[1]; + $sub_r = ''; + if ($this->isOptionalPattern($id)) { + $pattern = $this->getPattern($id); + do { + $pattern = $this->getPattern($pattern['parent_id']); + } while ($pattern['parent_id'] && ($pattern['type'] != 'optional')); + $sub_r = $this->getPatternSQL($pattern, 'sub_join__V_' . $id); + } + $sub_r = $sub_r ? $nl . ' AND (' . $sub_r . ')' : ''; + /* lang dt only on literals */ + if ($col == 'o_lang_dt') { + $sub_sub_r = 'T_' . $id . '.o_type = 2'; + $sub_r .= $nl . ' AND (' . $sub_sub_r . ')'; + } + //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_'; + $cur_prefix = $prefix ? $prefix . ' ' : ''; + if ($col == 'g') { + $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. ' (G_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')'); + } + else { + $r .= trim($cur_prefix . 'JOIN '. $this->getValueTable($col) . ' V_' .$id . '_' . $col. ' ON (' .$nl. ' (T_' . $id . '.' . $col. ' = V_' . $id. '_' . $col. '.id) ' . $sub_r . $nl . ')'); + } + } + elseif (preg_match('/^G_' . $id . '\.g$/', $alias, $m)) { + $pattern = $this->getPattern($id); + $sub_r = $this->getPatternSQL($pattern, 'graph_sub_join__G_' . $id); + $sub_r = $sub_r ? $nl . ' AND ' . $sub_r : ''; + /* dataset restrictions */ + $gi = $this->getGraphInfos($id); + $sub_sub_r = ''; + $added_gts = array(); + foreach ($gi as $set) { + if (isset($set['graph']) && !in_array($set['graph'], $added_gts)) { + $sub_sub_r .= $sub_sub_r !== '' ? ',' : ''; + $sub_sub_r .= $this->getTermID($set['graph'], 'g'); + $added_gts[] = $set['graph']; + } + } + $sub_r .= ($sub_sub_r !== '') ? $nl . ' AND (G_' . $id . '.g IN (' . $sub_sub_r . '))' : ''; // /* ' . str_replace('#' , '::', $set['graph']) . ' */'; + /* other graph join conditions */ + foreach ($this->index['graph_vars'] as $var => $occurs) { + $occur_tbls = array(); + foreach ($occurs as $occur) { + $occur_tbls[] = $occur['table']; + if ($occur['table'] == $id) break; + } + foreach($occur_tbls as $tbl) { + if (($tbl != $id) && in_array($id, $occur_tbls) && $this->isJoinedBefore($tbl, $id)) { + $sub_r .= $nl . ' AND (G_' .$id. '.g = G_' .$tbl. '.g)'; + } + } + } + //$cur_prefix = $prefix ? $prefix . ' ' : 'STRAIGHT_'; + $cur_prefix = $prefix ? $prefix . ' ' : ''; + $r .= trim($cur_prefix . 'JOIN '. $this->getGraphTable() . ' G_' .$id . ' ON (' .$nl. ' (T_' . $id . '.t = G_' .$id. '.t)' . $sub_r . $nl . ')'); + } + } + return $r; + } + + /* */ + + function getWHERESQL() { + $r = ''; + $nl = "\n"; + /* standard constraints */ + $sub_r = $this->getPatternSQL($this->getPattern('0'), 'where'); + /* additional constraints */ + foreach ($this->index['from'] as $id) { + if ($sub_sub_r = $this->getConstraintSQL($id)) { + $sub_r .= $sub_r ? $nl . ' AND ' . $sub_sub_r : $sub_sub_r; + } + } + $r .= $sub_r ? $sub_r : ''; + /* left join dependencies */ + foreach ($this->index['left_join'] as $id) { + $d_joins = $this->getDependentJoins($id); + $added = array(); + $d_aliases = array(); + //echo $id . ' =>' . print_r($d_joins, 1); + $id_alias = 'T_' . $id . '.s'; + foreach ($d_joins as $alias) { + if (preg_match('/^(T|V|G)_([0-9\_]+)(_[spo])?\.([a-z\_]+)/', $alias, $m)) { + $tbl_type = $m[1]; + $tbl_pattern_id = $m[2]; + $suffix = $m[3]; + if (($tbl_pattern_id >= $id) && $this->sameOptional($tbl_pattern_id, $id)) {/* get rid of dependency permutations and nested optionals */ + if (!in_array($tbl_type . '_' . $tbl_pattern_id . $suffix, $added)) { + $sub_r .= $sub_r ? ' AND ' : ''; + $sub_r .= $alias . ' IS NULL'; + $d_aliases[] = $alias; + $added[] = $tbl_type . '_' . $tbl_pattern_id . $suffix; + $id_alias = ($tbl_pattern_id == $id) ? $alias : $id_alias; + } + } + } + } + if (count($d_aliases) > 2) {/* @@todo fix this! */ + $sub_r1 = ' /* '.$id_alias.' dependencies */'; + $sub_r2 = '((' . $id_alias . ' IS NULL) OR (CONCAT(' . join(', ', $d_aliases) . ') IS NOT NULL))'; + $r .= $r ? $nl . $sub_r1 . $nl . ' AND ' .$sub_r2 : $sub_r1 . $nl . $sub_r2; + } + } + return $r ? $nl . 'WHERE ' . $r : ''; + } + + /* */ + + function addConstraintSQLEntry($id, $sql) { + if (!isset($this->index['constraints'][$id])) { + $this->index['constraints'][$id] = array(); + } + if (!in_array($sql, $this->index['constraints'][$id])) { + $this->index['constraints'][$id][] = $sql; + } + } + + function getConstraintSQL($id) { + $r = ''; + $nl = "\n"; + $constraints = $this->v($id, array(), $this->index['constraints']); + foreach ($constraints as $constraint) { + $r .= $r ? $nl . ' AND ' . $constraint : $constraint; + } + return $r; + } + + /* */ + + function getPatternSQL($pattern, $context) { + $type = $this->v('type', '', $pattern); + if (!$type) { + return ''; + } + $m = 'get' . ucfirst($type) . 'PatternSQL'; + return method_exists($this, $m) ? $this->$m($pattern, $context) : $this->getDefaultPatternSQL($pattern, $context); + } + + function getDefaultPatternSQL($pattern, $context) { + $r = ''; + $nl = "\n"; + $sub_ids = $this->v('patterns', array(), $pattern); + foreach ($sub_ids as $sub_id) { + $sub_r = $this->getPatternSQL($this->getPattern($sub_id), $context); + $r .= ($r && $sub_r) ? $nl . ' AND (' . $sub_r . ')' : ($sub_r ? $sub_r : ''); + } + return $r ? $r : ''; + } + + function getTriplePatternSQL($pattern, $context) { + $r = ''; + $nl = "\n"; + $id = $pattern['id']; + /* s p o */ + $vars = array(); + foreach (array('s', 'p', 'o') as $term) { + $sub_r = ''; + $type = $pattern[$term . '_type']; + if ($type == 'uri') { + $term_id = $this->getTermID($pattern[$term], $term); + $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . preg_replace('/[\#\*\>]/' , '::', $pattern[$term]) . ' */'; + } + elseif ($type == 'literal') { + $term_id = $this->getTermID($pattern[$term], $term); + $sub_r = '(T_' . $id . '.' . $term . ' = ' . $term_id . ') /* ' . preg_replace('/[\#\n\*\>]/' , ' ', $pattern[$term]) . ' */'; + if (($lang_dt = $this->v1($term . '_lang', '', $pattern)) || ($lang_dt = $this->v1($term . '_datatype', '', $pattern))) { + $lang_dt_id = $this->getTermID($lang_dt); + $sub_r .= $nl . ' AND (T_' . $id . '.' .$term. '_lang_dt = ' . $lang_dt_id . ') /* ' . preg_replace('/[\#\*\>]/' , '::', $lang_dt) . ' */'; + } + } + elseif ($type == 'var') { + $val = $pattern[$term]; + if (isset($vars[$val])) {/* repeated var in pattern */ + $sub_r = '(T_' . $id . '.' . $term . '=' . 'T_' . $id . '.' . $vars[$val] . ')'; + } + $vars[$val] = $term; + if ($infos = $this->v($val, 0, $this->index['graph_vars'])) {/* graph var in triple pattern */ + $sub_r .= $sub_r ? $nl . ' AND ' : ''; + $tbl = $infos[0]['table']; + $sub_r .= 'G_' . $tbl . '.g = T_' . $id . '.' . $term; + } + } + if ($sub_r) { + if (preg_match('/^(join)/', $context) || (preg_match('/^where/', $context) && in_array($id, $this->index['from']))) { + $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r; + } + } + } + /* g */ + if ($infos = $pattern['graph_infos']) { + $tbl_alias = 'G_' . $id . '.g'; + if (!in_array($tbl_alias, $this->index['sub_joins'])) { + $this->index['sub_joins'][] = $tbl_alias; + } + $sub_r = array('graph_var' => '', 'graph_uri' => '', 'from' => '', 'from_named' => ''); + foreach ($infos as $info) { + $type = $info['type']; + if ($type == 'graph') { + if ($info['uri']) { + $term_id = $this->getTermID($info['uri'], 'g'); + $sub_r['graph_uri'] .= $sub_r['graph_uri'] ? $nl . ' AND ' : ''; + $sub_r['graph_uri'] .= '(' .$tbl_alias. ' = ' . $term_id . ') /* ' . preg_replace('/[\#\*\>]/' , '::', $info['uri']) . ' */'; + } + } + } + if ($sub_r['from'] && $sub_r['from_named']) { + $sub_r['from_named'] = ''; + } + if (!$sub_r['from'] && !$sub_r['from_named']) { + $sub_r['graph_var'] = ''; + } + if (preg_match('/^(graph_sub_join)/', $context)) { + foreach ($sub_r as $g_type => $g_sql) { + if ($g_sql) { + $r .= $r ? $nl . ' AND ' . $g_sql : $g_sql; + } + } + } + } + /* optional sibling filters? */ + if (preg_match('/^(join|sub_join)/', $context) && $this->isOptionalPattern($id)) { + $o_pattern = $pattern; + do { + $o_pattern = $this->getPattern($o_pattern['parent_id']); + } while ($o_pattern['parent_id'] && ($o_pattern['type'] != 'optional')); + if ($sub_r = $this->getPatternSQL($o_pattern, 'optional_filter' . preg_replace('/^(.*)(__.*)$/', '\\2', $context))) { + $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r; + } + /* created constraints */ + if ($sub_r = $this->getConstraintSQL($id)) { + $r .= $r ? $nl . ' AND ' . $sub_r : $sub_r; + } + } + /* result */ + if (preg_match('/^(where)/', $context) && $this->isOptionalPattern($id)) { + return ''; + } + return $r; + } + + /* */ + + function getFilterPatternSQL($pattern, $context) { + $r = ''; + $id = $pattern['id']; + $constraint_id = $this->v1('constraint', '', $pattern); + $constraint = $this->getPattern($constraint_id); + $constraint_type = $constraint['type']; + if ($constraint_type == 'built_in_call') { + $r = $this->getBuiltInCallSQL($constraint, $context); + } + elseif ($constraint_type == 'expression') { + $r = $this->getExpressionSQL($constraint, $context, '', 'filter'); + } + else { + $m = 'get' . ucfirst($constraint_type) . 'ExpressionSQL'; + if (method_exists($this, $m)) { + $r = $this->$m($constraint, $context, '', 'filter'); + } + } + if ($this->isOptionalPattern($id) && !preg_match('/^(join|optional_filter)/', $context)) { + return ''; + } + /* unconnected vars in FILTERs eval to false */ + $sub_r = $this->hasUnconnectedFilterVars($id); + if ($sub_r) { + if ($sub_r == 'alias') { + if (!in_array($r, $this->index['havings'])) $this->index['havings'][] = $r; + return ''; + } + elseif (preg_match('/^T([^\s]+\.)g (.*)$/s', $r, $m)) {/* graph filter */ + return 'G' . $m[1] . 't ' . $m[2]; + } + elseif (preg_match('/^\(*V[^\s]+_g\.val .*$/s', $r, $m)) {/* graph value filter, @@improveMe */ + //return $r; + } + else { + return 'FALSE'; + } + } + /* some really ugly tweaks */ + /* empty language filter: FILTER ( lang(?v) = '' ) */ + $r = preg_replace('/\(\/\* language call \*\/ ([^\s]+) = ""\)/s', '((\\1 = "") OR (\\1 LIKE "%:%"))', $r); + return $r; + } + + /** + * Checks if vars in the given (filter) pattern are used within the filter's scope. + */ + + function hasUnconnectedFilterVars($filter_pattern_id) { + $scope_id = $this->getFilterScope($filter_pattern_id); + $vars = $this->getFilterVars($filter_pattern_id); + $r = 0; + foreach ($vars as $var_name) { + if ($this->isUsedTripleVar($var_name, $scope_id)) continue; + if ($this->isAliasVar($var_name)) { + $r = 'alias'; + break; + } + $r = 1; + break; + } + return $r; + } + + /** + * Returns the given filter pattern's scope (the id of the parent group pattern). + */ + + function getFilterScope($filter_pattern_id) { + $patterns = $this->initial_index['patterns']; + $r = ''; + foreach ($patterns as $id => $p) { + /* the id has to be sub-part of the given filter id */ + if (!preg_match('/^' . $id . '.+/', $filter_pattern_id)) continue; + /* we are looking for a group or union */ + if (!preg_match('/^(group|union)$/', $p['type'])) continue; + /* we are looking for the longest/deepest match */ + if (strlen($id) > strlen($r)) $r = $id; + } + return $r; + } + + /** + * Builds a list of vars used in the given (filter) pattern. + */ + + function getFilterVars($filter_pattern_id) { + $r = array(); + $patterns = $this->initial_index['patterns']; + /* find vars in the given filter (i.e. the given id is part of their pattern id) */ + foreach ($patterns as $id => $p) { + if (!preg_match('/^' . $filter_pattern_id . '.+/', $id)) continue; + $var_name = ''; + if ($p['type'] == 'var') { + $var_name = $p['value']; + } + elseif (($p['type'] == 'built_in_call') && ($p['call'] == 'bound')) { + $var_name = $p['args'][0]['value']; + } + if ($var_name && !in_array($var_name, $r)) { + $r[] = $var_name; + } + } + return $r; + } + + /** + * Checks if $var_name appears as result projection alias. + */ + + function isAliasVar($var_name) { + foreach ($this->infos['query']['result_vars'] as $r_var) { + if ($r_var['alias'] == $var_name) return 1; + } + return 0; + } + + /** + * Checks if $var_name is used in a triple pattern in the given scope + */ + + function isUsedTripleVar($var_name, $scope_id = '0') { + $patterns = $this->initial_index['patterns']; + foreach ($patterns as $id => $p) { + if ($p['type'] != 'triple') continue; + if (!preg_match('/^' . $scope_id . '.+/', $id)) continue; + foreach (array('s', 'p', 'o') as $term) { + if ($p[$term . '_type'] != 'var') continue; + if ($p[$term] == $var_name) return 1; + } + } + } + + /* */ + + function getExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') { + $r = ''; + $nl = "\n"; + $type = $this->v1('type', '', $pattern); + $sub_type = $this->v1('sub_type', $type, $pattern); + if (preg_match('/^(and|or)$/', $sub_type)) { + foreach ($pattern['patterns'] as $sub_id) { + $sub_pattern = $this->getPattern($sub_id); + $sub_pattern_type = $sub_pattern['type']; + if ($sub_pattern_type == 'built_in_call') { + $sub_r = $this->getBuiltInCallSQL($sub_pattern, $context, '', $parent_type); + } + else { + $sub_r = $this->getExpressionSQL($sub_pattern, $context, '', $parent_type); + } + if ($sub_r) { + $r .= $r ? ' ' . strtoupper($sub_type). ' (' .$sub_r. ')' : '(' . $sub_r . ')'; + } + } + } + elseif ($sub_type == 'built_in_call') { + $r = $this->getBuiltInCallSQL($pattern, $context, $val_type, $parent_type); + } + elseif (preg_match('/literal/', $sub_type)) { + $r = $this->getLiteralExpressionSQL($pattern, $context, $val_type, $parent_type); + } + elseif ($sub_type) { + $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL'; + if (method_exists($this, $m)) { + $r = $this->$m($pattern, $context, '', $parent_type); + } + } + /* skip expressions that reference non-yet-joined tables */ + if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) { + $context_pattern_id = $m[2]; + $context_table_type = $m[1]; + if (preg_match_all('/((T|V|G)(\_[0-9])+)/', $r, $m)) { + $aliases = $m[1]; + $keep = 1; + foreach ($aliases as $alias) { + if (preg_match('/(T|V|G)_(.*)$/', $alias, $m)) { + $tbl_type = $m[1]; + $tbl = $m[2]; + if (!$this->isJoinedBefore($tbl, $context_pattern_id)) { + $keep = 0; + } + elseif (($context_pattern_id == $tbl) && preg_match('/(TV)/', $context_table_type . $tbl_type)) { + $keep = 0; + } + } + } + $r = $keep ? $r : ''; + } + } + return $r ? '(' . $r . ')' : $r; + } + + function detectExpressionValueType($pattern_ids) { + foreach ($pattern_ids as $id) { + $pattern = $this->getPattern($id); + $type = $this->v('type', '', $pattern); + if (($type == 'literal') && isset($pattern['datatype'])) { + if (in_array($pattern['datatype'], array($this->xsd . 'integer', $this->xsd . 'float', $this->xsd . 'double'))) { + return 'numeric'; + } + } + } + return ''; + } + + /* */ + + function getRelationalExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') { + $r = ''; + $val_type = $this->detectExpressionValueType($pattern['patterns']); + $op = $pattern['operator']; + foreach ($pattern['patterns'] as $sub_id) { + $sub_pattern = $this->getPattern($sub_id); + $sub_pattern['parent_op'] = $op; + $sub_type = $sub_pattern['type']; + $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL'; + $m = str_replace('ExpressionExpression', 'Expression', $m); + $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'relational') : ''; + $r .= $r ? ' ' . $op . ' ' . $sub_r : $sub_r; + } + return $r ? '(' . $r . ')' : $r; + } + + function getAdditiveExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') { + $r = ''; + $val_type = $this->detectExpressionValueType($pattern['patterns']); + foreach ($pattern['patterns'] as $sub_id) { + $sub_pattern = $this->getPattern($sub_id); + $sub_type = $this->v('type', '', $sub_pattern); + $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL'; + $m = str_replace('ExpressionExpression', 'Expression', $m); + $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'additive') : ''; + $r .= $r ? ' ' . $sub_r : $sub_r; + } + return $r; + } + + function getMultiplicativeExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') { + $r = ''; + $val_type = $this->detectExpressionValueType($pattern['patterns']); + foreach ($pattern['patterns'] as $sub_id) { + $sub_pattern = $this->getPattern($sub_id); + $sub_type = $sub_pattern['type']; + $m = ($sub_type == 'built_in_call') ? 'getBuiltInCallSQL' : 'get' . ucfirst($sub_type) . 'ExpressionSQL'; + $m = str_replace('ExpressionExpression', 'Expression', $m); + $sub_r = method_exists($this, $m) ? $this->$m($sub_pattern, $context, $val_type, 'multiplicative') : ''; + $r .= $r ? ' ' . $sub_r : $sub_r; + } + return $r; + } + + /* */ + + function getVarExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') { + $var = $pattern['value']; + $info = $this->getVarTableInfos($var); + if (!$tbl = $info['table']) { + /* might be an aggregate var */ + $vars = $this->infos['query']['result_vars']; + foreach ($vars as $test_var) { + if ($test_var['alias'] == $pattern['value']) { + return '`' . $pattern['value'] . '`'; + } + } + return ''; + } + $col = $info['col']; + if (($context == 'order') && ($col == 'o')) { + $tbl_alias = 'T_' . $tbl . '.o_comp'; + } + elseif ($context == 'sameterm') { + $tbl_alias = 'T_' . $tbl . '.' . $col; + } + elseif (($parent_type == 'relational') && ($col == 'o') && (preg_match('/[\<\>]/', $this->v('parent_op', '', $pattern)))) { + $tbl_alias = 'T_' . $tbl . '.o_comp'; + } + else { + $tbl_alias = 'V_' . $tbl . '_' . $col . '.val'; + if (!in_array($tbl_alias, $this->index['sub_joins'])) { + $this->index['sub_joins'][] = $tbl_alias; + } + } + $op = $this->v('operator', '', $pattern); + if (preg_match('/^(filter|and)/', $parent_type)) { + if ($op == '!') { + $r = '(((' . $tbl_alias . ' = 0) AND (CONCAT("1", ' . $tbl_alias . ') != 1))'; /* 0 and no string */ + $r .= ' OR (' . $tbl_alias . ' IN ("", "false")))'; /* or "", or "false" */ + } + else { + $r = '((' . $tbl_alias . ' != 0)'; /* not null */ + $r .= ' OR ((CONCAT("1", ' . $tbl_alias . ') = 1) AND (' . $tbl_alias . ' NOT IN ("", "false"))))'; /* string, and not "" or "false" */ + } + } + else { + $r = trim($op . ' ' . $tbl_alias); + if ($val_type == 'numeric') { + if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) { + $context_pattern_id = $m[2]; + $context_table_type = $m[1]; + } + else { + $context_pattern_id = $pattern['id']; + $context_table_type = 'T'; + } + if ($this->isJoinedBefore($tbl, $context_pattern_id)) { + $add = ($tbl != $context_pattern_id) ? 1 : 0; + $add = (!$add && ($context_table_type == 'V')) ? 1 : 0; + if ($add) { + $this->addConstraintSQLEntry($context_pattern_id, '(' .$r. ' = "0" OR ' . $r . '*1.0 != 0)'); + } + } + } + } + return $r; + } + + /* */ + + function getUriExpressionSQL($pattern, $context, $val_type = '') { + $val = $pattern['uri']; + $r = $pattern['operator']; + $r .= is_numeric($val) ? ' ' . $val : ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"'; + return $r; + } + + /* */ + + function getLiteralExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') { + $val = $pattern['value']; + $r = $pattern['operator']; + if (is_numeric($val) && $this->v('datatype', 0, $pattern)) { + $r .= ' ' . $val; + } + elseif (preg_match('/^(true|false)$/i', $val) && ($this->v1('datatype', '', $pattern) == 'http://www.w3.org/2001/XMLSchema#boolean')) { + $r .= ' ' . strtoupper($val); + } + elseif ($parent_type == 'regex') { + $sub_r = mysql_real_escape_string($val, $this->store->getDBCon()); + $r .= ' "' . preg_replace('/\x5c\x5c/', '\\', $sub_r) . '"'; + } + else { + $r .= ' "' . mysql_real_escape_string($val, $this->store->getDBCon()) . '"'; + } + if (($lang_dt = $this->v1('lang', '', $pattern)) || ($lang_dt = $this->v1('datatype', '', $pattern))) { + /* try table/alias via var in siblings */ + if ($var = $this->findSiblingVarExpression($pattern['id'])) { + if (isset($this->index['vars'][$var])) { + $infos = $this->index['vars'][$var]; + foreach ($infos as $info) { + if ($info['col'] == 'o') { + $tbl = $info['table']; + $term_id = $this->getTermID($lang_dt); + if ($pattern['operator'] != '!=') { + if (preg_match('/__(T|V|G)_(.+)$/', $context, $m)) { + $context_pattern_id = $m[2]; + $context_table_type = $m[1]; + } + elseif ($context == 'where') { + $context_pattern_id = $tbl; + } + else { + $context_pattern_id = $pattern['id']; + } + if ($tbl == $context_pattern_id) {/* @todo better dependency check */ + if ($term_id || ($lang_dt != 'http://www.w3.org/2001/XMLSchema#integer')) {/* skip if simple int, but no id */ + $this->addConstraintSQLEntry($context_pattern_id, 'T_' . $tbl . '.o_lang_dt = ' . $term_id . ' /* ' . preg_replace('/[\#\*\>]/' , '::', $lang_dt) . ' */'); + } + } + } + break; + } + } + } + } + } + return trim($r); + } + + function findSiblingVarExpression($id) { + $pattern = $this->getPattern($id); + do { + $pattern = $this->getPattern($pattern['parent_id']); + } while ($pattern['parent_id'] && ($pattern['type'] != 'expression')); + $sub_patterns = $this->v('patterns', array(), $pattern); + foreach ($sub_patterns as $sub_id) { + $sub_pattern = $this->getPattern($sub_id); + if ($sub_pattern['type'] == 'var') { + return $sub_pattern['value']; + } + } + return ''; + } + + /* */ + + function getFunctionExpressionSQL($pattern, $context, $val_type = '', $parent_type = '') { + $fnc_uri = $pattern['uri']; + $op = $this->v('operator', '', $pattern); + if ($op) $op .= ' '; + if ($this->allow_extension_functions) { + /* mysql functions */ + if (preg_match('/^http\:\/\/web\-semantics\.org\/ns\/mysql\/(.*)$/', $fnc_uri, $m)) { + $fnc_name = strtoupper($m[1]); + $sub_r = ''; + foreach ($pattern['args'] as $arg) { + $sub_r .= $sub_r ? ', ' : ''; + $sub_r .= $this->getExpressionSQL($arg, $context, $val_type, $parent_type); + } + return $op . $fnc_name . '(' . $sub_r . ')'; + } + /* any other: ignore */ + } + /* simple type conversions */ + if (strpos($fnc_uri, 'http://www.w3.org/2001/XMLSchema#') === 0) { + return $op . $this->getExpressionSQL($pattern['args'][0], $context, $val_type, $parent_type); + } + return ''; + } + + /* */ + + function getBuiltInCallSQL($pattern, $context) { + $call = $pattern['call']; + $m = 'get' . ucfirst($call) . 'CallSQL'; + if (method_exists($this, $m)) { + return $this->$m($pattern, $context); + } + else { + $this->addError('Unknown built-in call "' . $call . '"'); + } + return ''; + } + + function getBoundCallSQL($pattern, $context) { + $r = ''; + $var = $pattern['args'][0]['value']; + $info = $this->getVarTableInfos($var); + if (!$tbl = $info['table']) { + return ''; + } + $col = $info['col']; + $tbl_alias = 'T_' . $tbl . '.' . $col; + if ($pattern['operator'] == '!') { + return $tbl_alias . ' IS NULL'; + } + return $tbl_alias . ' IS NOT NULL'; + } + + function getHasTypeCallSQL($pattern, $context, $type) { + $r = ''; + $var = $pattern['args'][0]['value']; + $info = $this->getVarTableInfos($var); + if (!$tbl = $info['table']) { + return ''; + } + $col = $info['col']; + $tbl_alias = 'T_' . $tbl . '.' . $col . '_type'; + return $tbl_alias . ' ' .$this->v('operator', '', $pattern) . '= ' . $type; + } + + function getIsliteralCallSQL($pattern, $context) { + return $this->getHasTypeCallSQL($pattern, $context, 2); + } + + function getIsblankCallSQL($pattern, $context) { + return $this->getHasTypeCallSQL($pattern, $context, 1); + } + + function getIsiriCallSQL($pattern, $context) { + return $this->getHasTypeCallSQL($pattern, $context, 0); + } + + function getIsuriCallSQL($pattern, $context) { + return $this->getHasTypeCallSQL($pattern, $context, 0); + } + + function getStrCallSQL($pattern, $context) { + $sub_pattern = $pattern['args'][0]; + $sub_type = $sub_pattern['type']; + $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL'; + if (method_exists($this, $m)) { + return $this->$m($sub_pattern, $context); + } + } + + function getFunctionCallSQL($pattern, $context) { + $f_uri = $pattern['uri']; + if (preg_match('/(integer|double|float|string)$/', $f_uri)) {/* skip conversions */ + $sub_pattern = $pattern['args'][0]; + $sub_type = $sub_pattern['type']; + $m = 'get' . ucfirst($sub_type) . 'ExpressionSQL'; + if (method_exists($this, $m)) { + return $this->$m($sub_pattern, $context); + } + } + } + + function getLangDatatypeCallSQL($pattern, $context) { + $r = ''; + if (isset($pattern['patterns'])) { /* proceed with first argument only (assumed as base type for type promotion) */ + $sub_pattern = array('args' => array($pattern['patterns'][0])); + return $this->getLangDatatypeCallSQL($sub_pattern, $context); + } + if (!isset($pattern['args'])) { + return 'FALSE'; + } + $sub_type = $pattern['args'][0]['type']; + if ($sub_type != 'var') { + return $this->getLangDatatypeCallSQL($pattern['args'][0], $context); + } + $var = $pattern['args'][0]['value']; + $info = $this->getVarTableInfos($var); + if (!$tbl = $info['table']) { + return ''; + } + $col = 'o_lang_dt'; + $tbl_alias = 'V_' . $tbl . '_' . $col . '.val'; + if (!in_array($tbl_alias, $this->index['sub_joins'])) { + $this->index['sub_joins'][] = $tbl_alias; + } + $op = $this->v('operator', '', $pattern); + $r = trim($op . ' ' . $tbl_alias); + return $r; + } + + function getDatatypeCallSQL($pattern, $context) { + return '/* datatype call */ ' . $this->getLangDatatypeCallSQL($pattern, $context); + } + + function getLangCallSQL($pattern, $context) { + return '/* language call */ ' . $this->getLangDatatypeCallSQL($pattern, $context); + } + + function getLangmatchesCallSQL($pattern, $context) { + if (count($pattern['args']) == 2) { + $arg_1 = $pattern['args'][0]; + $arg_2 = $pattern['args'][1]; + $sub_r_1 = $this->getBuiltInCallSQL($arg_1, $context);/* adds value join */ + $sub_r_2 = $this->getExpressionSQL($arg_2, $context); + $op = $this->v('operator', '', $pattern); + if (preg_match('/^([\"\'])([^\'\"]+)/', $sub_r_2, $m)) { + if ($m[2] == '*') { + $r = ($op == '!') ? 'NOT (' . $sub_r_1 . ' REGEXP "^[a-zA-Z\-]+$"' . ')' : $sub_r_1 . ' REGEXP "^[a-zA-Z\-]+$"'; + } + else { + $r = ($op == '!') ? $sub_r_1 . ' NOT LIKE ' . $m[1] . $m[2] . '%' . $m[1] : $sub_r_1 . ' LIKE ' . $m[1] . $m[2] . '%' . $m[1]; + } + } + else { + $r = ($op == '!') ? $sub_r_1 . ' NOT LIKE CONCAT(' . $sub_r_2 . ', "%")' : $sub_r_1 . ' LIKE CONCAT(' . $sub_r_2 . ', "%")'; + } + return $r; + } + return ''; + } + + function getSametermCallSQL($pattern, $context) { + if (count($pattern['args']) == 2) { + $arg_1 = $pattern['args'][0]; + $arg_2 = $pattern['args'][1]; + $sub_r_1 = $this->getExpressionSQL($arg_1, 'sameterm'); + $sub_r_2 = $this->getExpressionSQL($arg_2, 'sameterm'); + $op = $this->v('operator', '', $pattern); + $r = $sub_r_1 . ' ' . $op . '= ' . $sub_r_2; + return $r; + } + return ''; + } + + function getRegexCallSQL($pattern, $context) { + $ac = count($pattern['args']); + if ($ac >= 2) { + foreach ($pattern['args'] as $i => $arg) { + $var = 'sub_r_' . ($i + 1); + $$var = $this->getExpressionSQL($arg, $context, '', 'regex'); + } + $sub_r_3 = (isset($sub_r_3) && preg_match('/[\"\'](.+)[\"\']/', $sub_r_3, $m)) ? strtolower($m[1]) : ''; + $op = ($this->v('operator', '', $pattern) == '!') ? ' NOT' : ''; + if (!$sub_r_1 || !$sub_r_2) return ''; + $is_simple_search = preg_match('/^[\(\"]+(\^)?([a-z0-9\_\-\s]+)(\$)?[\)\"]+$/is', $sub_r_2, $m); + $is_simple_search = preg_match('/^[\(\"]+(\^)?([^\\\*\[\]\}\{\(\)\"\'\?\+\.]+)(\$)?[\)\"]+$/is', $sub_r_2, $m); + $is_o_search = preg_match('/o\.val\)*$/', $sub_r_1); + /* fulltext search (may have "|") */ + if ($is_simple_search && $is_o_search && !$op && (strlen($m[2]) > 8) && $this->store->hasFulltextIndex()) { + /* MATCH variations */ + if (($val_parts = preg_split('/\|/', $m[2]))) { + return 'MATCH(' . trim($sub_r_1, '()') . ') AGAINST("' . join(' ', $val_parts) . '")'; + } + else { + return 'MATCH(' . trim($sub_r_1, '()') . ') AGAINST("' . $m[2] . '")'; + } + } + if (preg_match('/\|/', $sub_r_2)) $is_simple_search = 0; + /* LIKE */ + if ($is_simple_search && ($sub_r_3 == 'i')) { + $sub_r_2 = $m[1] ? $m[2] : '%' . $m[2]; + $sub_r_2 .= isset($m[3]) && $m[3] ? '' : '%'; + return $sub_r_1 . $op . ' LIKE "' . $sub_r_2 . '"'; + } + /* REGEXP */ + $opt = ($sub_r_3 == 'i') ? '' : 'BINARY '; + return $sub_r_1 . $op . ' REGEXP ' . $opt . $sub_r_2; + } + return ''; + } + + /* */ + + function getGROUPSQL() { + $r = ''; + $nl = "\n"; + $infos = $this->v('group_infos', array(), $this->infos['query']); + foreach ($infos as $info) { + $var = $info['value']; + if ($tbl_infos = $this->getVarTableInfos($var, 0)) { + $tbl_alias = $tbl_infos['table_alias']; + $r .= $r ? ', ' : 'GROUP BY '; + $r .= $tbl_alias; + } + } + $hr = ''; + foreach ($this->index['havings'] as $having) { + $hr .= $hr ? ' AND' : ' HAVING'; + $hr .= '(' . $having . ')'; + } + $r .= $hr; + return $r ? $nl . $r : $r; + } + + /* */ + + function getORDERSQL() { + $r = ''; + $nl = "\n"; + $infos = $this->v('order_infos', array(), $this->infos['query']); + foreach ($infos as $info) { + $type = $info['type']; + $ms = array('expression' => 'getExpressionSQL', 'built_in_call' => 'getBuiltInCallSQL', 'function_call' => 'getFunctionCallSQL'); + $m = isset($ms[$type]) ? $ms[$type] : 'get' . ucfirst($type) . 'ExpressionSQL'; + if (method_exists($this, $m)) { + $sub_r = '(' . $this->$m($info, 'order') . ')'; + $sub_r .= $this->v('direction', '', $info) == 'desc' ? ' DESC' : ''; + $r .= $r ? ',' .$nl . $sub_r : $sub_r; + } + } + return $r ? $nl . 'ORDER BY ' . $r : ''; + } + + /* */ + + function getLIMITSQL() { + $r = ''; + $nl = "\n"; + $limit = $this->v('limit', -1, $this->infos['query']); + $offset = $this->v('offset', -1, $this->infos['query']); + if ($limit != -1) { + $offset = ($offset == -1) ? 0 : mysql_real_escape_string($offset, $this->store->getDBCon()); + $r = 'LIMIT ' . $offset . ',' . $limit; + } + elseif ($offset != -1) { + $r = 'LIMIT ' . mysql_real_escape_string($offset, $this->store->getDBCon()) . ',999999999999'; /* mysql doesn't support stand-alone offsets .. */ + } + return $r ? $nl . $r : ''; + } + + /* */ + + function getValueSQL($q_tbl, $q_sql) { + $r = ''; + /* result vars */ + $vars = $this->infos['query']['result_vars']; + $nl = "\n"; + $v_tbls = array('JOIN' => array(), 'LEFT JOIN' => array()); + $vc = 1; + foreach ($vars as $var) { + $var_name = $var['var']; + $r .= $r ? ',' . $nl . ' ' : ' '; + $col = ''; + $tbl = ''; + if ($var_name != '*') { + if (in_array($var_name, $this->infos['null_vars'])) { + if (isset($this->initial_index['vars'][$var_name])) { + $col = $this->initial_index['vars'][$var_name][0]['col']; + $tbl = $this->initial_index['vars'][$var_name][0]['table']; + } + if (isset($this->initial_index['graph_vars'][$var_name])) { + $col = 'g'; + $tbl = $this->initial_index['graph_vars'][$var_name][0]['table']; + } + } + elseif (isset($this->index['vars'][$var_name])) { + $col = $this->index['vars'][$var_name][0]['col']; + $tbl = $this->index['vars'][$var_name][0]['table']; + } + } + if ($var['aggregate']) { + $r .= 'TMP.`' . $var['alias'] . '`'; + } + else { + $join_type = in_array($tbl, array_merge($this->index['from'], $this->index['join'])) ? 'JOIN' : 'LEFT JOIN';/* val may be NULL */ + $v_tbls[$join_type][] = array('t_col' => $col, 'q_col' => $var_name, 'vc' => $vc); + $r .= 'V' . $vc . '.val AS `' . $var_name . '`'; + if (in_array($col, array('s', 'o'))) { + if (strpos($q_sql, '`' . $var_name . ' type`')) { + $r .= ', ' . $nl . ' TMP.`' . $var_name . ' type` AS `' . $var_name . ' type`'; + //$r .= ', ' . $nl . ' CASE TMP.`' . $var_name . ' type` WHEN 2 THEN "literal" WHEN 1 THEN "bnode" ELSE "uri" END AS `' . $var_name . ' type`'; + } + else { + $r .= ', ' . $nl . ' NULL AS `' . $var_name . ' type`'; + } + } + $vc++; + if ($col == 'o') { + $v_tbls[$join_type][] = array('t_col' => 'id', 'q_col' => $var_name . ' lang_dt', 'vc' => $vc); + if (strpos($q_sql, '`' . $var_name . ' lang_dt`')) { + $r .= ', ' .$nl. ' V' . $vc . '.val AS `' . $var_name . ' lang_dt`'; + $vc++; + } + else { + $r .= ', ' .$nl. ' NULL AS `' . $var_name . ' lang_dt`'; + } + } + } + } + if (!$r) $r = '*'; + /* from */ + $r .= $nl . 'FROM (' . $q_tbl . ' TMP)'; + foreach (array('JOIN', 'LEFT JOIN') as $join_type) { + foreach ($v_tbls[$join_type] as $v_tbl) { + $tbl = $this->getValueTable($v_tbl['t_col']); + $var_name = preg_replace('/^([^\s]+)(.*)$/', '\\1', $v_tbl['q_col']); + $cur_join_type = in_array($var_name, $this->infos['null_vars']) ? 'LEFT JOIN' : $join_type; + if (!strpos($q_sql, '`' . $v_tbl['q_col'].'`')) continue; + $r .= $nl . ' ' . $cur_join_type . ' ' . $tbl . ' V' . $v_tbl['vc'] . ' ON ( + (V' . $v_tbl['vc'] . '.id = TMP.`' . $v_tbl['q_col'].'`) + )'; + } + } + /* create pos columns, id needed */ + if ($this->v('order_infos', array(), $this->infos['query'])) { + $r .= $nl . ' ORDER BY _pos_'; + } + return 'SELECT' . $nl . $r; + } + + /* */ + +} + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreSemHTMLLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreSemHTMLLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,36 @@ +extractRDF(); + } + + function addT($t) { + $this->caller->addT($t['s'], $t['p'], $t['o'], $t['s_type'], $t['o_type'], $t['o_datatype'], $t['o_lang']); + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreTableManager.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreTableManager.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,290 @@ +engine_type = $this->v('store_engine_type', 'MyISAM', $this->a); + } + + /* */ + + function getTableOptionsCode() { + $v = $this->getDBVersion(); + $r = ""; + $r .= (($v < '04-01-00') && ($v >= '04-00-18')) ? 'ENGINE' : (($v >= '04-01-02') ? 'ENGINE' : 'TYPE'); + $r .= "=" . $this->engine_type; + $r .= ($v >= '04-00-00') ? " CHARACTER SET utf8" : ""; + $r .= ($v >= '04-01-00') ? " COLLATE utf8_unicode_ci" : ""; + $r .= " DELAY_KEY_WRITE = 1"; + return $r; + } + + /* */ + + function createTables() { + $con = $this->getDBCon(); + if(!$this->createTripleTable()) { + return $this->addError('Could not create "triple" table (' . mysql_error($con) . ').'); + } + if(!$this->createG2TTable()) { + return $this->addError('Could not create "g2t" table (' . mysql_error($con) . ').'); + } + if(!$this->createID2ValTable()) { + return $this->addError('Could not create "id2val" table (' . mysql_error($con) . ').'); + } + if(!$this->createS2ValTable()) { + return $this->addError('Could not create "s2val" table (' . mysql_error($con) . ').'); + } + if(!$this->createO2ValTable()) { + return $this->addError('Could not create "o2val" table (' . mysql_error($con) . ').'); + } + if(!$this->createSettingTable()) { + return $this->addError('Could not create "setting" table (' . mysql_error($con) . ').'); + } + return 1; + } + + /* */ + + function createTripleTable($suffix = 'triple') { + /* keep in sync with merge def in StoreQueryHandler ! */ + $indexes = $this->v('store_indexes', array('sp (s,p)', 'os (o,s)', 'po (p,o)'), $this->a); + $index_code = $indexes ? 'KEY ' . join(', KEY ', $indexes) . ', ' : ''; + $sql = " + CREATE TABLE IF NOT EXISTS " . $this->getTablePrefix() . $suffix . " ( + t mediumint UNSIGNED NOT NULL, + s mediumint UNSIGNED NOT NULL, + p mediumint UNSIGNED NOT NULL, + o mediumint UNSIGNED NOT NULL, + o_lang_dt mediumint UNSIGNED NOT NULL, + o_comp char(35) NOT NULL, /* normalized value for ORDER BY operations */ + s_type tinyint(1) NOT NULL default 0, /* uri/bnode => 0/1 */ + o_type tinyint(1) NOT NULL default 0, /* uri/bnode/literal => 0/1/2 */ + misc tinyint(1) NOT NULL default 0, /* temporary flags */ + UNIQUE KEY (t), " . $index_code . " KEY (misc) + ) ". $this->getTableOptionsCode() . " + "; + return mysql_query($sql, $this->getDBCon()); + } + + function extendTripleTableColumns($suffix = 'triple') { + $sql = " + ALTER TABLE " . $this->getTablePrefix() . $suffix . " + MODIFY t int(10) UNSIGNED NOT NULL, + MODIFY s int(10) UNSIGNED NOT NULL, + MODIFY p int(10) UNSIGNED NOT NULL, + MODIFY o int(10) UNSIGNED NOT NULL, + MODIFY o_lang_dt int(10) UNSIGNED NOT NULL + "; + return mysql_query($sql, $this->getDBCon()); + } + + /* */ + + function createG2TTable() { + $sql = " + CREATE TABLE IF NOT EXISTS " . $this->getTablePrefix() . "g2t ( + g mediumint UNSIGNED NOT NULL, + t mediumint UNSIGNED NOT NULL, + UNIQUE KEY gt (g,t), KEY tg (t,g) + ) ". $this->getTableOptionsCode() . " + "; + return mysql_query($sql, $this->getDBCon()); + } + + function extendG2tTableColumns($suffix = 'g2t') { + $sql = " + ALTER TABLE " . $this->getTablePrefix() . $suffix . " + MODIFY g int(10) UNSIGNED NOT NULL, + MODIFY t int(10) UNSIGNED NOT NULL + "; + return mysql_query($sql, $this->getDBCon()); + } + + /* */ + + function createID2ValTable() { + $sql = " + CREATE TABLE IF NOT EXISTS " . $this->getTablePrefix() . "id2val ( + id mediumint UNSIGNED NOT NULL, + misc tinyint(1) NOT NULL default 0, + val text NOT NULL, + val_type tinyint(1) NOT NULL default 0, /* uri/bnode/literal => 0/1/2 */ + UNIQUE KEY (id,val_type), KEY v (val(64)) + ) ". $this->getTableOptionsCode() . " + "; + return mysql_query($sql, $this->getDBCon()); + } + + function extendId2valTableColumns($suffix = 'id2val') { + $sql = " + ALTER TABLE " . $this->getTablePrefix() . $suffix . " + MODIFY id int(10) UNSIGNED NOT NULL + "; + return mysql_query($sql, $this->getDBCon()); + } + + /* */ + + function createS2ValTable() { + //$indexes = 'UNIQUE KEY (id), KEY vh (val_hash), KEY v (val(64))'; + $indexes = 'UNIQUE KEY (id), KEY vh (val_hash)'; + $sql = " + CREATE TABLE IF NOT EXISTS " . $this->getTablePrefix() . "s2val ( + id mediumint UNSIGNED NOT NULL, + misc tinyint(1) NOT NULL default 0, + val_hash char(32) NOT NULL, + val text NOT NULL, + " . $indexes . " + ) " . $this->getTableOptionsCode() . " + "; + return mysql_query($sql, $this->getDBCon()); + } + + function extendS2valTableColumns($suffix = 's2val') { + $sql = " + ALTER TABLE " . $this->getTablePrefix() . $suffix . " + MODIFY id int(10) UNSIGNED NOT NULL + "; + return mysql_query($sql, $this->getDBCon()); + } + + /* */ + + function createO2ValTable() { + /* object value index, e.g. "KEY v (val(64))" and/or "FULLTEXT KEY vft (val)" */ + $val_index = $this->v('store_object_index', 'KEY v (val(64))', $this->a); + if ($val_index) $val_index = ', ' . ltrim($val_index, ','); + $sql = " + CREATE TABLE IF NOT EXISTS " . $this->getTablePrefix() . "o2val ( + id mediumint UNSIGNED NOT NULL, + misc tinyint(1) NOT NULL default 0, + val_hash char(32) NOT NULL, + val text NOT NULL, + UNIQUE KEY (id), KEY vh (val_hash)" . $val_index . " + ) ". $this->getTableOptionsCode() . " + "; + return mysql_query($sql, $this->getDBCon()); + } + + function extendO2valTableColumns($suffix = 'o2val') { + $sql = " + ALTER TABLE " . $this->getTablePrefix() . $suffix . " + MODIFY id int(10) UNSIGNED NOT NULL + "; + return mysql_query($sql, $this->getDBCon()); + } + + /* */ + + function createSettingTable() { + $sql = " + CREATE TABLE IF NOT EXISTS " . $this->getTablePrefix() . "setting ( + k char(32) NOT NULL, + val text NOT NULL, + UNIQUE KEY (k) + ) ". $this->getTableOptionsCode() . " + "; + return mysql_query($sql, $this->getDBCon()); + } + + /* */ + + function extendColumns() { + $con = $this->getDBCon(); + $tbl_prefix = $this->getTablePrefix(); + $tbls = $this->getTables(); + foreach ($tbls as $suffix) { + if (preg_match('/^(triple|g2t|id2val|s2val|o2val)/', $suffix, $m)) { + $mthd = 'extend' . ucfirst($m[1]) . 'TableColumns'; + $this->$mthd($suffix); + } + } + } + + /* */ + + function splitTables() { + $old_ps = $this->getSetting('split_predicates', array()); + $new_ps = $this->retrieveSplitPredicates(); + $add_ps = array_diff($new_ps, $old_ps); + $del_ps = array_diff($old_ps, $new_ps); + $final_ps = array(); + foreach ($del_ps as $p) { + if (!$this->unsplitPredicate($p)) $final_ps[] = $p; + } + foreach ($add_ps as $p) { + if ($this->splitPredicate($p)) $final_ps[] = $p; + } + $this->setSetting('split_predicates', $new_ps); + } + + function unsplitPredicate($p) { + $suffix = 'triple_' . abs(crc32($p)); + $old_tbl = $this->getTablePrefix() . $suffix; + $new_tbl = $this->getTablePrefix() . 'triple'; + $p_id = $this->getTermID($p, 'p'); + $con = $this->getDBCon(); + $sql = ' + INSERT IGNORE INTO ' . $new_tbl .' + SELECT * FROM ' . $old_tbl . ' WHERE ' . $old_tbl . '.p = ' . $p_id . ' + '; + if ($rs = mysql_query($sql, $con)) { + mysql_query('DROP TABLE ' . $old_tbl, $con); + return 1; + } + else { + return 0; + } + } + + function splitPredicate($p) { + $suffix = 'triple_' . abs(crc32($p)); + $this->createTripleTable($suffix); + $old_tbl = $this->getTablePrefix() . 'triple'; + $new_tbl = $this->getTablePrefix() . $suffix; + $p_id = $this->getTermID($p, 'p'); + $con = $this->getDBCon(); + $sql = ' + INSERT IGNORE INTO ' . $new_tbl .' + SELECT * FROM ' . $old_tbl . ' WHERE ' . $old_tbl . '.p = ' . $p_id . ' + '; + if ($rs = mysql_query($sql, $con)) { + mysql_query('DELETE FROM ' . $old_tbl . ' WHERE ' . $old_tbl . '.p = ' . $p_id, $con); + return 1; + } + else { + mysql_query('DROP TABLE ' . $new_tbl, $con); + return 0; + } + } + + function retrieveSplitPredicates() { + $r = $this->split_predicates; + $limit = $this->max_split_tables - count($r); + $q = 'SELECT ?p COUNT(?p) AS ?pc WHERE { ?s ?p ?o } GROUP BY ?p ORDER BY DESC(?pc) LIMIT ' . $limit; + $rows = $this->query($q, 'rows'); + foreach ($rows as $row) { + $r[] = $row['p']; + } + return $r; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/store/ARC2_StoreTurtleLoader.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/store/ARC2_StoreTurtleLoader.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,32 @@ +caller->addT($t['s'], $t['p'], $t['o'], $t['s_type'], $t['o_type'], $t['o_datatype'], $t['o_lang']); + $this->t_count++; + } + + /* */ + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/ARC2_TestCase.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/ARC2_TestCase.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +data_store = $data_store; + } + + function __init() { + parent::__init(); + $this->store = $this->caller; + ARC2::inc('Reader'); + $this->reader = new ARC2_Reader($this->a, $this); + } + + /* */ + + function runTest($id) { + $type = $this->getTestType($id); + $m = 'run' . $type; + $r = method_exists($this, $m) ? $this->$m($id) : array('pass' => 0, 'info' => 'not supported'); + sleep(1); + return $r; + } + + /* */ + + function getTestType($id) { + $q = 'SELECT ?type WHERE { <' .$id. '> a ?type . }'; + $qr = $this->store->query($q); + $r = isset($qr['result']['rows'][0]) ? $qr['result']['rows'][0]['type'] : '#QueryEvaluationTest'; + $r = preg_replace('/^.*\#([^\#]+)$/', '$1', $r); + return $r; + } + + /* */ + + function getFile($url) { + $fname = 'f' . crc32($url) . '.txt'; + if (!file_exists('tmp/' . $fname)) { + $r = ''; + if (!isset($this->reader)) { + $this->reader = new ARC2_Reader($this->a, $this); + } + $this->reader->activate($url); + while ($d = $this->reader->readStream()) { + $r .= $d; + } + $this->reader->closeStream(); + unset($this->reader); + $fp = @fopen('tmp/' . $fname, "w"); + @fwrite($fp, $r); + @fclose($fp); + return $r; + } + return file_get_contents('tmp/' . $fname); + } + + function runPositiveSyntaxTest($id) { + $nl = "\n"; + $r = ''; + /* get action */ + $q = ' + PREFIX mf: . + SELECT DISTINCT ?action WHERE { <' .$id. '> mf:action ?action . } + '; + $qr = $this->store->query($q); + $action = $qr['result']['rows'][0]['action']; + /* get code */ + $q = $this->getFile($action); + /* parse */ + ARC2::inc('SPARQLPlusParser'); + $parser = new ARC2_SPARQLPlusParser($this->a, $this); + $parser->parse($q, $action); + $infos = $parser->getQueryInfos(); + $rest = $parser->getUnparsedCode(); + $errors = $parser->getErrors(); + $r .= $nl . '
    ' . htmlspecialchars($q) . '
    ' . $nl ; + if ($errors || $rest) { + $pass = 0; + $r .= htmlspecialchars($nl . $nl . print_r($errors, 1) . $nl . print_r($rest, 1)); + } + else { + $pass = 1; + $r .= htmlspecialchars($nl . $nl . print_r($infos, 1)); + } + return array('pass' => $pass, 'info' => $r); + } + + /* */ + + function runNegativeSyntaxTest($id) { + $nl = "\n"; + $r = ''; + /* get action */ + $q = ' + PREFIX mf: . + SELECT DISTINCT ?action WHERE { <' .$id. '> mf:action ?action . } + '; + $qr = $this->store->query($q); + $action = $qr['result']['rows'][0]['action']; + /* get code */ + $q = $this->getFile($action); + /* parse */ + ARC2::inc('SPARQLPlusParser'); + $parser = new ARC2_SPARQLPlusParser($this->a, $this); + $parser->parse($q, $action); + $infos = $parser->getQueryInfos(); + $rest = $parser->getUnparsedCode(); + $errors = $parser->getErrors(); + $r .= $nl . '
    ' . htmlspecialchars($q) . '
    ' . $nl ; + if ($errors || $rest) { + $pass = 1; + $r .= htmlspecialchars($nl . $nl . print_r($errors, 1) . $nl . print_r($rest, 1)); + } + else { + $pass = 0; + $r .= htmlspecialchars($nl . $nl . print_r($infos, 1)); + } + return array('pass' => $pass, 'info' => $r); + } + + /* */ + + function runQueryEvaluationTest($id) { + $nl = "\n"; + $r = ''; + /* get action */ + $q = ' + PREFIX mf: . + PREFIX qt: . + SELECT DISTINCT ?query ?data ?graph_data ?result WHERE { + <' .$id. '> mf:action ?action ; + mf:result ?result . + ?action qt:query ?query . + OPTIONAL { + ?action qt:data ?data . + } + OPTIONAL { + ?action qt:graphData ?graph_data . + } + } + '; + $qr = $this->store->query($q); + $rows = $qr['result']['rows']; + $infos = array(); + foreach (array('query', 'data', 'result', 'graph_data') as $var) { + $infos[$var] = array(); + $infos[$var . '_value'] = array(); + foreach ($rows as $row) { + if (isset($row[$var])) { + if (!in_array($row[$var], $infos[$var])) { + $infos[$var][] = $row[$var]; + $infos[$var . '_value'][] = $this->getFile($row[$var]); + } + } + } + $$var = $infos[$var]; + ${$var . '_value'} = $infos[$var . '_value']; + if (count($infos[$var]) == 1) { + $$var = $infos[$var][0]; + ${$var . '_value'} = $infos[$var . '_value'][0]; + } + if ($$var && ($var != '-result')) { + //echo '
    ' . $$var . $nl . $nl . htmlspecialchars(${$var . '_value'}) . '

    '; + } + } + /* query infos */ + ARC2::inc('SPARQLPlusParser'); + $parser = new ARC2_SPARQLPlusParser($this->a, $this); + $parser->parse($query_value, $query); + $infos = $parser->getQueryInfos(); + $rest = $parser->getUnparsedCode(); + $errors = $parser->getErrors(); + $q_type = !$errors ? $infos['query']['type'] : ''; + /* add data */ + $dsets = array(); + $gdsets = array(); + if ($data) { + $dsets = is_array($data) ? array_merge($dsets, $data) : array_merge($dsets, array($data)); + } + if ($graph_data) { + $gdsets = is_array($graph_data) ? array_merge($gdsets, $graph_data) : array_merge($gdsets, array($graph_data)); + } + if (!$dsets && !$gdsets) { + foreach ($infos['query']['dataset'] as $set) { + if ($set['named']) { + $gdsets[] = $set['graph']; + } + else { + $dsets[] = $set['graph']; + } + } + } + $store = $this->data_store; + $store->reset(); + foreach ($dsets as $graph) { + $qr = $store->query('LOAD <' .$graph. '>'); + } + foreach ($gdsets as $graph) { + $qr = $store->query('LOAD <' .$graph. '> INTO <' .$graph. '>'); + } + /* run query */ + if ($query) { + $sql = $store->query($query_value, 'sql', $query); + $qr = $store->query($query_value, '', $query); + $qr_result = $qr['result']; + if ($q_type == 'select') { + $qr_result = $this->adjustBnodes($qr['result'], $id); + } + elseif ($q_type == 'construct') { + $ser = ARC2::getTurtleSerializer($this->a); + $qr_result = $ser->getSerializedIndex($qr_result); + } + } + //echo '
    query result: ' . $nl . htmlspecialchars(print_r($qr_result, 1)) . '
    '; + if (!$query || $errors || $rest) { + return array('pass' => 0, 'info' => 'query could not be parsed' . htmlspecialchars($query_value)); + } + $m = 'isSame' . $q_type . 'Result'; + $sub_r = $this->$m($qr_result, $result_value, $result, $id); + $pass = $sub_r['pass']; + if (in_array($id, array( + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/sort/manifest#dawg-sort-6', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/sort/manifest#dawg-sort-8', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/sort/manifest#dawg-sort-builtin', + ))) { + $pass = 0; /* manually checked 2007-09-18 */ + } + if (in_array($id, array( + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/sort/manifest#dawg-sort-function', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/reduced/manifest#reduced-1', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/reduced/manifest#reduced-2', + ))) { + $pass = 1; /* manually checked 2007-11-28 */ + } + $pass_info = $sub_r['info']; + $info = print_r($pass_info, 1) . $nl; + $info .= '
    sql: ' . $nl . htmlspecialchars($sql['result']) . '
    '; + $info .= $pass ? '' : print_r($graph_data, 1) . $nl . htmlspecialchars(print_r($graph_data_value, 1)) . '
    '; + $info .= $pass ? '' : print_r($data, 1) . $nl . htmlspecialchars(print_r($data_value, 1)) . '
    '; + $info .= $pass ? '' : $query . $nl . htmlspecialchars($query_value) . '
    '; + $info .= $pass ? '' : '
    query result: ' . $nl . htmlspecialchars(print_r($qr_result, 1)) . '
    ' . '
    '; + $info .= $pass ? '' : print_r($infos, 1); + return array('pass' => $pass, 'info' => $info); + } + + /* */ + + function isSameSelectResult($qr, $result, $result_base) { + if (strpos($result, 'http://www.w3.org/2001/sw/DataAccess/tests/result-set#')) { + $parser = ARC2::getRDFParser($this->a); + $parser->parse($result_base, $result); + $index = $parser->getSimpleIndex(0); + //echo '
    ' . print_r($index, 1) .'
    '; + $valid_qr = $this->buildTurtleSelectQueryResult($index); + } + else { + $parser = ARC2::getSPARQLXMLResultParser($this->a); + $parser->parse('', $result); + $valid_qr = $parser->getStructure(); + } + if (isset($valid_qr['boolean'])) { + $pass = $valid_qr['boolean'] == $this->v('boolean', '', $qr); + } + else { + $pass = 1; + if (count($valid_qr['variables']) != count($qr['variables'])) { + $pass = 0; + } + if (count($valid_qr['rows']) != count($qr['rows'])) { + $pass = 0; + } + if ($pass) { + foreach ($valid_qr['variables'] as $var) { + if (!in_array($var, $qr['variables'])) { + $pass = 0; + break; + } + } + } + if ($pass) { + $index = $this->buildArrayHashIndex($qr['rows']); + $valid_index = $this->buildArrayHashIndex($valid_qr['rows']); + if (($diff = array_diff($index, $valid_index)) || ($diff = array_diff($valid_index, $index))) { + $pass = 0; + //echo '
    ' . print_r($diff, 1) . '
    '; + } + } + } + return array('pass' => $pass, 'info' => $valid_qr); + } + + /* */ + + function isSameConstructResult($qr, $result, $result_base, $test) { + $parser = ARC2::getRDFParser($this->a); + $parser->parse('', $result); + $valid_triples = $parser->getTriples(); + $parser = ARC2::getRDFParser($this->a); + $parser->parse('', $qr); + $triples = $parser->getTriples(); + $info = '
    ' . print_r($valid_triples, 1) .'
    '; + $info = ''; + + //echo '
    ' . print_r($index, 1) .'
    '; + $pass = 0; + if (in_array($test, array(/* manually checked 2007-09-21 */ + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest#construct-1', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest#construct-2', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest#construct-3', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest#construct-4', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/construct/manifest#construct-5', + ))) { + $pass = 1; + } + return array('pass' => $pass, 'info' => $valid_triples); + } + + /* */ + + function isSameAskResult($qr, $result, $result_base) { + if (preg_match('/(true|false)\.(ttl|n3)$/', $result_base, $m)) { + $valid_r = $m[1]; + } + else { + $valid_r = preg_match('/boolean\>([^\<]+)/s', $result, $m) ? trim($m[1]) : '-'; + } + $r = ($qr === true) ? 'true' : 'false'; + $pass = ($r == $valid_r) ? 1 : 0; + return array('pass' => $pass, 'info' => $valid_r); + } + + /* */ + + function buildTurtleSelectQueryResult($index) { + $rs = 'http://www.w3.org/2001/sw/DataAccess/tests/result-set#'; + $rdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + $r = array('variables' => array(), 'rows' => array()); + foreach ($index as $node => $props) { + $types = $this->v($rdf . 'type', array(), $props); + foreach ($types as $type) { + if ($type['value'] == $rs . 'ResultSet') { + $vars = $this->v($rs . 'resultVariable', array(), $props); + foreach ($vars as $var) { + $r['variables'][] = $var['value']; + } + } + } + $bindings = $this->v($rs . 'binding', array(), $props); + if ($bindings) { + $row = array(); + foreach ($bindings as $binding) { + $binding_id = $binding['value']; + $var = $index[$binding_id][$rs . 'variable'][0]['value']; + $val = $index[$binding_id][$rs . 'value'][0]['value']; + $val_type = $index[$binding_id][$rs . 'value'][0]['type']; + //$val_type = preg_match('/literal/', $val_type) ? 'literal' : $val_type; + $row[$var] = $val; + $row[$var . ' type'] = $val_type; + if ($dt = $this->v('datatype', 0, $index[$binding_id][$rs . 'value'][0])) { + $row[$var . ' datatype'] = $dt; + } + if ($lang = $this->v('lang', 0, $index[$binding_id][$rs . 'value'][0])) { + $row[$var . ' lang'] = $lang; + } + } + $r['rows'][] = $row; + } + } + return $r; + } + + /* */ + + function buildArrayHashIndex($rows) { + $r = array(); + foreach ($rows as $row) { + $hash = ''; + ksort($row); + foreach ($row as $k => $v) { + $hash .= is_numeric($k) ? '' : ' ' . md5($k) . ' ' . md5($v); + } + $r[] = $hash; + } + return $r; + } + + /* */ + + function adjustBnodes($result, $data) { + $mappings = array( + '_:b1371233574_bob' => '_:b10', + '_:b1114277307_alice' => '_:b1f', + '_:b1368422168_eve' => '_:b20', + '_:b1638119969_fred' => '_:b21', + + '_:b288335586_a' => array( + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#no-distinct-3' => '_:b0', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-3' => '_:b0', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#distinct-9' => '_:b0', + 'http://www.w3.org/2001/sw/DataAccess/tests/data-r2/distinct/manifest#no-distinct-9' => '_:b0', + 'default' => '_:bn5', + ), + ); + if (isset($result['rows'])) { + foreach ($result['rows'] as $i => $row) { + foreach ($result['variables'] as $var) { + if (isset($row[$var]) && isset($mappings[$row[$var]])) { + if (is_array($mappings[$row[$var]])) { + $result['rows'][$i][$var] = isset($mappings[$row[$var]][$data]) ? $mappings[$row[$var]][$data] : $mappings[$row[$var]]['default']; + } + else { + $result['rows'][$i][$var] = $mappings[$row[$var]]; + } + } + } + } + } + return $result; + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/data/atom/feed.atom --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/data/atom/feed.atom Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,20 @@ + + + + Example Feed + + 2003-12-13T18:30:02Z + + John Doe + + urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 + + + Atom-Powered Robots Run Amok + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2003-12-13T18:30:02Z + Some text. + + + \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/data/json/crunchbase-facebook.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/data/json/crunchbase-facebook.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,450 @@ +{"name": "Facebook", + "permalink": "facebook", + "homepage_url": "http://facebook.com", + "blog_url": "http://blog.facebook.com", + "blog_feed_url": "http://blog.facebook.com/atom.php", + "category_code": "web", + "number_of_employees": 450, + "founded_year": 2004, + "founded_month": 2, + "founded_day": 1, + "deadpooled_year": null, + "deadpooled_month": null, + "deadpooled_day": null, + "deadpooled_url": "", + "tag_list": "social, facebook, college, students, profiles, network, socialnetwork, socialmedia, platform", + "email_address": "", + "phone_number": "", + "overview": "\u003Cp\u003EOn February 4th, 2004 \u003Ca href=\"http://www.crunchbase.com/person/mark-zuckerberg\" title=\"Mark Zuckerberg\"\u003EMark Zuckerberg\u003C/a\u003E launched The Facebook, a social network that was at the time exclusively for Harvard students. It was a huge hit, in 2 weeks, half of the student body at Harvard had signed up. Other schools in the Boston area began demanding a Facebook network. Zuckerberg immediately recruited his friends \u003Ca href=\"http://www.crunchbase.com/person/dustin-moskovitz\" title=\"Dustin Moskowitz\"\u003EDustin Moskowitz\u003C/a\u003E and Chris Hughes to help build Facebook, and within four months, Facebook added 30 more college networks. \u003C/p\u003E\n\n\u003Cp\u003EThe original idea for the term Facebook came from Zuckerberg\u0026#8217;s high school (Phillips Exeter Academy). The Exeter Face Book was passed around to every student as a way for students to get to know their classmates for the following year. It was a physical paper book until Zuckerberg brought it to the internet.\u003C/p\u003E\n\n\u003Cp\u003EWith this success, Zuckerberg, Moskowitz and Hughes moved out to Palo Alto for the summer and rented a sublet. A few weeks later, Zuckerberg ran into the former cofounder of Napster, Sean Parker. Parker soon moved in to Zuckerberg\u0026#8217;s apartment and they began working together. Parker provided the introduction to their first investor, Peter Thiel, cofounder of PayPal and managing partner of the Founders Fund. Thiel invested $500,000 into Facebook. \u003C/p\u003E\n\n\u003Cp\u003EWith millions more users, Friendster \u003Ca href=\"http://www.techcrunch.com/2006/12/12/yahoos-project-fraternity-docs-leaked/\" title=\"attempted\"\u003Eattempted\u003C/a\u003E to acquire the company for $10 million in mid 2004. Facebook turned down the offer and subsequently received $12.7 million in funding from Accel Partners, at a valuation of \u003Ca href=\"http://www.techcrunch.com/2005/09/07/85-of-college-students-use-facebook/\" title=\"around $100 million\"\u003Earound $100 million\u003C/a\u003E. Facebook continued to grow, opening up to high school students in September 2005 and adding an immensely popular photo sharing feature the next month. The next spring, Facebook received $25 million in funding from Greylock Partners and Meritech Capital, as well as previous investors Accel Partners and Peter Thiel. The pre-money valuation for this deal was about $525 million. Facebook subsequently \u003Ca href=\"http://www.techcrunch.com/2006/04/26/facebook-goes-beyond-college-high-school-markets/\" title=\"opened\"\u003Eopened\u003C/a\u003E up to work networks, eventually amassing over 20,000 work networks. Finally in September 2006, Facebook \u003Ca href=\"http://www.techcrunch.com/2006/09/26/facebook-just-launched-open-registrations/\" title=\"opened\"\u003Eopened\u003C/a\u003E to anyone with an email address. \u003C/p\u003E\n\n\u003Cp\u003EIn the summer of 2006, Yahoo \u003Ca href=\"http://www.techcrunch.com/2006/09/21/facebook-and-yahoo-in-acquisition-talks-for-1-billion/\" title=\"attempted to acquire\"\u003Eattempted to acquire\u003C/a\u003E the company for $1 billion dollars. \u003Ca href=\"http://www.wired.com/techbiz/startups/news/2007/09/ff_facebook\" title=\"Reports\" rel=\"nofollow\"\u003EReports\u003C/a\u003E actually indicated that Zuckerberg made a verbal agreement to sell Facebook to Yahoo. A few days later when Yahoo\u0026#8217;s stock price took a dive, the offer was lowered to $800 million and Zuckerberg walked away from the deal. Yahoo later \u003Ca href=\"http://www.techcrunch.com/2006/12/12/yahoos-project-fraternity-docs-leaked/\" title=\"offered\"\u003Eoffered\u003C/a\u003E $1 billion again, this time Zuckerberg turned Yahoo down and earned instant notoriety as the \u0026#8220;kid\u0026#8221; who turned down a billion. This was not the first time Zuckerberg turned down an acquisition offer; Viacom had previously \u003Ca href=\"http://www.techcrunch.com/2006/03/28/facebook-is-doing-the-skype-dance/\" title=\"unsuccessfully\"\u003Eunsuccessfully\u003C/a\u003E attempted to acquire the company for $750 million in March, 2006. \u003C/p\u003E\n\n\u003Cp\u003EOne sour note for Facebook has been the \u003Ca href=\"http://www.techcrunch.com/2007/07/16/the-ghost-of-zuckerbergs-past-may-haunt-facebook-ipo/\" title=\"controversy\"\u003Econtroversy\u003C/a\u003E with social network Uconnect. The founders of Uconnect, former classmates of Mark Zuckerberg at Harvard, allege that Zuckerberg stole their original source code for Facebook. The ordeal has \u003Ca href=\"http://www.techcrunch.com/2007/10/10/facebook-vs-connectu-facebook-makes-untrue-assertions-claims-connectu/\" title=\"gone to court\"\u003Egone to court\u003C/a\u003E, but is still unresolved. \u003C/p\u003E\n\n\u003Cp\u003ENotwithstanding this lingering controversy, Facebook\u0026#8217;s growth in the fall of 2007 was staggering. Over 1 million new users signed up every week, 200,000 daily, totaling over 50 million active users. Facebook received 40 billion page views a month. Long gone were the days of Facebook as a social network for college students. 11% of users are over the age of 35, and the fastest growing demographic is users over 30. Facebook has also seen huge growth internationally; 15% of the user base is in Canada. Facebook users\u0026#8217; \u003Ca href=\"http://www.techcrunch.com/2007/11/13/i-just-cant-be-a-college-student-without-facebook/\" title=\"passion\"\u003Epassion\u003C/a\u003E, or \u003Ca href=\"http://www.techcrunch.com/2007/03/09/career-advice-dont-choose-facebook-over-your-job/\" title=\"addiction\"\u003Eaddiction\u003C/a\u003E, to the site is unparalleled: more than half use the product every single day and users spend an average of 19 minutes a day on Facebook. Facebook is 6th most trafficked site in the US and top photo sharing site with \u003Ca href=\"http://www.techcrunch.com/2007/11/13/2-billion-photos-on-flickr/\" title=\"4.1 billion photos uploaded\"\u003E4.1 billion photos uploaded\u003C/a\u003E. \u003C/p\u003E\n\n\u003Cp\u003EBased on these types of numbers, \u003Ca href=\"http://www.techcrunch.com/2007/10/24/facebook-takes-the-microsoft-money-and-runs/\" title=\"Microsoft invested\"\u003EMicrosoft invested\u003C/a\u003E $240 million into Facebook for 1.6 percent of the company in October 2007. This meant a valuation of over $15 billion, making Facebook the \u003Ca href=\"http://www.techcrunch.com/2007/10/25/perspective-facebook-is-now-5th-most-valuable-us-internet-company/\" title=\"5th most valuable US Internet company\"\u003E5th most valuable US Internet company\u003C/a\u003E, yet with only $150 million in annual revenue. Many explained Microsoft\u0026#8217;s decision as being solely driven by the desire to outbid Google. \u003C/p\u003E\n\n\u003Cp\u003EFacebook\u0026#8217;s competitors include \u003Ca href=\"http://www.crunchbase.com/company/myspace\" title=\"MySpace\"\u003EMySpace\u003C/a\u003E, \u003Ca href=\"http://www.crunchbase.com/company/Bebo\" title=\"Bebo\"\u003EBebo\u003C/a\u003E, \u003Ca href=\"http://www.crunchbase.com/company/Friendster\" title=\"Friendster\"\u003EFriendster\u003C/a\u003E, \u003Ca href=\"http://www.crunchbase.com/company/LinkedIn\" title=\"LinkedIn\"\u003ELinkedIn\u003C/a\u003E, \u003Ca href=\"http://www.crunchbase.com/company/tagged\" title=\"Tagged\"\u003ETagged\u003C/a\u003E, \u003Ca href=\"http://www.techcrunch.com/2007/01/20/hi5-traffic-surges-may-be-second-largest-social-network/\" title=\"Hi5\"\u003EHi5\u003C/a\u003E, \u003Ca href=\"http://www.techcrunch.com/2006/09/25/a-look-at-piczo-and-its-competitors/\" title=\"Piczo\"\u003EPiczo\u003C/a\u003E, and \u003Ca href=\"http://www.techcrunch.com/2007/10/30/details-revealed-google-opensocial-to-be-common-apis-for-building-social-apps/\" title=\"Open Social\"\u003EOpen Social\u003C/a\u003E. \u003C/p\u003E\n\n\u003Cp\u003E\u003Cimg src=\"http://farm3.static.flickr.com/2059/2046940872_73672f2007.jpg\" alt=\"Facebook Traffic\"/\u003E\u003C/p\u003E", + "image": + {"available_sizes": + [[[150, + 56], + "assets/images/resized/0000/4552/4552v2-max-150x150.jpg"], + [[250, + 94], + "assets/images/resized/0000/4552/4552v2-max-250x250.jpg"], + [[450, + 169], + "assets/images/resized/0000/4552/4552v2-max-450x450.jpg"]], + "attribution": null}, + "products": + [{"name": "Facebook Platform", + "permalink": "facebook-platform"}, + {"name": "Facebook News Feed", + "permalink": "facebook-news-feed"}, + {"name": "Facebook Chat", + "permalink": "facebook-chat"}, + {"name": "Facebook Connect", + "permalink": "facebook-connect"}, + {"name": "Facebook iPhone App", + "permalink": "facebook-iphone-app"}], + "relationships": + [{"is_past": false, + "title": "Founder and CEO, Board Of Directors", + "person": + {"first_name": "Mark", + "last_name": "Zuckerberg", + "permalink": "mark-zuckerberg"}}, + {"is_past": false, + "title": "Co-founder and VP Engineering", + "person": + {"first_name": "Dustin", + "last_name": "Moskovitz", + "permalink": "dustin-moskovitz"}}, + {"is_past": true, + "title": "Chief Revenue Officer, VP of Operations", + "person": + {"first_name": "Owen", + "last_name": "Van Natta", + "permalink": "owen-van-natta"}}, + {"is_past": true, + "title": "VP of Product Management", + "person": + {"first_name": "Matt", + "last_name": "Cohler", + "permalink": "matt-cohler"}}, + {"is_past": false, + "title": "Co-founder", + "person": + {"first_name": "Chris", + "last_name": "Hughes", + "permalink": "chris-hughes"}}, + {"is_past": false, + "title": "VP of Product Marketing", + "person": + {"first_name": "Chamath", + "last_name": "Palihapitiya", + "permalink": "chamath-palihapitiya"}}, + {"is_past": false, + "title": "CFO", + "person": + {"first_name": "Gideon", + "last_name": "Yu", + "permalink": "gideon-yu"}}, + {"is_past": true, + "title": "CTO", + "person": + {"first_name": "Adam", + "last_name": "D'Angelo", + "permalink": "adam-d-angelo"}}, + {"is_past": false, + "title": "COO", + "person": + {"first_name": "Sheryl", + "last_name": "Sandberg", + "permalink": "sheryl-sandberg"}}, + {"is_past": false, + "title": "Senior Platform Manager", + "person": + {"first_name": "Dave", + "last_name": "Morin", + "permalink": "dave-morin"}}, + {"is_past": false, + "title": "Director of Business Development", + "person": + {"first_name": "Ethan", + "last_name": "Beard", + "permalink": "ethan-beard"}}, + {"is_past": false, + "title": "Chief Privacy Officer", + "person": + {"first_name": "Chris", + "last_name": "Kelly", + "permalink": "chris-kelly"}}, + {"is_past": false, + "title": "", + "person": + {"first_name": "Justin", + "last_name": "Rosenstein", + "permalink": "justin-rosenstein"}}, + {"is_past": false, + "title": "VP of Technical Operations", + "person": + {"first_name": "Jonathan", + "last_name": "Heiliger", + "permalink": "jonathan-heiliger"}}, + {"is_past": false, + "title": "Director Platform Product Marketing", + "person": + {"first_name": "Ben", + "last_name": "Ling", + "permalink": "ben-ling"}}, + {"is_past": false, + "title": "Product Lead for Facebook Platform", + "person": + {"first_name": "Ruchi", + "last_name": "Sanghvi", + "permalink": "ruchi-sanghvi"}}, + {"is_past": false, + "title": "Board Of Directors", + "person": + {"first_name": "Jim", + "last_name": "Breyer", + "permalink": "jim-breyer"}}, + {"is_past": false, + "title": "VP of Communications and Public Policy", + "person": + {"first_name": "Elliot", + "last_name": "Schrage", + "permalink": "elliot-schrage"}}, + {"is_past": false, + "title": "Board Of Directors", + "person": + {"first_name": "Peter", + "last_name": "Thiel", + "permalink": "peter-thiel"}}, + {"is_past": false, + "title": "Observer to Board of Directors", + "person": + {"first_name": "David", + "last_name": "Sze", + "permalink": "david-sze"}}, + {"is_past": true, + "title": "President, Board of Directors", + "person": + {"first_name": "Sean", + "last_name": "Parker", + "permalink": "sean-parker"}}, + {"is_past": false, + "title": "Board of directors", + "person": + {"first_name": "Marc", + "last_name": "Andreessen", + "permalink": "marc-andreessen"}}], + "competitions": + [{"competitor": + {"name": "MySpace", + "permalink": "myspace"}}, + {"competitor": + {"name": "Friendster", + "permalink": "friendster"}}, + {"competitor": + {"name": "Slide", + "permalink": "slide"}}, + {"competitor": + {"name": "Zvents", + "permalink": "zvents"}}, + {"competitor": + {"name": "FriendFeed", + "permalink": "friendfeed"}}, + {"competitor": + {"name": "Qik", + "permalink": "qik"}}, + {"competitor": + {"name": "hi5", + "permalink": "hi5"}}, + {"competitor": + {"name": "Photobucket", + "permalink": "photobucket"}}, + {"competitor": + {"name": "Webshots", + "permalink": "webshots"}}, + {"competitor": + {"name": "Flickr", + "permalink": "flickr"}}, + {"competitor": + {"name": "Spokeo", + "permalink": "spokeo"}}, + {"competitor": + {"name": "HOT or NOT", + "permalink": "hotornot"}}, + {"competitor": + {"name": "Piczo", + "permalink": "piczo"}}, + {"competitor": + {"name": "Zyb", + "permalink": "zyb"}}, + {"competitor": + {"name": "Multiply", + "permalink": "multiply"}}, + {"competitor": + {"name": "YouTube", + "permalink": "youtube"}}, + {"competitor": + {"name": "badoo", + "permalink": "badoo"}}, + {"competitor": + {"name": "openPeople", + "permalink": "openpeople"}}, + {"competitor": + {"name": "Bigsight Media Group", + "permalink": "bigsight-media-group"}}, + {"competitor": + {"name": "Xiaonei", + "permalink": "xiaonei"}}, + {"competitor": + {"name": "Daikana", + "permalink": "daikana"}}, + {"competitor": + {"name": "Bebo", + "permalink": "bebo"}}, + {"competitor": + {"name": "AOL", + "permalink": "aol"}}, + {"competitor": + {"name": "CityIN", + "permalink": "cityin"}}, + {"competitor": + {"name": "Xanga", + "permalink": "xanga"}}, + {"competitor": + {"name": "Spleak", + "permalink": "spleak"}}, + {"competitor": + {"name": "yuwie", + "permalink": "yuwie"}}, + {"competitor": + {"name": "Rekatu", + "permalink": "rekatu"}}, + {"competitor": + {"name": "5icampus", + "permalink": "5icampus"}}, + {"competitor": + {"name": "Tagged", + "permalink": "tagged"}}], + "providerships": + [{"title": "", + "is_past": false, + "provider": + {"name": "OutCast Communications", + "permalink": "outcast-communications"}}, + {"title": "Public Relations", + "is_past": false, + "provider": + {"name": "Blanc \u0026 Otus", + "permalink": "blanc-otus"}}], + "funding_rounds": + [{"round_code": "angel", + "source_url": "", + "source_description": "", + "raised_amount": 500000.0, + "raised_currency_code": "USD", + "funded_year": 2004, + "funded_month": 9, + "funded_day": 1, + "investments": + [{"company": null, + "financial_org": + {"name": "The Founders Fund", + "permalink": "founders-fund"}, + "person": null}]}, + {"round_code": "a", + "source_url": "http://www.techcrunch.com/2007/11/02/jim-breyer-extra-500-million-round-for-facebook-a-total-fiction/", + "source_description": "Jim Breyer: Extra $500 Million Round For Facebook A \u201cTotal Fiction\u201d", + "raised_amount": 12700000.0, + "raised_currency_code": "USD", + "funded_year": 2005, + "funded_month": 5, + "funded_day": 1, + "investments": + [{"company": null, + "financial_org": + {"name": "Accel Partners", + "permalink": "accel-partners"}, + "person": null}]}, + {"round_code": "b", + "source_url": "http://www.facebook.com/press/info.php?factsheet", + "source_description": "Facebook Funding", + "raised_amount": 27500000.0, + "raised_currency_code": "USD", + "funded_year": 2006, + "funded_month": 4, + "funded_day": 1, + "investments": + [{"company": null, + "financial_org": + {"name": "Greylock", + "permalink": "greylock"}, + "person": null}, + {"company": null, + "financial_org": + {"name": "Meritech Capital Partners", + "permalink": "meritech-capital-partners"}, + "person": null}, + {"company": null, + "financial_org": null, + "person": + {"first_name": "Peter", + "last_name": "Thiel", + "permalink": "peter-thiel"}}]}, + {"round_code": "c", + "source_url": "http://www.techcrunch.com/2007/10/24/liveblogging-the-facebook-press-conference/#more-10260", + "source_description": "Liveblogging The Facebook Press Conference", + "raised_amount": 300000000.0, + "raised_currency_code": "USD", + "funded_year": 2007, + "funded_month": 10, + "funded_day": 1, + "investments": + [{"company": + {"name": "Microsoft", + "permalink": "microsoft"}, + "financial_org": null, + "person": null}, + {"company": null, + "financial_org": null, + "person": + {"first_name": "Li", + "last_name": "Ka-shing", + "permalink": "li-ka-shing"}}, + {"company": null, + "financial_org": null, + "person": + {"first_name": "Marc", + "last_name": "Samwer", + "permalink": "marc-samwer"}}, + {"company": null, + "financial_org": null, + "person": + {"first_name": "Oliver", + "last_name": "Samwer", + "permalink": "oliver-samwer"}}, + {"company": null, + "financial_org": null, + "person": + {"first_name": "Alexander", + "last_name": "Samwer", + "permalink": "alexander-samwer"}}]}, + {"round_code": "c", + "source_url": "http://www.marketwatch.com/news/story/hong-kong-tycoon-li-raises/story.aspx?guid=%7BE4097AA2-9EA3-4773-9100-456E68EE1C9A%7D", + "source_description": "", + "raised_amount": 40000000.0, + "raised_currency_code": "USD", + "funded_year": 2008, + "funded_month": 3, + "funded_day": null, + "investments": + [{"company": null, + "financial_org": null, + "person": + {"first_name": "Li", + "last_name": "Ka-shing", + "permalink": "li-ka-shing"}}]}, + {"round_code": "c", + "source_url": "", + "source_description": "Reneigh is sexy", + "raised_amount": 15000000.0, + "raised_currency_code": "USD", + "funded_year": 2008, + "funded_month": 1, + "funded_day": 15, + "investments": + [{"company": null, + "financial_org": + {"name": "European Founders Fund", + "permalink": "european-founders-fund"}, + "person": null}]}, + {"round_code": "debt_round", + "source_url": "http://www.businessweek.com/technology/content/may2008/tc2008059_855064.htm", + "source_description": "", + "raised_amount": 100000000.0, + "raised_currency_code": "USD", + "funded_year": 2008, + "funded_month": 5, + "funded_day": null, + "investments": + [{"company": null, + "financial_org": + {"name": "TriplePoint Capital", + "permalink": "triplepoint-capital"}, + "person": null}]}], + "investments": + [], + "acquisition": null, + "acquisitions": + [{"price_amount": null, + "price_currency_code": "USD", + "term_code": "cash", + "source_url": null, + "source_description": null, + "acquired_year": 2007, + "acquired_month": 7, + "acquired_day": 1, + "company": + {"name": "Parakey", + "permalink": "parakey"}}], + "offices": + [{"description": null, + "address1": "156 University Avenue", + "address2": "", + "zip_code": "94301", + "city": "Palo Alto", + "state_code": "CA", + "country_code": "USA", + "latitude": 37.444173, + "longitude": -122.163294}], + "milestones": + [{"description": "Facebook adds comments to Mini-Feed", + "stoned_year": 2008, + "stoned_month": 6, + "stoned_day": 25, + "source_url": "http://venturebeat.com/2008/06/25/facebook-adds-comment-to-the-mini-feed-its-like-friendfeed-is-looking-in-the-mirror/", + "source_description": "Facebook adds comments to the Mini-Feed. It\u2019s like FriendFeed is looking in the mirror"}], + "ipo": null, + "video_embeds": + [{"embed_code": "\u003Cscript type=\"text/javascript\" src=\"http://www.podtech.net/player/popup.js\"\u003E\u003C/script\u003E\u003Cobject classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" codebase=\"http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0\" width=\"450\" height=\"299\" id=\"player73536e08d3f742de9d31b25a319af359\" align=\"middle\"\u003E\u003Cparam name=\"allowScriptAccess\" value=\"always\" /\u003E\u003Cparam name=\"FlashVars\" value=\"content=http://media1.podtech.net/media/2007/09/PID012533/PodtechRandiZuckerberg2.flv\u0026totalTime=2149000\u0026permalink=http://www.podtech.net/home/4118/the-first-sister-of-facebook\u0026breadcrumb=73536e08d3f742de9d31b25a319af359\" height=\"299\" width=\"450\" /\u003E\u003Cparam name=\"movie\" value=\"http://www.podtech.net/player/podtech-player.swf?bc=73536e08d3f742de9d31b25a319af359\" /\u003E\u003Cparam name=\"quality\" value=\"high\" /\u003E\u003Cparam name=\"scale\" value=\"noscale\" /\u003E\u003Cparam name=\"bgcolor\" value=\"#000000\" /\u003E\u003Cembed name=\"player73536e08d3f742de9d31b25a319af359\" type=\"application/x-shockwave-flash\" src=\"http://www.podtech.net/player/podtech-player.swf?bc=73536e08d3f742de9d31b25a319af359\" flashvars=\"content=http://media1.podtech.net/media/2007/09/PID012533/PodtechRandiZuckerberg2.flv\u0026totalTime=2149000\u0026permalink=http://www.podtech.net/home/4118/the-first-sister-of-facebook\u0026breadcrumb=73536e08d3f742de9d31b25a319af359\" height=\"299\" width=\"450\" allowScriptAccess=\"always\" /\u003E\u003C/object\u003E\u003Cnoscript\u003EYour browser does not support JavaScript. This media can be viewed at http://www.podtech.net/home/4118/the-first-sister-of-facebook\u003C/noscript\u003E", + "description": "\u003Cp\u003ERobert Scoble\u2019s video from www.podtech.net/scobleshow:\u003C/p\u003E"}], + "external_links": + [{"external_url": "http://www.marketwatch.com/news/story/story.aspx?guid={E4097AA2-9EA3-4773-9100-456E68EE1C9A}", + "title": "Hong Kong tycoon Li raises personal Facebook investment above $100 mln"}]} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/data/json/sparql-select-result.json --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/data/json/sparql-select-result.json Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,78 @@ +{ + "head": { + "link": [ + "http://www.w3.org/TR/rdf-sparql-XMLres/example.rq" + ], + "vars": [ + "x", + "hpage", + "name", + "mbox", + "age", + "blurb", + "friend" + ] + }, + "results": { + "bindings": [ + { + "x" : { + "type": "bnode", + "value": "r1" + }, + + "hpage" : { + "type": "uri", + "value": "http://work.example.org/alice/" + }, + + "name" : { + "type": "literal", + "value": "Alice" + }, + + "mbox" : { + "type": "literal", + "value": "" + }, + + "blurb" : { + "datatype": "http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral", + "type": "typed-literal", + "value": "

    My name is alice

    " + }, + + "friend" : { + "type": "bnode", + "value": "r2" + } + },{ + "x" : { + "type": "bnode", + "value": "r2" + }, + + "hpage" : { + "type": "uri", + "value": "http://work.example.org/bob/" + }, + + "name" : { + "type": "literal", + "value": "Bob", + "xml:lang": "en" + }, + + "mbox" : { + "type": "uri", + "value": "mailto:bob@work.example.org" + }, + + "friend" : { + "type": "bnode", + "value": "r1" + } + } + ] + } + } diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/data/nt/test.nt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/data/nt/test.nt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,78 @@ +# +# Copyright World Wide Web Consortium, (Massachusetts Institute of +# Technology, Institut National de Recherche en Informatique et en +# Automatique, Keio University). +# +# All Rights Reserved. +# +# Please see the full Copyright clause at +# +# +# Test file with a variety of legal N-Triples +# +# Dave Beckett - http://purl.org/net/dajobe/ +# +# $Id: test.nt,v 1.7 2003/10/06 15:52:19 dbeckett2 Exp $ +# +##################################################################### + +# comment lines + # comment line after whitespace +# empty blank line, then one with spaces and tabs + + + . +_:anon . + _:anon . +# spaces and tabs throughout: + . + +# line ending with CR NL (ASCII 13, ASCII 10) + . + +# 2 statement lines separated by single CR (ASCII 10) + . . + + +# All literal escapes + "simple literal" . + "backslash:\\" . + "dquote:\"" . + "newline:\n" . + "return\r" . + "tab:\t" . + +# Space is optional before final . + . + "x". + _:anon. + +# \u and \U escapes +# latin small letter e with acute symbol \u00E9 - 3 UTF-8 bytes #xC3 #A9 + "\u00E9" . +# Euro symbol \u20ac - 3 UTF-8 bytes #xE2 #x82 #xAC + "\u20AC" . +# resource18 test removed +# resource19 test removed +# resource20 test removed + +# XML Literals as Datatyped Literals + ""^^ . + " "^^ . + "x"^^ . + "\""^^ . + ""^^ . + "a "^^ . + "a c"^^ . + "a\n\nc"^^ . + "chat"^^ . +# resource28 test removed 2003-08-03 +# resource29 test removed 2003-08-03 + +# Plain literals with languages + "chat"@fr . + "chat"@en . + +# Typed Literals + "abc"^^ . +# resource33 test removed 2003-08-03 diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/data/rdfxml/planetrdf-bloggers.rdf --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/data/rdfxml/planetrdf-bloggers.rdf Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1439 @@ + + + + + +Planet RDF + + +Planet RDF site blog + + + + + + + + + + + + + + +AKSW Group - University of Leipzig + + +AKSW Group - University of Leipzig + + + + + + + + + + + + + + +Dean Allemang + + + +S is for Semantics by Dean Allemang + + + + + + + + + + + + + + +Zoltan Andrejkovics + + +Zoltan Andrejkovics + + + + + + + + + + + + + + +Al Baker +AlBaker_Dev + + +Linked Java by Al Baker + + + + + + + + + + + + + + +Dave Beckett +dajobe + + +Journalblog by Dave Beckett + + + + + + + + + + + + + + +Tim Berners-Leetimberners_lee + + +Tim Berners-Lee + + + + + + + + + + + + + + +Uldis BojarsCaptSolo + + +Uldis Bojars (Captain Solo) + + + + + + + + + + + + + + +John Breslin +johnbreslin + + +Cloudlands by John Breslin + + + + + + + + + + + + + + +Dan Brickley +danbri + + +danbri's foaf stories + + + + + + + + + + + + + + +Jeen Broekstra +jeenbroekstra + + +Rivuli by Jeen Broekstra + + + + + + + + + + + + + + +Dries Buytaertdries + + +Dries Buytaert - Semantic Web + + + + + + + + + + + + + + +Cambridge Semantics + + +Cambridge Semantics + + + + + + + + + + + + + + +Clark and Parsiacandp + + +Tales of a Semantic Web Consultancy by Clark and Parsia + + + + + + + + + + + + + + +Dan Connolly +dckc + + +Dan Connolly + + + + + + + + + + + + + + +Richard Cyganiakcygri + + +Richard Cyganiak + + + + + + + + + + + + + + +Datagraphdatagraph + + +Datagraph + + + + + + + + + + + + + + +Phil Dawesphildawes + + +Phil Dawes + + + + + + + + + + + + + + +Yves Raimond +moustaki + + +DBTune by Yves Raimond + + + + + + + + + + + + + + +DERI Galway + + +DERI Galway + + + + + + + + + + + + + + +DOAP Project + + +DOAP Project + + + + + + + + + + + + + + +Leigh Dodds +ldodds + + +Lost Boy by Leigh Dodds + + + + + + + + + + + + + + +Dublin Core Metadata InitiativeDublinCore + + +Dublin Core Metadata Initiative + + + + + + + + + + + + + + +Bob DuCharme +bobdc + + +bobdc.blog by Bob DuCharme + + + + + + + + + + + + + + +Edd Dumbill +edd + + +behind the times by Edd Dumbill + + + + + + + + + + + + + + +Ebiquity research group UMBC + + +Ebiquity research group UMBC + + + + + + + + + + + + + + +Dydradydradata + + +Dydra + + + + + + + + + + + + + + +EnAKTing project + + +EnAKTing project + + + + + + + + + + + + + + +Orri Erling + + +Orri Erling by + + + + + + + + + + + + + + +Lee FeigenbaumLeeFeigenbaum + + +Lee Feigenbaum + + + + + + + + + + + + + + +Sergio Fernández + + + +Sergio Fernández + + + + + + + + + + + + + + +FOAF Project + + +FOAF Project blog + + + + + + + + + + + + + + +Morten Frederiksen +mortenf + + +Binary Relations by Morten Frederiksen + + + + + + + + + + + + + + +Frederick Giasson + + +Frederick Giasson + + + + + + + + + + + + + + +John Goodwingothwin + + +John Goodwin + + + + + + + + + + + + + + +Richard Hancock + + + +3kbo by Richard Hancock + + + + + + + + + + + + + + +Michael Hausenblas +mhausenblas + + +Web of Data by Michael Hausenblas + + + + + + + + + + + + + + +Sandro Hawke +sandhawke + + + Decentralyze – Programming the Data Cloud by Sandro Hawke + + + + + + + + + + + + + + +Tom Heath +tommyh + + +Displacement Activities by Tom Heath + + + + + + + + + + + + + + +Ivan Hermanivan_herman + + +Ivan Herman + + + + + + + + + + + + + + +Kingsley Idehen +Kidehen + + +Data Space by Kingsley Idehen + + + + + + + + + + + + + + +Learn Linked Datalearnlinkeddata + + +Learn Linked Data + + + + + + + + + + + + + + +Talisnodalities + + +Nodalities by Talis + + + + + + + + + + + + + + +Seevlseevl + + +Seevl technology + + + + + + + + + + + + + + +Semantic Web Company (Austria)semwebcompany + + +Semantic Puzzle by Semantic Web Company (Austria) + + + + + + + + + + + + + + +SchemaWeb + + +SchemaWeb + + + + + + + + + + + + + + +Ora Lassila + + + +Wilbur-and-O by Ora Lassila + + + + + + + + + + + + + + +Peter Mika + + + +Tripletalk by Peter Mika + + + + + + + + + + + + + + +Andrew Matthews + + + +The Wandering Glitch 2 by Andrew Matthews + + + + + + + + + + + + + + +ZDNet Semantic Web by Paul MillerPaulMiller + + +ZDNet Semantic Web by Paul Miller + + + + + + + + + + + + + + +Libby Miller +libbymiller + + +Plan B by Libby Miller + + + + + + + + + + + + + + +Benjamin Nowack +bengee + + +bnode by Benjamin Nowack + + + + + + + + + + + + + + +Open Sahara blog + + +Open Sahara blog + + + + + + + + + + + + + + +Alexandre Passantterraces + + +Alexandre Passant + + + + + + + + + + + + + + +Davide Palmisano +dpalmisano + + +turn off the lights, please by Davide Palmisano + + + + + + + + + + + + + + +POWDER WG blog + + +POWDER WG blog + + + + + + + + + + + + + + +RDFa + + +RDFa + + + + + + + + + + + + + + +Dave Raggettdraggett + + +Dave Raggett + + + + + + + + + + + + + + +David Robillarddrobilla + + +David Robillard + + + + + + + + + + + + + + +RDF Resource Guide + + +Dave Beckett's RDF Resource Guide + + + + + + + + + + + + + + +Peter Shaw + + + +Shawfactor by Peter Shaw + + + + + + + + + + + + + + +schema.org + + +schema.org + + + + + + + + + + + + + + +Henry Story +bblfish + + +BabelFish by Henry Story + + + + + + + + + + + + + + +Jeni TennisonJeniT + + +Jeni Tennison - Musings + + + + + + + + + + + + + + +Tetherless World Constellation group RPIjahendler + + +Tetherless World Constellation group RPI + + + + + + + + + + + + + + +Elias Torres + + +Elias Torres + + + + + + + + + + + + + + +Sebastian Trueg + + + +Semantic Desktop by Sebastian Trueg + + + + + + + + + + + + + + +W3C Semantic Web News + + +W3C Semantic Web News + + + + + + + + + + + + + + +W3C Read Write Web Community Group + + +W3C Read Write Web Community Group Blog + + + + + + + + + + + + + + +W3C Blog Semantic Web News + + +W3C Blog Semantic Web News + + + + + + + + + + + + + + +Norm Walshndw + + +Norm Walsh + + + + + + + + + + + + + + +Mark Watsonmark_l_watson + + +Mark Watson + + + + + + + + + + + + + + +Danny Weitzner +djweitzner + + +Open Internet Policy by Danny Weitzner + + + + + + + + + + + + + + +Web of Data + + +Web of Data + + + + + + + + + + + + + + +Bill Roberts +billroberts + + +Web of Data by Bill Roberts + + + + + + + + + + + + + + +Web Semántica Hoy + + +Web Semántica Hoy + + + + + + + + + + + + + + +Gregory Williamskasei + + +Gregory Williams + + + + + + + + + + + + + + +Egon Willighagenegonwillighagen + + +chem-bla-ics by Egon Willighagen + + + + + + + + + + + + + + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/data/turtle/manifest.ttl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/data/turtle/manifest.ttl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,2190 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test named *subm* are (c) W3C and taken from the Turtle submission. + +@prefix rdf: . +@prefix rdfs: . +@prefix mf: . +@prefix qt: . + +@prefix rdft: . + +<> rdf:type mf:Manifest ; + rdfs:comment "Turtle tests" ; + mf:entries + ( + + # atomic tests + <#IRI_subject> + <#IRI_with_four_digit_numeric_escape> + <#IRI_with_eight_digit_numeric_escape> + <#IRI_with_all_punctuation> + <#bareword_a_predicate> + <#old_style_prefix> + <#SPARQL_style_prefix> + <#prefixed_IRI_predicate> + <#prefixed_IRI_object> + <#prefix_only_IRI> + <#prefix_with_PN_CHARS_BASE_character_boundaries> + <#prefix_with_non_leading_extras> + <#default_namespace_IRI> + <#prefix_reassigned_and_used> + <#reserved_escaped_localName> + <#percent_escaped_localName> + <#HYPHEN_MINUS_in_localName> + <#underscore_in_localName> + <#localname_with_COLON> + <#localName_with_assigned_nfc_bmp_PN_CHARS_BASE_character_boundaries> + <#localName_with_assigned_nfc_PN_CHARS_BASE_character_boundaries> + <#localName_with_nfc_PN_CHARS_BASE_character_boundaries> + <#localName_with_PN_CHARS_BASE_character_boundaries> + <#localName_with_leading_underscore> + <#localName_with_leading_digit> + <#localName_with_non_leading_extras> + <#old_style_base> + <#SPARQL_style_base> + <#labeled_blank_node_subject> + <#labeled_blank_node_object> + <#labeled_blank_node_with_PN_CHARS_BASE_character_boundaries> + <#labeled_blank_node_with_leading_underscore> + <#labeled_blank_node_with_leading_digit> + <#labeled_blank_node_with_non_leading_extras> + <#anonymous_blank_node_subject> + <#anonymous_blank_node_object> + <#sole_blankNodePropertyList> + <#blankNodePropertyList_as_subject> + <#blankNodePropertyList_as_object> + <#blankNodePropertyList_with_multiple_triples> + <#nested_blankNodePropertyLists> + <#blankNodePropertyList_containing_collection> + <#collection_subject> + <#collection_object> + <#empty_collection> + <#nested_collection> + <#first> + <#last> + <#LITERAL1> + <#LITERAL1_ascii_boundaries> + <#LITERAL1_with_UTF8_boundaries> + <#LITERAL1_all_controls> + <#LITERAL1_all_punctuation> + <#LITERAL_LONG1> + <#LITERAL_LONG1_ascii_boundaries> + <#LITERAL_LONG1_with_UTF8_boundaries> + <#LITERAL_LONG1_with_1_squote> + <#LITERAL_LONG1_with_2_squotes> + <#LITERAL2> + <#LITERAL2_ascii_boundaries> + <#LITERAL2_with_UTF8_boundaries> + <#LITERAL_LONG2> + <#LITERAL_LONG2_ascii_boundaries> + <#LITERAL_LONG2_with_UTF8_boundaries> + <#LITERAL_LONG2_with_1_squote> + <#LITERAL_LONG2_with_2_squotes> + <#literal_with_CHARACTER_TABULATION> + <#literal_with_BACKSPACE> + <#literal_with_LINE_FEED> + <#literal_with_CARRIAGE_RETURN> + <#literal_with_FORM_FEED> + <#literal_with_REVERSE_SOLIDUS> + <#literal_with_escaped_CHARACTER_TABULATION> + <#literal_with_escaped_BACKSPACE> + <#literal_with_escaped_LINE_FEED> + <#literal_with_escaped_CARRIAGE_RETURN> + <#literal_with_escaped_FORM_FEED> + <#literal_with_numeric_escape4> + <#literal_with_numeric_escape8> + <#IRIREF_datatype> + <#prefixed_name_datatype> + <#bareword_integer> + <#bareword_decimal> + <#bareword_double> + <#double_lower_case_e> + <#negative_numeric> + <#positive_numeric> + <#numeric_with_leading_0> + <#literal_true> + <#literal_false> + <#langtagged_non_LONG> + <#langtagged_LONG> + <#lantag_with_subtag> + <#objectList_with_two_objects> + <#predicateObjectList_with_two_objectLists> + <#repeated_semis_at_end> + <#repeated_semis_not_at_end> + + # original tests-ttl + <#turtle-syntax-file-01> + <#turtle-syntax-file-02> + <#turtle-syntax-file-03> + <#turtle-syntax-uri-01> + <#turtle-syntax-uri-02> + <#turtle-syntax-uri-03> + <#turtle-syntax-uri-04> + <#turtle-syntax-base-01> + <#turtle-syntax-base-02> + <#turtle-syntax-base-03> + <#turtle-syntax-base-04> + <#turtle-syntax-prefix-01> + <#turtle-syntax-prefix-02> + <#turtle-syntax-prefix-03> + <#turtle-syntax-prefix-04> + <#turtle-syntax-prefix-05> + <#turtle-syntax-prefix-06> + <#turtle-syntax-prefix-07> + <#turtle-syntax-prefix-08> + <#turtle-syntax-prefix-09> + <#turtle-syntax-string-01> + <#turtle-syntax-string-02> + <#turtle-syntax-string-03> + <#turtle-syntax-string-04> + <#turtle-syntax-string-05> + <#turtle-syntax-string-06> + <#turtle-syntax-string-07> + <#turtle-syntax-string-08> + <#turtle-syntax-string-09> + <#turtle-syntax-string-10> + <#turtle-syntax-string-11> + <#turtle-syntax-str-esc-01> + <#turtle-syntax-str-esc-02> + <#turtle-syntax-str-esc-03> + <#turtle-syntax-pname-esc-01> + <#turtle-syntax-pname-esc-02> + <#turtle-syntax-pname-esc-03> + <#turtle-syntax-bnode-01> + <#turtle-syntax-bnode-02> + <#turtle-syntax-bnode-03> + <#turtle-syntax-bnode-04> + <#turtle-syntax-bnode-05> + <#turtle-syntax-bnode-06> + <#turtle-syntax-bnode-07> + <#turtle-syntax-bnode-08> + <#turtle-syntax-bnode-09> + <#turtle-syntax-bnode-10> + <#turtle-syntax-number-01> + <#turtle-syntax-number-02> + <#turtle-syntax-number-03> + <#turtle-syntax-number-04> + <#turtle-syntax-number-05> + <#turtle-syntax-number-06> + <#turtle-syntax-number-07> + <#turtle-syntax-number-08> + <#turtle-syntax-number-09> + <#turtle-syntax-number-10> + <#turtle-syntax-number-11> + <#turtle-syntax-datatypes-01> + <#turtle-syntax-datatypes-02> + <#turtle-syntax-kw-01> + <#turtle-syntax-kw-02> + <#turtle-syntax-kw-03> + <#turtle-syntax-struct-01> + <#turtle-syntax-struct-02> + <#turtle-syntax-struct-03> + <#turtle-syntax-struct-04> + <#turtle-syntax-struct-05> + <#turtle-syntax-lists-01> + <#turtle-syntax-lists-02> + <#turtle-syntax-lists-03> + <#turtle-syntax-lists-04> + <#turtle-syntax-lists-05> + <#turtle-syntax-bad-uri-01> + <#turtle-syntax-bad-uri-02> + <#turtle-syntax-bad-uri-03> + <#turtle-syntax-bad-uri-04> + <#turtle-syntax-bad-uri-05> + <#turtle-syntax-bad-prefix-01> + <#turtle-syntax-bad-prefix-02> + <#turtle-syntax-bad-prefix-03> + <#turtle-syntax-bad-prefix-04> + <#turtle-syntax-bad-prefix-05> + <#turtle-syntax-bad-base-01> + <#turtle-syntax-bad-base-02> + <#turtle-syntax-bad-base-03> + <#turtle-syntax-bad-struct-01> + <#turtle-syntax-bad-struct-02> + <#turtle-syntax-bad-struct-03> + <#turtle-syntax-bad-struct-04> + <#turtle-syntax-bad-struct-05> + <#turtle-syntax-bad-struct-06> + <#turtle-syntax-bad-struct-07> + <#turtle-syntax-bad-kw-01> + <#turtle-syntax-bad-kw-02> + <#turtle-syntax-bad-kw-03> + <#turtle-syntax-bad-kw-04> + <#turtle-syntax-bad-kw-05> + <#turtle-syntax-bad-n3-extras-01> + <#turtle-syntax-bad-n3-extras-02> + <#turtle-syntax-bad-n3-extras-03> + <#turtle-syntax-bad-n3-extras-04> + <#turtle-syntax-bad-n3-extras-05> + <#turtle-syntax-bad-n3-extras-06> + <#turtle-syntax-bad-n3-extras-07> + <#turtle-syntax-bad-n3-extras-08> + <#turtle-syntax-bad-n3-extras-09> + <#turtle-syntax-bad-n3-extras-10> + <#turtle-syntax-bad-n3-extras-11> + <#turtle-syntax-bad-n3-extras-12> + <#turtle-syntax-bad-n3-extras-13> + <#turtle-syntax-bad-struct-08> + <#turtle-syntax-bad-struct-09> + <#turtle-syntax-bad-struct-10> + <#turtle-syntax-bad-struct-11> + <#turtle-syntax-bad-struct-12> + <#turtle-syntax-bad-struct-13> + <#turtle-syntax-bad-struct-14> + <#turtle-syntax-bad-struct-15> + <#turtle-syntax-bad-struct-16> + <#turtle-syntax-bad-struct-17> + <#turtle-syntax-bad-lang-01> + <#turtle-syntax-bad-esc-01> + <#turtle-syntax-bad-esc-02> + <#turtle-syntax-bad-esc-03> + <#turtle-syntax-bad-esc-04> + <#turtle-syntax-bad-pname-01> + <#turtle-syntax-bad-pname-02> + <#turtle-syntax-bad-pname-03> + <#turtle-syntax-bad-string-01> + <#turtle-syntax-bad-string-02> + <#turtle-syntax-bad-string-03> + <#turtle-syntax-bad-string-04> + <#turtle-syntax-bad-string-05> + <#turtle-syntax-bad-string-06> + <#turtle-syntax-bad-string-07> + <#turtle-syntax-bad-num-01> + <#turtle-syntax-bad-num-02> + <#turtle-syntax-bad-num-03> + <#turtle-syntax-bad-num-04> + <#turtle-syntax-bad-num-05> + <#turtle-eval-struct-01> + <#turtle-eval-struct-02> + <#turtle-subm-01> + <#turtle-subm-02> + <#turtle-subm-03> + <#turtle-subm-04> + <#turtle-subm-05> + <#turtle-subm-06> + <#turtle-subm-07> + <#turtle-subm-08> + <#turtle-subm-09> + <#turtle-subm-10> + <#turtle-subm-11> + <#turtle-subm-12> + <#turtle-subm-13> + <#turtle-subm-14> + <#turtle-subm-15> + <#turtle-subm-16> + <#turtle-subm-17> + <#turtle-subm-18> + <#turtle-subm-19> + <#turtle-subm-20> + <#turtle-subm-21> + <#turtle-subm-22> + <#turtle-subm-23> + <#turtle-subm-24> + <#turtle-subm-25> + <#turtle-subm-26> + <#turtle-subm-27> + <#turtle-eval-bad-01> + <#turtle-eval-bad-02> + <#turtle-eval-bad-03> + <#turtle-eval-bad-04> + + # tests from Dave Beckett + # http://www.w3.org/2011/rdf-wg/wiki/Turtle_Candidate_Recommendation_Comments#c28 + <#LITERAL_LONG2_with_REVERSE_SOLIDUS> + <#turtle-syntax-bad-LITERAL2_with_langtag_and_datatype> + <#two_LITERAL_LONG2s> + <#langtagged_LONG_with_subtag> + + # tests from David Robillard + # http://www.w3.org/2011/rdf-wg/wiki/Turtle_Candidate_Recommendation_Comments#c21 + <#turtle-syntax-bad-blank-label-dot-end> + <#turtle-syntax-bad-ln-dash-start> + <#turtle-syntax-bad-ln-escape-start> + <#turtle-syntax-bad-ln-escape> + <#turtle-syntax-bad-missing-ns-dot-end> + <#turtle-syntax-bad-missing-ns-dot-start> + <#turtle-syntax-bad-ns-dot-end> + <#turtle-syntax-bad-ns-dot-start> + <#turtle-syntax-bad-number-dot-in-anon> + <#turtle-syntax-blank-label> + <#turtle-syntax-ln-colons> + <#turtle-syntax-ln-dots> + <#turtle-syntax-ns-dots> + ) . + +# atomic tests +<#IRI_subject> rdf:type rdft:TestTurtleEval ; + mf:name "IRI_subject" ; + rdfs:comment "IRI subject" ; + mf:action ; + mf:result ; + . + +<#IRI_with_four_digit_numeric_escape> rdf:type rdft:TestTurtleEval ; + mf:name "IRI_with_four_digit_numeric_escape" ; + rdfs:comment "IRI with four digit numeric escape (\\u)" ; + mf:action ; + mf:result ; + . + +<#IRI_with_eight_digit_numeric_escape> rdf:type rdft:TestTurtleEval ; + mf:name "IRI_with_eight_digit_numeric_escape" ; + rdfs:comment "IRI with eight digit numeric escape (\\U)" ; + mf:action ; + mf:result ; + . + +<#IRI_with_all_punctuation> rdf:type rdft:TestTurtleEval ; + mf:name "IRI_with_all_punctuation" ; + rdfs:comment "IRI with all punctuation" ; + mf:action ; + mf:result ; + . + +<#bareword_a_predicate> rdf:type rdft:TestTurtleEval ; + mf:name "bareword_a_predicate" ; + rdfs:comment "bareword a predicate" ; + mf:action ; + mf:result ; + . + +<#old_style_prefix> rdf:type rdft:TestTurtleEval ; + mf:name "old_style_prefix" ; + rdfs:comment "old-style prefix" ; + mf:action ; + mf:result ; + . + +<#SPARQL_style_prefix> rdf:type rdft:TestTurtleEval ; + mf:name "SPARQL_style_prefix" ; + rdfs:comment "SPARQL-style prefix" ; + mf:action ; + mf:result ; + . + +<#prefixed_IRI_predicate> rdf:type rdft:TestTurtleEval ; + mf:name "prefixed_IRI_predicate" ; + rdfs:comment "prefixed IRI predicate" ; + mf:action ; + mf:result ; + . + +<#prefixed_IRI_object> rdf:type rdft:TestTurtleEval ; + mf:name "prefixed_IRI_object" ; + rdfs:comment "prefixed IRI object" ; + mf:action ; + mf:result ; + . + +<#prefix_only_IRI> rdf:type rdft:TestTurtleEval ; + mf:name "prefix_only_IRI" ; + rdfs:comment "prefix-only IRI (p:)" ; + mf:action ; + mf:result ; + . + +<#prefix_with_PN_CHARS_BASE_character_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "prefix_with_PN_CHARS_BASE_character_boundaries" ; + rdfs:comment "prefix with PN CHARS BASE character boundaries (prefix: AZazÀÖØöø...:)" ; + mf:action ; + mf:result ; + . + +<#prefix_with_non_leading_extras> rdf:type rdft:TestTurtleEval ; + mf:name "prefix_with_non_leading_extras" ; + rdfs:comment "prefix with_non_leading_extras (_:a·̀ͯ‿.⁀)" ; + mf:action ; + mf:result ; + . + +<#localName_with_assigned_nfc_bmp_PN_CHARS_BASE_character_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "localName_with_assigned_nfc_bmp_PN_CHARS_BASE_character_boundaries" ; + rdfs:comment "localName with assigned, NFC-normalized, basic-multilingual-plane PN CHARS BASE character boundaries (p:AZazÀÖØöø...)" ; + mf:action ; + mf:result ; + . + +<#localName_with_assigned_nfc_PN_CHARS_BASE_character_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "localName_with_assigned_nfc_PN_CHARS_BASE_character_boundaries" ; + rdfs:comment "localName with assigned, NFC-normalized PN CHARS BASE character boundaries (p:AZazÀÖØöø...)" ; + mf:action ; + mf:result ; + . + +<#localName_with_nfc_PN_CHARS_BASE_character_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "localName_with_nfc_PN_CHARS_BASE_character_boundaries" ; + rdfs:comment "localName with nfc-normalize PN CHARS BASE character boundaries (p:AZazÀÖØöø...)" ; + mf:action ; + mf:result ; + . + +<#localName_with_PN_CHARS_BASE_character_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "localName_with_PN_CHARS_BASE_character_boundaries" ; + rdfs:comment "localName with PN CHARS BASE character boundaries (p:AZazÀÖØöø...)" ; + mf:action ; + mf:result ; + . + +<#default_namespace_IRI> rdf:type rdft:TestTurtleEval ; + mf:name "default_namespace_IRI" ; + rdfs:comment "default namespace IRI (:ln)" ; + mf:action ; + mf:result ; + . + +<#prefix_reassigned_and_used> rdf:type rdft:TestTurtleEval ; + mf:name "prefix_reassigned_and_used" ; + rdfs:comment "prefix reassigned and used" ; + mf:action ; + mf:result ; + . + +<#reserved_escaped_localName> rdf:type rdft:TestTurtleEval ; + mf:name "reserved_escaped_localName" ; + rdfs:comment "reserved-escaped local name" ; + mf:action ; + mf:result ; + . + +<#percent_escaped_localName> rdf:type rdft:TestTurtleEval ; + mf:name "percent_escaped_localName" ; + rdfs:comment "percent-escaped local name" ; + mf:action ; + mf:result ; + . + +<#HYPHEN_MINUS_in_localName> rdf:type rdft:TestTurtleEval ; + mf:name "HYPHEN_MINUS_in_localName" ; + rdfs:comment "HYPHEN-MINUS in local name" ; + mf:action ; + mf:result ; + . + +<#underscore_in_localName> rdf:type rdft:TestTurtleEval ; + mf:name "underscore_in_localName" ; + rdfs:comment "underscore in local name" ; + mf:action ; + mf:result ; + . + +<#localname_with_COLON> rdf:type rdft:TestTurtleEval ; + mf:name "localname_with_COLON" ; + rdfs:comment "localname with COLON" ; + mf:action ; + mf:result ; + . + +<#localName_with_leading_underscore> rdf:type rdft:TestTurtleEval ; + mf:name "localName_with_leading_underscore" ; + rdfs:comment "localName with leading underscore (p:_)" ; + mf:action ; + mf:result ; + . + +<#localName_with_leading_digit> rdf:type rdft:TestTurtleEval ; + mf:name "localName_with_leading_digit" ; + rdfs:comment "localName with leading digit (p:_)" ; + mf:action ; + mf:result ; + . + +<#localName_with_non_leading_extras> rdf:type rdft:TestTurtleEval ; + mf:name "localName_with_non_leading_extras" ; + rdfs:comment "localName with_non_leading_extras (_:a·̀ͯ‿.⁀)" ; + mf:action ; + mf:result ; + . + +<#old_style_base> rdf:type rdft:TestTurtleEval ; + mf:name "old_style_base" ; + rdfs:comment "old-style base" ; + mf:action ; + mf:result ; + . + +<#SPARQL_style_base> rdf:type rdft:TestTurtleEval ; + mf:name "SPARQL_style_base" ; + rdfs:comment "SPARQL-style base" ; + mf:action ; + mf:result ; + . + +<#labeled_blank_node_subject> rdf:type rdft:TestTurtleEval ; + mf:name "labeled_blank_node_subject" ; + rdfs:comment "labeled blank node subject" ; + mf:action ; + mf:result ; + . + +<#labeled_blank_node_object> rdf:type rdft:TestTurtleEval ; + mf:name "labeled_blank_node_object" ; + rdfs:comment "labeled blank node object" ; + mf:action ; + mf:result ; + . + +<#labeled_blank_node_with_PN_CHARS_BASE_character_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "labeled_blank_node_with_PN_CHARS_BASE_character_boundaries" ; + rdfs:comment "labeled blank node with PN_CHARS_BASE character boundaries (_:AZazÀÖØöø...)" ; + mf:action ; + mf:result ; + . + +<#labeled_blank_node_with_leading_underscore> rdf:type rdft:TestTurtleEval ; + mf:name "labeled_blank_node_with_leading_underscore" ; + rdfs:comment "labeled blank node with_leading_underscore (_:_)" ; + mf:action ; + mf:result ; + . + +<#labeled_blank_node_with_leading_digit> rdf:type rdft:TestTurtleEval ; + mf:name "labeled_blank_node_with_leading_digit" ; + rdfs:comment "labeled blank node with_leading_digit (_:0)" ; + mf:action ; + mf:result ; + . + +<#labeled_blank_node_with_non_leading_extras> rdf:type rdft:TestTurtleEval ; + mf:name "labeled_blank_node_with_non_leading_extras" ; + rdfs:comment "labeled blank node with_non_leading_extras (_:a·̀ͯ‿.⁀)" ; + mf:action ; + mf:result ; + . + +<#anonymous_blank_node_subject> rdf:type rdft:TestTurtleEval ; + mf:name "anonymous_blank_node_subject" ; + rdfs:comment "anonymous blank node subject" ; + mf:action ; + mf:result ; + . + +<#anonymous_blank_node_object> rdf:type rdft:TestTurtleEval ; + mf:name "anonymous_blank_node_object" ; + rdfs:comment "anonymous blank node object" ; + mf:action ; + mf:result ; + . + +<#sole_blankNodePropertyList> rdf:type rdft:TestTurtleEval ; + mf:name "sole_blankNodePropertyList" ; + rdfs:comment "sole blankNodePropertyList [

    ] ." ; + mf:action ; + mf:result ; + . + +<#blankNodePropertyList_as_subject> rdf:type rdft:TestTurtleEval ; + mf:name "blankNodePropertyList_as_subject" ; + rdfs:comment "blankNodePropertyList as subject [ … ]

    ." ; + mf:action ; + mf:result ; + . + +<#blankNodePropertyList_as_object> rdf:type rdft:TestTurtleEval ; + mf:name "blankNodePropertyList_as_object" ; + rdfs:comment "blankNodePropertyList as object

    [ … ] ." ; + mf:action ; + mf:result ; + . + +<#blankNodePropertyList_with_multiple_triples> rdf:type rdft:TestTurtleEval ; + mf:name "blankNodePropertyList_with_multiple_triples" ; + rdfs:comment "blankNodePropertyList with multiple triples [

    ; ]" ; + mf:action ; + mf:result ; + . + +<#nested_blankNodePropertyLists> rdf:type rdft:TestTurtleEval ; + mf:name "nested_blankNodePropertyLists" ; + rdfs:comment "nested blankNodePropertyLists [ [ ] ; ]" ; + mf:action ; + mf:result ; + . + +<#blankNodePropertyList_containing_collection> rdf:type rdft:TestTurtleEval ; + mf:name "blankNodePropertyList_containing_collection" ; + rdfs:comment "blankNodePropertyList containing collection [ ( … ) ]" ; + mf:action ; + mf:result ; + . + +<#collection_subject> rdf:type rdft:TestTurtleEval ; + mf:name "collection_subject" ; + rdfs:comment "collection subject" ; + mf:action ; + mf:result ; + . + +<#collection_object> rdf:type rdft:TestTurtleEval ; + mf:name "collection_object" ; + rdfs:comment "collection object" ; + mf:action ; + mf:result ; + . + +<#empty_collection> rdf:type rdft:TestTurtleEval ; + mf:name "empty_collection" ; + rdfs:comment "empty collection ()" ; + mf:action ; + mf:result ; + . + +<#nested_collection> rdf:type rdft:TestTurtleEval ; + mf:name "nested_collection" ; + rdfs:comment "nested collection (())" ; + mf:action ; + mf:result ; + . + +<#first> rdf:type rdft:TestTurtleEval ; + mf:name "first" ; + rdfs:comment "first, not last, non-empty nested collection" ; + mf:action ; + mf:result ; + . + +<#last> rdf:type rdft:TestTurtleEval ; + mf:name "last" ; + rdfs:comment "last, not first, non-empty nested collection" ; + mf:action ; + mf:result ; + . + +<#LITERAL1> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL1" ; + rdfs:comment "LITERAL1 'x'" ; + mf:action ; + mf:result ; + . + +<#LITERAL1_ascii_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL1_ascii_boundaries" ; + rdfs:comment "LITERAL1_ascii_boundaries '\\x00\\x09\\x0b\\x0c\\x0e\\x26\\x28...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL1_with_UTF8_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL1_with_UTF8_boundaries" ; + rdfs:comment "LITERAL1_with_UTF8_boundaries '\\x80\\x7ff\\x800\\xfff...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL1_all_controls> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL1_all_controls" ; + rdfs:comment "LITERAL1_all_controls '\\x00\\x01\\x02\\x03\\x04...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL1_all_punctuation> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL1_all_punctuation" ; + rdfs:comment "LITERAL1_all_punctuation '!\"#$%&()...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG1> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG1" ; + rdfs:comment "LITERAL_LONG1 '''x'''" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG1_ascii_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG1_ascii_boundaries" ; + rdfs:comment "LITERAL_LONG1_ascii_boundaries '\\x00\\x26\\x28...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG1_with_UTF8_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG1_with_UTF8_boundaries" ; + rdfs:comment "LITERAL_LONG1_with_UTF8_boundaries '\\x80\\x7ff\\x800\\xfff...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG1_with_1_squote> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG1_with_1_squote" ; + rdfs:comment "LITERAL_LONG1 with 1 squote '''a'b'''" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG1_with_2_squotes> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG1_with_2_squotes" ; + rdfs:comment "LITERAL_LONG1 with 2 squotes '''a''b'''" ; + mf:action ; + mf:result ; + . + +<#LITERAL2> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL2" ; + rdfs:comment "LITERAL2 \"x\"" ; + mf:action ; + mf:result ; + . + +<#LITERAL2_ascii_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL2_ascii_boundaries" ; + rdfs:comment "LITERAL2_ascii_boundaries '\\x00\\x09\\x0b\\x0c\\x0e\\x21\\x23...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL2_with_UTF8_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL2_with_UTF8_boundaries" ; + rdfs:comment "LITERAL2_with_UTF8_boundaries '\\x80\\x7ff\\x800\\xfff...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG2> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG2" ; + rdfs:comment "LITERAL_LONG2 \"\"\"x\"\"\"" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG2_ascii_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG2_ascii_boundaries" ; + rdfs:comment "LITERAL_LONG2_ascii_boundaries '\\x00\\x21\\x23...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG2_with_UTF8_boundaries> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG2_with_UTF8_boundaries" ; + rdfs:comment "LITERAL_LONG2_with_UTF8_boundaries '\\x80\\x7ff\\x800\\xfff...'" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG2_with_1_squote> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG2_with_1_squote" ; + rdfs:comment "LITERAL_LONG2 with 1 squote \"\"\"a\"b\"\"\"" ; + mf:action ; + mf:result ; + . + +<#LITERAL_LONG2_with_2_squotes> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG2_with_2_squotes" ; + rdfs:comment "LITERAL_LONG2 with 2 squotes \"\"\"a\"\"b\"\"\"" ; + mf:action ; + mf:result ; + . + +<#literal_with_CHARACTER_TABULATION> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_CHARACTER_TABULATION" ; + rdfs:comment "literal with CHARACTER TABULATION" ; + mf:action ; + mf:result ; + . + +<#literal_with_BACKSPACE> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_BACKSPACE" ; + rdfs:comment "literal with BACKSPACE" ; + mf:action ; + mf:result ; + . + +<#literal_with_LINE_FEED> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_LINE_FEED" ; + rdfs:comment "literal with LINE FEED" ; + mf:action ; + mf:result ; + . + +<#literal_with_CARRIAGE_RETURN> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_CARRIAGE_RETURN" ; + rdfs:comment "literal with CARRIAGE RETURN" ; + mf:action ; + mf:result ; + . + +<#literal_with_FORM_FEED> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_FORM_FEED" ; + rdfs:comment "literal with FORM FEED" ; + mf:action ; + mf:result ; + . + +<#literal_with_REVERSE_SOLIDUS> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_REVERSE_SOLIDUS" ; + rdfs:comment "literal with REVERSE SOLIDUS" ; + mf:action ; + mf:result ; + . + +<#literal_with_escaped_CHARACTER_TABULATION> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_escaped_CHARACTER_TABULATION" ; + rdfs:comment "literal with escaped CHARACTER TABULATION" ; + mf:action ; + mf:result ; + . + +<#literal_with_escaped_BACKSPACE> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_escaped_BACKSPACE" ; + rdfs:comment "literal with escaped BACKSPACE" ; + mf:action ; + mf:result ; + . + +<#literal_with_escaped_LINE_FEED> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_escaped_LINE_FEED" ; + rdfs:comment "literal with escaped LINE FEED" ; + mf:action ; + mf:result ; + . + +<#literal_with_escaped_CARRIAGE_RETURN> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_escaped_CARRIAGE_RETURN" ; + rdfs:comment "literal with escaped CARRIAGE RETURN" ; + mf:action ; + mf:result ; + . + +<#literal_with_escaped_FORM_FEED> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_escaped_FORM_FEED" ; + rdfs:comment "literal with escaped FORM FEED" ; + mf:action ; + mf:result ; + . + +<#literal_with_numeric_escape4> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_numeric_escape4" ; + rdfs:comment "literal with numeric escape4 \\u" ; + mf:action ; + mf:result ; + . + +<#literal_with_numeric_escape8> rdf:type rdft:TestTurtleEval ; + mf:name "literal_with_numeric_escape8" ; + rdfs:comment "literal with numeric escape8 \\U" ; + mf:action ; + mf:result ; + . + +<#IRIREF_datatype> rdf:type rdft:TestTurtleEval ; + mf:name "IRIREF_datatype" ; + rdfs:comment "IRIREF datatype \"\"^^" ; + mf:action ; + mf:result ; + . + +<#prefixed_name_datatype> rdf:type rdft:TestTurtleEval ; + mf:name "prefixed_name_datatype" ; + rdfs:comment "prefixed name datatype \"\"^^p:t" ; + mf:action ; + mf:result ; + . + +<#bareword_integer> rdf:type rdft:TestTurtleEval ; + mf:name "bareword_integer" ; + rdfs:comment "bareword integer" ; + mf:action ; + mf:result ; + . + +<#bareword_decimal> rdf:type rdft:TestTurtleEval ; + mf:name "bareword_decimal" ; + rdfs:comment "bareword decimal" ; + mf:action ; + mf:result ; + . + +<#bareword_double> rdf:type rdft:TestTurtleEval ; + mf:name "bareword_double" ; + rdfs:comment "bareword double" ; + mf:action ; + mf:result ; + . + +<#double_lower_case_e> rdf:type rdft:TestTurtleEval ; + mf:name "double_lower_case_e" ; + rdfs:comment "double lower case e" ; + mf:action ; + mf:result ; + . + +<#negative_numeric> rdf:type rdft:TestTurtleEval ; + mf:name "negative_numeric" ; + rdfs:comment "negative numeric" ; + mf:action ; + mf:result ; + . + +<#positive_numeric> rdf:type rdft:TestTurtleEval ; + mf:name "positive_numeric" ; + rdfs:comment "positive numeric" ; + mf:action ; + mf:result ; + . + +<#numeric_with_leading_0> rdf:type rdft:TestTurtleEval ; + mf:name "numeric_with_leading_0" ; + rdfs:comment "numeric with leading 0" ; + mf:action ; + mf:result ; + . + +<#literal_true> rdf:type rdft:TestTurtleEval ; + mf:name "literal_true" ; + rdfs:comment "literal true" ; + mf:action ; + mf:result ; + . + +<#literal_false> rdf:type rdft:TestTurtleEval ; + mf:name "literal_false" ; + rdfs:comment "literal false" ; + mf:action ; + mf:result ; + . + +<#langtagged_non_LONG> rdf:type rdft:TestTurtleEval ; + mf:name "langtagged_non_LONG" ; + rdfs:comment "langtagged non-LONG \"x\"@en" ; + mf:action ; + mf:result ; + . + +<#langtagged_LONG> rdf:type rdft:TestTurtleEval ; + mf:name "langtagged_LONG" ; + rdfs:comment "langtagged LONG \"\"\"x\"\"\"@en" ; + mf:action ; + mf:result ; + . + +<#lantag_with_subtag> rdf:type rdft:TestTurtleEval ; + mf:name "lantag_with_subtag" ; + rdfs:comment "lantag with subtag \"x\"@en-us" ; + mf:action ; + mf:result ; + . + +<#objectList_with_two_objects> rdf:type rdft:TestTurtleEval ; + mf:name "objectList_with_two_objects" ; + rdfs:comment "objectList with two objects … ," ; + mf:action ; + mf:result ; + . + +<#predicateObjectList_with_two_objectLists> rdf:type rdft:TestTurtleEval ; + mf:name "predicateObjectList_with_two_objectLists" ; + rdfs:comment "predicateObjectList with two objectLists … ," ; + mf:action ; + mf:result ; + . + +<#repeated_semis_at_end> rdf:type rdft:TestTurtleEval ; + mf:name "repeated_semis_at_end" ; + rdfs:comment "repeated semis at end

    ;; ." ; + mf:action ; + mf:result ; + . + +<#repeated_semis_not_at_end> rdf:type rdft:TestTurtleEval ; + mf:name "repeated_semis_not_at_end" ; + rdfs:comment "repeated semis not at end

    ;;." ; + mf:action ; + mf:result ; + . + +# original tests-ttl +<#turtle-syntax-file-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-file-01" ; + rdfs:comment "Empty file" ; + mf:action ; + . + +<#turtle-syntax-file-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-file-02" ; + rdfs:comment "Only comment" ; + mf:action ; + . + +<#turtle-syntax-file-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-file-03" ; + rdfs:comment "One comment, one empty line" ; + mf:action ; + . + +<#turtle-syntax-uri-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-uri-01" ; + rdfs:comment "Only IRIs" ; + mf:action ; + . + +<#turtle-syntax-uri-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-uri-02" ; + rdfs:comment "IRIs with Unicode escape" ; + mf:action ; + . + +<#turtle-syntax-uri-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-uri-03" ; + rdfs:comment "IRIs with long Unicode escape" ; + mf:action ; + . + +<#turtle-syntax-uri-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-uri-04" ; + rdfs:comment "Legal IRIs" ; + mf:action ; + . + +<#turtle-syntax-base-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-base-01" ; + rdfs:comment "@base" ; + mf:action ; + . + +<#turtle-syntax-base-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-base-02" ; + rdfs:comment "BASE" ; + mf:action ; + . + +<#turtle-syntax-base-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-base-03" ; + rdfs:comment "@base with relative IRIs" ; + mf:action ; + . + +<#turtle-syntax-base-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-base-04" ; + rdfs:comment "base with relative IRIs" ; + mf:action ; + . + +<#turtle-syntax-prefix-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-01" ; + rdfs:comment "@prefix" ; + mf:action ; + . + +<#turtle-syntax-prefix-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-02" ; + rdfs:comment "PreFIX" ; + mf:action ; + . + +<#turtle-syntax-prefix-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-03" ; + rdfs:comment "Empty PREFIX" ; + mf:action ; + . + +<#turtle-syntax-prefix-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-04" ; + rdfs:comment "Empty @prefix with % escape" ; + mf:action ; + . + +<#turtle-syntax-prefix-05> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-05" ; + rdfs:comment "@prefix with no suffix" ; + mf:action ; + . + +<#turtle-syntax-prefix-06> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-06" ; + rdfs:comment "colon is a legal pname character" ; + mf:action ; + . + +<#turtle-syntax-prefix-07> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-07" ; + rdfs:comment "dash is a legal pname character" ; + mf:action ; + . + +<#turtle-syntax-prefix-08> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-08" ; + rdfs:comment "underscore is a legal pname character" ; + mf:action ; + . + +<#turtle-syntax-prefix-09> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-prefix-09" ; + rdfs:comment "percents in pnames" ; + mf:action ; + . + +<#turtle-syntax-string-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-01" ; + rdfs:comment "string literal" ; + mf:action ; + . + +<#turtle-syntax-string-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-02" ; + rdfs:comment "langString literal" ; + mf:action ; + . + +<#turtle-syntax-string-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-03" ; + rdfs:comment "langString literal with region" ; + mf:action ; + . + +<#turtle-syntax-string-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-04" ; + rdfs:comment "squote string literal" ; + mf:action ; + . + +<#turtle-syntax-string-05> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-05" ; + rdfs:comment "squote langString literal" ; + mf:action ; + . + +<#turtle-syntax-string-06> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-06" ; + rdfs:comment "squote langString literal with region" ; + mf:action ; + . + +<#turtle-syntax-string-07> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-07" ; + rdfs:comment "long string literal with embedded single- and double-quotes" ; + mf:action ; + . + +<#turtle-syntax-string-08> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-08" ; + rdfs:comment "long string literal with embedded newline" ; + mf:action ; + . + +<#turtle-syntax-string-09> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-09" ; + rdfs:comment "squote long string literal with embedded single- and double-quotes" ; + mf:action ; + . + +<#turtle-syntax-string-10> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-10" ; + rdfs:comment "long langString literal with embedded newline" ; + mf:action ; + . + +<#turtle-syntax-string-11> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-string-11" ; + rdfs:comment "squote long langString literal with embedded newline" ; + mf:action ; + . + +<#turtle-syntax-str-esc-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-str-esc-01" ; + rdfs:comment "string literal with escaped newline" ; + mf:action ; + . + +<#turtle-syntax-str-esc-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-str-esc-02" ; + rdfs:comment "string literal with Unicode escape" ; + mf:action ; + . + +<#turtle-syntax-str-esc-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-str-esc-03" ; + rdfs:comment "string literal with long Unicode escape" ; + mf:action ; + . + +<#turtle-syntax-pname-esc-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-pname-esc-01" ; + rdfs:comment "pname with back-slash escapes" ; + mf:action ; + . + +<#turtle-syntax-pname-esc-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-pname-esc-02" ; + rdfs:comment "pname with back-slash escapes (2)" ; + mf:action ; + . + +<#turtle-syntax-pname-esc-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-pname-esc-03" ; + rdfs:comment "pname with back-slash escapes (3)" ; + mf:action ; + . + +<#turtle-syntax-bnode-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-01" ; + rdfs:comment "bnode subject" ; + mf:action ; + . + +<#turtle-syntax-bnode-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-02" ; + rdfs:comment "bnode object" ; + mf:action ; + . + +<#turtle-syntax-bnode-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-03" ; + rdfs:comment "bnode property list object" ; + mf:action ; + . + +<#turtle-syntax-bnode-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-04" ; + rdfs:comment "bnode property list object (2)" ; + mf:action ; + . + +<#turtle-syntax-bnode-05> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-05" ; + rdfs:comment "bnode property list subject" ; + mf:action ; + . + +<#turtle-syntax-bnode-06> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-06" ; + rdfs:comment "labeled bnode subject" ; + mf:action ; + . + +<#turtle-syntax-bnode-07> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-07" ; + rdfs:comment "labeled bnode subject and object" ; + mf:action ; + . + +<#turtle-syntax-bnode-08> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-08" ; + rdfs:comment "bare bnode property list" ; + mf:action ; + . + +<#turtle-syntax-bnode-09> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-09" ; + rdfs:comment "bnode property list" ; + mf:action ; + . + +<#turtle-syntax-bnode-10> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-bnode-10" ; + rdfs:comment "mixed bnode property list and triple" ; + mf:action ; + . + +<#turtle-syntax-number-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-01" ; + rdfs:comment "integer literal" ; + mf:action ; + . + +<#turtle-syntax-number-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-02" ; + rdfs:comment "negative integer literal" ; + mf:action ; + . + +<#turtle-syntax-number-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-03" ; + rdfs:comment "positive integer literal" ; + mf:action ; + . + +<#turtle-syntax-number-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-04" ; + rdfs:comment "decimal literal" ; + mf:action ; + . + +<#turtle-syntax-number-05> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-05" ; + rdfs:comment "decimal literal (no leading digits)" ; + mf:action ; + . + +<#turtle-syntax-number-06> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-06" ; + rdfs:comment "negative decimal literal" ; + mf:action ; + . + +<#turtle-syntax-number-07> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-07" ; + rdfs:comment "positive decimal literal" ; + mf:action ; + . + +<#turtle-syntax-number-08> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-08" ; + rdfs:comment "integer literal with decimal lexical confusion" ; + mf:action ; + . + +<#turtle-syntax-number-09> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-09" ; + rdfs:comment "double literal" ; + mf:action ; + . + +<#turtle-syntax-number-10> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-10" ; + rdfs:comment "negative double literal" ; + mf:action ; + . + +<#turtle-syntax-number-11> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-number-11" ; + rdfs:comment "double literal no fraction" ; + mf:action ; + . + +<#turtle-syntax-datatypes-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-datatypes-01" ; + rdfs:comment "xsd:byte literal" ; + mf:action ; + . + +<#turtle-syntax-datatypes-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-datatypes-02" ; + rdfs:comment "integer as xsd:string" ; + mf:action ; + . + +<#turtle-syntax-kw-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-kw-01" ; + rdfs:comment "boolean literal (true)" ; + mf:action ; + . + +<#turtle-syntax-kw-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-kw-02" ; + rdfs:comment "boolean literal (false)" ; + mf:action ; + . + +<#turtle-syntax-kw-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-kw-03" ; + rdfs:comment "'a' as keyword" ; + mf:action ; + . + +<#turtle-syntax-struct-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-struct-01" ; + rdfs:comment "object list" ; + mf:action ; + . + +<#turtle-syntax-struct-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-struct-02" ; + rdfs:comment "predicate list with object list" ; + mf:action ; + . + +<#turtle-syntax-struct-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-struct-03" ; + rdfs:comment "predicate list with object list and dangling ';'" ; + mf:action ; + . + +<#turtle-syntax-struct-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-struct-04" ; + rdfs:comment "predicate list with multiple ;;" ; + mf:action ; + . + +<#turtle-syntax-struct-05> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-struct-05" ; + rdfs:comment "predicate list with multiple ;;" ; + mf:action ; + . + +<#turtle-syntax-lists-01> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-lists-01" ; + rdfs:comment "empty list" ; + mf:action ; + . + +<#turtle-syntax-lists-02> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-lists-02" ; + rdfs:comment "mixed list" ; + mf:action ; + . + +<#turtle-syntax-lists-03> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-lists-03" ; + rdfs:comment "isomorphic list as subject and object" ; + mf:action ; + . + +<#turtle-syntax-lists-04> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-lists-04" ; + rdfs:comment "lists of lists" ; + mf:action ; + . + +<#turtle-syntax-lists-05> rdf:type rdft:TestTurtlePositiveSyntax ; + mf:name "turtle-syntax-lists-05" ; + rdfs:comment "mixed lists with embedded lists" ; + mf:action ; + . + +<#turtle-syntax-bad-uri-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-uri-01" ; + rdfs:comment "Bad IRI : space (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-uri-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-uri-02" ; + rdfs:comment "Bad IRI : bad escape (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-uri-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-uri-03" ; + rdfs:comment "Bad IRI : bad long escape (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-uri-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-uri-04" ; + rdfs:comment "Bad IRI : character escapes not allowed (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-uri-05> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-uri-05" ; + rdfs:comment "Bad IRI : character escapes not allowed (2) (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-prefix-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-prefix-01" ; + rdfs:comment "No prefix (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-prefix-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-prefix-02" ; + rdfs:comment "No prefix (2) (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-prefix-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-prefix-03" ; + rdfs:comment "@prefix without URI (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-prefix-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-prefix-04" ; + rdfs:comment "@prefix without prefix name (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-prefix-05> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-prefix-05" ; + rdfs:comment "@prefix without ':' (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-base-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-base-01" ; + rdfs:comment "@base without URI (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-base-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-base-02" ; + rdfs:comment "@base in wrong case (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-base-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-base-03" ; + rdfs:comment "BASE without URI (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-01" ; + rdfs:comment "Turtle is not TriG (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-02" ; + rdfs:comment "Turtle is not N3 (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-03" ; + rdfs:comment "Turtle is not NQuads (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-04" ; + rdfs:comment "Turtle does not allow literals-as-subjects (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-05> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-05" ; + rdfs:comment "Turtle does not allow literals-as-predicates (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-06> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-06" ; + rdfs:comment "Turtle does not allow bnodes-as-predicates (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-07> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-07" ; + rdfs:comment "Turtle does not allow labeled bnodes-as-predicates (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-kw-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-kw-01" ; + rdfs:comment "'A' is not a keyword (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-kw-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-kw-02" ; + rdfs:comment "'a' cannot be used as subject (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-kw-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-kw-03" ; + rdfs:comment "'a' cannot be used as object (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-kw-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-kw-04" ; + rdfs:comment "'true' cannot be used as subject (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-kw-05> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-kw-05" ; + rdfs:comment "'true' cannot be used as object (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-01" ; + rdfs:comment "{} fomulae not in Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-02" ; + rdfs:comment "= is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-03" ; + rdfs:comment "N3 paths not in Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-04" ; + rdfs:comment "N3 paths not in Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-05> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-05" ; + rdfs:comment "N3 is...of not in Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-06> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-06" ; + rdfs:comment "N3 paths not in Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-07> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-07" ; + rdfs:comment "@keywords is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-08> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-08" ; + rdfs:comment "@keywords is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-09> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-09" ; + rdfs:comment "=> is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-10> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-10" ; + rdfs:comment "<= is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-11> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-11" ; + rdfs:comment "@forSome is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-12> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-12" ; + rdfs:comment "@forAll is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-n3-extras-13> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-n3-extras-13" ; + rdfs:comment "@keywords is not Turtle (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-08> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-08" ; + rdfs:comment "missing '.' (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-09> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-09" ; + rdfs:comment "extra '.' (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-10> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-10" ; + rdfs:comment "extra '.' (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-11> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-11" ; + rdfs:comment "trailing ';' no '.' (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-12> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-12" ; + rdfs:comment "subject, predicate, no object (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-13> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-13" ; + rdfs:comment "subject, predicate, no object (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-14> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-14" ; + rdfs:comment "literal as subject (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-15> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-15" ; + rdfs:comment "literal as predicate (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-16> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-16" ; + rdfs:comment "bnode as predicate (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-struct-17> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-struct-17" ; + rdfs:comment "labeled bnode as predicate (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-lang-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-lang-01" ; + rdfs:comment "langString with bad lang (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-esc-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-esc-01" ; + rdfs:comment "Bad string escape (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-esc-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-esc-02" ; + rdfs:comment "Bad string escape (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-esc-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-esc-03" ; + rdfs:comment "Bad string escape (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-esc-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-esc-04" ; + rdfs:comment "Bad string escape (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-pname-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-pname-01" ; + rdfs:comment "'~' must be escaped in pname (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-pname-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-pname-02" ; + rdfs:comment "Bad %-sequence in pname (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-pname-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-pname-03" ; + rdfs:comment "Bad unicode escape in pname (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-string-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-string-01" ; + rdfs:comment "mismatching string literal open/close (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-string-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-string-02" ; + rdfs:comment "mismatching string literal open/close (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-string-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-string-03" ; + rdfs:comment "mismatching string literal long/short (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-string-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-string-04" ; + rdfs:comment "mismatching long string literal open/close (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-string-05> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-string-05" ; + rdfs:comment "Long literal with missing end (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-string-06> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-string-06" ; + rdfs:comment "Long literal with extra quote (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-string-07> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-string-07" ; + rdfs:comment "Long literal with extra squote (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-num-01> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-num-01" ; + rdfs:comment "Bad number format (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-num-02> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-num-02" ; + rdfs:comment "Bad number format (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-num-03> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-num-03" ; + rdfs:comment "Bad number format (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-num-04> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-num-04" ; + rdfs:comment "Bad number format (negative test)" ; + mf:action ; + . + +<#turtle-syntax-bad-num-05> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-num-05" ; + rdfs:comment "Bad number format (negative test)" ; + mf:action ; + . + +<#turtle-eval-struct-01> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-eval-struct-01" ; + rdfs:comment "triple with IRIs" ; + mf:action ; + mf:result ; + . + +<#turtle-eval-struct-02> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-eval-struct-02" ; + rdfs:comment "triple with IRIs and embedded whitespace" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-01> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-01" ; + rdfs:comment "Blank subject" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-02> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-02" ; + rdfs:comment "@prefix and qnames" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-03> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-03" ; + rdfs:comment ", operator" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-04> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-04" ; + rdfs:comment "; operator" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-05> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-05" ; + rdfs:comment "empty [] as subject and object" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-06> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-06" ; + rdfs:comment "non-empty [] as subject and object" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-07> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-07" ; + rdfs:comment "'a' as predicate" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-08> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-08" ; + rdfs:comment "simple collection" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-09> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-09" ; + rdfs:comment "empty collection" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-10> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-10" ; + rdfs:comment "integer datatyped literal" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-11> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-11" ; + rdfs:comment "decimal integer canonicalization" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-12> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-12" ; + rdfs:comment "- and _ in names and qnames" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-13> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-13" ; + rdfs:comment "tests for rdf:_ and other qnames starting with _" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-14> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-14" ; + rdfs:comment "bare : allowed" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-15> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-15" ; + rdfs:comment "simple long literal" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-16> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-16" ; + rdfs:comment "long literals with escapes" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-17> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-17" ; + rdfs:comment "floating point number" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-18> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-18" ; + rdfs:comment "empty literals, normal and long variant" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-19> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-19" ; + rdfs:comment "positive integer, decimal and doubles" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-20> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-20" ; + rdfs:comment "negative integer, decimal and doubles" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-21> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-21" ; + rdfs:comment "long literal ending in double quote" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-22> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-22" ; + rdfs:comment "boolean literals" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-23> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-23" ; + rdfs:comment "comments" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-24> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-24" ; + rdfs:comment "no final mewline" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-25> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-25" ; + rdfs:comment "repeating a @prefix changes pname definition" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-26> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-26" ; + rdfs:comment "Variations on decimal canonicalization" ; + mf:action ; + mf:result ; + . + +<#turtle-subm-27> rdf:type rdft:TestTurtleEval ; + mf:name "turtle-subm-27" ; + rdfs:comment "Repeating @base changes base for relative IRI lookup" ; + mf:action ; + mf:result ; + . + +<#turtle-eval-bad-01> rdf:type rdft:TestTurtleNegativeEval ; + mf:name "turtle-eval-bad-01" ; + rdfs:comment "Bad IRI : good escape, bad charcater (negative evaluation test)" ; + mf:action ; + . + +<#turtle-eval-bad-02> rdf:type rdft:TestTurtleNegativeEval ; + mf:name "turtle-eval-bad-02" ; + rdfs:comment "Bad IRI : hex 3C is < (negative evaluation test)" ; + mf:action ; + . + +<#turtle-eval-bad-03> rdf:type rdft:TestTurtleNegativeEval ; + mf:name "turtle-eval-bad-03" ; + rdfs:comment "Bad IRI : hex 3E is (negative evaluation test)" ; + mf:action ; + . + +<#turtle-eval-bad-04> rdf:type rdft:TestTurtleNegativeEval ; + mf:name "turtle-eval-bad-04" ; + rdfs:comment "Bad IRI : {abc} (negative evaluation test)" ; + mf:action ; + . + +# tests from Dave Beckett +# http://www.w3.org/2011/rdf-wg/wiki/Turtle_Candidate_Recommendation_Comments#c28 +<#LITERAL_LONG2_with_REVERSE_SOLIDUS> rdf:type rdft:TestTurtleEval ; + mf:name "LITERAL_LONG2_with_REVERSE_SOLIDUS" ; + rdfs:comment "REVERSE SOLIDUS at end of LITERAL_LONG2" ; + mf:action ; + mf:result ; + . + +<#turtle-syntax-bad-LITERAL2_with_langtag_and_datatype> rdf:type rdft:TestTurtleNegativeSyntax ; + mf:name "turtle-syntax-bad-num-05" ; + rdfs:comment "Bad number format (negative test)" ; + mf:action ; + . + +<#two_LITERAL_LONG2s> rdf:type rdft:TestTurtleEval ; + mf:name "two_LITERAL_LONG2s" ; + rdfs:comment "two LITERAL_LONG2s testing quote delimiter overrun" ; + mf:action ; + mf:result ; + . + +<#langtagged_LONG_with_subtag> rdf:type rdft:TestTurtleEval ; + mf:name "langtagged_LONG_with_subtag" ; + rdfs:comment "langtagged LONG with subtag \"\"\"Cheers\"\"\"@en-UK" ; + mf:action ; + mf:result ; + . + +# tests from David Robillard +# http://www.w3.org/2011/rdf-wg/wiki/Turtle_Candidate_Recommendation_Comments#c21 +<#turtle-syntax-bad-blank-label-dot-end> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Blank node label must not end in dot" ; + mf:name "turtle-syntax-bad-blank-label-dot-end" ; + mf:action . + +<#turtle-syntax-bad-number-dot-in-anon> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Dot delimeter may not appear in anonymous nodes" ; + mf:name "turtle-syntax-bad-number-dot-in-anon" ; + mf:action . + +<#turtle-syntax-bad-ln-dash-start> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Local name must not begin with dash" ; + mf:name "turtle-syntax-bad-ln-dash-start" ; + mf:action . + +<#turtle-syntax-bad-ln-escape> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Bad hex escape in local name" ; + mf:name "turtle-syntax-bad-ln-escape" ; + mf:action . + +<#turtle-syntax-bad-ln-escape-start> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Bad hex escape at start of local name" ; + mf:name "turtle-syntax-bad-ln-escape-start" ; + mf:action . + +<#turtle-syntax-bad-ns-dot-end> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Prefix must not end in dot" ; + mf:name "turtle-syntax-bad-ns-dot-end" ; + mf:action . + +<#turtle-syntax-bad-ns-dot-start> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Prefix must not start with dot" ; + mf:name "turtle-syntax-bad-ns-dot-start" ; + mf:action . + +<#turtle-syntax-bad-missing-ns-dot-end> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Prefix must not end in dot (error in triple, not prefix directive like turtle-syntax-bad-ns-dot-end)" ; + mf:name "turtle-syntax-bad-missing-ns-dot-end" ; + mf:action . + +<#turtle-syntax-bad-missing-ns-dot-start> + rdf:type rdft:TestTurtleNegativeSyntax ; + rdfs:comment "Prefix must not start with dot (error in triple, not prefix directive like turtle-syntax-bad-ns-dot-end)" ; + mf:name "turtle-syntax-bad-missing-ns-dot-start" ; + mf:action . + +<#turtle-syntax-ln-dots> + rdf:type rdft:TestTurtlePositiveSyntax ; + rdfs:comment "Dots in pname local names" ; + mf:name "turtle-syntax-ln-dots" ; + mf:action . + +<#turtle-syntax-ln-colons> + rdf:type rdft:TestTurtlePositiveSyntax ; + rdfs:comment "Colons in pname local names" ; + mf:name "turtle-syntax-ln-colons" ; + mf:action . + +<#turtle-syntax-ns-dots> + rdf:type rdft:TestTurtlePositiveSyntax ; + rdfs:comment "Dots in namespace names" ; + mf:name "turtle-syntax-ns-dots" ; + mf:action . + +<#turtle-syntax-blank-label> + rdf:type rdft:TestTurtlePositiveSyntax ; + rdfs:comment "Characters allowed in blank node labels" ; + mf:name "turtle-syntax-blank-label" ; + mf:action . diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/functional/ARC2_ReaderTest.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/functional/ARC2_ReaderTest.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,12 @@ +arc2 = new ARC2_Class(array(), new stdClass); + } + + public function testCamelCase() { + $this->assertSame("Fish", $this->arc2->camelCase("fish")); + $this->assertSame("fish", $this->arc2->camelCase("fish", true)); + $this->assertSame("fish", $this->arc2->camelCase("fish", true, true)); + + $this->assertSame("FishHeads", $this->arc2->camelCase("fish_heads")); + $this->assertSame("fishHeads", $this->arc2->camelCase("fish_heads", true)); + $this->assertSame("fishHeads", $this->arc2->camelCase("fish_heads", true, true)); + + $this->assertSame("ALLCAPITALS", $this->arc2->camelCase("ALL_CAPITALS")); + } + + public function testDeCamelCase() { + $this->assertSame("fish", $this->arc2->deCamelCase("fish")); + $this->assertSame("Fish", $this->arc2->deCamelCase("fish", true)); + + $this->assertSame("fish heads", $this->arc2->deCamelCase("fish_heads")); + $this->assertSame("Fish heads", $this->arc2->deCamelCase("fish_heads", true)); + + $this->assertSame("ALL CAPITALS", $this->arc2->deCamelCase("ALL_CAPITALS")); + } + + + public function testV() { + $this->assertSame(false, $this->arc2->v(null)); + $this->assertSame(false, $this->arc2->v("cats", false, array())); + $this->assertSame(true, $this->arc2->v("cats", false, array("cats" => true))); + + $o = new stdclass; + $o->cats = true; + $this->assertSame(true, $this->arc2->v("cats", false, $o)); + } + + public function testV1() { + $this->assertSame(false, $this->arc2->v1(null)); + $this->assertSame(false, $this->arc2->v1("cats", false, array())); + $this->assertSame(true, $this->arc2->v1("cats", false, array("cats" => true))); + $this->assertSame("blackjack", $this->arc2->v1("cats", "blackjack", array("cats" => null))); + + $o = new stdclass; + $o->cats = true; + $this->assertSame(true, $this->arc2->v1("cats", false, $o)); + + $o = new stdclass; + $o->cats = 0; + $this->assertSame("blackjack", $this->arc2->v1("cats", "blackjack", $o)); + } + + public function testExtractTermLabel() { + $this->assertSame("bar", $this->arc2->extractTermLabel('http://example.com/foo#bar')); + $this->assertSame("bar cats", $this->arc2->extractTermLabel('http://example.com/foo#bar?cats')); + $this->assertSame("bar", $this->arc2->extractTermLabel('#bar')); + $this->assertSame("bar", $this->arc2->extractTermLabel('http://example.com/bar')); + $this->assertSame("bar", $this->arc2->extractTermLabel('http://example.com/bar/')); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/unit/ARC2_GraphTest.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/unit/ARC2_GraphTest.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,208 @@ +obj = ARC2::getGraph(); + $this->res1 = array( + 'http://example.com/s1' => array( + 'http://example.com/p1' => array( + array('value' => 'o1', 'type' => 'literal'), + array('value' => 'http://example.com/o1', 'type' => 'uri'), + ), + ), + ); + $this->res2 = array( + 'http://example.com/s2' => array( + 'http://example.com/p2' => array( + array('value' => 'o2', 'type' => 'literal'), + array('value' => 'http://example.com/o2', 'type' => 'uri'), + ), + ), + ); + $this->res3 = array( + 'http://example.com/s1' => array( + 'http://example.com/p3' => array( + array('value' => 'o3', 'type' => 'literal'), + ), + ), + ); + } + + public function testSetIndex() { + $actual = $this->obj->setIndex($this->res1); + $this->assertSame($this->obj, $actual); + + $actual = $this->obj->getIndex(); + $this->assertEquals($this->res1, $actual); + } + + public function testGetIndex() { + $actual = $this->obj->getIndex(); + $this->assertTrue(is_array($actual), 'should return array'); + } + + public function testAddIndex() { + $actual = $this->obj->addIndex($this->res1); + $this->assertSame($this->obj, $actual); + + $actual = $this->obj->getIndex(); + $this->assertEquals($this->res1, $actual); + + $this->obj->addIndex($this->res1); + $actual = $this->obj->getIndex(); + $this->assertEquals($this->res1, $actual); + + $this->obj->addIndex($this->res2); + $actual = $this->obj->getIndex(); + $this->assertEquals(array_merge($this->res1, $this->res2), $actual); + + $this->obj->addIndex($this->res3); + $actual = $this->obj->getIndex(); + $this->assertEquals(2, count(array_keys($actual['http://example.com/s1']))); + $this->assertEquals(1, count(array_keys($actual['http://example.com/s2']))); + } + + public function testAddGraph() { + $this->obj->addIndex($this->res1); + $g2 = ARC2::getGraph()->addIndex($this->res2); + + $actual = $this->obj->addGraph($g2); + $this->assertSame($this->obj, $actual); + + $actual = $this->obj->getIndex(); + $this->assertEquals(array_merge($this->res1, $this->res2), $actual); + } + + public function testAddGraphWithNamespaces() { + $g2 = ARC2::getGraph()->setPrefix('ex', 'http://example.com/'); + + $actual = $this->obj->addGraph($g2); + $this->assertArrayHasKey('ex', $actual->ns); + } + + public function testAddRdf() { + $rdf = $this->obj->toTurtle($this->res1); + $this->obj->addRdf($rdf, 'turtle'); + $actual = $this->obj->getIndex(); + $this->assertEquals($this->res1, $actual); + + $rdf = json_encode($this->res2); + $this->obj->addRdf($rdf, 'json'); + $actual = $this->obj->getIndex(); + $this->assertEquals(array_merge($this->res1, $this->res2), $actual); + } + + public function testHasSubject() { + $actual = $this->obj->setIndex($this->res1); + $this->assertTrue($actual->hasSubject('http://example.com/s1')); + $this->assertFalse($actual->hasSubject('http://example.com/s2')); + } + + public function testHasTriple() { + $actual = $this->obj->setIndex($this->res1); + $this->assertTrue($actual->hasTriple('http://example.com/s1', 'http://example.com/p1', 'o1')); + $this->assertFalse($actual->hasTriple('http://example.com/s1', 'http://example.com/p1', 'o2')); + $this->assertTrue($actual->hasTriple('http://example.com/s1', 'http://example.com/p1', array('value' => 'o1', 'type' => 'literal'))); + $this->assertFalse($actual->hasTriple('http://example.com/s1', 'http://example.com/p1', array('value' => 'o1', 'type' => 'uri'))); + } + + public function testHasLiteralTriple() { + $actual = $this->obj->setIndex($this->res2); + $this->assertTrue($actual->hasLiteralTriple('http://example.com/s2', 'http://example.com/p2', 'o2')); + $this->assertFalse($actual->hasLiteralTriple('http://example.com/s1', 'http://example.com/p1', 'o2')); + } + + public function testHasLinkTriple() { + $actual = $this->obj->setIndex($this->res2); + $this->assertTrue($actual->hasLinkTriple('http://example.com/s2', 'http://example.com/p2', 'http://example.com/o2')); + $this->assertFalse($actual->hasLinkTriple('http://example.com/s2', 'http://example.com/p2', 'o2')); + } + + public function testAddTriple() { + $actual = $this->obj->addTriple('_:s1', '_:p1', 'o1'); + $this->assertTrue($actual->hasLiteralTriple('_:s1', '_:p1', 'o1')); + + $actual = $this->obj->addTriple('_:s1', '_:p1', 'o1', 'bnode'); + $this->assertTrue($actual->hasLinkTriple('_:s1', '_:p1', 'o1')); + } + + public function testGetSubjects() { + $g = $this->obj->setIndex($this->res1); + + $actual = $g->getSubjects(); + $this->assertEquals(array('http://example.com/s1'), $actual); + + $actual = $g->getSubjects('p'); + $this->assertEquals(array(), $actual); + + $actual = $g->getSubjects('http://example.com/p1'); + $this->assertEquals(array('http://example.com/s1'), $actual); + + $actual = $g->getSubjects(null, 'o'); + $this->assertEquals(array(), $actual); + + $actual = $g->getSubjects(null, 'o1'); + $this->assertEquals(array('http://example.com/s1'), $actual); + + $actual = $g->getSubjects(null, array('value' => 'http://example.com/o1', 'type' => 'uri')); + $this->assertEquals(array('http://example.com/s1'), $actual); + + $actual = $g->getSubjects('http://example.com/p1', 'o'); + $this->assertEquals(array(), $actual); + + $actual = $g->getSubjects('http://example.com/p1', 'o1'); + $this->assertEquals(array('http://example.com/s1'), $actual); + + } + + public function testGetPredicates() { + $g = $this->obj->setIndex($this->res1)->addIndex($this->res2); + + $actual = $g->getPredicates(); + $this->assertEquals(array('http://example.com/p1', 'http://example.com/p2'), $actual); + + $actual = $g->getPredicates('http://example.com/s2'); + $this->assertEquals(array('http://example.com/p2'), $actual); + } + + public function testGetObjects() { + $actual = $this->obj->setIndex($this->res1)->getObjects('http://example.com/s1', 'http://example.com/p1', true); + $this->assertEmpty(array_diff(array('http://example.com/o1', 'o1'), $actual)); + $this->assertEmpty(array_diff($actual, array('http://example.com/o1', 'o1'))); + + $actual = $this->obj->setIndex($this->res3)->getObjects('http://example.com/s1', 'http://example.com/p3'); + $this->assertEquals(array(array('value' => 'o3', 'type' => 'literal')), $actual); + } + + public function testGetObject() { + $actual = $this->obj->setIndex($this->res1)->getObject('http://example.com/s1', 'http://example.com/p1', true); + $this->assertEquals('o1', $actual); + + $actual = $this->obj->setIndex($this->res3)->getObject('http://example.com/s1', 'http://example.com/p3'); + $this->assertEquals(array('value' => 'o3', 'type' => 'literal'), $actual); + } + + public function testGetNtriples() { + $actual = $this->obj->setIndex($this->res3)->getNTriples(); + $this->assertContains(' "o3"', $actual); + } + + public function testGetTurtle() { + $actual = $this->obj->setIndex($this->res3)->setPrefix('ex', 'http://example.com/')->getTurtle(); + $this->assertContains(' ex:p3 "o3"', $actual); + } + + public function testGetRDFXML() { + $actual = $this->obj->setIndex($this->res3)->getRDFXML(); + $this->assertContains('', $actual); + } + + public function testGetJSON() { + $actual = $this->obj->setIndex($this->res3)->getJSON(); + $this->assertContains('{"http:\/\/example.com\/s1":', $actual); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/unit/ARC2_Test.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/unit/ARC2_Test.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,124 @@ +assertRegExp('/^[0-9]{4}-[0-9]{2}-[0-9]{2}/', $actual, "should start with date"); + } + + public function testGetIncPath() { + $actual = ARC2::getIncPath('RDFParser'); + $this->assertStringEndsWith('parsers/', $actual, 'should create correct path'); + $this->assertTrue(is_dir($actual), 'should create correct pointer'); + } + + public function testGetScriptURI() { + $tmp = $_SERVER; + unset($_SERVER); + $actual = ARC2::getScriptURI(); + $this->assertEquals('http://localhost/unknown_path', $actual); + $_SERVER = $tmp; + + $_SERVER = array( + 'SERVER_PROTOCOL' => 'http', + 'SERVER_PORT' => 443, + 'HTTP_HOST' => 'example.com', + 'SCRIPT_NAME' => '/foo' + ); + $actual = ARC2::getScriptURI(); + $this->assertEquals('https://example.com/foo', $actual); + $_SERVER = $tmp; + + unset($_SERVER['HTTP_HOST']); + unset($_SERVER['SERVER_NAME']); + $_SERVER['SCRIPT_FILENAME'] = __FILE__; + $actual = ARC2::getScriptURI(); + $this->assertEquals('file://' . __FILE__, $actual); + $_SERVER = $tmp; + } + + public function testGetRequestURI() { + $tmp = $_SERVER; + unset($_SERVER); + $actual = ARC2::getRequestURI(); + $this->assertEquals(ARC2::getScriptURI(), $actual); + $_SERVER = $tmp; + + $_SERVER = array( + 'SERVER_PROTOCOL' => 'http', + 'SERVER_PORT' => 1234, + 'HTTP_HOST' => 'example.com', + 'REQUEST_URI' => '/foo' + ); + $actual = ARC2::getRequestURI(); + $this->assertEquals('http://example.com:1234/foo', $actual); + $_SERVER = $tmp; + } + + public function testInc() { + $actual = ARC2::inc('Class'); + $this->assertNotEquals(0, $actual); + + $actual = ARC2::inc('RDFParser'); + $this->assertNotEquals(0, $actual); + + $actual = ARC2::inc('ARC2_RDFParser'); + $this->assertNotEquals(0, $actual); + + $actual = ARC2::inc('Foo'); + $this->assertEquals(0, $actual); + + $actual = ARC2::inc('Vendor_Foo'); + $this->assertEquals(0, $actual); + } + + public function testMtime() { + $actual = ARC2::mtime(); + $this->assertTrue(is_float($actual)); + } + + public function testX() { + $actual = ARC2::x('foo', ' foobar'); + $this->assertEquals('bar', $actual[1]); + } + + public function testToUTF8() { + $actual = ARC2::toUTF8('foo'); + $this->assertEquals('foo', $actual); + + $actual = ARC2::toUTF8(utf8_encode('Iñtërnâtiônàlizætiøn')); + $this->assertEquals('Iñtërnâtiônàlizætiøn', $actual); + } + + public function testSplitURI() { + $actual = ARC2::splitURI('http://www.w3.org/XML/1998/namespacefoo'); + $this->assertEquals(array('http://www.w3.org/XML/1998/namespace', 'foo'), $actual); + + $actual = ARC2::splitURI('http://www.w3.org/2005/Atomfoo'); + $this->assertEquals(array('http://www.w3.org/2005/Atom', 'foo'), $actual); + + $actual = ARC2::splitURI('http://www.w3.org/2005/Atom#foo'); + $this->assertEquals(array('http://www.w3.org/2005/Atom#', 'foo'), $actual); + + $actual = ARC2::splitURI('http://www.w3.org/1999/xhtmlfoo'); + $this->assertEquals(array('http://www.w3.org/1999/xhtml', 'foo'), $actual); + + $actual = ARC2::splitURI('http://www.w3.org/1999/02/22-rdf-syntax-ns#foo'); + $this->assertEquals(array('http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'foo'), $actual); + + $actual = ARC2::splitURI('http://example.com/foo'); + $this->assertEquals(array('http://example.com/', 'foo'), $actual); + + $actual = ARC2::splitURI('http://example.com/foo/bar'); + $this->assertEquals(array('http://example.com/foo/', 'bar'), $actual); + + $actual = ARC2::splitURI('http://example.com/foo#bar'); + $this->assertEquals(array('http://example.com/foo#', 'bar'), $actual); + + } + + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/unit/ARC2_getFormatTest.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/unit/ARC2_getFormatTest.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,73 @@ +assertEquals('atom', $actual); + + $actual = ARC2::getFormat($data); + $this->assertEquals('atom', $actual); + } + + public function testGetFormatWithRdfXml() { + $data = file_get_contents('../data/rdfxml/planetrdf-bloggers.rdf'); + + $actual = ARC2::getFormat($data, 'application/rdf+xml'); + $this->assertEquals('rdfxml', $actual); + + $actual = ARC2::getFormat($data); + $this->assertEquals('rdfxml', $actual); + } + + public function testGetFormatWithTurtle() { + $data = file_get_contents('../data/turtle/manifest.ttl'); + + $actual = ARC2::getFormat($data, 'text/turtle'); + $this->assertEquals('turtle', $actual); + + $actual = ARC2::getFormat($data); + $this->assertEquals('turtle', $actual); + } + + public function testGetFormatWithJson() { + $data = file_get_contents('../data/json/sparql-select-result.json'); + + $actual = ARC2::getFormat($data, 'application/json'); + $this->assertEquals('json', $actual); + + $actual = ARC2::getFormat($data); + $this->assertEquals('json', $actual); + + $data = file_get_contents('../data/json/crunchbase-facebook.js'); + + $actual = ARC2::getFormat($data); + $this->assertEquals('cbjson', $actual); + + } + + public function testGetFormatWithN3() { + $data = file_get_contents('../data/nt/test.nt'); + + $actual = ARC2::getFormat($data, 'application/rdf+n3'); + $this->assertEquals('n3', $actual); + + $actual = ARC2::getFormat($data, '', 'n3'); + $this->assertEquals('n3', $actual); + } + + public function testGetFormatWithNTriples() { + $data = file_get_contents('../data/nt/test.nt'); + + $actual = ARC2::getFormat($data); + $this->assertEquals('ntriples', $actual); + + $actual = ARC2::getFormat($data, '', 'nt'); + $this->assertEquals('ntriples', $actual); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/unit/ARC2_getPreferredFormatTest.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/unit/ARC2_getPreferredFormatTest.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,24 @@ +assertEquals('XML', $actual); + + $actual = ARC2::getPreferredFormat('foo'); + $this->assertEquals(null, $actual); + + $_SERVER['HTTP_ACCEPT'] = 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; + $actual = ARC2::getPreferredFormat(); + $this->assertEquals('HTML', $actual); + + $_SERVER['HTTP_ACCEPT'] = 'application/rdf+xml,text/html;q=0.9,*/*;q=0.8'; + $actual = ARC2::getPreferredFormat(); + $this->assertEquals('RDFXML', $actual); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/libraries/ARC2/arc/tests/unit/phpunit.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/libraries/ARC2/arc/tests/unit/phpunit.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,16 @@ + + + + + + + + + ../.. + + ../../tests + + + + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/.DS_Store Binary file sites/all/modules/.DS_Store has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/CHANGELOG.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/CHANGELOG.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,366 @@ + +Admin Menu 7.x-3.x, xxxx-xx-xx +------------------------------ +#1177202 by das-peter, dereine, sun: Fixed copying and re-injection of child + router items breaks badly. Change them by reference instead. + Requires latest Views 3.x code. +#1212064 by sun: Updated admin_views default views for VBO 3.x. +#1196590 by sun: Fixed errors and notices for admin_views with Views 3. +#1144768 by idlewilder, sun: Fixed devel modules to skip are not displayed. +#1079374 by WillHall: Fixed JS Error: unrecognized expression: [href=/]. +#1114132 by joelstein, sun: Added Field UI to list of developer modules. +#1008380 by bdragon, sun: Updated admin_views for D7. +#1146644 by sun: Fixed PHP 5.3 compability in tests. +#442560 by sun: Fixed 'defer' script attribute breaks Drupal.behaviors in FF3.6. +#1022902 by matglas86, sun: Updated for changed core Toolbar styles. + + +Admin Menu 7.x-3.0-RC1, 2010-01-07 +---------------------------------- +#990774 by sun: Updated for $closure renamed to $page_bottom. +#991906 by johnv, sun: Added configure to .info file. +#947198 by sun: Added hint about disabling Toolbar. +by sun: Updated administrative settings form for D7 UX guidelines. +by sun: Disabled "Rebuild system links" button on settings form. +#420816 by sun, smk-ka: Improved on-demand loading of dynamic paths. +#420816 by tim.plunkett: Fixed dynamic Field UI paths for vocabularies. +#871774 by swentel: Fixed developer modules toggle still uses referer_uri(). +#671760 by sun: Updated for new preprocess defaults. +#731462 by sun: Updated for system_rebuild_theme_data(). +by sun: Re-added a "Rebuild system links" button to settings form. +#420816 by tim.plunkett: Updated taxonomy path map for machine names. +#857688 by sun: Updated for reverted system_settings_form(). +#420816 by smk-ka, sun: Added merging of menu trees containing dynamic paths. +by sun: Fixed tests and minor admin_menu_toolbar styling issues. +by sun: Fixed various styling issues for admin_menu and admin_menu_toolbar. +by sun: Updated for Schema API, DBTNG, coding standards. +by sun: Updated for new admin/modules path. +#701424 by hutch: Updated for new admin/people/people path. +by sun: Removed orphan menu rewrite function. +#667858 by sun: Don't remove the current user from the switch user list. +#631550 by sun: Updated for fixed MENU_VISIBLE_IN_BREADCRUMB behavior. +#658344 by dereine, sun: Updated for removed drupal_session_count(). +by sun: Added separate permission to flush cashes. +by sun: Updated for changed Devel settings form. +by sun: Updated for new {system}.info module data. +#614730 by azriprajwala, sun: Updated for hook_theme() key changes. +by sun: Updated for all theme functions should take a single argument. +by sun: Reverted removal of registry cache flushing option. +#578520 by sun: Fixed destination query parameter is processed wrongly. +#578520 by sun: Updated for $query in url() should always be an array. +by Dave Reid: Updated for PHP 5 date constants. +by sun: Updated for new database API. +by smk-ka: Removed remnants of the registry. Fixed flush admin menu cache + command. +#567618 by smk-ka: Revised test cases. Abstracted out base web test class. +by sun: Updated for removed registry, new admin paths. +#326539 by sun: Updated for class attribute array. +#519782 by sun: Updated for hook_footer() replaced by hook_page_alter(). +#525638 by Razorraser: Updated for admin/build renamed to admin/structure. +by Dave Reid: Updated for hook_permission(). +#482314 by Dave Reid: Updated for node_type_get_types(). +#437506 by yched, Dave Reid: Updated for menu_router_build(). +#376816 by sun: Updated for compatibility for other JavaScript libraries. +#337820 by Dave Reid: Updated for new user/logout path. +#340546 by Dave Reid: Updated for drupal_add_js(). +#340531 by Dave Reid: Updated for module_list(). +#266358 by sun: Updated for drupal_add_css(). +#320526 by yettyn, sun: Updated to UNSTABLE-2 (DBTNG queries, permissions, etc). +by sun: Changed admin_menu_wipe() to admin_menu_flush_caches(). +by sun: Updated content-type edit menu item locations. +by sun: Fixed sess_count() changed to drupal_session_count(). + + +Admin Menu 6.x-3.x, xxxx-xx-xx +------------------------------ +#588936 by fenstrat: Fixed Toolbar shortcuts not visible. +#860390 by Kevin Rogers: Fixed .info file parsing error on uncertain platform. +#551484 by sun: Fixed stale hook_admin_menu_output_alter() docs. + + +Admin Menu 6.x-3.0-ALPHA4, 2010-03-11 +------------------------------------- +#730156 by sun: Fixed Administration views. +by sun: Fixed missing .element-hidden style in D6 for permissions tweak. +#645526 by TravisCarden: Fixed stale local tasks markup after moving them. +#366442 by sun: Added tweak to collapse modules on permissions page. +#655926 by donquixote, sun: Improved performance of delayed mouseout. +#557062 by sun: Fixed admin_menu_toolbar JS/CSS loaded before admin_menu's. +#599462 by sun, koyama: Added background-color to avoid unintentional override. +#601918 by BWPanda: Fixed admin_menu.css overrides admin_menu_toolbar.css. +#586228 by Island Usurper: Fixed for PHP 5.3. +#554124 by Dave Reid: Added missing toolbar.png. +#557062 by Dave Reid: Fixed undefined Drupal.admin error when including + admin_menu_toolbar.js before admin_menu.js. +#511744 by smk-ka, sun: Fixed /admin page links are broken. +by smk-ka: Added missing variables to hook_uninstall(). +#571038 by smk-ka: Removed call to admin_menu_wipe() and cleaned install file. +#552190 by Bartezz: Fixed missing t() for user logout link. + + +Admin Menu 6.x-3.0-ALPHA3, 2009-08-16 +------------------------------------- +#502500 by sun: Added "Create content" menu. +#538714 by sun: Fixed wrong re-parenting in Drupal's menu system. +#550132 by sun: Fixed (temporarily) admin_views menu items. +by sun: Added Administration views sub-module, converting all administrative + listing pages in Drupal core into real, ajaxified, and alterable views. +#547206 by sun: Fixed menu link descriptions lead to mouseover clashes. +#540954 by Rob Loach: Added String Overrides to list of developer modules. +#540762 by Deslack: Added Malay translation. + + +Admin Menu 6.x-3.0-ALPHA2, 2009-08-02 +------------------------------------- +#527908 by sun: Changed theme_admin_menu_links() to use $element['#children']. +#527908 by markus_petrux, sun: Changed admin menu into a renderable array. +#420812 by sun, smk-ka: Added support for hook_js(). +by sun: Fixed destination query string of current page not applied to links. +by sun: Changed Drupal.admin.attachBehaviors() to accept local JS settings. +#276751 by sun: Revamped rendering of menu additions/widgets. +#500866 by sun: Updated for removed t() from getInfo() in tests. +#402058 by sun: Added Administration menu toolbar module. +by markus_petrux, sun: Added API documentation. +#461264 by sun: Added site/domain as CSS class. +#451270 by smk-ka, sun: Changed visual indication for uid1. +by sun: Minor code clean-up. +#490670 by sun: Fixed missing menu after installation/upgrade. +#515718 by joelstein, sun: Added rules_admin module to developer modules list. +#352065 by sun: Added setting to select developer modules to keep enabled. +#511854 by psynaptic: Fixed logout link. +#424960 by markus_petrux, sun: Fixed gzip compression for cached output. +by sun: Fixed opacity of links in sub-menus. +#479922 by sun: Fixed fieldsets not collapsed on admin/build/modules/list*. +#495148 by sun: Fixed MENU_NORMAL_ITEMs do not appear in administration menu. +by sun: Fixed tests for new content-type locations. +#345984 by markus_petrux, sun: Fixed old menu links not removed on upgrade. +#276751 by sun: Major rewrite. Fixed menu items cannot be moved, altered, or + added as well as various performance issues. +by sun: Added variable to allow to disable caching (rewrite). + + +Admin Menu 6.x-3.0-ALPHA1, 2009-06-10 +------------------------------------- +#236657 by sun: Updated for corrected arguments of system_clear_cache_submit(). +#483870 by sun: Fixed compatibility with new Admin module. +#483152 by sun: Fixed admin_menu caches not flushed when clean URLs are toggled. +#479922 by danep: Fixed fieldsets not collapsed on admin/build/modules/list. +#469716 by sun: Fixed wrong AJAX callback URL under various conditions. +#471504 by wulff: Updated Danish translation. +by sun: Fixed admin_menu_suppress() does not suppress margin-top. +#451270 by sun: Added visual indication when working as uid 1. +by Dave Reid: Updated for getInfo() in tests. +#420828 by sun: Added dynamic replacements for cached administration menu. +#420840 by sun: Fixed Drupal.behaviors.adminMenu must be only executed once. +#345984 by markus_petrux, sun: Added client-side caching of administration menu. + Attention: A new era of Drupal user experience starts here. This is the very + first issue of a series of improvements targeting plain awesomeness. +#349169 by sun: Fixed Devel switch user links contain multiple path prefixes. +#345984 by sun: Code clean-up in preparation for client-side caching. +#415196 by psynaptic: Updated for CSS coding standards. +#406672 by mr.j, sun: Fixed "Move local tasks" option leaves stale UL. +by sun: Major code clean-up and sync across 3.x branches. +#349505 by smk-ka, sun: Performance: Added caching of entire menu output. +#315342 by wulff: Added "My account" link (by splitting the "Log out" item). +#384100 by kepol, sun: Fixed content-type items displayed in wrong place. +#373339 by sun: Fixed double-escaped 'Edit ' link titles. +#373372 by sun: Turned procedural JavaScript into admin menu behaviors. +by sun: Fixed admin menu tests (and updated to 6.x for SimpleTest 2.x). +#359158 by nitrospectide, sun: Fixed Devel Themer breaks admin menu. +#365335 by sun: Fixed not all variables removed after uninstall. + + +Admin Menu 6.x-1.3, 2009-01-24 +------------------------------ +#362454 by sun: Fixed Drupal.settings.admin_menu is undefined JS error in some + browsers. + + +Admin Menu 6.x-1.2, 2009-01-20 +------------------------------ +#358697 by sun: Added docs about admin_menu_suppress() to README.txt. +#342684 by darumaki, sun: Added notice about Opera configuration to README.txt. +#350932 by sun: Fixed "Run updates" link repeated per language/site. +#342298 by gustz: Updated Spanish translation. +#346106 by sun: Fixed XHTML-Strict validation for admin menu icon. +#287448 by sun: Fixed unnecessary menu rebuild for users without permission to + use admin menu. +#342002 by AltaVida: Fixed improper test for node/add paths. +#272920 by keith.smith: Changed all text strings throughout the module. +#322731 by sun: Fixed improper use of t() in module install file. +#282030 by sun: Fixed "Run updates" item visible for unprivileged users. +#322877 by sun: Added tweak to move page tabs into administration menu. +#287468 by sun: Fixed module paths directly below "admin" get the wrong parent. +#310423 by sun: Added optional position: fixed configuration setting. +#292657 by smk-ka: Improved rendering performance. +#234149 by yhager, sun: Fixed RTL support for IE. +#323726 by danez1972: Added Spanish translation. +#325057 by sun: Updated README.txt. +#234149 by yhager, levavie, sun: Added RTL support. +#325057 by sun: Added links to flush specific caches. +#324334 by AltaVida: Fixed usernames with spaces not in Devel user switch links. +#319382 by betz: Added Dutch translation. + + +Admin Menu 6.x-1.1, 2008-09-12 +------------------------------ +#295476 by pwolanin, use for icon path to fix front-page path-change + bug and pathauto conflict, add wipe button to admin form. +#301370 by sun: Disabled module fieldset collapsing behavior by default. +#288672 by sun: Fixed JS hover behavior not working in IE. +#290803 by sun: Fixed missing devel_themer in devel modules; added some others. +#286636 by sun: Fixed menus do not drop down in IE6. +#249537 by pwolanin, sun: Added admin_menu_suppress() to allow other modules to + disable the display of admin_menu on certain pages (f.e. popups). +#268211 by sun: Fixed invalid issue queue links for custom modules and + sub-modules of projects. +#261461 by sun: Added FAQ entry for displaying other menus like admin_menu. +#264067 by sun: Added FAQ entry for huge amount of anonymous users displayed. +#280002 by pwolanin: Clean up .test setUp function. +#242377 by sun: Fixed sub-level menu items exceed total document height. + + +Admin Menu 6.x-1.0, 2008-06-26 +------------------------------ +#266308 by sun: Fixed jQuery 1.0.x incompatible selector for collapsing modules. +#268373 by sun: Added hook_update to cleanup for alpha/beta testers. +#268373 by sun: Added menu callback to disable/enable developer modules. +#132524 by pwolanin: Fixed admin_menu links are re-inserted each time menu links + are rebuilt. +by smk-ka: Performance: Use 'defer' attribute for JavaScript to delay execution. +#266099 by sun: Fixed description of "Apply margin-top" configuration setting. +#266308 by sun: Usability: Added Utility module features to collapse module + fieldsets on Modules page. +#251341 by sun: Added docs about display drupal links permission. + + +Admin Menu 6.x-1.0-BETA, 2008-06-08 +----------------------------------- +#132524 by sun: Fixed support for sub-content-types below node/add. +#132524 by pwolanin: Added support for localizable menu links. +#132524 by pwolanin, sun: Fixed menu links adjustments. +#132524 by pwolanin: Added simpletest. +#132524 by pwolanin: Major rewrite to better use Drupal 6 menu system. +#132524 by sun: Moved gettext translation files into translations. +#132524 by sun: Committing pre-alpha code for D6 due to public demand. + + +Admin Menu 5.x-2.x, xxxx-xx-xx +------------------------------ +#246221 by sun: Fixed user counter displays different values than Who's online + block. +#239022 by mikl: Added Danish translation. +#234444 by smk-ka: Fixed admin_menu icon does not respect theme settings. +#198240 by sun: Fixed admin_menu displayed in print output. + + +Admin Menu 5.x-2.4, 2008-02-24 +------------------------------ +#214740 by sun: Regression: Fixed directly applied marginTop not supported by IE. +#214725 by sun: Fixed wrong CSS id in admin_menu.js (missed in 5.x-2.3). + + +Admin Menu 5.x-2.3, 2008-02-24 +------------------------------ +#214725 by sun: Fixed CSS id and classes should not contain underscores. +#209390 by sun: Added note about interaction with user role permissions. +#214740 by jjeff, sun: Added module settings to configure margin-top CSS. +#200737 by sun: Changed admin_menu (fav)icon to use theme setting, if defined. +#203116 by smk-ka: Improved performance of non-cached admin_menu by storing + already processed URLs in the cache. +#224605 by sun: 'Add ' items do not appear without 'administer + nodes' permission. +#210615 by robertgarrigos: Fixed Mozilla Mac: Collapsible fieldsets display + error. + + +Admin Menu 5.x-2.2, 2007-01-08 +------------------------------ +#204884 by jjeff: Usability: Override theme font family declaration. +#204935 by jjeff: Added mouseout delay for hovered menus (yay!). +#193941 by downgang: Fixed margin in IE6 using Garland theme. +#197306 by sun: Fixed 'Run updates' leads to wrong url with clean URLs disabled. +Moved images into sub-folder. +by smk-ka: Fixed icon title for user counter not displayed & coding style. +Fixed user count not displayed without 'administer users' permission. + + +Admin Menu 5.x-2.1, 2007-12-02 +------------------------------ +Fixed adding menu items with negative weight not always working. +Fixed admin_menu_copy_items() is overwriting already existing items. +Fixed display menu item ids in devel settings does not work. + + +Admin Menu 5.x-2.0, 2007-12-02 +------------------------------ +Added devel_admin_menu() for fast access to clear-cache, variable editor and + switch_user. +Added username to logout button. +Added hook_admin_menu() to allow other modules to alter admin_menu. +#194189 by sun: Added counter for current anonymous/authenticated users. +Added Drupal.org project issue queue links for all enabled contrib modules. +#189701 by sun: Changed admin_menu icon to be a menu. +#193925 by sun: Removed obsolete menu slicing code. +#193669 by smk-ka: Moved admin_menu builder functions into include file. + + +Admin Menu 5.x-1.2, 2007-11-18 +------------------------------ +#176969 by smk-ka: Fixed performance issues with path(auto) module by + introducing a menu cache for admin_menu. +#179648 by sun: Inject admin_menu into theme. + Fixes several CSS bugs in various themes and also activation of admin_menu + immediately after installation. +#191213 by Standard: Fixed block info shouldn't contain the word "block". +#187816 by sun: Fixed "Add" not translatable. +#186218 by sun: Fixed admin menu icon too big in Safari. +#182563 by sun: Fixed wrong datatype for array_search in _admin_menu_get_children(). +#183496 by sun: Fixed invalid argument supplied for foreach in admin_menu_copy_items(). + + +Admin Menu 5.x-1.1, 2007-10-10 +------------------------------ +#178876 by sun: Fixed 3rd-level submenus expand without hover over. +#153455 by sun: Fixed add product node sub-elements are empty. +Fixed path_to_theme() call breaking blocks page. +#177582 by sun: Fixed bluebreeze theme compatibility. + + +Admin Menu 5.x-1.0, 2007-09-06 +------------------------------ +#156952 by sun: Fixed admin menu inaccessible due to margins. +#149229 by sun: Fixed admin menu not expanding in IE7. +#172545 by sun: Use opacity instead of -moz-opacity. +#132867 Fixed z-index too low. +- Fixed admin menu block selectors to override any other theme styles. +#155589 by sun: Added permission to access administration menu. +- Fixed a PHP warning when there are no content types defined in the system, as + node/add then has no child menu items. +#155312 by sun: Fixed menu item tooltip clashes. +Added support for custom stylesheets per theme. +Removed 4.7.x compatibility. + + +Admin Menu 4.7-1.3, 2007-03-30 +------------------------------ +#126601 Fixed Users can see inaccessible items. +#121027 Fixed Page not found entries for menu-collapsed.png. + + +Admin Menu 4.7-1.2, 2007-03-04 +------------------------------ +- Fixed menu item adjustments +- Fixed IE / Safari support +- Fixed base_path for IE support +- Added create content options to content management menu + + +Admin Menu 4.7-1.1, 2007-01-24 +------------------------------ +First stable release, compatible to Drupal 4.7.x and 5.x. + + +Admin Menu 4.7-1.0, 2007-01-16 +------------------------------ +Initial release of admin_menu module. Already supporting Drupal 5.0. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,188 @@ + +-- SUMMARY -- + +The Administration menu module displays the entire administrative menu tree (and +most local tasks) in a drop-down menu, providing administrators one- or +two-click access to most pages. Other modules may also add menu links to the +menu using hook_admin_menu_output_alter(). + +For a full description of the module, visit the project page: + http://drupal.org/project/admin_menu + +To submit bug reports and feature suggestions, or to track changes: + http://drupal.org/project/issues/admin_menu + + +-- REQUIREMENTS -- + +None. + + +-- INSTALLATION -- + +* Install as usual, see http://drupal.org/node/70151 for further information. + +* You likely want to disable Toolbar module, since its output clashes with + Administration menu. + + +-- CONFIGURATION -- + +* Configure user permissions in Administration » People » Permissions: + + - Use the administration pages and help (System module) + + The top-level administration categories require this permission to be + accessible. The administration menu will be empty unless this permission is + granted. + + - Access administration menu + + Users in roles with the "Access administration menu" permission will see + the administration menu at the top of each page. + + - Display Drupal links + + Users in roles with the "Display drupal links" permission will receive + links to drupal.org issue queues for all enabled contributed modules. The + issue queue links appear under the administration menu icon. + + Note that the menu items displayed in the administration menu depend on the + actual permissions of the viewing user. For example, the "People" menu item + is not displayed to a user who is not a member of a role with the "Administer + users" permission. + +* Customize the menu settings in Administration » Configuration and modules » + Administration » Administration menu. + +* To prevent administrative menu items from appearing twice, you may hide the + "Management" menu block. + + +-- CUSTOMIZATION -- + +* To override the default administration menu icon, you may: + + 1) Disable it via CSS in your theme: + + body #admin-menu-icon { display: none; } + + 2) Alter the image by overriding the theme function: + + Copy the entire theme_admin_menu_icon() function into your template.php, + rename it to phptemplate_admin_menu_icon() or THEMENAME_admin_menu_icon(), + and customize the output according to your needs. + + Remember that the output of the administration menu is cached. To see changes + from your theme override function, you must clear your site cache (via + the "Flush all caches" link on the menu). + +* To override the font size, add the following line to your theme's stylesheet: + + body #admin-menu { font-size: 10px; } + + +-- TROUBLESHOOTING -- + +* If the menu does not display, check the following: + + - Are the "Access administration menu" and "Use the administration pages and help" + permissions enabled for the appropriate roles? + + - Does html.tpl.php of your theme output the $page_bottom variable? + +* If the menu is rendered behind a Flash movie object, add this property to your + Flash object(s): + + + + See http://drupal.org/node/195386 for further information. + + +-- FAQ -- + +Q: When the administration menu module is enabled, blank space is added to the + bottom of my theme. Why? + +A: This is caused by a long list of links to module issue queues at Drupal.org. + Use Administer >> User management >> Permissions to disable the "display + drupal links" permission for all appropriate roles. Note that since UID 1 + automatically receives all permissions, the list of issue queue links cannot + be disabled for UID 1. + + +Q: After upgrading to 6.x-1.x, the menu disappeared. Why? + +A: You may need to regenerate your menu. Visit + http://example.com/admin/build/modules to regenerate your menu (substitute + your site name for example.com). + + +Q: Can I configure the administration menu module to display another menu (like + the Navigation menu, for instance)? + +A: No. As the name implies, administration menu module is for administrative + menu links only. However, you can copy and paste the contents of + admin_menu.css into your theme's stylesheet and replace #admin-menu with any + other menu block id (#block-menu-1, for example). + + +Q: Sometimes, the user counter displays a lot of anonymous users, but no spike + of users or requests appear in Google Analytics or other tracking tools. + +A: If your site was concurrently spidered by search-engine robots, it may have + a significant number of anonymous users for a short time. Most web tracking + tools like Google Analytics automatically filter out these requests. + + +Q: I enabled "Aggregate and compress CSS files", but admin_menu.css is still + there. Is this normal? + +A: Yes, this is the intended behavior. the administration menu module only loads + its stylesheet as needed (i.e., on page requests by logged-on, administrative + users). + + +Q: Why are sub-menus not visible in Opera? + +A: In the Opera browser preferences under "web pages" there is an option to fit + to width. By disabling this option, sub-menus in the administration menu + should appear. + + +Q: How can the administration menu be hidden on certain pages? + +A: You can suppress it by simply calling the following function in PHP: + + module_invoke('admin_menu', 'suppress'); + + However, this needs to happen as early as possible in the page request, so + placing it in the theming layer (resp. a page template file) is too late. + Ideally, the function is called in hook_init() in a custom module. If you do + not have a custom module, placing it into some conditional code at the top of + template.php may work out, too. + + +-- CONTACT -- + +Current maintainers: +* Daniel F. Kudwien (sun) - http://drupal.org/user/54136 +* Peter Wolanin (pwolanin) - http://drupal.org/user/49851 +* Stefan M. Kudwien (smk-ka) - http://drupal.org/user/48898 +* Dave Reid (Dave Reid) - http://drupal.org/user/53892 + +Major rewrite for Drupal 6 by Peter Wolanin (pwolanin). + +This project has been sponsored by: +* UNLEASHED MIND + Specialized in consulting and planning of Drupal powered sites, UNLEASHED + MIND offers installation, development, theming, customization, and hosting + to get you started. Visit http://www.unleashedmind.com for more information. + +* Lullabot + Friendly Drupal experts providing professional consulting & education + services. Visit http://www.lullabot.com for more information. + +* Acquia + Commercially Supported Drupal. Visit http://acquia.com for more information. + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_devel/admin_devel.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_devel/admin_devel.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,12 @@ +name = Administration Development tools +description = Administration and debugging functionality for developers and site builders. +package = Administration +core = 7.x +scripts[] = admin_devel.js + +; Information added by drupal.org packaging script on 2013-01-31 +version = "7.x-3.0-rc4" +core = "7.x" +project = "admin_menu" +datestamp = "1359651687" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_devel/admin_devel.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_devel/admin_devel.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,40 @@ +(function($) { + +/** + * jQuery debugging helper. + * + * Invented for Dreditor. + * + * @usage + * $.debug(var [, name]); + * $variable.debug( [name] ); + */ +jQuery.extend({ + debug: function () { + // Setup debug storage in global window. We want to look into it. + window.debug = window.debug || []; + + args = jQuery.makeArray(arguments); + // Determine data source; this is an object for $variable.debug(). + // Also determine the identifier to store data with. + if (typeof this == 'object') { + var name = (args.length ? args[0] : window.debug.length); + var data = this; + } + else { + var name = (args.length > 1 ? args.pop() : window.debug.length); + var data = args[0]; + } + // Store data. + window.debug[name] = data; + // Dump data into Firebug console. + if (typeof console != 'undefined') { + console.log(name, data); + } + return this; + } +}); +// @todo Is this the right way? +jQuery.fn.debug = jQuery.debug; + +})(jQuery); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_devel/admin_devel.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_devel/admin_devel.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,33 @@ + 'submit', + '#value' => t('Rebuild system links'), + '#submit' => array('admin_devel_form_admin_menu_theme_settings_alter_rebuild_submit'), + // @todo Not necessarily ready for mass-consumption yet. + '#access' => FALSE, + ); +} + +/** + * Form submit handler to wipe and rebuild all 'module' = 'system' menu links. + */ +function admin_devel_form_admin_menu_theme_settings_alter_rebuild_submit($form, &$form_state) { + // Delete all auto-generated menu links derived from menu router items. + db_delete('menu_links') + ->condition('module', 'system') + ->execute(); + // Rebuild menu links from current menu router items. + menu_rebuild(); + + drupal_set_message(t('System links derived from menu router paths have been rebuilt.')); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu-rtl.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu-rtl.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,64 @@ + +#admin-menu { + text-align: right; +} +#admin-menu .dropdown .admin-menu-users a { + background-position: 10% center; + padding-left: 22px; + padding-right: 0; +} +#admin-menu .dropdown .admin-menu-action, +#admin-menu .dropdown .admin-menu-search { + float: left; +} +#admin-menu .dropdown .admin-menu-action a { + border-left: none; + border-right: 1px solid #323232; +} + +/* All lists */ +#admin-menu a { + text-align: right; +} +#admin-menu .dropdown a { + border-left: 1px solid #323232; + border-right: 0; +} +#admin-menu .dropdown .admin-menu-tab a { + border-left: 1px solid #52565E; + border-right: 0; +} +#admin-menu .dropdown li li a { + border-left: 0; +} + +/* All list items */ +#admin-menu .dropdown li { + float: right; +} +#admin-menu .dropdown li li { +} + +/* Second-level lists */ +#admin-menu .dropdown li ul { + left: auto; + right: -999em; +} + +/* Third-and-above-level lists */ +#admin-menu .dropdown li li.expandable ul { + margin-left: 0; + margin-right: 160px; +} + +/* Lists nested under hovered list items */ +#admin-menu .dropdown li.admin-menu-action:hover ul { + left: 0 !important; + right: auto; +} + +/* Second-and-more-level hovering */ +#admin-menu .dropdown li li.expandable { + background-image: url(images/arrow-rtl.png); + background-position: 5px 6px; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.admin.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.admin.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,62 @@ +(function($) { + +/** + * Live preview of Administration menu components. + */ +Drupal.behaviors.adminMenuLivePreview = { + attach: function (context, settings) { + $('input[name^="admin_menu_components"]', context).once('admin-menu-live-preview') + .change(function () { + var target = $(this).attr('rel'); + $(target).toggle(this.checked); + }) + .trigger('change'); + } +}; + +/** + * Automatically enables required permissions on demand. + * + * Many users do not understand that two permissions are required for the + * administration menu to appear. Since Drupal core provides no facility for + * this, we implement a simple manual confirmation for automatically enabling + * the "other" permission. + */ +Drupal.behaviors.adminMenuPermissionsSetupHelp = { + attach: function (context, settings) { + $('#permissions', context).once('admin-menu-permissions-setup', function () { + // Retrieve matrix/mapping - these need to use the same indexes for the + // same permissions and roles. + var $roles = $(this).find('th:not(:first)'); + var $admin = $(this).find('input[name$="[access administration pages]"]'); + var $menu = $(this).find('input[name$="[access administration menu]"]'); + + // Retrieve the permission label - without description. + var adminPermission = $.trim($admin.eq(0).parents('td').prev().children().get(0).firstChild.textContent); + var menuPermission = $.trim($menu.eq(0).parents('td').prev().children().get(0).firstChild.textContent); + + $admin.each(function (index) { + // Only proceed if both are not enabled already. + if (!(this.checked && $menu[index].checked)) { + // Stack both checkboxes and attach a click event handler to both. + $(this).add($menu[index]).click(function () { + // Do nothing when disabling a permission. + if (this.checked) { + // Figure out which is the other, check whether it still disabled, + // and if so, ask whether to auto-enable it. + var other = (this == $admin[index] ? $menu[index] : $admin[index]); + if (!other.checked && confirm(Drupal.t('Also allow !name role to !permission?', { + '!name': $roles[index].textContent, + '!permission': (this == $admin[index] ? menuPermission : adminPermission) + }))) { + other.checked = 'checked'; + } + } + }); + } + }); + }); + } +}; + +})(jQuery); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.api.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,164 @@ + Content types. + // The key denotes the dynamic path to expand to multiple menu items. + $map['admin/structure/types/manage/%node_type'] = array( + // Link generated items directly to the "Content types" item. + 'parent' => 'admin/structure/types', + // Create expansion arguments for the '%node_type' placeholder. + 'arguments' => array( + array( + '%node_type' => array_keys(node_type_get_types()), + ), + ), + ); + return $map; +} + +/** + * Add to the administration menu content before it is rendered. + * + * Only use this hook to add new data to the menu structure. Use + * hook_admin_menu_output_alter() to *alter* existing data. + * + * @param array $content + * A structured array suitable for drupal_render(), potentially containing: + * - menu: The administrative menu of links below the path 'admin/*'. + * - icon: The icon menu. + * - account: The user account name and log out link. + * - users: The user counter. + * Additionally, these special properties: + * - #components: The actual components contained in $content are configurable + * and depend on the 'admin_menu_components' configuration value. #components + * holds a copy of that for convenience. + * - #complete: A Boolean indicating whether the complete menu should be built, + * ignoring the current configuration in #components. + * Passed by reference. + * + * @see hook_admin_menu_output_alter() + * @see admin_menu_links_menu() + * @see admin_menu_links_icon() + * @see admin_menu_links_user() + * @see theme_admin_menu_links() + */ +function hook_admin_menu_output_build(&$content) { + // In case your implementation provides a configurable component, check + // whether the component should be displayed: + if (empty($content['#components']['shortcut.links']) && !$content['#complete']) { + return; + } + + // Add new top-level item to the menu. + if (isset($content['menu'])) { + $content['menu']['myitem'] = array( + '#title' => t('My item'), + // #attributes are used for list items (LI). + '#attributes' => array('class' => array('mymodule-myitem')), + '#href' => 'mymodule/path', + // #options are passed to l(). + '#options' => array( + 'query' => drupal_get_destination(), + // Apply a class on the link (anchor). + 'attributes' => array('class' => array('myitem-link-anchor')), + ), + // #weight controls the order of links in the resulting item list. + '#weight' => 50, + ); + } + // Add link to the icon menu to manually run cron. + if (isset($content['icon'])) { + $content['icon']['myitem']['cron'] = array( + '#title' => t('Run cron'), + '#access' => user_access('administer site configuration'), + '#href' => 'admin/reports/status/run-cron', + ); + } +} + +/** + * Change the administration menu content before it is rendered. + * + * Only use this hook to alter existing data in the menu structure. Use + * hook_admin_menu_output_build() to *add* new data. + * + * @param array $content + * A structured array suitable for drupal_render(). Passed by reference. + * + * @see hook_admin_menu_output_build() + */ +function hook_admin_menu_output_alter(&$content) { +} + +/** + * Return content to be replace via JS in the cached menu output. + * + * @param bool $complete + * A Boolean indicating whether all available components of the menu will be + * output and the cache will be skipped. + * + * @return array + * An associative array whose keys are jQuery selectors and whose values are + * strings containing the replacement content. + */ +function hook_admin_menu_replacements($complete) { + $items = array(); + // If the complete menu is output, then it is uncached and will contain the + // current counts already. + if (!$complete) { + // Check whether the users count component is enabled. + $components = variable_get('admin_menu_components', array()); + if (!empty($components['admin_menu.users']) && ($user_count = admin_menu_get_user_count())) { + // Replace the counters in the cached menu output with current counts. + $items['.admin-menu-users a'] = $user_count; + } + } + return $items; +} + +/** + * Inform about additional module-specific caches that can be cleared. + * + * Administration menu uses this hook to gather information about available + * caches that can be flushed individually. Each returned item forms a separate + * menu link below the "Flush all caches" link in the icon menu. + * + * @return array + * An associative array whose keys denote internal identifiers for a + * particular caches (which can be freely defined, but should be in a module's + * namespace) and whose values are associative arrays containing: + * - title: The name of the cache, without "cache" suffix. This label is + * output as link text, but also for the "!title cache cleared." + * confirmation message after flushing the cache; make sure it works and + * makes sense to users in both locations. + * - callback: The name of a function to invoke to flush the individual cache. + */ +function hook_admin_menu_cache_info() { + $caches['update'] = array( + 'title' => t('Update data'), + 'callback' => '_update_cache_clear', + ); + return $caches; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.color.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.color.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,46 @@ + +/** + * @file + * Administration menu color override. + */ + +#admin-menu { + background-color: #911; + background-image: url(images/bkg-red.png); +} +#admin-menu li.admin-menu-action a { + border-left-color: #a91f1f; +} + +/* All lists */ +#admin-menu ul a { + border-right-color: #a91f1f; +} +#admin-menu ul li.admin-menu-tab a { + border-right-color: #52565E; +} +#admin-menu li li a { + border-top-color: #801f1f; +} + +/* All list items */ +#admin-menu li li { + background-color: #991f1f; +} + +/* Second-and-more-level hovering */ +#admin-menu li li.expandable { + background-color: #b93f3f; +} +#admin-menu li li:hover, +#admin-menu li li.iehover { + background-color: #690f0f; +} +#admin-menu li li.expandable:hover a, +#admin-menu li li.expandable:hover li.expandable:hover a { + border-color: #801f1f; +} +#admin-menu li li.expandable:hover li a, +#admin-menu li li.expandable:hover li.expandable:hover li a { + border-color: #801f1f; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,250 @@ + +/** + * @file + * Administration menu. + * + * Implementation of Sons of Suckerfish Dropdowns. + * + * @see www.htmldog.com/articles/suckerfish + */ + +#admin-menu { + background: #101010 url(images/bkg.png) bottom left repeat-x; + font-size: 9px; + font-family: "lucida grande", tahoma, verdana, arial, sans-serif; + left: 0; + position: absolute; + text-align: left; + top: 0; + width: 100%; +} +#admin-menu-wrapper { + overflow: hidden; +} +#admin-menu .dropdown .admin-menu-icon a { + padding: 1px 8px 4px; +} +#admin-menu .dropdown .admin-menu-icon ul a { + padding: 4px 8px; +} +#admin-menu .dropdown .admin-menu-icon img { + vertical-align: bottom; +} +#admin-menu .dropdown .admin-menu-users a { + background: transparent url(images/icon_users.png) 90% center no-repeat; + padding-right: 22px; +} +#admin-menu .dropdown .admin-menu-action, +#admin-menu .dropdown .admin-menu-search { + float: right; +} +#admin-menu .dropdown .admin-menu-action a { + border-left: 1px solid #323232; + border-right: none; +} +body.admin-menu { + margin-top: 20px !important; +} + +/* All lists */ +#admin-menu, +#admin-menu .dropdown { + line-height: 1.4em; + list-style: none; + margin: 0; + padding: 0; + z-index: 999; +} +#admin-menu .dropdown { + position: static; +} +#admin-menu a, +#admin-menu li > span { + background: transparent none; + border: none; + color: #EEE; + font-weight: normal; + text-align: left; /* LTR */ + text-decoration: none; +} +#admin-menu .dropdown a, +#admin-menu .dropdown li > span { + border-right: 1px solid #323232; /* LTR */ + display: block; + padding: 4px 8px; +} +#admin-menu .dropdown .admin-menu-tab a { + border-right: 1px solid #52565E; /* LTR */ +} +#admin-menu .dropdown li li a { + border-right: none; /* LTR */ + border-top: 1px solid #323232; +} + +/* All list items */ +#admin-menu .dropdown li { + background-image: none; + float: left; /* LTR */ + height: 100%; + list-style-image: none; + list-style-type: none; + margin: 0 !important; + padding: 0; +} +#admin-menu .dropdown .admin-menu-tab { + background: url(images/bkg_tab.png) repeat-x left bottom; + padding-bottom: 1px; +} +#admin-menu .dropdown li li { + background: #202020; + filter: Alpha(opacity=88); + opacity: 0.88; + width: 160px; /* Required for Opera */ +} +#admin-menu .dropdown li li li { + filter: Alpha(opacity=100); + opacity: 1; +} + +/* Second-level lists */ +/* Note: We must hide sub-lists or scrollbars might appear (display: none is not read by screen readers). */ +#admin-menu .dropdown li ul { + background: none; + display: none; + left: -999em; /* LTR */ + line-height: 1.2em; + margin: 0; + position: absolute; + width: 160px; +} + +/* Third-and-above-level lists */ +#admin-menu .dropdown li li.expandable ul { + margin: -20px 0 0 160px; /* LTR */ +} + +#admin-menu .dropdown li:hover ul ul, +#admin-menu .dropdown li:hover ul ul ul, +#admin-menu .dropdown li:hover ul ul ul ul, +#admin-menu .dropdown li:hover ul ul ul ul ul, +#admin-menu .dropdown li.iehover ul ul, +#admin-menu .dropdown li.iehover ul ul ul, +#admin-menu .dropdown li.iehover ul ul ul ul, +#admin-menu .dropdown li.iehover ul ul ul ul ul { + display: none; + left: -999em; /* LTR */ +} + +/* Lists nested under hovered list items */ +#admin-menu .dropdown li:hover ul, +#admin-menu .dropdown li li:hover ul, +#admin-menu .dropdown li li li:hover ul, +#admin-menu .dropdown li li li li:hover ul, +#admin-menu .dropdown li li li li li:hover ul, +#admin-menu .dropdown li.iehover ul, +#admin-menu .dropdown li li.iehover ul, +#admin-menu .dropdown li li li.iehover ul, +#admin-menu .dropdown li li li li.iehover ul, +#admin-menu .dropdown li li li li li.iehover ul { + display: block; + left: auto; /* LTR */ +} +#admin-menu .dropdown li.admin-menu-action:hover ul { + right: 0; /* LTR */ +} + +/* Second-and-more-level hovering */ +#admin-menu .dropdown li li.expandable { + background: #45454A url(images/arrow.png) no-repeat 145px 6px; +} +#admin-menu .dropdown li li:hover, +#admin-menu .dropdown li li.iehover { + background-color: #111; +} +#admin-menu .dropdown li li:hover a, +#admin-menu .dropdown li li:hover li:hover a, +#admin-menu .dropdown li li:hover li:hover li:hover a { + color: #FFF; +} +#admin-menu .dropdown li li.expandable:hover a, +#admin-menu .dropdown li li.expandable:hover li.expandable:hover a { + border-color: #444; + color: #EEE; +} +#admin-menu .dropdown li li.expandable:hover li a, +#admin-menu .dropdown li li.expandable:hover li.expandable:hover li a { + border-color: #323232; +} +#admin-menu .dropdown li li:hover li a, +#admin-menu .dropdown li li.iehover li a, +#admin-menu .dropdown li li.iehover li.iehover li a { + color: #EEE; +} +#admin-menu .dropdown li li.iehover a, +#admin-menu .dropdown li li.iehover li.iehover a, +#admin-menu .dropdown li li.iehover li.iehover li.iehover a { + color: #FFF; + width: 90%; /* IE */ +} + +/* Search form */ +#admin-menu .admin-menu-search .form-item { + margin: 0; + padding: 0; +} +#admin-menu .admin-menu-search input { + background: #fff none center right no-repeat; + border: none; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + font-size: 10px; + margin: 1px 0; + outline: none; + padding: 2px 22px 2px 4px; + width: 158px; +} + +#admin-menu .dropdown .admin-menu-search-results { + display: block !important; + left: auto !important; + top: 100%; +} +#admin-menu .admin-menu-search-results, +#admin-menu .admin-menu-search-results li { + width: 186px; +} + +#admin-menu li.highlight { + background-color: #eee !important; +} +#admin-menu li.highlight > a { + border-color: #ccc !important; + color: #111 !important; +} + +/* #210615: Mozilla on Mac fix */ +html.js fieldset.collapsible div.fieldset-wrapper { + overflow: visible; +} + +/* Hide the menu on print output. */ +@media print { + #admin-menu { + display: none !important; + } + body.admin-menu { + margin-top: 0 !important; + } +} + +/** + * Tweaks permissions, if enabled. + */ +tr.admin-menu-tweak-permissions-processed { + cursor: pointer; + cursor: hand; +} +tr.admin-menu-tweak-permissions-processed td.module { + border-top: 0; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1033 @@ + $data) { + // Convert named placeholders to anonymous placeholders, since the menu + // system stores paths using anonymous placeholders. + $replacements = array_fill_keys(array_keys($data['arguments'][0]), '%'); + $data['parent'] = strtr($data['parent'], $replacements); + $new_map[strtr($path, $replacements)] = $data; + } + $expand_map = $new_map; + unset($new_map); + + // Retrieve dynamic menu link tree for the expansion mappings. + // @todo Skip entire processing if initial $expand_map is empty and directly + // return $tree? + if (!empty($expand_map)) { + $tree_dynamic = admin_menu_tree_dynamic($expand_map); + } + else { + $tree_dynamic = array(); + } + + // Merge local tasks with static menu tree. + $tree = menu_tree_all_data($menu_name); + admin_menu_merge_tree($tree, $tree_dynamic, array()); + + return $tree; +} + +/** + * Load menu link trees for router paths containing dynamic arguments. + * + * @param $expand_map + * An array containing menu router path placeholder expansion argument + * mappings. + * + * @return + * An associative array whose keys are the parent paths of the menu router + * paths given in $expand_map as well as the parent paths of any child link + * deeper down the tree. The parent paths are used in admin_menu_merge_tree() + * to check whether anything needs to be merged. + * + * @see hook_admin_menu_map() + */ +function admin_menu_tree_dynamic(array $expand_map) { + $p_columns = array(); + for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { + $p_columns[] = 'p' . $i; + } + + // Fetch p* columns for all router paths to expand. + $router_paths = array_keys($expand_map); + $plids = db_select('menu_links', 'ml') + ->fields('ml', $p_columns) + ->condition('router_path', $router_paths) + ->execute() + ->fetchAll(PDO::FETCH_ASSOC); + + // Unlikely, but possible. + if (empty($plids)) { + return array(); + } + + // Use queried plid columns to query sub-trees for the router paths. + $query = db_select('menu_links', 'ml'); + $query->join('menu_router', 'm', 'ml.router_path = m.path'); + $query + ->fields('ml') + ->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), drupal_schema_fields_sql('menu_links'))); + + // The retrieved menu link trees have to be ordered by depth, so parents + // always come before their children for the storage logic below. + foreach ($p_columns as $column) { + $query->orderBy($column, 'ASC'); + } + + $db_or = db_or(); + foreach ($plids as $path_plids) { + $db_and = db_and(); + // plids with value 0 may be ignored. + foreach (array_filter($path_plids) as $column => $plid) { + $db_and->condition($column, $plid); + } + $db_or->condition($db_and); + } + $query->condition($db_or); + $result = $query + ->execute() + ->fetchAllAssoc('mlid', PDO::FETCH_ASSOC); + + // Store dynamic links grouped by parent path for later merging and assign + // placeholder expansion arguments. + $tree_dynamic = array(); + foreach ($result as $mlid => $link) { + // If contained in $expand_map, then this is a (first) parent, and we need + // to store by the defined 'parent' path for later merging, as well as + // provide the expansion map arguments to apply to the dynamic tree. + if (isset($expand_map[$link['path']])) { + $parent_path = $expand_map[$link['path']]['parent']; + $link['expand_map'] = $expand_map[$link['path']]['arguments']; + } + // Otherwise, just store this link keyed by its parent path; the expand_map + // is automatically derived from parent paths. + else { + $parent_path = $result[$link['plid']]['path']; + } + + $tree_dynamic[$parent_path][] = $link; + } + + return $tree_dynamic; +} + +/** + * Walk through the entire menu tree and merge in expanded dynamic menu links. + * + * @param &$tree + * A menu tree structure as returned by menu_tree_all_data(). + * @param $tree_dynamic + * A dynamic menu tree structure as returned by admin_menu_tree_dynamic(). + * @param $expand_map + * An array containing menu router path placeholder expansion argument + * mappings. + * + * @see hook_admin_menu_map() + * @see admin_menu_tree_dynamic() + * @see menu_tree_all_data() + */ +function admin_menu_merge_tree(array &$tree, array $tree_dynamic, array $expand_map) { + foreach ($tree as $key => $data) { + $path = $data['link']['router_path']; + + // Recurse into regular menu tree. + if ($tree[$key]['below']) { + admin_menu_merge_tree($tree[$key]['below'], $tree_dynamic, $expand_map); + } + // Nothing to merge, if this parent path is not in our dynamic tree. + if (!isset($tree_dynamic[$path])) { + continue; + } + + // Add expanded dynamic items. + foreach ($tree_dynamic[$path] as $link) { + // If the dynamic item has custom placeholder expansion parameters set, + // use them, otherwise keep current. + if (isset($link['expand_map'])) { + // If there are currently no expansion parameters, we may use the new + // set immediately. + if (empty($expand_map)) { + $current_expand_map = $link['expand_map']; + } + else { + // Otherwise we need to filter out elements that differ from the + // current set, i.e. that are not in the same path. + $current_expand_map = array(); + foreach ($expand_map as $arguments) { + foreach ($arguments as $placeholder => $value) { + foreach ($link['expand_map'] as $new_arguments) { + // Skip the new argument if it doesn't contain the current + // replacement placeholders or if their values differ. + if (!isset($new_arguments[$placeholder]) || $new_arguments[$placeholder] != $value) { + continue; + } + $current_expand_map[] = $new_arguments; + } + } + } + } + } + else { + $current_expand_map = $expand_map; + } + + // Skip dynamic items without expansion parameters. + if (empty($current_expand_map)) { + continue; + } + + // Expand anonymous to named placeholders. + // @see _menu_load_objects() + $path_args = explode('/', $link['path']); + $load_functions = unserialize($link['load_functions']); + if (is_array($load_functions)) { + foreach ($load_functions as $index => $function) { + if ($function) { + if (is_array($function)) { + list($function,) = each($function); + } + // Add the loader function name minus "_load". + $placeholder = '%' . substr($function, 0, -5); + $path_args[$index] = $placeholder; + } + } + } + $path_dynamic = implode('/', $path_args); + + // Create new menu items using expansion arguments. + foreach ($current_expand_map as $arguments) { + // Create the cartesian product for all arguments and create new + // menu items for each generated combination thereof. + foreach (admin_menu_expand_args($arguments) as $replacements) { + $newpath = strtr($path_dynamic, $replacements); + // Skip this item, if any placeholder could not be replaced. + // Faster than trying to invoke _menu_translate(). + if (strpos($newpath, '%') !== FALSE) { + continue; + } + $map = explode('/', $newpath); + $item = admin_menu_translate($link, $map); + // Skip this item, if the current user does not have access. + if (empty($item)) { + continue; + } + // Build subtree using current replacement arguments. + $new_expand_map = array(); + foreach ($replacements as $placeholder => $value) { + $new_expand_map[$placeholder] = array($value); + } + admin_menu_merge_tree($item, $tree_dynamic, array($new_expand_map)); + $tree[$key]['below'] += $item; + } + } + } + // Sort new subtree items. + ksort($tree[$key]['below']); + } +} + +/** + * Translate an expanded router item into a menu link suitable for rendering. + * + * @param $router_item + * A menu router item. + * @param $map + * A path map with placeholders replaced. + */ +function admin_menu_translate($router_item, $map) { + _menu_translate($router_item, $map, TRUE); + + // Run through hook_translated_menu_link_alter() to add devel information, + // if configured. + $router_item['menu_name'] = 'management'; + // @todo Invoke as usual like _menu_link_translate(). + admin_menu_translated_menu_link_alter($router_item, NULL); + + if ($router_item['access']) { + // Override mlid to make this item unique; since these items are expanded + // from dynamic items, the mlid is always the same, so each item would + // replace any other. + // @todo Doing this instead leads to plenty of duplicate links below + // admin/structure/menu; likely a hidden recursion problem. + // $router_item['mlid'] = $router_item['href'] . $router_item['mlid']; + $router_item['mlid'] = $router_item['href']; + // Turn menu callbacks into regular menu items to make them visible. + if ($router_item['type'] == MENU_CALLBACK) { + $router_item['type'] = MENU_NORMAL_ITEM; + } + + // @see _menu_tree_check_access() + $key = (50000 + $router_item['weight']) . ' ' . $router_item['title'] . ' ' . $router_item['mlid']; + return array($key => array( + 'link' => $router_item, + 'below' => array(), + )); + } + + return array(); +} + +/** + * Create the cartesian product of multiple varying sized argument arrays. + * + * @param $arguments + * A two dimensional array of arguments. + * + * @see hook_admin_menu_map() + */ +function admin_menu_expand_args($arguments) { + $replacements = array(); + + // Initialize line cursors, move out array keys (placeholders) and assign + // numeric keys instead. + $i = 0; + $placeholders = array(); + $new_arguments = array(); + foreach ($arguments as $placeholder => $values) { + // Skip empty arguments. + if (empty($values)) { + continue; + } + $cursor[$i] = 0; + $placeholders[$i] = $placeholder; + $new_arguments[$i] = $values; + $i++; + } + $arguments = $new_arguments; + unset($new_arguments); + + if ($rows = count($arguments)) { + do { + // Collect current argument from each row. + $row = array(); + for ($i = 0; $i < $rows; ++$i) { + $row[$placeholders[$i]] = $arguments[$i][$cursor[$i]]; + } + $replacements[] = $row; + + // Increment cursor position. + $j = $rows - 1; + $cursor[$j]++; + while (!array_key_exists($cursor[$j], $arguments[$j])) { + // No more arguments left: reset cursor, go to next line and increment + // that cursor instead. Repeat until argument found or out of rows. + $cursor[$j] = 0; + if (--$j < 0) { + // We're done. + break 2; + } + $cursor[$j]++; + } + } while (1); + } + + return $replacements; +} + +/** + * Build the administration menu as renderable menu links. + * + * @param $tree + * A data structure representing the administration menu tree as returned from + * menu_tree_all_data(). + * + * @return + * The complete administration menu, suitable for theme_admin_menu_links(). + * + * @see theme_admin_menu_links() + * @see admin_menu_menu_alter() + */ +function admin_menu_links_menu($tree) { + $links = array(); + foreach ($tree as $data) { + // Skip items that are inaccessible, invisible, or link to their parent. + // (MENU_DEFAULT_LOCAL_TASK), and MENU_CALLBACK-alike items that should only + // appear in the breadcrumb. + if (!$data['link']['access'] || $data['link']['type'] & MENU_LINKS_TO_PARENT || $data['link']['type'] == MENU_VISIBLE_IN_BREADCRUMB || $data['link']['hidden'] == 1) { + continue; + } + // Hide 'Administer' and make child links appear on this level. + // @todo Make this configurable. + if ($data['link']['router_path'] == 'admin') { + if ($data['below']) { + $links = array_merge($links, admin_menu_links_menu($data['below'])); + } + continue; + } + // Omit alias lookups. + $data['link']['localized_options']['alias'] = TRUE; + // Remove description to prevent mouseover tooltip clashes. + unset($data['link']['localized_options']['attributes']['title']); + + // Make action links (typically "Add ...") appear first in dropdowns. + // They might appear first already, but only as long as there is no link + // that comes alphabetically first (e.g., a node type with label "Ad"). + if ($data['link']['type'] & MENU_IS_LOCAL_ACTION) { + $data['link']['weight'] -= 1000; + } + + $links[$data['link']['href']] = array( + '#title' => $data['link']['title'], + '#href' => $data['link']['href'], + '#options' => $data['link']['localized_options'], + '#weight' => $data['link']['weight'], + ); + + // Recurse to add any child links. + $children = array(); + if ($data['below']) { + $children = admin_menu_links_menu($data['below']); + $links[$data['link']['href']] += $children; + } + + // Handle links pointing to category/overview pages. + if ($data['link']['page_callback'] == 'system_admin_menu_block_page' || $data['link']['page_callback'] == 'system_admin_config_page') { + // Apply a marker for others to consume. + $links[$data['link']['href']]['#is_category'] = TRUE; + // Automatically hide empty categories. + // Check for empty children first for performance. Only when non-empty + // (typically 'admin/config'), check whether children are accessible. + if (empty($children) || !element_get_visible_children($children)) { + $links[$data['link']['href']]['#access'] = FALSE; + } + } + } + return $links; +} + +/** + * Build icon menu links; mostly containing maintenance helpers. + * + * @see theme_admin_menu_links() + */ +function admin_menu_links_icon() { + $destination = drupal_get_destination(); + + $links = array( + '#theme' => 'admin_menu_links', + '#wrapper_attributes' => array('id' => 'admin-menu-icon'), + '#weight' => -100, + ); + $links['icon'] = array( + '#title' => theme('admin_menu_icon'), + '#attributes' => array('class' => array('admin-menu-icon')), + '#href' => '', + '#options' => array( + 'html' => TRUE, + ), + ); + // Add link to manually run cron. + $links['icon']['cron'] = array( + '#title' => t('Run cron'), + '#weight' => 50, + '#access' => user_access('administer site configuration'), + '#href' => 'admin/reports/status/run-cron', + ); + // Add link to run update.php. + $links['icon']['update'] = array( + '#title' => t('Run updates'), + '#weight' => 50, + // @see update_access_allowed() + '#access' => $GLOBALS['user']->uid == 1 || !empty($GLOBALS['update_free_access']) || user_access('administer software updates'), + '#href' => base_path() . 'update.php', + '#options' => array( + 'external' => TRUE, + ), + ); + // Add link to drupal.org. + $links['icon']['drupal.org'] = array( + '#title' => 'Drupal.org', + '#weight' => 100, + '#access' => user_access('display drupal links'), + '#href' => 'http://drupal.org', + ); + // Add links to project issue queues. + foreach (module_list(FALSE, TRUE) as $module) { + $info = drupal_parse_info_file(drupal_get_path('module', $module) . '/' . $module . '.info'); + if (!isset($info['project']) || isset($links['icon']['drupal.org'][$info['project']])) { + continue; + } + $links['icon']['drupal.org'][$info['project']] = array( + '#title' => t('@project issue queue', array('@project' => $info['name'])), + '#weight' => ($info['project'] == 'drupal' ? -10 : 0), + '#href' => 'http://drupal.org/project/issues/' . $info['project'], + '#options' => array( + 'query' => array('version' => (isset($info['core']) ? $info['core'] : 'All')), + ), + ); + } + // Add items to flush caches. + $links['icon']['flush-cache'] = array( + '#title' => t('Flush all caches'), + '#weight' => 20, + '#access' => user_access('flush caches'), + '#href' => 'admin_menu/flush-cache', + '#options' => array( + 'query' => $destination + array('token' => drupal_get_token('admin_menu/flush-cache')), + ), + ); + $caches = module_invoke_all('admin_menu_cache_info'); + foreach ($caches as $name => $cache) { + $links['icon']['flush-cache'][$name] = array( + '#title' => $cache['title'], + '#href' => 'admin_menu/flush-cache/' . $name, + '#options' => array( + 'query' => $destination + array('token' => drupal_get_token('admin_menu/flush-cache/' . $name)), + ), + ); + } + + // Add link to toggle developer modules (performance). + $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL); + $links['icon']['toggle-modules'] = array( + '#title' => isset($saved_state) ? t('Enable developer modules') : t('Disable developer modules'), + '#weight' => 88, + '#access' => user_access('administer modules'), + '#href' => 'admin_menu/toggle-modules', + '#options' => array( + 'query' => $destination + array('token' => drupal_get_token('admin_menu/toggle-modules')), + ), + ); + + // Add Devel module menu links. + if (module_exists('devel')) { + $devel_tree = menu_build_tree('devel'); + $devel_links = admin_menu_links_menu($devel_tree); + if (element_get_visible_children($devel_links)) { + $links['icon']['devel'] = array( + '#title' => t('Development'), + '#weight' => 30, + ) + $devel_links; + } + } + + return $links; +} + +/** + * Builds the account links. + * + * @see theme_admin_menu_links() + */ +function admin_menu_links_account() { + $links = array( + '#theme' => 'admin_menu_links', + '#wrapper_attributes' => array('id' => 'admin-menu-account'), + '#weight' => 100, + ); + $links['account'] = array( + '#title' => format_username($GLOBALS['user']), + '#weight' => -99, + '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-account')), + '#href' => 'user/' . $GLOBALS['user']->uid, + ); + $links['logout'] = array( + '#title' => t('Log out'), + '#weight' => -100, + '#attributes' => array('class' => array('admin-menu-action')), + '#href' => 'user/logout', + ); + // Add Devel module switch user links. + $switch_links = module_invoke('devel', 'switch_user_list'); + if (!empty($switch_links) && count($switch_links) > 1) { + foreach ($switch_links as $uid => $link) { + $links['account'][$link['title']] = array( + '#title' => $link['title'], + '#description' => $link['attributes']['title'], + '#href' => $link['href'], + '#options' => array( + 'query' => $link['query'], + 'html' => !empty($link['html']), + ), + ); + } + } + return $links; +} + +/** + * Builds user counter. + * + * @see theme_admin_menu_links() + */ +function admin_menu_links_users() { + $links = array( + '#theme' => 'admin_menu_links', + '#wrapper_attributes' => array('id' => 'admin-menu-users'), + '#weight' => 150, + ); + // Add link to show current authenticated/anonymous users. + $links['user-counter'] = array( + '#title' => admin_menu_get_user_count(), + '#description' => t('Current anonymous / authenticated users'), + '#weight' => -90, + '#attributes' => array('class' => array('admin-menu-action', 'admin-menu-users')), + '#href' => (user_access('administer users') ? 'admin/people/people' : 'user'), + ); + return $links; +} + +/** + * Build search widget. + * + * @see theme_admin_menu_links() + */ +function admin_menu_links_search() { + $links = array( + '#theme' => 'admin_menu_links', + '#wrapper_attributes' => array('id' => 'admin-menu-search'), + '#weight' => 180, + ); + $links['search'] = array( + '#type' => 'textfield', + '#title' => t('Search'), + '#title_display' => 'attribute', + '#attributes' => array( + 'placeholder' => t('Search'), + 'class' => array('admin-menu-search'), + ), + ); + return $links; +} + +/** + * Form builder function for module settings. + */ +function admin_menu_theme_settings() { + $form['admin_menu_margin_top'] = array( + '#type' => 'checkbox', + '#title' => t('Adjust top margin'), + '#default_value' => variable_get('admin_menu_margin_top', 1), + '#description' => t('Shifts the site output down by approximately 20 pixels from the top of the viewport. If disabled, absolute- or fixed-positioned page elements may be covered by the administration menu.'), + ); + $form['admin_menu_position_fixed'] = array( + '#type' => 'checkbox', + '#title' => t('Keep menu at top of page'), + '#default_value' => variable_get('admin_menu_position_fixed', 1), + '#description' => t('Displays the administration menu always at the top of the browser viewport (even when scrolling the page).'), + ); + // @todo Re-confirm this with latest browser versions. + $form['admin_menu_position_fixed']['#description'] .= '
    ' . t('In some browsers, this setting may result in a malformed page, an invisible cursor, non-selectable elements in forms, or other issues.') . ''; + + $form['advanced'] = array( + '#type' => 'vertical_tabs', + '#title' => t('Advanced settings'), + ); + + $form['plugins'] = array( + '#type' => 'fieldset', + '#title' => t('Plugins'), + '#group' => 'advanced', + ); + $form['plugins']['admin_menu_components'] = array( + '#type' => 'checkboxes', + '#title' => t('Enabled components'), + '#options' => array( + 'admin_menu.icon' => t('Icon menu'), + 'admin_menu.menu' => t('Administration menu'), + 'admin_menu.search' => t('Search bar'), + 'admin_menu.users' => t('User counts'), + 'admin_menu.account' => t('Account links'), + ), + ); + $form['plugins']['admin_menu_components']['#default_value'] = array_keys(array_filter(variable_get('admin_menu_components', $form['plugins']['admin_menu_components']['#options']))); + + $process = element_info_property('checkboxes', '#process', array()); + $form['plugins']['admin_menu_components']['#process'] = array_merge(array('admin_menu_settings_process_components'), $process); + $form['#attached']['js'][] = drupal_get_path('module', 'admin_menu') . '/admin_menu.admin.js'; + + $form['tweaks'] = array( + '#type' => 'fieldset', + '#title' => t('System tweaks'), + '#group' => 'advanced', + ); + $form['tweaks']['admin_menu_tweak_modules'] = array( + '#type' => 'checkbox', + '#title' => t('Collapse module groups on the %modules page', array( + '%modules' => t('Modules'), + '!modules-url' => url('admin/modules'), + )), + '#default_value' => variable_get('admin_menu_tweak_modules', 0), + ); + if (module_exists('util')) { + $form['tweaks']['admin_menu_tweak_modules']['#description'] .= '
    ' . t('If the Utility module was installed for this purpose, it can be safely disabled and uninstalled.') . ''; + } + $form['tweaks']['admin_menu_tweak_permissions'] = array( + '#type' => 'checkbox', + '#title' => t('Collapse module groups on the %permissions page', array( + '%permissions' => t('Permissions'), + '@permissions-url' => url('admin/people/permissions'), + )), + '#default_value' => variable_get('admin_menu_tweak_permissions', 0), + ); + $form['tweaks']['admin_menu_tweak_tabs'] = array( + '#type' => 'checkbox', + '#title' => t('Move local tasks into menu'), + '#default_value' => variable_get('admin_menu_tweak_tabs', 0), + '#description' => t('Moves the tabs on all pages into the administration menu. Only possible for themes using the CSS classes tabs primary and tabs secondary.'), + ); + + $form['performance'] = array( + '#type' => 'fieldset', + '#title' => t('Performance'), + '#group' => 'advanced', + ); + $form['performance']['admin_menu_cache_client'] = array( + '#type' => 'checkbox', + '#title' => t('Cache menu in client-side browser'), + '#default_value' => variable_get('admin_menu_cache_client', 1), + ); + // Fetch all available modules manually, since module_list() only returns + // currently enabled modules, which makes this setting pointless if developer + // modules are currently disabled. + $all_modules = array(); + $result = db_query("SELECT name, filename, info FROM {system} WHERE type = 'module' ORDER BY name ASC"); + foreach ($result as $module) { + if (file_exists($module->filename)) { + $info = unserialize($module->info); + $all_modules[$module->name] = $info['name']; + } + } + $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules()); + $devel_modules = array_intersect_key($all_modules, array_flip($devel_modules)); + $form['performance']['admin_menu_devel_modules_skip'] = array( + '#type' => 'checkboxes', + '#title' => t('Developer modules to keep enabled'), + '#default_value' => variable_get('admin_menu_devel_modules_skip', array()), + '#options' => $devel_modules, + '#access' => !empty($devel_modules), + '#description' => t('The selected modules will not be disabled when the link %disable-developer-modules below the icon in the menu is invoked.', array( + '%disable-developer-modules' => t('Disable developer modules'), + )), + ); + + return system_settings_form($form); +} + +/** + * #process callback for component plugin form element in admin_menu_theme_settings(). + */ +function admin_menu_settings_process_components($element) { + // Assign 'rel' attributes to all options to achieve a live preview. + // Unfortunately, #states relies on wrapping .form-wrapper classes, so it + // cannot be used here. + foreach ($element['#options'] as $key => $label) { + if (!isset($element[$key]['#attributes']['rel'])) { + $id = preg_replace('/[^a-z]/', '-', $key); + $element[$key]['#attributes']['rel'] = '#' . $id; + } + } + return $element; +} + +/** + * Form validation handler for admin_menu_theme_settings(). + */ +function admin_menu_theme_settings_validate(&$form, &$form_state) { + // Change the configured components to Boolean values. + foreach ($form_state['values']['admin_menu_components'] as $component => &$enabled) { + $enabled = (bool) $enabled; + } +} + +/** + * Implementation of hook_form_FORM_ID_alter(). + * + * Extends Devel module with Administration menu developer settings. + */ +function _admin_menu_form_devel_admin_settings_alter(&$form, $form_state) { + // Shift system_settings_form buttons. + $weight = isset($form['buttons']['#weight']) ? $form['buttons']['#weight'] : 0; + $form['buttons']['#weight'] = $weight + 1; + + $form['admin_menu'] = array( + '#type' => 'fieldset', + '#title' => t('Administration menu settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $display_options = array('mid', 'weight', 'pid'); + $display_options = array(0 => t('None'), 'mlid' => t('Menu link ID'), 'weight' => t('Weight'), 'plid' => t('Parent link ID')); + $form['admin_menu']['admin_menu_display'] = array( + '#type' => 'radios', + '#title' => t('Display additional data for each menu item'), + '#default_value' => variable_get('admin_menu_display', 0), + '#options' => $display_options, + '#description' => t('Display the selected items next to each menu item link.'), + ); + $form['admin_menu']['admin_menu_show_all'] = array( + '#type' => 'checkbox', + '#title' => t('Display all menu items'), + '#default_value' => variable_get('admin_menu_show_all', 0), + '#description' => t('If enabled, all menu items are displayed regardless of your site permissions. Note: Do not enable on a production site.'), + ); +} + +/** + * Menu callback; Enable/disable developer modules. + * + * This can save up to 150ms on each uncached page request. + */ +function admin_menu_toggle_modules() { + if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) { + return MENU_ACCESS_DENIED; + } + + $rebuild = FALSE; + $saved_state = variable_get('admin_menu_devel_modules_enabled', NULL); + if (isset($saved_state)) { + // Re-enable modules that were enabled before. + module_enable($saved_state); + variable_del('admin_menu_devel_modules_enabled'); + drupal_set_message(t('Enabled these modules: !module-list.', array('!module-list' => implode(', ', $saved_state)))); + $rebuild = TRUE; + } + else { + // Allow site admins to override this variable via settings.php. + $devel_modules = variable_get('admin_menu_devel_modules', _admin_menu_developer_modules()); + // Store currently enabled modules in a variable. + $devel_modules = array_intersect(module_list(FALSE, FALSE), $devel_modules); + $devel_modules = array_diff($devel_modules, variable_get('admin_menu_devel_modules_skip', array())); + if (!empty($devel_modules)) { + variable_set('admin_menu_devel_modules_enabled', $devel_modules); + // Disable developer modules. + module_disable($devel_modules); + drupal_set_message(t('Disabled these modules: !module-list.', array('!module-list' => implode(', ', $devel_modules)))); + $rebuild = TRUE; + } + else { + drupal_set_message(t('No developer modules are enabled.')); + } + } + if ($rebuild) { + // Make sure everything is rebuilt, basically a combination of the calls + // from system_modules() and system_modules_submit(). + drupal_theme_rebuild(); + menu_rebuild(); + cache_clear_all('schema', 'cache'); + cache_clear_all(); + drupal_clear_css_cache(); + drupal_clear_js_cache(); + // Synchronize to catch any actions that were added or removed. + actions_synchronize(); + // Finally, flush admin_menu's cache. + admin_menu_flush_caches(); + } + drupal_goto(); +} + +/** + * Helper function to return a default list of developer modules. + */ +function _admin_menu_developer_modules() { + return array( + 'admin_devel', + 'cache_disable', + 'coder', + 'content_copy', + 'context_ui', + 'debug', + 'delete_all', + 'demo', + 'devel', + 'devel_node_access', + 'devel_themer', + 'field_ui', + 'fontyourface_ui', + 'form_controller', + 'imagecache_ui', + 'journal', + 'l10n_client', + 'l10n_update', + 'macro', + 'rules_admin', + 'stringoverrides', + 'trace', + 'upgrade_status', + 'user_display_ui', + 'util', + 'views_ui', + 'views_theme_wizard', + ); +} + +/** + * Flush all caches or a specific one. + * + * @param $name + * (optional) Name of cache to flush. + */ +function admin_menu_flush_cache($name = NULL) { + if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], current_path())) { + return MENU_ACCESS_DENIED; + } + if (isset($name)) { + $caches = module_invoke_all('admin_menu_cache_info'); + if (!isset($caches[$name])) { + return MENU_NOT_FOUND; + } + } + else { + $caches[$name] = array( + 'title' => t('Every'), + 'callback' => 'drupal_flush_all_caches', + ); + } + // Pass the cache to flush forward to the callback. + $function = $caches[$name]['callback']; + $function($name); + + drupal_set_message(t('!title cache cleared.', array('!title' => $caches[$name]['title']))); + + // The JavaScript injects a destination request parameter pointing to the + // originating page, so the user is redirected back to that page. Without + // destination parameter, the redirect ends on the front page. + drupal_goto(); +} + +/** + * Implements hook_admin_menu_cache_info(). + */ +function admin_menu_admin_menu_cache_info() { + $caches['admin_menu'] = array( + 'title' => t('Administration menu'), + 'callback' => '_admin_menu_flush_cache', + ); + return $caches; +} + +/** + * Implements hook_admin_menu_cache_info() on behalf of System module. + */ +function system_admin_menu_cache_info() { + $caches = array( + 'assets' => t('CSS and JavaScript'), + 'cache' => t('Page and else'), + 'menu' => t('Menu'), + 'registry' => t('Class registry'), + 'theme' => t('Theme registry'), + ); + foreach ($caches as $name => $cache) { + $caches[$name] = array( + 'title' => $cache, + 'callback' => '_admin_menu_flush_cache', + ); + } + return $caches; +} + +/** + * Implements hook_admin_menu_cache_info() on behalf of Update module. + */ +function update_admin_menu_cache_info() { + $caches['update'] = array( + 'title' => t('Update data'), + 'callback' => '_update_cache_clear', + ); + return $caches; +} + +/** + * Flush all caches or a specific one. + * + * @param $name + * (optional) Name of cache to flush. + * + * @see system_admin_menu_cache_info() + */ +function _admin_menu_flush_cache($name = NULL) { + switch ($name) { + case 'admin_menu': + admin_menu_flush_caches(); + break; + + case 'menu': + menu_rebuild(); + break; + + case 'registry': + registry_rebuild(); + // Fall-through to clear cache tables, since registry information is + // usually the base for other data that is cached (e.g. SimpleTests). + case 'cache': + // Don't clear cache_form - in-progress form submissions may break. + // Ordered so clearing the page cache will always be the last action. + // @see drupal_flush_all_caches() + $core = array('cache', 'cache_bootstrap', 'cache_filter', 'cache_page'); + $cache_tables = array_merge(module_invoke_all('flush_caches'), $core); + foreach ($cache_tables as $table) { + cache_clear_all('*', $table, TRUE); + } + break; + + case 'assets': + // Change query-strings on css/js files to enforce reload for all users. + _drupal_flush_css_js(); + + drupal_clear_css_cache(); + drupal_clear_js_cache(); + + // Clear the page cache, since cached HTML pages might link to old CSS and + // JS aggregates. + cache_clear_all('*', 'cache_page', TRUE); + break; + + case 'theme': + system_rebuild_theme_data(); + drupal_theme_rebuild(); + break; + } +} + +/** + * Preprocesses variables for theme_admin_menu_icon(). + */ +function template_preprocess_admin_menu_icon(&$variables) { + // Image source might have been passed in as theme variable. + if (!isset($variables['src'])) { + if (theme_get_setting('toggle_favicon')) { + $variables['src'] = theme_get_setting('favicon'); + } + else { + $variables['src'] = base_path() . 'misc/favicon.ico'; + } + } + // Strip the protocol without delimiters for transient HTTP/HTTPS support. + // Since the menu is cached on the server-side and client-side, the cached + // version might contain a HTTP link, whereas the actual page is on HTTPS. + // Relative paths will work fine, but theme_get_setting() returns an + // absolute URI. + $variables['src'] = preg_replace('@^https?:@', '', $variables['src']); + $variables['src'] = check_plain($variables['src']); + $variables['alt'] = t('Home'); +} + +/** + * Renders an icon to display in the administration menu. + * + * @ingroup themeable + */ +function theme_admin_menu_icon($variables) { + return '' . $variables['alt'] . ''; +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,16 @@ +name = Administration menu +description = "Provides a dropdown menu to most administrative tasks and other common destinations (to users with the proper permissions)." +package = Administration +core = 7.x +configure = admin/config/administration/admin_menu +; Requires menu_build_tree() conditions; available after 7.10. +; @see http://drupal.org/node/1025582 +dependencies[] = system (>7.10) +files[] = tests/admin_menu.test + +; Information added by drupal.org packaging script on 2013-01-31 +version = "7.x-3.0-rc4" +core = "7.x" +project = "admin_menu" +datestamp = "1359651687" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,122 @@ +fields(array('weight' => 100)) + ->condition('type', 'module') + ->condition('name', 'admin_menu') + ->execute(); +} + +/** + * Implements hook_uninstall(). + */ +function admin_menu_uninstall() { + // Delete variables. + variable_del('admin_menu_components'); + variable_del('admin_menu_devel_modules'); + variable_del('admin_menu_devel_modules_enabled'); + variable_del('admin_menu_devel_modules_skip'); + variable_del('admin_menu_margin_top'); + variable_del('admin_menu_position_fixed'); + variable_del('admin_menu_tweak_modules'); + variable_del('admin_menu_tweak_tabs'); + variable_del('admin_menu_show_all'); + variable_del('admin_menu_display'); + variable_del('admin_menu_cache_server'); + variable_del('admin_menu_cache_client'); +} + +/** + * Ensure that admin_menu is rebuilt after upgrading to D6. + */ +function admin_menu_update_6000() { + // Drop the {admin_menu} table in admin_menu_update_6000() on sites that used + // one of the later patches in #132524. + if (db_table_exists('admin_menu')) { + db_drop_table('admin_menu'); + } +} + +/** + * Wipe and rebuild so we can switch the icon path to . + */ +function admin_menu_update_6001() { + db_delete('menu_links')->condition('module', 'admin_menu')->execute(); + menu_cache_clear('admin_menu'); +} + +/** + * Add {cache_admin_menu} table. + */ +function admin_menu_update_7300() { + if (!db_table_exists('cache_admin_menu')) { + $schema = drupal_get_schema_unprocessed('system', 'cache'); + db_create_table('cache_admin_menu', $schema); + } +} + +/** + * Increase the module weight. + * + * @see admin_menu_install() + */ +function admin_menu_update_7302() { + db_update('system') + ->fields(array('weight' => 100)) + ->condition('type', 'module') + ->condition('name', 'admin_menu') + ->execute(); +} + +/** + * Remove local tasks from {menu_links} table. + */ +function admin_menu_update_7303() { + db_delete('menu_router') + ->condition('path', 'admin/%', 'LIKE') + ->condition('type', MENU_IS_LOCAL_TASK, '&') + ->execute(); +} + +/** + * Remove obsolete 'admin_menu' menu and all orphan links in it. + */ +function admin_menu_update_7304() { + // Remove the custom menu used by 6.x-1.x. + if (db_table_exists('menu_custom')) { + db_delete('menu_custom')->condition('menu_name', 'admin_menu')->execute(); + } + + // 6.x-1.x cloned the entire link structure below the path 'admin' into a + // separate 'menu_name' "admin_menu" with 'module' "admin_menu". 6.x-3.x and + // early alpha versions of 7.x-3.x still did something similar. All of these + // records are obsolete. Removal of the 'module' records (without different + // menu_name) is particularly important, since they would otherwise appear + // as duplicate links. + db_delete('menu_links') + ->condition(db_or() + ->condition('module', 'admin_menu') + ->condition('menu_name', 'admin_menu') + ) + ->execute(); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,397 @@ +(function($) { + +Drupal.admin = Drupal.admin || {}; +Drupal.admin.behaviors = Drupal.admin.behaviors || {}; +Drupal.admin.hashes = Drupal.admin.hashes || {}; + +/** + * Core behavior for Administration menu. + * + * Test whether there is an administration menu is in the output and execute all + * registered behaviors. + */ +Drupal.behaviors.adminMenu = { + attach: function (context, settings) { + // Initialize settings. + settings.admin_menu = $.extend({ + suppress: false, + margin_top: false, + position_fixed: false, + tweak_modules: false, + tweak_permissions: false, + tweak_tabs: false, + destination: '', + basePath: settings.basePath, + hash: 0, + replacements: {} + }, settings.admin_menu || {}); + // Check whether administration menu should be suppressed. + if (settings.admin_menu.suppress) { + return; + } + var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context); + // Client-side caching; if administration menu is not in the output, it is + // fetched from the server and cached in the browser. + if (!$adminMenu.length && settings.admin_menu.hash) { + Drupal.admin.getCache(settings.admin_menu.hash, function (response) { + if (typeof response == 'string' && response.length > 0) { + $('body', context).append(response); + } + var $adminMenu = $('#admin-menu:not(.admin-menu-processed)', context); + // Apply our behaviors. + Drupal.admin.attachBehaviors(context, settings, $adminMenu); + // Allow resize event handlers to recalculate sizes/positions. + $(window).triggerHandler('resize'); + }); + } + // If the menu is in the output already, this means there is a new version. + else { + // Apply our behaviors. + Drupal.admin.attachBehaviors(context, settings, $adminMenu); + } + } +}; + +/** + * Collapse fieldsets on Modules page. + */ +Drupal.behaviors.adminMenuCollapseModules = { + attach: function (context, settings) { + if (settings.admin_menu.tweak_modules) { + $('#system-modules fieldset:not(.collapsed)', context).addClass('collapsed'); + } + } +}; + +/** + * Collapse modules on Permissions page. + */ +Drupal.behaviors.adminMenuCollapsePermissions = { + attach: function (context, settings) { + if (settings.admin_menu.tweak_permissions) { + // Freeze width of first column to prevent jumping. + $('#permissions th:first', context).css({ width: $('#permissions th:first', context).width() }); + // Attach click handler. + $modules = $('#permissions tr:has(td.module)', context).once('admin-menu-tweak-permissions', function () { + var $module = $(this); + $module.bind('click.admin-menu', function () { + // @todo Replace with .nextUntil() in jQuery 1.4. + $module.nextAll().each(function () { + var $row = $(this); + if ($row.is(':has(td.module)')) { + return false; + } + $row.toggleClass('element-hidden'); + }); + }); + }); + // Collapse all but the targeted permission rows set. + if (window.location.hash.length) { + $modules = $modules.not(':has(' + window.location.hash + ')'); + } + $modules.trigger('click.admin-menu'); + } + } +}; + +/** + * Apply margin to page. + * + * Note that directly applying marginTop does not work in IE. To prevent + * flickering/jumping page content with client-side caching, this is a regular + * Drupal behavior. + */ +Drupal.behaviors.adminMenuMarginTop = { + attach: function (context, settings) { + if (!settings.admin_menu.suppress && settings.admin_menu.margin_top) { + $('body:not(.admin-menu)', context).addClass('admin-menu'); + } + } +}; + +/** + * Retrieve content from client-side cache. + * + * @param hash + * The md5 hash of the content to retrieve. + * @param onSuccess + * A callback function invoked when the cache request was successful. + */ +Drupal.admin.getCache = function (hash, onSuccess) { + if (Drupal.admin.hashes.hash !== undefined) { + return Drupal.admin.hashes.hash; + } + $.ajax({ + cache: true, + type: 'GET', + dataType: 'text', // Prevent auto-evaluation of response. + global: false, // Do not trigger global AJAX events. + url: Drupal.settings.admin_menu.basePath.replace(/admin_menu/, 'js/admin_menu/cache/' + hash), + success: onSuccess, + complete: function (XMLHttpRequest, status) { + Drupal.admin.hashes.hash = status; + } + }); +}; + +/** + * TableHeader callback to determine top viewport offset. + * + * @see toolbar.js + */ +Drupal.admin.height = function() { + var $adminMenu = $('#admin-menu'); + var height = $adminMenu.outerHeight(); + // In IE, Shadow filter adds some extra height, so we need to remove it from + // the returned height. + if ($adminMenu.css('filter') && $adminMenu.css('filter').match(/DXImageTransform\.Microsoft\.Shadow/)) { + height -= $adminMenu.get(0).filters.item("DXImageTransform.Microsoft.Shadow").strength; + } + return height; +}; + +/** + * @defgroup admin_behaviors Administration behaviors. + * @{ + */ + +/** + * Attach administrative behaviors. + */ +Drupal.admin.attachBehaviors = function (context, settings, $adminMenu) { + if ($adminMenu.length) { + $adminMenu.addClass('admin-menu-processed'); + $.each(Drupal.admin.behaviors, function() { + this(context, settings, $adminMenu); + }); + } +}; + +/** + * Apply 'position: fixed'. + */ +Drupal.admin.behaviors.positionFixed = function (context, settings, $adminMenu) { + if (settings.admin_menu.position_fixed) { + $adminMenu.addClass('admin-menu-position-fixed'); + $adminMenu.css('position', 'fixed'); + } +}; + +/** + * Move page tabs into administration menu. + */ +Drupal.admin.behaviors.pageTabs = function (context, settings, $adminMenu) { + if (settings.admin_menu.tweak_tabs) { + var $tabs = $(context).find('ul.tabs.primary'); + $adminMenu.find('#admin-menu-wrapper > ul').eq(1) + .append($tabs.find('li').addClass('admin-menu-tab')); + $(context).find('ul.tabs.secondary') + .appendTo('#admin-menu-wrapper > ul > li.admin-menu-tab.active') + .removeClass('secondary'); + $tabs.remove(); + } +}; + +/** + * Perform dynamic replacements in cached menu. + */ +Drupal.admin.behaviors.replacements = function (context, settings, $adminMenu) { + for (var item in settings.admin_menu.replacements) { + $(item, $adminMenu).html(settings.admin_menu.replacements[item]); + } +}; + +/** + * Inject destination query strings for current page. + */ +Drupal.admin.behaviors.destination = function (context, settings, $adminMenu) { + if (settings.admin_menu.destination) { + $('a.admin-menu-destination', $adminMenu).each(function() { + this.search += (!this.search.length ? '?' : '&') + Drupal.settings.admin_menu.destination; + }); + } +}; + +/** + * Apply JavaScript-based hovering behaviors. + * + * @todo This has to run last. If another script registers additional behaviors + * it will not run last. + */ +Drupal.admin.behaviors.hover = function (context, settings, $adminMenu) { + // Hover emulation for IE 6. + if ($.browser.msie && parseInt(jQuery.browser.version) == 6) { + $('li', $adminMenu).hover( + function () { + $(this).addClass('iehover'); + }, + function () { + $(this).removeClass('iehover'); + } + ); + } + + // Delayed mouseout. + $('li.expandable', $adminMenu).hover( + function () { + // Stop the timer. + clearTimeout(this.sfTimer); + // Display child lists. + $('> ul', this) + .css({left: 'auto', display: 'block'}) + // Immediately hide nephew lists. + .parent().siblings('li').children('ul').css({left: '-999em', display: 'none'}); + }, + function () { + // Start the timer. + var uls = $('> ul', this); + this.sfTimer = setTimeout(function () { + uls.css({left: '-999em', display: 'none'}); + }, 400); + } + ); +}; + +/** + * Apply the search bar functionality. + */ +Drupal.admin.behaviors.search = function (context, settings, $adminMenu) { + // @todo Add a HTML ID. + var $input = $('input.admin-menu-search', $adminMenu); + // Initialize the current search needle. + var needle = $input.val(); + // Cache of all links that can be matched in the menu. + var links; + // Minimum search needle length. + var needleMinLength = 2; + // Append the results container. + var $results = $('

    ').insertAfter($input); + + /** + * Executes the search upon user input. + */ + function keyupHandler() { + var matches, $html, value = $(this).val(); + // Only proceed if the search needle has changed. + if (value !== needle) { + needle = value; + // Initialize the cache of menu links upon first search. + if (!links && needle.length >= needleMinLength) { + // @todo Limit to links in dropdown menus; i.e., skip menu additions. + links = buildSearchIndex($adminMenu.find('li:not(.admin-menu-action, .admin-menu-action li) > a')); + } + // Empty results container when deleting search text. + if (needle.length < needleMinLength) { + $results.empty(); + } + // Only search if the needle is long enough. + if (needle.length >= needleMinLength && links) { + matches = findMatches(needle, links); + // Build the list in a detached DOM node. + $html = buildResultsList(matches); + // Display results. + $results.empty().append($html); + } + } + } + + /** + * Builds the search index. + */ + function buildSearchIndex($links) { + return $links + .map(function () { + var text = (this.textContent || this.innerText); + // Skip menu entries that do not contain any text (e.g., the icon). + if (typeof text === 'undefined') { + return; + } + return { + text: text, + textMatch: text.toLowerCase(), + element: this + }; + }); + } + + /** + * Searches the index for a given needle and returns matching entries. + */ + function findMatches(needle, links) { + var needleMatch = needle.toLowerCase(); + // Select matching links from the cache. + return $.grep(links, function (link) { + return link.textMatch.indexOf(needleMatch) !== -1; + }); + } + + /** + * Builds the search result list in a detached DOM node. + */ + function buildResultsList(matches) { + var $html = $(''; + } + return $output; +} + +/** + * Function used by uasort to sort structured arrays by #weight AND #title. + */ +function admin_menu_element_sort($a, $b) { + // @see element_sort() + $a_weight = isset($a['#weight']) ? $a['#weight'] : 0; + $b_weight = isset($b['#weight']) ? $b['#weight'] : 0; + if ($a_weight == $b_weight) { + // @see element_sort_by_title() + $a_title = isset($a['#title']) ? $a['#title'] : ''; + $b_title = isset($b['#title']) ? $b['#title'] : ''; + return strnatcasecmp($a_title, $b_title); + } + return ($a_weight < $b_weight) ? -1 : 1; +} + +/** + * Implements hook_translated_menu_link_alter(). + * + * Here is where we make changes to links that need dynamic information such + * as the current page path or the number of users. + */ +function admin_menu_translated_menu_link_alter(&$item, $map) { + global $user, $base_url; + static $access_all; + + if ($item['menu_name'] != 'admin_menu') { + return; + } + + // Check whether additional development output is enabled. + if (!isset($access_all)) { + $access_all = variable_get('admin_menu_show_all', 0) && module_exists('devel'); + } + // Prepare links that would not be displayed normally. + if ($access_all && !$item['access']) { + $item['access'] = TRUE; + // Prepare for http://drupal.org/node/266596 + if (!isset($item['localized_options'])) { + _menu_item_localize($item, $map, TRUE); + } + } + + // Don't waste cycles altering items that are not visible + if (!$item['access']) { + return; + } + + // Add developer information to all links, if enabled. + if ($extra = variable_get('admin_menu_display', 0)) { + $item['title'] .= ' ' . $extra[0] . ': ' . $item[$extra]; + } +} + +/** + * Implements hook_flush_caches(). + * + * Flushes client-side caches. + * + * @param int $uid + * (optional) A user ID to limit the cache flush to. + */ +function admin_menu_flush_caches($uid = NULL) { + // A call to menu_rebuild() will trigger potentially thousands of calls into + // menu_link_save(), for which admin_menu has to implement the corresponding + // CRUD hooks, in order to take up any menu link changes, since any menu link + // change could affect the admin menu (which essentially is an aggregate) and + // since there is no other way to get notified about stale caches. The cache + // only needs to be flushed once though, so we prevent a ton of needless + // subsequent calls with this static. + // @see http://drupal.org/node/918538 + $was_flushed = &drupal_static(__FUNCTION__, array()); + // $uid can be NULL. PHP automatically converts that into '' (empty string), + // which is different to uid 0 (zero). + if (isset($was_flushed[$uid])) { + return; + } + $was_flushed[$uid] = TRUE; + + $cid = 'admin_menu:'; + if (isset($uid)) { + $cid .= $uid . ':'; + } + // Flush cached output of admin_menu. + cache_clear_all($cid, 'cache_menu', TRUE); + // Flush client-side cache hashes. + drupal_static_reset('admin_menu_cache_get'); + // db_table_exists() required for SimpleTest. + if (db_table_exists('cache_admin_menu')) { + cache_clear_all(isset($uid) ? $cid : '*', 'cache_admin_menu', TRUE); + } +} + +/** + * Implements hook_form_alter(). + */ +function admin_menu_form_alter(&$form, &$form_state, $form_id) { + $global_flush_ids = array( + 'admin_menu_theme_settings' => 1, + // Update links for clean/non-clean URLs. + 'system_clean_url_settings' => 1, + // Incorporate changed user permissions. + 'user_admin_permissions' => 1, + // Removing a role potentially means less permissions. + 'user_admin_role_delete_confirm' => 1, + // User name and roles may be changed on the user account form. + 'user_profile_form' => 1, + ); + if (isset($global_flush_ids[$form_id])) { + $form['#submit'][] = 'admin_menu_form_alter_flush_cache_submit'; + + // Optionally limit the cache flush to a certain user ID. + $form_state['admin_menu_uid'] = NULL; + if ($form_id == 'user_profile_form') { + $form_state['admin_menu_uid'] = $form_state['user']->uid; + } + } + + // UX: Add a confirmation to the permissions form to ask the user whether to + // auto-enable the 'access administration menu' permission along with + // 'access administration pages'. + if ($form_id == 'user_admin_permissions') { + $form['#attached']['js'][] = drupal_get_path('module', 'admin_menu') . '/admin_menu.admin.js'; + } +} + +/** + * Form submission handler to flush Administration menu caches. + */ +function admin_menu_form_alter_flush_cache_submit($form, &$form_state) { + admin_menu_flush_caches($form_state['admin_menu_uid']); +} + +/** + * Implements hook_form_FORM_ID_alter(). + * + * Extends Devel module with Administration menu developer settings. + */ +function admin_menu_form_devel_admin_settings_alter(&$form, &$form_state) { + form_load_include($form_state, 'inc', 'admin_menu'); + _admin_menu_form_devel_admin_settings_alter($form, $form_state); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu.uid1.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu.uid1.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,9 @@ + +/** + * @file + * Administration menu color override for uid1. + */ + +#admin-menu li.admin-menu-account > a { + background: #911 url(images/bkg-red.png) bottom left repeat-x; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,145 @@ + +/** + * @file + * Toolbar style for Administration menu. + * + * Important: We cannot re-use toolbar.png from Toolbar module, since we cannot + * reliably determine the path to it. + * + * @todo Separate shortcut functionality into own module/widget. + */ + +/* Adjust margin/height */ +html body.admin-menu { + margin-top: 29px !important; +} +html body.admin-menu-with-shortcuts { + margin-top: 65px !important; +} +/* Displace the core Toolbar, if concurrently output. */ +body div#toolbar.toolbar { + top: 30px; +} + +/** + * Base styles. + * + * We use a keyword for the toolbar font size to make it display consistently + * across different themes, while still allowing browsers to resize the text. + */ +#admin-menu { + font: normal small "Lucida Grande", Verdana, sans-serif; + -moz-box-shadow: 0 -10px 20px 13px #000; + -webkit-box-shadow: 0 -10px 20px 13px #000; + box-shadow: 0 -10px 20px 13px #000; + right: 0; + width: auto; +} +#admin-menu-wrapper { + font-size: .846em; + padding: 5px 10px 0; +} + +#admin-menu .dropdown a { + color: #fafafa; +} + +/* Remove border from all lists and actions */ +#admin-menu .dropdown .admin-menu-action a { + border-left: 0; +} +#admin-menu .dropdown .admin-menu-icon > a { + padding: 2px 10px 3px; +} + +/** + * Administration menu. + */ +#admin-menu .dropdown .admin-menu-icon > a span { + vertical-align: text-bottom; + width: 11px; + height: 14px; + display: block; + background: url(toolbar.png) no-repeat 0 -45px; + text-indent: -9999px; +} +#admin-menu > div > .dropdown > li > a { + border-right: 0; + margin-bottom: 4px; + padding: 2px 10px 3px; +} +#admin-menu .dropdown .admin-menu-toolbar-category > a, +#admin-menu .dropdown .admin-menu-action > a { + border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; +} +#admin-menu .dropdown .admin-menu-toolbar-category > a.active-trail { + text-shadow: #333 0 1px 0; + background: url(toolbar.png) 0 0 repeat-x; +} +#admin-menu .dropdown .admin-menu-toolbar-category > a:hover { + background-color: #444; +} +#admin-menu .dropdown .admin-menu-tab a { + border-right: 0; +} +#admin-menu .dropdown li li.expandable ul { + margin: -22px 0 0 160px; +} + +/** + * Shortcuts toggle. + */ +#admin-menu .shortcut-toggle { + cursor: pointer; + background: url(toolbar.png) 0 -20px no-repeat; + display: block; + float: right; + margin: 0 0 0 1.3em; + text-indent: -9999px; + overflow: hidden; + width: 25px; + height: 25px; +} +#admin-menu .shortcut-toggle:focus, +#admin-menu .shortcut-toggle:hover { + background-position: -50px -20px; +} +#admin-menu .shortcut-toggle.active { + background-position: -25px -20px; +} +#admin-menu .shortcut-toggle.active:focus, +#admin-menu .shortcut-toggle.active:hover { + background-position: -75px -20px; +} + +/** + * Shortcuts widget. + */ +#admin-menu .shortcut-toolbar { + background-color: #666; + clear: both; + display: none; + margin: 0 -10px; + overflow: hidden; + /* Align with icon; @see shortcut.css */ + padding-left: 5px; +} +#admin-menu .shortcut-toolbar.active { + display: block; +} +/* Override theme list style; @see shortcut.css */ +#admin-menu .shortcut-toolbar ul { + margin: 0; +} +/* @see toolbar.css */ +#admin-menu .shortcut-toolbar li { + float: left; + list-style-image: none; + list-style-type: none; +} +#admin-menu .shortcut-toolbar a { + display: block; +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,12 @@ +name = Administration menu Toolbar style +description = A better Toolbar. +package = Administration +core = 7.x +dependencies[] = admin_menu + +; Information added by drupal.org packaging script on 2013-01-31 +version = "7.x-3.0-rc4" +core = "7.x" +project = "admin_menu" +datestamp = "1359651687" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,37 @@ +fields(array('weight' => 101)) + ->condition('type', 'module') + ->condition('name', 'admin_menu_toolbar') + ->execute(); +} + +/** + * Set module weight to a value higher than admin_menu. + * + * At this point, admin_menu should have a weight of 100. To account for + * customized weights, we increase the weight relatively. + * + * @see admin_menu_toolbar_install() + */ +function admin_menu_toolbar_update_6300() { + $weight = db_query("SELECT weight FROM {system} WHERE type = 'module' AND name = 'admin_menu'")->fetchField(); + $weight++; + db_update('system') + ->fields(array('weight' => $weight)) + ->condition('type', 'module') + ->condition('name', 'admin_menu_toolbar') + ->execute(); +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,56 @@ +(function($) { + +Drupal.admin = Drupal.admin || {}; +Drupal.admin.behaviors = Drupal.admin.behaviors || {}; + +/** + * @ingroup admin_behaviors + * @{ + */ + +/** + * Apply active trail highlighting based on current path. + * + * @todo Not limited to toolbar; move into core? + */ +Drupal.admin.behaviors.toolbarActiveTrail = function (context, settings, $adminMenu) { + if (settings.admin_menu.toolbar && settings.admin_menu.toolbar.activeTrail) { + $adminMenu.find('> div > ul > li > a[href="' + settings.admin_menu.toolbar.activeTrail + '"]').addClass('active-trail'); + } +}; + +/** + * Toggles the shortcuts bar. + */ +Drupal.admin.behaviors.shortcutToggle = function (context, settings, $adminMenu) { + var $shortcuts = $adminMenu.find('.shortcut-toolbar'); + if (!$shortcuts.length) { + return; + } + var storage = window.localStorage || false; + var storageKey = 'Drupal.admin_menu.shortcut'; + var $body = $(context).find('body'); + var $toggle = $adminMenu.find('.shortcut-toggle'); + $toggle.click(function () { + var enable = !$shortcuts.hasClass('active'); + $shortcuts.toggleClass('active', enable); + $toggle.toggleClass('active', enable); + if (settings.admin_menu.margin_top) { + $body.toggleClass('admin-menu-with-shortcuts', enable); + } + // Persist toggle state across requests. + storage && enable ? storage.setItem(storageKey, 1) : storage.removeItem(storageKey); + this.blur(); + return false; + }); + + if (!storage || storage.getItem(storageKey)) { + $toggle.trigger('click'); + } +}; + +/** + * @} End of "ingroup admin_behaviors". + */ + +})(jQuery); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/admin_menu_toolbar/admin_menu_toolbar.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,118 @@ + TRUE); + + $attached['css'][$path . '/admin_menu_toolbar.css'] = $options; + $attached['js'][$path . '/admin_menu_toolbar.js'] = $options; + + // @todo Stop-gap fix until cached rendering is resolved. + // @see http://drupal.org/node/1567622 + if (module_exists('shortcut')) { + $attached['css'][drupal_get_path('module', 'shortcut') . '/shortcut.css'] = $options; + } + + $settings = array(); + // Add current path to support menu item highlighting. + // @todo Compile real active trail here? + $args = explode('/', $_GET['q']); + if ($args[0] == 'admin' && !empty($args[1])) { + $settings['activeTrail'] = url($args[0] . '/' . $args[1]); + } + elseif (drupal_is_front_page()) { + $settings['activeTrail'] = url(''); + } + + $attached['js'][] = array( + 'data' => array('admin_menu' => array('toolbar' => $settings)), + 'type' => 'setting', + ); +} + +/** + * Implements hook_admin_menu_output_build(). + */ +function admin_menu_toolbar_admin_menu_output_build(&$content) { + if (empty($content['#components']['shortcut.links']) && !$content['#complete']) { + return; + } + // Add shortcuts toggle. + $content['shortcut-toggle'] = array( + '#access' => module_exists('shortcut'), + '#weight' => -200, + '#type' => 'link', + '#title' => t('Show shortcuts'), + '#href' => '', + '#options' => array( + 'attributes' => array('class' => 'shortcut-toggle'), + ), + ); + + // Add shortcuts bar. + $content['shortcut'] = array( + '#access' => module_exists('shortcut'), + '#weight' => 200, + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + $content['shortcut']['shortcuts'] = array( + // Shortcut module's CSS relies on Toolbar module's markup. + // @see http://drupal.org/node/1217038 + '#prefix' => '
    ', + '#suffix' => '
    ', + // @todo Links may contain .active-trail classes. + '#pre_render' => array('shortcut_toolbar_pre_render'), + ); +} + +/** + * Implements hook_admin_menu_output_alter(). + */ +function admin_menu_toolbar_admin_menu_output_alter(&$content) { + // Add a class to top-level items for styling. + if (isset($content['menu'])) { + foreach (element_children($content['menu']) as $link) { + $content['menu'][$link]['#attributes']['class'][] = 'admin-menu-toolbar-category'; + } + } + + // Alter icon. + if (isset($content['icon'])) { + unset($content['icon']['icon']['#theme']); + $content['icon']['icon']['#title'] = '' . t('Home') . ''; + $content['icon']['icon']['#attributes']['class'][] = 'admin-menu-toolbar-category'; + } + + // Alter user account link. + if (isset($content['account'])) { + $content['account']['account']['#title'] = t('Hello @username', array('@username' => $content['account']['account']['#title'])); + $content['account']['account']['#options']['html'] = TRUE; + } +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/admin_menu_toolbar/toolbar.png Binary file sites/all/modules/admin_menu/admin_menu_toolbar/toolbar.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/images/arrow-rtl.png Binary file sites/all/modules/admin_menu/images/arrow-rtl.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/images/arrow.png Binary file sites/all/modules/admin_menu/images/arrow.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/images/bkg-red.png Binary file sites/all/modules/admin_menu/images/bkg-red.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/images/bkg.png Binary file sites/all/modules/admin_menu/images/bkg.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/images/bkg_tab.png Binary file sites/all/modules/admin_menu/images/bkg_tab.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/images/icon_users.png Binary file sites/all/modules/admin_menu/images/icon_users.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/admin_menu/tests/admin_menu.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/admin_menu/tests/admin_menu.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,520 @@ + 'access administration pages', + 'admin_menu' => 'access administration menu', + ); + + function setUp() { + // Enable admin menu module and any other modules. + $modules = func_get_args(); + $modules = isset($modules[0]) ? $modules[0] : $modules; + $modules[] = 'admin_menu'; + parent::setUp($modules); + + // Disable client-side caching. + variable_set('admin_menu_cache_client', FALSE); + // Disable Clean URLs to ensure drupal.org testbot compatibility. + variable_set('clean_url', 0); + } + + /** + * Check that an element exists in HTML markup. + * + * @param $xpath + * An XPath expression. + * @param array $arguments + * (optional) An associative array of XPath replacement tokens to pass to + * DrupalWebTestCase::buildXPathQuery(). + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * + * @return + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertElementByXPath($xpath, array $arguments = array(), $message, $group = 'Other') { + $elements = $this->xpath($xpath, $arguments); + return $this->assertTrue(!empty($elements[0]), $message, $group); + } + + /** + * Check that an element does not exist in HTML markup. + * + * @param $xpath + * An XPath expression. + * @param array $arguments + * (optional) An associative array of XPath replacement tokens to pass to + * DrupalWebTestCase::buildXPathQuery(). + * @param $message + * The message to display along with the assertion. + * @param $group + * The type of assertion - examples are "Browser", "PHP". + * + * @return + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertNoElementByXPath($xpath, array $arguments = array(), $message, $group = 'Other') { + $elements = $this->xpath($xpath, $arguments); + return $this->assertTrue(empty($elements), $message, $group); + } + + /** + * Asserts that links appear in the menu in a specified trail. + * + * @param array $trail + * A list of menu link titles to assert in the menu. + */ + protected function assertLinkTrailByTitle(array $trail) { + $xpath = array(); + $args = array(); + $message = ''; + foreach ($trail as $i => $title) { + $xpath[] = '/li/a[text()=:title' . $i . ']'; + $args[':title' . $i] = $title; + $message .= ($i ? ' » ' : '') . check_plain($title); + } + $xpath = '//div[@id="admin-menu"]/div/ul' . implode('/parent::li/ul', $xpath); + $this->assertElementByXPath($xpath, $args, $message . ' link found.'); + } + + /** + * Asserts that no link appears in the menu for a specified trail. + * + * @param array $trail + * A list of menu link titles to assert in the menu. + */ + protected function assertNoLinkTrailByTitle(array $trail) { + $xpath = array(); + $args = array(); + $message = ''; + foreach ($trail as $i => $title) { + $xpath[] = '/li/a[text()=:title' . $i . ']'; + $args[':title' . $i] = $title; + $message .= ($i ? ' » ' : '') . check_plain($title); + } + $xpath = '//div[@id="admin-menu"]/div/ul' . implode('/parent::li/ul', $xpath); + $this->assertNoElementByXPath($xpath, $args, $message . ' link not found.'); + } +} + +/** + * Tests menu links depending on user permissions. + */ +class AdminMenuPermissionsTestCase extends AdminMenuWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Menu link access permissions', + 'description' => 'Tests appearance of menu links depending on user permissions.', + 'group' => 'Administration menu', + ); + } + + function setUp() { + parent::setUp(array('node')); + } + + /** + * Test that the links are added to the page (no JS testing). + */ + function testPermissions() { + module_enable(array('contact')); + $this->resetAll(); + + // Anonymous users should not see the menu. + $this->drupalGet(''); + $this->assertNoElementByXPath('//div[@id="admin-menu"]', array(), t('Administration menu not found.')); + + // Create a user who + // - can access content overview + // - cannot access drupal.org links + // - cannot administer Contact module + $permissions = $this->basePermissions + array( + 'access content overview', + ); + $admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user); + + // Check that the user can see the admin links, but not the drupal links. + $this->assertElementByXPath('//div[@id="admin-menu"]', array(), 'Administration menu found.'); + $this->assertElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path)]', array(':path' => 'admin/content'), 'Content link found.'); + $this->assertNoElementByXPath('//div[@id="admin-menu"]//a[@href=:path]', array(':path' => 'http://drupal.org'), 'Icon » Drupal.org link not found.'); + $this->assertNoElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path)]', array(':path' => 'admin/structure/contact'), 'Structure » Contact link not found.'); + + // Create a user "reversed" to the above; i.e., who + // - cannot access content overview + // - can access drupal.org links + // - can administer Contact module + $permissions = $this->basePermissions + array( + 'display drupal links', + 'administer contact forms', + ); + $admin_user2 = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user2); + $this->assertElementByXPath('//div[@id="admin-menu"]', array(), 'Administration menu found.'); + $this->assertNoElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path)]', array(':path' => 'admin/content'), 'Content link not found.'); + $this->assertElementByXPath('//div[@id="admin-menu"]//a[@href=:path]', array(':path' => 'http://drupal.org'), 'Icon » Drupal.org link found.'); + $this->assertElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path)]', array(':path' => 'admin/structure/contact'), 'Structure » Contact link found.'); + } + + /** + * Tests handling of links pointing to category/overview pages. + */ + function testCategories() { + // Create a user with minimum permissions. + $admin_user = $this->drupalCreateUser($this->basePermissions); + $this->drupalLogin($admin_user); + + // Verify that no category links appear. + $this->assertNoLinkTrailByTitle(array(t('Structure'))); + $this->assertNoLinkTrailByTitle(array(t('Configuration'))); + + // Create a user with access to one configuration category. + $permissions = $this->basePermissions + array( + 'administer users', + ); + $admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user); + + // Verify that only expected category links appear. + $this->assertNoLinkTrailByTitle(array(t('Structure'))); + $this->assertLinkTrailByTitle(array(t('People'))); + $this->assertLinkTrailByTitle(array(t('Configuration'))); + $this->assertLinkTrailByTitle(array(t('Configuration'), t('People'))); + // Random picks are sufficient. + $this->assertNoLinkTrailByTitle(array(t('Configuration'), t('Media'))); + $this->assertNoLinkTrailByTitle(array(t('Configuration'), t('System'))); + } + + /** + * Tests that user role and permission changes are properly taken up. + */ + function testPermissionChanges() { + // Create a user who is able to change permissions. + $permissions = $this->basePermissions + array( + 'administer permissions', + ); + $admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($admin_user); + + // Extract the user role ID that was created for above permissions. + $rid = key(array_diff_key($admin_user->roles, array(DRUPAL_AUTHENTICATED_RID => 0))); + + // Verify that Configuration does not appear. + $this->assertNoLinkTrailByTitle(array(t('Configuration'))); + // Grant the 'administer site configuration' permission to ourselves. + $edit = array( + $rid . '[administer site configuration]' => TRUE, + ); + $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); + // Verify that Configuration appears. + $this->assertLinkTrailByTitle(array(t('Configuration'))); + + // Verify that Structure » Content types does not appear. + $this->assertNoLinkTrailByTitle(array(t('Structure'), t('Content types'))); + // Create a new role. + $edit = array( + 'name' => 'test', + ); + $this->drupalPost('admin/people/permissions/roles', $edit, t('Add role')); + // It should be safe to assume that the new role gets the next ID. + $test_rid = $rid + 1; + // Grant the 'administer content types' permission for the role. + $edit = array( + $test_rid . '[administer content types]' => TRUE, + ); + $this->drupalPost('admin/people/permissions/' . $test_rid, $edit, t('Save permissions')); + // Verify that Structure » Content types does not appear. + $this->assertNoLinkTrailByTitle(array(t('Structure'), t('Content types'))); + + // Assign the role to ourselves. + $edit = array( + 'roles[' . $test_rid . ']' => TRUE, + ); + $this->drupalPost('user/' . $admin_user->uid . '/edit', $edit, t('Save')); + // Verify that Structure » Content types appears. + $this->assertLinkTrailByTitle(array(t('Structure'), t('Content types'))); + + // Delete the role. + $this->drupalPost('admin/people/permissions/roles/edit/' . $test_rid, array(), t('Delete role')); + $this->drupalPost(NULL, array(), t('Delete')); + // Verify that Structure » Content types does not appear. + $this->assertNoLinkTrailByTitle(array(t('Structure'), t('Content types'))); + } +} + +/** + * Tests appearance, localization, and escaping of dynamic links. + */ +class AdminMenuDynamicLinksTestCase extends AdminMenuWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Dynamic links', + 'description' => 'Tests appearance, localization, and escaping of dynamic links.', + 'group' => 'Administration menu', + ); + } + + function setUp() { + parent::setUp(array('node')); + } + + /** + * Tests node type links. + */ + function testNode() { + $type = $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + // Create a content-type with special characters. + $type = $this->drupalCreateContentType(array('type' => 'special', 'name' => 'Cool & Special')); + + $permissions = $this->basePermissions + array( + 'administer content types', + 'create article content', + 'create special content', + ); + $this->admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->admin_user); + + // Verify that dynamic links are displayed. + $this->drupalGet(''); + $this->assertElementByXPath('//div[@id="admin-menu"]', array(), t('Administration menu found.')); + $this->assertElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path)]', array(':path' => 'admin/structure/types'), "Structure » Content types link found."); + + // Verify link title output escaping. + $this->assertNoRaw('Cool & Special'); + $this->assertRaw(check_plain('Cool & Special')); + + // Verify Manage content type links. + $links = array( + 'admin/structure/types/manage/article' => 'Article', + 'admin/structure/types/manage/special' => 'Cool & Special', + ); + foreach ($links as $path => $title) { + $this->assertElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path) and text()=:title]', array( + ':path' => $path, + ':title' => $title, + ), "Structure » Content types » $title link found."); + } + + // Verify Add content links. + $links = array( + 'node/add/article' => 'Article', + 'node/add/special' => 'Cool & Special', + ); + foreach ($links as $path => $title) { + $this->assertElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path) and text()=:title]', array( + ':path' => $path, + ':title' => $title, + ), "Add content » $title link found."); + } + } + + /** + * Tests Add content links. + */ + function testNodeAdd() { + $type = $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + + // Verify that "Add content" does not appear for unprivileged users. + $permissions = $this->basePermissions + array( + 'access content', + ); + $this->web_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->web_user); + $this->assertNoText(t('Add content')); + + // Verify "Add content" appears below "Content" for administrative users. + $permissions = $this->basePermissions + array( + 'access content overview', + 'access content', + 'create article content', + ); + $this->admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->admin_user); + $this->assertLinkTrailByTitle(array( + t('Content'), + t('Add content'), + )); + + // Verify "Add content" appears on the top-level for regular users. + $permissions = $this->basePermissions + array( + 'access content', + 'create article content', + ); + $this->web_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->web_user); + $this->assertLinkTrailByTitle(array( + t('Add content'), + )); + } +} + +/** + * Tests appearance of different types of links. + */ +class AdminMenuLinkTypesTestCase extends AdminMenuWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Link types', + 'description' => 'Tests appearance of different types of links.', + 'group' => 'Administration menu', + ); + } + + function setUp() { + parent::setUp(array('help')); + + $permissions = module_invoke_all('permission'); + $permissions = array_keys($permissions); + $this->admin_user = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->admin_user); + } + + /** + * Tests appearance of different router item link types. + */ + function testLinkTypes() { + // Verify that MENU_NORMAL_ITEMs appear. + $this->assertLinkTrailByTitle(array( + t('Configuration'), + t('System'), + t('Site information'), + )); + + // Verify that MENU_LOCAL_TASKs appear. + $this->assertLinkTrailByTitle(array(t('People'), t('Permissions'))); + $this->assertLinkTrailByTitle(array(t('Appearance'), t('Settings'))); + $this->assertLinkTrailByTitle(array(t('Modules'), t('Uninstall'))); + + // Verify that MENU_LOCAL_ACTIONs appear. + $this->assertLinkTrailByTitle(array( + t('People'), + t('Add user'), + )); + + // Verify that MENU_DEFAULT_LOCAL_TASKs do NOT appear. + $this->assertNoLinkTrailByTitle(array(t('Modules'), t('List'))); + $this->assertNoLinkTrailByTitle(array(t('People'), t('List'))); + $this->assertNoLinkTrailByTitle(array(t('People'), t('Permissions'), t('Permissions'))); + $this->assertNoLinkTrailByTitle(array(t('Appearance'), t('List'))); + + // Verify that MENU_VISIBLE_IN_BREADCRUMB items (exact type) do NOT appear. + $this->assertNoLinkTrailByTitle(array(t('Modules'), t('Uninstall'), t('Uninstall'))); + $this->assertNoLinkTrailByTitle(array(t('Help'), 'admin_menu')); + + // Verify that special "Index" link appears below icon. + $this->assertElementByXPath('//div[@id="admin-menu"]//a[contains(@href, :path) and text()=:title]', array( + ':path' => 'admin/index', + ':title' => t('Index'), + ), "Icon » Index link found."); + } +} + +/** + * Tests customized menu links. + */ +class AdminMenuCustomizedTestCase extends AdminMenuWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Customized links', + 'description' => 'Tests customized menu links.', + 'group' => 'Administration menu', + ); + } + + function setUp() { + parent::setUp(array('menu')); + + $this->admin_user = $this->drupalCreateUser($this->basePermissions + array( + 'administer menu', + )); + $this->drupalLogin($this->admin_user); + } + + /** + * Test disabled custom links. + */ + function testCustomDisabled() { + $type = $this->drupalCreateContentType(); + $node = $this->drupalCreateNode(array('type' => $type->type)); + $text = $this->randomName(); + $xpath = $this->buildXPathQuery('//div[@id=:id]//a[contains(text(), :text)]', array( + ':id' => 'admin-menu', + ':text' => $text, + )); + + // Verify that the link does not appear in the menu. + $this->drupalGet('node'); + $elements = $this->xpath($xpath); + $this->assertFalse($elements, 'Custom link not found.'); + + // Add a custom link to the node to the menu. + $edit = array( + 'link_path' => 'node/' . $node->nid, + 'link_title' => $text, + 'parent' => 'management:' . $this->queryMlidByPath('admin'), + ); + $this->drupalPost('admin/structure/menu/manage/management/add', $edit, t('Save')); + + // Verify that the link appears in the menu. + $this->drupalGet('node'); + $elements = $this->xpath($xpath); + $this->assertTrue($elements, 'Custom link found.'); + + // Disable the link. + $edit = array( + 'enabled' => FALSE, + ); + $this->drupalPost('admin/structure/menu/item/' . $this->queryMlidByPath('node/' . $node->nid) . '/edit', $edit, t('Save')); + + // Verify that the disabled link does not appear in the menu. + $this->drupalGet('node'); + $elements = $this->xpath($xpath); + $this->assertFalse($elements, 'Disabled custom link not found.'); + } + + /** + * Tests external links. + */ + function testCustomExternal() { + // Add a custom link to the node to the menu. + $edit = array( + 'link_path' => 'http://example.com', + 'link_title' => 'Example', + 'parent' => 'management:' . $this->queryMlidByPath('admin'), + ); + $this->drupalPost('admin/structure/menu/manage/management/add', $edit, t('Save')); + + // Verify that the link appears in the menu. + $this->drupalGet(''); + $elements = $this->xpath('//div[@id=:id]//a[@href=:href and contains(text(), :text)]', array( + ':id' => 'admin-menu', + ':href' => $edit['link_path'], + ':text' => $edit['link_title'], + )); + $this->assertTrue($elements, 'External link found.'); + } + + /** + * Returns the menu link ID for a given link path in the management menu. + */ + protected function queryMlidByPath($path) { + return db_query('SELECT mlid FROM {menu_links} WHERE menu_name = :menu AND link_path = :path', array( + ':menu' => 'management', + ':path' => $path, + ))->fetchField(); + } +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,2 @@ + +Enhanced autocomplete widget for drupal 7.x \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.api.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,54 @@ + 'a', 2 => + * 'b', 3 => 'c'). + * + * Besides this two, there are three other options, wich autocomplete deluxe + * accepts: + * - #multiple Indicates whether the user may select more than one item. Expects + * TRUE or FALSE, by default it is set to FALSE. + * - #autocomplete_multiple_delimiter If #multiple is TRUE, then you can use + * this option to set a seperator for multiple values. By default a string + * with the follwing content will be used: ', '. + * - #autocomplete_min_length Indicates how many characters must be entered + * until, the suggesion list can be opened. Especially helpfull, when your + * ajax callback returns only valid suggestion for a minimum characters. + * The default is 0. + */ +function somefunction() { + switch ($type) { + case 'list': + $element = array( + '#type' => 'autocomplete_deluxe', + '#autocomplete_options' => $options, + '#multiple' => FALSE, + '#autocomplete_min_length' => 0, + ); + break; + case 'ajax': + $element = array( + '#type' => 'autocomplete_deluxe', + '#autocomplete_deluxe_path' => url('some_uri', array('absolute' => TRUE)), + '#multiple' => TRUE, + '#autocomplete_min_length' => 1, + '#autocomplete_multiple_delimiter' => '|', + ); + break; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,184 @@ +@CHARSET "UTF-8"; + + +ul.ui-autocomplete { + max-height: 240px; + overflow-x: hidden; + overflow-y: scroll; + padding: 3px; +} + +a.autocomplete-deluxe-single:hover { + text-decoration: none; +} + +.ui-autocomplete .ui-state-hover { + background-color: #3875d7; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #3875d7), color-stop(90%, #2a62bc)); + background-image: -webkit-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -moz-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -o-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: -ms-linear-gradient(top, #3875d7 20%, #2a62bc 90%); + background-image: linear-gradient(top, #3875d7 20%, #2a62bc 90%); + color: #fff; + padding: 0; + margin: 0; +} + +.autocomplete-deluxe-container { + display: inline-block; + position: relative; + padding: 0; + border: 1px solid #CCCCCC; + background: no-repeat -38px -22px, -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background: no-repeat -38px -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, linear-gradient(top, #eeeeee 1%, #ffffff 15%); +} +div.autocomplete-deluxe-container input.autocomplete-deluxe-form { + background: #fff no-repeat -38px -22px; + padding: 4px 5px 4px 5px; + border: none; +} + +div.autocomplete-deluxe-container input.autocomplete-deluxe-form-single { + width: 90%; + border: none; + background: no-repeat -38px -22px, -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background: no-repeat -38px -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, linear-gradient(top, #eeeeee 1%, #ffffff 15%); +} + +.autocomplete-deluxe-search { + margin: 0px 0px 4px 0px; + padding: 3px 4px; + position: relative; + white-space: nowrap; + z-index: 1010; +} + +span.autocomplete-deluxe-value-delete { + display: inline-block; + float: right; + background:url('x.gif') no-repeat center; + width: 18px; + width: 18px; + + /* Prevent text selection. */ + -moz-user-select: -moz-none; + -khtml-user-select: none; + -webkit-user-select: none; + -o-user-select: none; + user-select: none; +} + +.autocomplete-deluxe-single-open { + border: 1px solid #aaa; + -webkit-box-shadow: 0 1px 0 #fff inset; + -moz-box-shadow : 0 1px 0 #fff inset; + -o-box-shadow : 0 1px 0 #fff inset; + box-shadow : 0 1px 0 #fff inset; + background-color: #eee; + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff', GradientType=0 ); + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #eeeeee), color-stop(80%, #ffffff)); + background-image: -webkit-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -moz-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -o-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: -ms-linear-gradient(top, #eeeeee 20%, #ffffff 80%); + background-image: linear-gradient(top, #eeeeee 20%, #ffffff 80%); + -webkit-border-bottom-left-radius : 0; + -webkit-border-bottom-right-radius: 0; + -moz-border-radius-bottomleft : 0; + -moz-border-radius-bottomright: 0; + border-bottom-left-radius : 0; + border-bottom-right-radius: 0; +} + +input.autocomplete-deluxe-closed { +} + +div.autocomplete-deluxe-multiple { + display: inline-block; + font-size: 13px; + position: relative; + background-color: #FFFFFF; + background: #fff no-repeat -38px -22px; + background: no-repeat -38px -22px, -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); + background: no-repeat -38px -22px, -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); + background: no-repeat -38px -22px, linear-gradient(top, #eeeeee 1%, #ffffff 15%); + padding: 4px 5px 4px 20px; + border: 1px solid #AAAAAA; + cursor: text; + height: auto !important; + margin: 0; + overflow: hidden; + padding: 5px; + position: relative; + line-height:15pt; + width: 503px; + cursor: text; +} + +input.autocomplete-deluxe-form.autocomplete-deluxe-multiple { + border: 3px none; + width: 25px; + margin-left: 5px; + float: left; +} + +div.autocomplete-deluxe-throbber { + width: 16px; + float: right; + height: 50%; +} + +.autocomplete-deluxe-closed { + background: url(throbber.gif) no-repeat 100% 6px; +} + +.autocomplete-deluxe-open { + background: url(throbber.gif) no-repeat 100% -14px; +} + +.autocomplete-deluxe-item { + float: left; + background-clip: padding-box; + background-color: #E4E4E4; + background-image: -moz-linear-gradient(center top , #F4F4F4 20%, #F0F0F0 50%, #E8E8E8 52%, #EEEEEE 100%); + border: 1px solid #AAAAAA; + border-radius: 3px 3px 3px 3px; + box-shadow: 0 0 2px #FFFFFF inset, 0 1px 0 rgba(0, 0, 0, 0.05); + color: #333333; + cursor: default; + line-height: 13px; + margin: 3px 0 3px 5px; + padding: 3px 20px 3px 5px; + position: relative; +} + +.autocomplete-deluxe-item-delete { + background: url("x.gif"); + display: block; + font-size: 1px; + height: 13px; + position: absolute; + right: 3px; + top: 3px; + width: 12px; +} +.autocomplete-deluxe-item-focus { + background: none repeat scroll 0 0 #D4D4D4; +} + +.autocomplete-deluxe-highlight-char { + color: blue; + font-weight: bold; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Autocomplete Deluxe +description = Enhanced autocomplete using Jquery UI autocomplete. +package = User interface +core = 7.x +files[] = autocomplete_deluxe.module +dependencies[] = taxonomy + +; Information added by drupal.org packaging script on 2013-08-05 +version = "7.x-2.0-beta3" +core = "7.x" +project = "autocomplete_deluxe" +datestamp = "1375695669" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,444 @@ + +/** + * @file: + * Converts textfield to a autocomplete deluxe widget. + */ + +(function($) { + Drupal.autocomplete_deluxe = Drupal.autocomplete_deluxe || {}; + + Drupal.behaviors.autocomplete_deluxe = { + attach: function(context) { + var autocomplete_settings = Drupal.settings.autocomplete_deluxe; + + $('input.autocomplete-deluxe-form').once( function() { + if (autocomplete_settings[$(this).attr('id')].multiple === true) { + new Drupal.autocomplete_deluxe.MultipleWidget(this, autocomplete_settings[$(this).attr('id')]); + } else { + new Drupal.autocomplete_deluxe.SingleWidget(autocomplete_settings[$(this).attr('id')]); + } + }); + } + }; + + /** + * Autogrow plugin which auto resizes the input of the multiple value. + * + * http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields + * + */ + $.fn.autoGrowInput = function(o) { + + o = $.extend({ + maxWidth: 1000, + minWidth: 0, + comfortZone: 70 + }, o); + + this.filter('input:text').each(function(){ + + var minWidth = o.minWidth || $(this).width(), + val = '', + input = $(this), + testSubject = $('').css({ + position: 'absolute', + top: -9999, + left: -9999, + width: 'auto', + fontSize: input.css('fontSize'), + fontFamily: input.css('fontFamily'), + fontWeight: input.css('fontWeight'), + letterSpacing: input.css('letterSpacing'), + whiteSpace: 'nowrap' + }), + check = function() { + + if (val === (val = input.val())) {return;} + + // Enter new content into testSubject + var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(//g, '>'); + testSubject.html(escaped); + + // Calculate new width + whether to change + var testerWidth = testSubject.width(), + newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth, + currentWidth = input.width(), + isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth) + || (newWidth > minWidth && newWidth < o.maxWidth); + + // Animate width + if (isValidWidthChange) { + input.width(newWidth); + } + + }; + + testSubject.insertAfter(input); + + $(this).bind('keyup keydown blur update', check); + + }); + + return this; + }; + + + Drupal.autocomplete_deluxe.empty = {label: '- ' + Drupal.t('None') + ' -', value: "" }; + + /** + * EscapeRegex function from jquery autocomplete, is not included in drupal. + */ + Drupal.autocomplete_deluxe.escapeRegex = function(value) { + return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/gi, "\\$&"); + }; + + /** + * Filter function from jquery autocomplete, is not included in drupal. + */ + Drupal.autocomplete_deluxe.filter = function(array, term) { + var matcher = new RegExp(Drupal.autocomplete_deluxe.escapeRegex(term), "i"); + return $.grep(array, function(value) { + return matcher.test(value.label || value.value || value); + }); + }; + + Drupal.autocomplete_deluxe.Widget = function() { + }; + + Drupal.autocomplete_deluxe.Widget.prototype.uri = null; + + /** + * Allows widgets to filter terms. + * @param term + * A term that should be accepted or not. + * @return {Boolean} + * True if the term should be accepted. + */ + Drupal.autocomplete_deluxe.Widget.prototype.acceptTerm = function(term) { + return true; + }; + + Drupal.autocomplete_deluxe.Widget.prototype.init = function(settings) { + if ($.browser.msie && $.browser.version === "6.0") { + return; + } + + this.id = settings.input_id; + this.jqObject = $('#' + this.id); + + this.uri = settings.uri; + this.multiple = settings.multiple; + this.required = settings.required; + this.limit = settings.limit; + this.synonyms = typeof settings.use_synonyms == 'undefined' ? false : settings.use_synonyms; + + this.wrapper = '""'; + + if (typeof settings.delimiter == 'undefined') { + this.delimiter = true; + } else { + this.delimiter = settings.delimiter.charCodeAt(0); + } + + + this.items = {}; + + var self = this; + var parent = this.jqObject.parent(); + var parents_parent = this.jqObject.parent().parent(); + + parents_parent.append(this.jqObject); + parent.remove(); + parent = parents_parent; + + var generateValues = function(data, term) { + var result = new Array(); + for (var terms in data) { + if (self.acceptTerm(terms)) { + result.push({ + label: data[terms], + value: terms + }); + } + } + if ($.isEmptyObject(result)) { + result.push({ + label: Drupal.t("The term '@term' will be added.", {'@term' : term}), + value: term, + newTerm: true + }); + } + return result; + }; + + var cache = {} + var lastXhr = null; + + this.source = function(request, response) { + var term = request.term; + if (term in cache) { + response(generateValues(cache[term], term)); + return; + } + + // Some server collapse two slashes if the term is empty, so insert at + // least a whitespace. This whitespace will later on be trimmed in the + // autocomplete callback. + if (!term) { + term = " "; + } + request.synonyms = self.synonyms; + var url = settings.uri + '/' + term +'/' + self.limit; + lastXhr = $.getJSON(url, request, function(data, status, xhr) { + cache[term] = data; + if (xhr === lastXhr) { + response(generateValues(data, term)); + } + }); + }; + + this.jqObject.autocomplete({ + 'source' : this.source, + 'minLength': settings.min_length + }); + + var jqObject = this.jqObject; + var throbber = $('
     
    ').insertAfter(jqObject); + + this.jqObject.bind("autocompletesearch", function(event, ui) { + throbber.removeClass('autocomplete-deluxe-closed'); + throbber.addClass('autocomplete-deluxe-open'); + }); + + this.jqObject.bind("autocompleteopen", function(event, ui) { + throbber.addClass('autocomplete-deluxe-closed'); + throbber.removeClass('autocomplete-deluxe-open'); + }); + + // Monkey patch the _renderItem function jquery so we can highlight the + // text, that we already entered. + $.ui.autocomplete.prototype._renderItem = function( ul, item) { + var t = item.label; + if (this.term != "") { + var escapedValue = Drupal.autocomplete_deluxe.escapeRegex( this.term ); + var re = new RegExp('()*""' + escapedValue + '""|' + escapedValue + '()*', 'gi'); + var t = item.label.replace(re,"$&"); + } + return $( "
  • " ) + .data( "item.autocomplete", item ) + .append( "" + t + "" ) + .appendTo( ul ); + }; + }; + + Drupal.autocomplete_deluxe.Widget.prototype.generateValues = function(data) { + var result = new Array(); + for (var index in data) { + result.push(data[index]); + } + return result; + }; + + /** + * Generates a single selecting widget. + */ + Drupal.autocomplete_deluxe.SingleWidget = function(settings) { + this.init(settings); + this.setup(); + this.jqObject.addClass('autocomplete-deluxe-form-single'); + }; + + Drupal.autocomplete_deluxe.SingleWidget.prototype = new Drupal.autocomplete_deluxe.Widget(); + + Drupal.autocomplete_deluxe.SingleWidget.prototype.setup = function() { + var jqObject = this.jqObject; + var parent = jqObject.parent(); + + parent.mousedown(function() { + if (parent.hasClass('autocomplete-deluxe-single-open')) { + jqObject.autocomplete('close'); + } else { + jqObject.autocomplete('search', ''); + } + }); + }; + + /** + * Creates a multiple selecting widget. + */ + Drupal.autocomplete_deluxe.MultipleWidget = function(input, settings) { + this.init(settings); + this.setup(); + }; + + Drupal.autocomplete_deluxe.MultipleWidget.prototype = new Drupal.autocomplete_deluxe.Widget(); + Drupal.autocomplete_deluxe.MultipleWidget.prototype.items = new Object(); + + + Drupal.autocomplete_deluxe.MultipleWidget.prototype.acceptTerm = function(term) { + // Accept only terms, that are not in our items list. + return !(term in this.items); + }; + + Drupal.autocomplete_deluxe.MultipleWidget.Item = function (widget, item) { + if (item.newTerm === true) { + item.label = item.value; + } + + this.value = item.value; + this.element = $('' + item.label + ''); + this.widget = widget; + this.item = item; + var self = this; + + var close = $('').appendTo(this.element); + // Use single quotes because of the double quote encoded stuff. + var input = $('').appendTo(this.element); + + close.mousedown(function() { + self.remove(item); + }); + }; + + Drupal.autocomplete_deluxe.MultipleWidget.Item.prototype.remove = function() { + this.element.remove(); + var values = this.widget.valueForm.val(); + var escapedValue = Drupal.autocomplete_deluxe.escapeRegex( this.item.value ); + var regex = new RegExp('()*""' + escapedValue + '""|' + escapedValue + '()*', 'gi'); + this.widget.valueForm.val(values.replace(regex, '')); + delete this.widget.items[this.value]; + }; + + Drupal.autocomplete_deluxe.MultipleWidget.prototype.setup = function() { + var jqObject = this.jqObject; + var parent = jqObject.parent(); + var value_container = jqObject.parent().parent().children('.autocomplete-deluxe-value-container'); + var value_input = value_container.children().children(); + var items = this.items; + var self = this; + this.valueForm = value_input; + + // Override the resize function, so that the suggestion list doesn't resizes + // all the time. + jqObject.data("autocomplete")._resizeMenu = function() {}; + + jqObject.show(); + + value_container.hide(); + + // Add the default values to the box. + var default_values = value_input.val(); + default_values = $.trim(default_values); + default_values = default_values.substr(2, default_values.length-4); + default_values = default_values.split('"" ""'); + + for (var index in default_values) { + var value = default_values[index]; + if (value != '') { + // If a terms is encoded in double quotes, then the label should have + // no double quotes. + var label = value.match(/["][\w|\s|\D|]*["]/gi) !== null ? value.substr(1, value.length-2) : value; + var item = { + label : label, + value : value + }; + var item = new Drupal.autocomplete_deluxe.MultipleWidget.Item(self, item); + item.element.insertBefore(jqObject); + items[item.value] = item; + } + } + + jqObject.addClass('autocomplete-deluxe-multiple'); + parent.addClass('autocomplete-deluxe-multiple'); + + + // Adds a value to the list. + this.addValue = function(ui_item) { + var item = new Drupal.autocomplete_deluxe.MultipleWidget.Item(self, ui_item); + item.element.insertBefore(jqObject); + items[ui_item.value] = item; + var new_value = ' ' + self.wrapper + ui_item.value + self.wrapper; + var values = value_input.val(); + value_input.val(values + new_value); + jqObject.val(''); + }; + + parent.mouseup(function() { + jqObject.autocomplete('search', ''); + jqObject.focus(); + }); + + jqObject.bind("autocompleteselect", function(event, ui) { + self.addValue(ui.item); + jqObject.width(25); + // Return false to prevent setting the last term as value for the jqObject. + return false; + }); + + jqObject.bind("autocompletechange", function(event, ui) { + jqObject.val(''); + }); + + jqObject.blur(function() { + var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last(); + last_element.removeClass('autocomplete-deluxe-item-focus'); + }); + + var clear = false; + + jqObject.keypress(function (event) { + var value = jqObject.val(); + // If a comma was entered and there is none or more then one comma,or the + // enter key was entered, then enter the new term. + if ((event.which == self.delimiter && (value.split('"').length - 1) != 1) || (event.which == 13 && jqObject.val() != "")) { + value = value.substr(0, value.length); + if (typeof self.items[value] == 'undefined' && value != '') { + var ui_item = { + label: value, + value: value + }; + self.addValue(ui_item); + } + clear = true; + if (event.which == 13) { + return false; + } + } + + // If the Backspace key was hit and the input is empty + if (event.which == 8 && value == '') { + var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last(); + // then mark the last item for deletion or deleted it if already marked. + if (last_element.hasClass('autocomplete-deluxe-item-focus')) { + var value = last_element.children('input').val(); + self.items[value].remove(self.items[value]); + jqObject.autocomplete('search', ''); + } else { + last_element.addClass('autocomplete-deluxe-item-focus'); + } + } else { + // Remove the focus class if any other key was hit. + var last_element = jqObject.parent().children('.autocomplete-deluxe-item').last(); + last_element.removeClass('autocomplete-deluxe-item-focus'); + } + }); + + jqObject.autoGrowInput({ + comfortZone: 50, + minWidth: 10, + maxWidth: 460 + }); + + + jqObject.keyup(function (event) { + if (clear) { + // Trigger the search, so it display the values for an empty string. + jqObject.autocomplete('search', ''); + jqObject.val(''); + clear = false; + // Return false to prevent entering the last character. + return false; + } + }); + }; +})(jQuery); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,392 @@ + array( + 'label' => t('Autocomplete Deluxe'), + 'field types' => array('taxonomy_term_reference'), + 'settings' => array( + 'size' => 60, + 'autocomplete_deluxe_path' => 'autocomplete_deluxe/taxonomy', + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ), + ); +} + +/** + * Custom taxonomy callback, which also accepts an empty string search. + */ +function taxonomy_autocomplete_deluxe($field_name, $tags_typed = '', $limit = 10) { + $field = field_info_field($field_name); + $use_synonyms = !empty($_GET['synonyms']); + + // The user enters a comma-separated list of tags. We only autocomplete the last tag. + $tags_typed = drupal_explode_tags($tags_typed); + $tag_last = drupal_strtolower(array_pop($tags_typed)); + + $matches = array(); + + // Part of the criteria for the query come from the field's own settings. + $vids = array(); + $vocabularies = taxonomy_vocabulary_get_names(); + foreach ($field['settings']['allowed_values'] as $tree) { + // If the content taxonomy setting content_taxonomy_ignore_in_suggestions + // is set, then the vocabulary is ignored. + if (empty($tree['content_taxonomy_ignore_in_suggestions'])) { + $vids[] = $vocabularies[$tree['vocabulary']]->vid; + } + } + + $query = db_select('taxonomy_term_data', 't'); + $query->addTag('translatable'); + $query->addTag('term_access'); + + if (module_exists('synonyms') && !empty($use_synonyms)) { + $query->leftJoin('field_data_synonyms_synonym', 'fdss', 'fdss.entity_id = t.tid'); + } + + if ($tag_last != '') { + // Do not select already entered terms. + if (!empty($tags_typed)) { + $query->condition('t.name', $tags_typed, 'NOT IN'); + } + // Select rows that match by term name. + $query + ->fields('t', array('tid', 'name')) + ->condition('t.vid', $vids); + + if (module_exists('synonyms') && !empty($use_synonyms)) { + $or = db_or(); + $or->condition('fdss.synonyms_synonym_value', '%' . db_like($tag_last) . '%', 'LIKE'); + $or->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE'); + $query->condition($or); + } + else { + $query->condition('t.name', '%' . db_like($tag_last) . '%', 'LIKE'); + } + + if (isset($limit) && $limit > 0) { + $query->range(0, $limit); + } + + $tags_return = $query->execute() + ->fetchAllKeyed(); + } + else { + $query + ->fields('t', array('tid', 'name')) + ->condition('t.vid', $vids); + + if (isset($limit) && $limit > 0) { + $query->range(0, $limit); + } + + $tags_return = $query->execute() + ->fetchAllKeyed(); + } + + $prefix = count($tags_typed) ? drupal_implode_tags($tags_typed) . ', ' : ''; + + $term_matches = array(); + foreach ($tags_return as $tid => $name) { + $n = $name; + // Term names containing commas or quotes must be wrapped in quotes. + if (strpos($name, ',') !== FALSE || strpos($name, '"') !== FALSE) { + $n = '"' . str_replace('"', '""', $name) . '"'; + } + $term_matches[$prefix . $n] = check_plain($name); + } + + drupal_json_output($term_matches); +} + +/** + * Returns all allowed terms for a field without any prefix. + */ +function autocomplete_deluxe_allowed_terms($field) { + $options = array(); + foreach ($field['settings']['allowed_values'] as $tree) { + if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) { + if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) { + foreach ($terms as $term) { + $options[$term->name] = $term->name; + } + } + } + } + return $options; +} + +/** + * Implements hook_field_widget_settings_form(). + */ +function autocomplete_deluxe_field_widget_settings_form($field, $instance) { + $widget = $instance['widget']; + $settings = $widget['settings']; + + $form['size'] = array( + '#type' => 'textfield', + '#title' => t('Size of textfield'), + '#default_value' => isset($settings['size']) ? $settings['size'] : 6, + '#element_validate' => array('_element_validate_integer_positive'), + '#required' => TRUE, + ); + $form['limit'] = array( + '#type' => 'textfield', + '#title' => t('Limit of the output.'), + '#description' => t('If set to zero no limit will be used'), + '#default_value' => isset($settings['limit']) ? $settings['limit'] : 10, + '#element_validate' => array('_element_validate_integer'), + ); + $form['min_length'] = array( + '#type' => 'textfield', + '#title' => t('Mininum length.'), + '#description' => t('The minimum length of characters to enter to open the suggestion list.'), + '#default_value' => isset($settings['min_length']) ? $settings['min_length'] : 0, + '#element_validate' => array('_element_validate_integer'), + ); + $form['delimiter'] = array( + '#type' => 'textfield', + '#title' => t('Delimiter.'), + '#description' => t('A character which should be used beside the enter key, to seperate terms.'), + '#default_value' => isset($settings['delimiter']) ? $settings['delimiter'] : '', + '#size' => 1, + ); + + if (module_exists('synonyms')) { + $form['use_synonyms'] = array( + '#type' => 'checkbox', + '#title' => t('Allow synonyms'), + '#description' => t('Should users be able to search for synonyms of terms?'), + '#default_value' => isset($settings['use_synonyms']) ? $settings['use_synonyms'] : FALSE, + ); + } + return $form; +} + +/** + * Implodes the tags from the taxonomy module. + * + * This function is essentially the same as axonomy_implode_tags, with the + * difference, that it uses only a comma instead of a comma and a space to + * implode the tags. It will help keep problems with delimiters to a minimum. + */ +function autocomplete_deluxe_taxonomy_implode_tags($tags, $vid = NULL) { + $typed_tags = array(); + foreach ($tags as $tag) { + // Extract terms belonging to the vocabulary in question. + if (!isset($vid) || $tag->vid == $vid) { + // Make sure we have a completed loaded taxonomy term. + if (isset($tag->name)) { + // Commas and quotes in tag names are special cases, so encode 'em. + if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) { + $tag->name = '"' . str_replace('"', '""', $tag->name) . '"'; + } + + $typed_tags[] = $tag->name; + } + } + } + return implode(',', $typed_tags); +} + +/** + * Implements hook_field_widget_error(). + */ +function autocomplete_deluxe_field_widget_error($element, $error, $form, &$form_state) { + form_error($element, $error['message']); +} + +/** + * Implements hook_field_widget_form(). + */ +function autocomplete_deluxe_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + $element += array( + '#type' => 'autocomplete_deluxe', + '#size' => $instance['widget']['settings']['size'], + '#limit' => isset($instance['widget']['settings']['limit']) ? $instance['widget']['settings']['limit'] : 10, + '#min_length' => isset($instance['widget']['settings']['min_length']) ? $instance['widget']['settings']['min_length'] : 0, + '#use_synonyms' =>isset($instance['widget']['settings']['use_synonyms']) ? $instance['widget']['settings']['use_synonyms'] : 0, + '#delimiter' =>isset($instance['widget']['settings']['delimiter']) ? $instance['widget']['settings']['delimiter'] : '', + ); + + $multiple = $field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED ? TRUE : FALSE; + + $tags = array(); + foreach ($items as $item) { + $tags[$item['tid']] = isset($item['taxonomy_term']) ? $item['taxonomy_term'] : taxonomy_term_load($item['tid']); + } + + $element['#element_validate'] = array('taxonomy_autocomplete_validate'); + $element += array( + '#multiple' => $multiple, + '#autocomplete_deluxe_path' => url($instance['widget']['settings']['autocomplete_deluxe_path'] . '/' . $field['field_name'], array('absolute' => TRUE)), + '#default_value' => autocomplete_deluxe_taxonomy_implode_tags($tags), + ); + + return $element; +} + +/** + * Generates the basic form elements and javascript settings. + */ +function autocomplete_deluxe_element_process($element) { + $element['#attached'] = array( + 'library' => array(array('system', 'ui.autocomplete'), array('system', 'ui.button')), + 'js' => array(drupal_get_path('module', 'autocomplete_deluxe') . '/autocomplete_deluxe.js'), + 'css' => array(drupal_get_path('module', 'autocomplete_deluxe') . '/autocomplete_deluxe.css'), + ); + // Workaround for problems with jquery css in seven theme. + if ($GLOBALS['theme'] == 'seven') { + $element['#attached']['css'][] = drupal_get_path('module', 'autocomplete_deluxe') . '/autocomplete_deluxe.seven.css'; + } + + $html_id = drupal_html_id('autocomplete-deluxe-input'); + + $element['#after_build'][] = 'autocomplete_deluxe_after_build'; + + // Set default options for multiple values. + $element['#multiple'] = isset($element['#multiple']) ? $element['#multiple'] : FALSE; + + $element['textfield'] = array( + '#type' => 'textfield', + '#size' => isset($element['#size']) ? $element['#size'] : '', + '#attributes' => array('class' => array('autocomplete-deluxe-form'), 'id' => array($html_id)), + '#default_value' => '', + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + + $js_settings['autocomplete_deluxe'][$html_id] = array( + 'input_id' => $html_id, + 'multiple' => $element['#multiple'], + 'required' => $element['#required'], + 'limit' => isset($element['#limit']) ? $element['#limit'] : 10, + 'min_length' => isset($element['#min_length']) ? $element['#min_length'] : 0, + 'use_synonyms' => isset($element['#use_synonyms']) ? $element['#use_synonyms'] : 0, + 'delimiter' => isset($element['#delimiter']) ? $element['#delimiter'] : '', + ); + + if (isset($element['#autocomplete_deluxe_path'])) { + if (isset($element['#default_value'])) { + // Split on the comma only if that comma has zero, or an even number of + // quotes in ahead of it. + // http://stackoverflow.com/questions/1757065/java-splitting-a-comma-separated-string-but-ignoring-commas-in-quotes + $default_value = preg_replace('/,(?=([^\"]*\"[^\"]*\")*[^\"]*$)/i', '"" ""', $element['#default_value']); + $default_value = '""' . $default_value . '""'; + } + else { + $default_value = ''; + } + + if ($element['#multiple']) { + $element['value_field'] = array( + '#type' => 'textfield', + '#attributes' => array('class' => array('autocomplete-deluxe-value-field')), + '#default_value' => $default_value, + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + $element['textfield']['#attributes']['style'] = array('display:none'); + } + else { + $element['textfield']['#default_value'] = isset($element['#default_value']) ? $element['#default_value'] : ''; + } + + $js_settings['autocomplete_deluxe'][$html_id] += array( + 'type' => 'ajax', + 'uri' => $element['#autocomplete_deluxe_path'], + ); + } + else { + // If there is no source (path or data), we don't want to add the js + // settings and so the functions will be abborted. + return $element; + } + $element['#attached']['js'][] = array('data' => $js_settings, 'type' => 'setting'); + $element['#tree'] = TRUE; + + return $element; +} + +/** + * Helper function to determine the value for a autocomplete deluxe form + * element. + */ +function autocomplete_deluxe_value(&$element, $input = FALSE, $form_state = NULL) { + // This runs before child elements are processed, so we cannot calculate the + // value here. But we have to make sure the value is an array, so the form + // API is able to proccess the children to set their values in the array. Thus + // once the form API has finished processing the element, the value is an + // array containing the child element values. Then finally the after build + // callback converts it back to the numeric value and sets that. + return array(); +} + +/** + * FAPI after build callback for the duration parameter type form. + * Fixes up the form value by applying the multiplier. + */ +function autocomplete_deluxe_after_build($element, &$form_state) { + // By default drupal sets the maxlength to 128 if the property isn't + // specified, but since the limit isn't useful in some cases, + // we unset the property. + unset($element['textfield']['#maxlength']); + + // Set the elements value from either the value field or text field input. + $element['#value'] = isset($element['value_field']) ? $element['value_field']['#value'] : $element['textfield']['#value']; + + if (isset($element['value_field'])) { + $element['#value'] = trim($element['#value']); + // Replace all double double quotes and space with a comma. This will allows + // us to keep entries in double quotes. + $element['#value'] = str_replace('"" ""', ',', $element['#value']); + $element['#value'] = str_replace('"" ""', ',', $element['#value']); + // Remove the double quotes at the beginning and the end from the first and + // the last term. + $element['#value'] = substr($element['#value'], 2, strlen($element['#value']) - 4); + + unset($element['value_field']['#maxlength']); + } + + + form_set_value($element, $element['#value'], $form_state); + return $element; +} + +/** + * Implements hook_element_info(). + */ +function autocomplete_deluxe_element_info() { + $types['autocomplete_deluxe'] = array( + '#input' => TRUE, + '#value_callback' => 'autocomplete_deluxe_value', + '#pre_render' => array('form_pre_render_conditional_form_element'), + '#process' => array('autocomplete_deluxe_element_process'), + ); + return $types; +} + +/** + * Implements hook_menu(). + */ +function autocomplete_deluxe_menu() { + $items['autocomplete_deluxe/taxonomy'] = array( + 'title' => 'Autocomplete deluxe taxonomy', + 'page callback' => 'taxonomy_autocomplete_deluxe', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); + return $items; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.seven.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/autocomplete_deluxe/autocomplete_deluxe.seven.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,89 @@ +@CHARSET "UTF-8"; + +/** + * JQuery UI style sheet fix for seven. + */ + +.ui-button { + border: 1px solid #cccccc; + background: #e6e6e6; +} + +.ui-state-hover, +.ui-state-focus { + border: 1px solid #bbbbbb; +} + +.ui-button.ui-state-active { + border: 1px solid #777777; + font-weight: bold; +} + +/** + * Corner radius + */ +.ui-corner-tl { + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; +} + +.ui-corner-tr { + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; +} + +.ui-corner-bl { + -moz-border-radius-bottomleft: 4px; + -webkit-border-bottom-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.ui-corner-br { + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.ui-corner-top { + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +.ui-corner-bottom { + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-bottomright: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +.ui-corner-right { + -moz-border-radius-bottomright: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-bottom-right-radius: 4px; + -webkit-border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; +} + +.ui-corner-left { + -moz-border-radius-bottomleft: 4px; + -moz-border-radius-topleft: 4px; + -webkit-border-bottom-left-radius: 4px; + -webkit-border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; +} + +.ui-corner-all { + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/throbber.gif Binary file sites/all/modules/autocomplete_deluxe/throbber.gif has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/autocomplete_deluxe/x.gif Binary file sites/all/modules/autocomplete_deluxe/x.gif has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,143 @@ + + biblio.module + +Author: Ron Jerome +Released under the GPL + + +Description: +============ +This module extends the node data type with additional fields to manage lists +of scholarly publications. + +It closely follows the EndNote model, thus both importing from and exporting +to Endote are supported. Other formats such as bibtex and RIS are also supported. + +Bibliographic information is displayed in lists with links to detailed +information on each publication. + +The lists can be sorted, filtered and ordered in many different ways. + + + + +Requirements: +============= +Drupal 7.x, Upgrades supported from any Biblio version after 6.x-1.9 + +Installation: +============= +Create a directory called biblio in the sites/all/modules directory, then place all of the +files packaged with this module in that directory. + +This module will auto-install the required database tables the first time you +enable it on the admin/modules page. This will also setup a number of pre-defined +publication types. These types can be changed or deleted on the +admin/config/content/biblio/types page. + +Robots.txt +========== +In order to limit recursive searches by web bots, it is recommended that you add the following +to your robots.txt file. Note: if you change the base url for biblio you will have to make the +corresponding change here. i.e. if you base url for biblio is "publications" then replace "biblio" +with "publications" in the directives below. + +# Biblio +Disallow: /biblio/export/ +Disallow: /biblio?* +Disallow: /biblio?page=*&* +Allow: /biblio?page=* + + +Settings: +========= +A number of settings are available at admin/config/content/biblio. They control how +the author names are displayed, whether export links are added to pages and the +number of entries per page to display. + +The 'admin/config/content/biblio/fields' page allows the the site administrator to set +the default field titles and set which fields are common to all publication +types. When a new publication type is added, it will contain all the common +fields and any that are specifically activated (custom is checked). This also +allows the admin to over ride any of the default settings for any given type. + +Access Control: +=============== +Three permissions are controlable on the admin/access page. I think they are fairly +self evident, they control who can create biblio entries, edit entries and who can +import from file. + +Adding/importing records: +========================= +Bibliographic entries can be added to the database in one of two ways, individualy +from the node/add/biblio link, or by importing records from one of the supported file +formats. Administrators can go to 'admin/config/content/biblio/import' and fill in +the form to upload and import publication data from files. + + +Features: +========= +By default, the /biblio page will list all of the entries in the database sorted +by Year in descending order. If you wish to sort by "Title" or "Type", you may +do so by clicking on the appropriate links at the top of the page. To reverse +the sort order, simply click the link a second time. + + +Filtering Search Results: +========================= +If you wish to filter the results, click on the "Filter" tab at the top of the +page. To add a filter, click the radio button to the left of the filter type +you wish to apply, then select the filter criteria from the drop down list +on the right, then click the filter button. + +It is possible to create complex filters by returning to the "Filter" tab and +adding additional filters. Simply follow the steps outlined above and press +the "Refine" button. + +All filters can be removed by clicking the Clear All Filters link at the top +of the result page, or on the "Filter" tab they can be removed one at a time +using the "Undo" button, or you can remove them all using the "Clear All" button + +You may also construct URLs which filter. For example, /biblio/year/2005 will +show all of the entries for 2005. /biblio/year/2005/author/smith will show all +of entries from 2005 for smith. + + +Exporting Search Results: +========================= +Assuming this option has been enabled by the administrator, you can export +search results directly into EndNote. The link at the top of the result page +will export all of the search results, and the links on individual entries will +export the information related to that single entry. + +Clicking on one of the export links should cause your browser to ask you +whether you want to Open, or Save To Disk, the file endnote.enw. If you choose +to open it, Endnote should start and ask you which library you would like +store the results in. Alternatively, you can save the file to disk and manually +import it into EndNote. + + +The information is exported in either EndNote "Tagged" format similar to this... + + %0 Book + %A John Smith + %D 1959 + %T The Works of John Smith + ... + +Or Endnote 7 XML format which is similar to this... + + + + + 10 + 1959 + The Works of John Smith + + John Smith + + + + + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/biblio.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/biblio.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,338 @@ +#biblio-filter ul { + padding: 1px; + margin: 1px; + width: 100%; +} + +#biblio-buttons { + float: left; + margin-left: 0.5em; + margin-top: 1em; + /* clear: right; */ +} + +.biblio-alpha-line { + text-align: center; +} + +dl.bibliomultiselect dd.b,dl.bibliomultiselect dd.b .form-item,dl.bibliomultiselect dd.b select + { + font-family: inherit; + font-size: inherit; + width: 14em; +} + +dl.bibliomultiselect dd.a,dl.bibliomultiselect dd.a .form-item { + width: 8em; +} + +dl.bibliomultiselect dt,dl.bibliomultiselect dd { + float: left; + line-height: 1.75em; + padding: 0; + margin: 0 1em 0 0; +} + +dl.bibliomultiselect .form-item { + height: 1.75em; + margin: 0; +} +#biblio-authors table, #biblio-tertiary-authors table { + width: 100%; +} +#biblio-authors .form-text { + width: 95%; +} +#biblio-authors td.biblio-contributor, +#biblio-secondary-authors td.biblio-contributor, +#biblio-tertiary-authors td.biblio-contributor, +#biblio-subsidiary-authors td.biblio-contributor, +#biblio-corp-authors td.biblio-contributor{ + width: 98%; +} +#biblio-authors #biblio-tertiary-authors .draggable a.tabledrag-handle { + padding: 0; +} + +.biblio-head { + width: 97%; + color: Black; + font-weight: normal; + background-color: #EAEAEA; + border: medium solid; + border-left-color: #F0F8FF; + border-right-color: Gray; + border-bottom-color: Gray; + border-top-color: #F0F8FF; + padding: 3px; +} + +.biblio-head a:link,.biblio-head a.active,.biblio-head a:visited,.biblio-head a:focus,.biblio-head a:hover + { + color: black; +} + +.biblio-current-filters { + background-color: #ffe1e1; +} + +.biblio-separator-bar { + color: #000000; + font-weight: bold; + background-color: #e1e1e1; + border: 1px solid #ccc; + padding: 0.5em; + margin: 1em 0 1em 0; +} + +.biblio-toolbar { + width: 97%; + color: Red; + font-weight: bold; + background-color: Silver; + border: medium solid; + border-left-color: #F0F8FF; + border-right-color: Gray; + border-bottom-color: Gray; + border-top-color: #F0F8FF; + padding: 3px; +} + +.biblio-entry { + margin: 1em 0 1em 0; +} + +.biblio-style-mla { + text-indent: -25px; + padding-left: 25px; +} + +.biblio-publisher { + font-style: oblique; + font-weight: bold; +} + +.biblio-title { + font-weight: bold; + text-decoration: none; + font-style: normal; + line-height: normal; + text-align: left; + font-family: "@Arial Unicode MS", Arial, sans-serif; + color: #336599; +} + +a:active { + +} + +.biblio-authors a { + font-weight: normal; + text-decoration: none; + font-style: normal; +} + +.biblio_type-1 { + background-color: #F2F2D9; +} + +.biblio_type-2 { + background-color: #D9E6F2; +} + +.biblio_type-3 { + background-color: #E5F2D9; +} + +.biblio_type-4 { + background-color: #D9F2E6; +} + +.biblio_type-5 { + background-color: #F2E6D9; +} + +.biblio_type-6 { + background-color: #D9E6F2; +} + +.biblio_type-7 { + background-color: #D9E6F2; +} + +.biblio_type-8 { + background-color: #D9E6F2; +} + +.biblio_type-9 { + background-color: #D9E6F2; +} + +.biblio-export { + text-align: right; + text-decoration: none; + float: right; +} + +.biblio-abstract-link { + text-align: left; + text-decoration: none; + font-style: normal; + font-weight: normal; + font-size: 75%; +} + +.biblio-export-links { + float: right; + text-align: left; + text-decoration: none; + font-style: normal; + font-weight: normal; + font-size: 75%; + line-height: 100%; +} + +ul.biblio-export-buttons,ul.biblio-export-buttons li { + background: transparent; + list-style-image: none; + list-style-type: none; + display: inline; + border-bottom: 0px; + border-right: 1px; + padding: 0; + margin: 0.1em; +} + +.biblio-annotation { + text-align: left; + text-decoration: none; + margin-left: 2.5em; + margin-top: 0.5em; + margin-right: 2.5em; +} + +.biblio-sort { + text-decoration: none; + text-align: left; +} + +.biblio-openurl-text { + text-align: right; + text-decoration: none; + float: right; +} + +.biblio-left-td { + text-align: right; + vertical-align: top; + width: 20%; +} + +#biblio-header { + display: block; +} +/* +#biblio-header ul.tabs { + border-bottom: 1px solid #BBBBBB; + padding: 0 0 1.765em 1em; +} + +#biblio-header ul li { + padding: 0 0 0 0; + float: left; + display: block; +} + +#biblio-header ul.tabs li a .a { + float: left; + height: 20px; + padding: 2px 0em 3px 0em; + margin: 0px; + padding: 0px; + background-image: url(main-tab1.png); + background-repeat: no-repeat; + background-position: 100% 0px; +} + +#biblio-header ul.tabs li a .a .b { + display: block; + margin: 0px; + height: 20px; + padding: 0px 1em 3px; + background-image: url(main-tab2.png); + background-repeat: no-repeat; + background-position: 0% 0px; +} + +#biblio-header ul.tabs li.active a { + background-color: transparent; + border-width: 0px; +} + +#biblio-header ul.tabs li.active a .a { + background-position: 100% -60px; +} + +#biblio-header ul.tabs li.active a .a .b { + background-position: 0% -60px; +} + +#biblio-header ul.tabs li a:hover { + border-width: 0px; + text-decoration: none !important; +} + +#biblio-header ul.tabs li a:hover .a { + background-position: 100% -30px; +} + +#biblio-header ul.tabs li a:hover .a .b { + background-position: 0% -30px; +} + +#biblio-header ul.tabs li.active a:hover { + background-color: #fff; + border-width: 0px; +} + +#biblio-header ul.tabs li.active a:hover .a { + background-position: 100% -60px; +} + +#biblio-header ul.tabs li.active a:hover .a .b { + background-position: 0% -60px; +} +*/ +.exposed-filters .filters { + float: left; /* LTR */ + margin-right: 1em; /* LTR */ + width: 25em; /* IE6 */ +} +.exposed-filters .form-item { + margin: 0 0 0.1em 0; + padding: 0; +} +.exposed-filters .form-item label { + float: left; /* LTR */ + font-weight: normal; + width: 10em; +} +.exposed-filters .form-select { + width: 14em; +} +/* Current filters */ +.exposed-filters .current-filters { + margin-bottom: 1em; +} +.exposed-filters .current-filters .placeholder { + font-style: normal; + font-weight: bold; +} +.exposed-filters .additional-filters { + float: left; /* LTR */ + margin-right: 1em; /* LTR */ +} +.biblio-highlight { + background-color: #FFF4F4; + border: 2px solid #494949; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/biblio.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/biblio.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,39 @@ +name = Biblio +description = Maintains bibliographic lists. +core = 7.x +dependencies[] = taxonomy +package = Biblio + +configure = admin/config/content/biblio +files[] = tests/biblio.test +files[] = tests/keyword.test +files[] = tests/contributor.test +files[] = tests/import.export.test + +files[] = includes/Name.php +files[] = includes/Parser.php + +files[] = views/biblio_handler_citation.inc +files[] = views/biblio_handler_field_keyword.inc +files[] = views/biblio_handler_field_biblio_keyword_data_word.inc +files[] = views/biblio_handler_field_biblio_keyword_kid.inc +files[] = views/biblio_handler_field_biblio_type.inc +files[] = views/biblio_handler_field_contributor.inc +files[] = views/biblio_handler_field.inc +files[] = views/biblio_handler_field_markup.inc +files[] = views/biblio_handler_filter_biblio_contributor_auth_type.inc +files[] = views/biblio_handler_filter_biblio_keyword_kid.inc +files[] = views/biblio_handler_filter_biblio_keyword_word.inc +files[] = views/biblio_handler_filter_biblio_type.inc +files[] = views/biblio_handler_filter_contributor_lastname.inc +files[] = views/biblio_handler_filter_contributor_uid.inc +files[] = views/biblio_handler_filter_contributor.inc +files[] = views/biblio_handler_sort_contributor_lastname.inc +files[] = views/biblio_handler_argument_many_to_one.inc +files[] = views/biblio_handler_field_export_link.inc +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/biblio.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/biblio.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,2218 @@ + 'biblio', + 'name' => $t('Biblio'), + 'base' => 'biblio', + 'description' => $t('Use Biblio for scholarly content, such as journal papers and books.'), + 'body_label' => $t('Full text') + ); + + // Complete the node type definition by setting any defaults not explicitly + // declared above. + // http://api.drupal.org/api/function/node_type_set_defaults/7 + $content_type = node_type_set_defaults($biblio); + node_add_body_field($content_type); + + // Save the content type + node_type_save($content_type); + + drupal_set_time_limit(300); + + $result[] = _add_db_field_data(); + + $result[] = _add_publication_types(); + + $result[] = _add_custom_field_data(); + + //_enable_biblio_keyword_vocabulary(); + + $result[] = _set_system_weight(); + + + +// if (count($result) == count(array_filter($result))) { +// drupal_set_message(t('The biblio module has successfully added its tables to the database.')); +// } +// else { +// drupal_set_message(t('Drupal encountered some errors while attempting to install the database tables for the biblio module.'), 'error'); +// } +} + +function biblio_enable() { +//TODO taxonomy + //if (module_exists('taxonomy')) _enable_biblio_vocabularies(); + _set_system_weight(); + //_enable_biblio_collection_vocabulary(); + // _add_biblio_keywords(); +} + +function _enable_biblio_vocabularies() { + $vids = variable_get('biblio_vocabularies', array()); + foreach ($vids as $vid ) { + if (($voc = taxonomy_vocabulary_load($vid))) { + $voc = (array) $voc; + $voc['nodes']['biblio'] = 1; + taxonomy_save_vocabulary($voc); + } + } +} + +function biblio_disable() { + $vids = array(); +//TODO taxonomy +/* + if (module_exists('taxonomy')) { + $voc = taxonomy_get_vocabularies(); + foreach ($voc as $vid => $vocabulary) { + if (isset($vocabulary->nodes['biblio'])) { + $vids[] = $vid; + } + } + variable_set('biblio_vocabularies', $vids); + } +*/ +} + +function biblio_uninstall() { + $batch = array( + 'title' => t('Remove all Biblio nodes'), + 'operations' => array( + array('biblio_batch_uninstall', array()), + ), + 'progress_message' => t('Deleteing Biblio nodes...'), + 'finished' => 'biblio_batch_uninstall_finished', + 'file' => drupal_get_path('module', 'biblio') . '/biblio.install' + ); + + batch_set($batch); +} +function biblio_batch_uninstall(&$context) { + + $limit = 5; + if (empty($context['sandbox'])) { + $context['sandbox']['progress'] = 0; + $context['sandbox']['current_node'] = 0; + $context['sandbox']['max'] = db_query("SELECT COUNT(DISTINCT nid) FROM {node} WHERE type='biblio'")->fetchField(); + $context['sandbox']['itters'] = $context['sandbox']['max'] / $limit; + $context['sandbox']['eta'] = 0; + } + // Bail out if the cache is empty. + if ($context['sandbox']['max'] == 0) { + $context['finished'] = 1; + return; + } + + timer_start('biblio_delete'); + + $result = db_select('node') + ->fields('node', array('nid')) + ->condition('nid', $context['sandbox']['current_node'], '>') + ->condition('type', 'biblio') + ->orderBy('nid') + ->range(0, $limit) + ->execute(); + + foreach ($result as $row) { + $node = node_delete($row->nid); + if (function_exists('search_wipe')) { + search_wipe($row->nid, 'node'); + } + $context['sandbox']['progress']++; + $context['sandbox']['current_node'] = $row->nid; + } + + $looptime = timer_stop('biblio_delete'); + $context['sandbox']['eta'] += $looptime['time']; + $itters = $context['sandbox']['progress'] / $limit; + if ($itters) { + $average_time = $context['sandbox']['eta'] / $itters; + $eta = (($context['sandbox']['itters'] * $average_time) - ($average_time * $itters)) / 1000; + if ($eta >= 60) { + $min = (int) $eta / 60; + } + else { + $min = 0; + } + $sec = $eta % 60; + $eta = sprintf("%d:%02d", $min, $sec); + $progress = sprintf("%d / %d", $context['sandbox']['progress'], $context['sandbox']['max'] ); + $context['message'] = t('
    Biblio nodes deleted: %progress
    Time remaining: %eta min.
    ' , array('%progress' => $progress, '%eta' => $eta)); + } + if ($context['sandbox']['progress'] != $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } +} + + +function biblio_batch_uninstall_finished($success, $results, $operations) { + if ($success) { +//TODO taxonomy +/* + if (module_exists('taxonomy')) { + $voc = taxonomy_get_vocabularies(); + foreach ($voc as $vid => $vocabulary) { + if ($vocabulary->module == 'biblio') taxonomy_del_vocabulary($vid); + } + } +*/ + $vars = db_query("SELECT * FROM {variable} WHERE name LIKE 'biblio_%'"); + foreach ($vars as $var) { + variable_del($var->name); + } + } +} + +function biblio_requirements($phase) { + $requirements = array(); + $message = ''; + // Ensure translations don't break at install time + $t = get_t(); + + // Report Drupal version + if ($phase == 'runtime') { + $dir = drupal_get_path('module', 'biblio'); + $files = file_scan_directory($dir, '/..*.inc$/', array('recurse' => FALSE)); + if (count($files) > 1) { + $message = $t('There is a problem with your Biblio installation! There should not be any ".inc" files in the %biblio directory. You probably forgot to delete the old biblio files when you upgraded the module. You should remove the following files from that directory...', array('%biblio' => $dir)); + $message .= "
      "; + foreach ($files as $file) { + $message .= "
    • " . $file->filename; + } + $message .= "
    "; + $requirements['biblio'] = array( + 'title' => $t('Biblio'), + 'value' => BIBLIO_VERSION, + 'severity' => empty($message) ? REQUIREMENT_OK : REQUIREMENT_WARNING, + 'description' => $message, + ); + } + + } + return $requirements; +} + +function _biblio_helper_modules($mode) { + + $modules = _biblio_get_helper_modules(); + switch ($mode) { + case 'install': + module_enable($modules); + break; + case 'uninstall': + foreach ($modules as $module) { + drupal_uninstall_modules($module); + } + break; + } +} + +function _biblio_get_helper_modules() { + return array( + 'biblio_bibtex', + 'biblio_crossref', + 'biblio_marc', + 'biblio_pm', + 'biblio_ris', + 'biblio_tagged', + 'biblio_xml', + 'biblio_citeproc', + 'biblio_rtf', + ); + +} + +function _set_system_weight() { + db_update('system') + ->fields(array( + 'weight' => 9, + )) + ->condition('name', 'biblio') + ->execute(); + return; +} + +function _enable_biblio_keyword_vocabulary() { + +//TODO taxonomy +/* + if ($vocabulary = taxonomy_vocabulary_load(variable_get('biblio_keyword_vocabulary', 0))) { + // Existing install. Add back forum node type, if the biblio + // vocabulary still exists. Keep all other node types intact there. + $vocabulary = (array) $vocabulary; + $vocabulary['nodes']['biblio'] = 1; + taxonomy_save_vocabulary($vocabulary); + } + + + + return $vocabulary['vid']; +*/ +} + +function _enable_biblio_collection_vocabulary() { +//TODO taxonomy +/* + if ($vocabulary = taxonomy_vocabulary_load(variable_get('biblio_collection_vocabulary', 0))) { + // Existing install. Add back forum node type, if the biblio + // vocabulary still exists. Keep all other node types intact there. + $vocabulary = (array) $vocabulary; + $vocabulary['nodes']['biblio'] = 1; + taxonomy_save_vocabulary($vocabulary); + } + else { + // Create the forum vocabulary if it does not exist. Assign the vocabulary + // a low weight so it will appear first in forum topic create and edit + // forms. + $vocabulary = array( + 'name' => 'Biblio Collections', + 'description' => 'You may organize your publications into collections by adding a collection names to this vocabulary', + 'help' => '', + 'nodes' => array('biblio' => 1), + 'hierarchy' => 0, + 'relations' => 1, + 'tags' => 0, + 'multiple' => 1, + 'required' => 0, + 'weight' => 0, + 'module' => 'biblio', + ); + taxonomy_save_vocabulary($vocabulary); + variable_set('biblio_collection_vocabulary', $vocabulary['vid']); + $default_collection = array( + 'name' => t('Default'), + 'description' => t("This is the collection that all biblio entries will be a part of if no other collection is selected. Deleting this term will render all your biblio entries inaccessable. (You've been warned!)" ), + 'parent' => array(), + 'relations' => array(), + 'synonyms' => '', + 'weight' => 0, + 'vid' => variable_get('biblio_collection_vocabulary', 0), + ); + taxonomy_save_term($default_collection); + } + return $vocabulary['vid']; +*/ +} + +/** + * Copies keywords from the biblio_keyword column of the biblio table + * to a taxonomy vocabulary + * + * @return none + */ +function _add_biblio_keywords() { +//TODO taxonomy +/* + set_time_limit(300); + $kw_sep = variable_get('biblio_keyword_sep', ','); + $vid = ($vid = variable_get('biblio_keyword_vocabulary', 0))? $vid:_enable_biblio_keyword_vocabulary(); + if ($vid ) { + $db_result = db_query("SELECT b.biblio_keywords, b.nid, b.vid FROM {biblio} b"); + $result = array(); + foreach ($db_result as $row) { + foreach (explode($kw_sep, $row->biblio_keywords) as $keyword) { + $result[] = array('value' => trim($keyword), 'nid' => $row->nid, 'vid' => $row->vid); + } + db_query('DELETE tn.* FROM {term_node} tn INNER JOIN {term_data} td ON tn.tid = td.tid WHERE nid = %d AND td.vid = %d', $row->nid, $vid); + } + $inserted = array(); + $count = 0; + foreach ($result as $keywords) { + // See if the term exists in the chosen vocabulary + // and return the tid; otherwise, add a new record. + $possibilities = taxonomy_get_term_by_name($keywords['value']); + $term_tid = NULL; // tid match, if any. + foreach ($possibilities as $possibility) { + if ($possibility->vid == $vid) { + $term_tid = $possibility->tid; + } + } + + if (!$term_tid) { + $term = array('vid' => $vid, 'name' => $keywords['value']); + $status = taxonomy_save_term($term); + $term_tid = $term['tid']; + } + + // Defend against duplicate, differently cased tags + if (!isset($inserted[$keywords['vid']][$term_tid])) { + db_query('INSERT INTO {term_node} (nid, vid, tid) VALUES (%d, %d, %d)', $keywords['nid'], $keywords['vid'], $term_tid); + $inserted[$keywords['vid']][$term_tid] = TRUE; + $count++; + } + } + return array('success' => TRUE, 'query' => 'Added ' . $count . ' keywords to the biblio/taxonomy keyword vocabulary'); + } + return array('success' => FALSE, 'query' => 'Biblio keyword vocabulary not available'); +*/ +} + +function biblio_schema() { + $schema['biblio'] = array( + 'fields' => array( + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => '', + ), + 'vid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => '', + ), + 'biblio_type' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => '', + ), + 'biblio_number' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_other_number' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_sort_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '64', + 'description' => 'A normalized version of the title, used for sorting on titles. (only first 64 characters saved)', + ), + 'biblio_secondary_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_tertiary_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_edition' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_publisher' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_place_published' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_year' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 9999, + 'description' => '', + ), + 'biblio_volume' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_pages' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_date' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '64', + 'description' => '', + ), + 'biblio_isbn' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_lang' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '24', + 'default' => 'eng', + 'description' => '', + ), + 'biblio_abst_e' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_abst_f' => array( + 'not null' => FALSE, + 'type' => 'text', + 'description' => '', + ), + 'biblio_full_text' => array( + 'type' => 'int', + 'not null' => FALSE, + 'default' => 0, + 'description' => '', + ), + 'biblio_url' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_issue' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_type_of_work' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_accession_number' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_call_number' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_notes' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_custom1' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_custom2' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_custom3' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_custom4' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_custom5' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_custom6' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_custom7' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_research_notes' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_number_of_volumes' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_short_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_alternate_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_original_publication' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_reprint_edition' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_translated_title' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_section' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_citekey' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_coins' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_doi' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_issn' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '128', + 'description' => '', + ), + 'biblio_auth_address' => array( + 'type' => 'text', + 'not null' => FALSE, + 'description' => '', + ), + 'biblio_remote_db_name' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_remote_db_provider' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_label' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_access_date' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => '', + ), + 'biblio_refereed' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '20', + 'description' => '', + ), + 'biblio_md5' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '32', + 'description' => '', + ), + 'biblio_formats' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'description' => '', + 'serialize' => TRUE, + ), + ), + 'foreign keys' => array( + 'node_revision' => array( + 'table' => 'node_revision', + 'columns' => array('vid' => 'vid'), + ), + 'node' => array( + 'table' => 'node', + 'columns' => array('nid' => 'nid'), + ), + 'biblio_type' => array( + 'table' => 'biblio_types', + 'columns' => array('biblio_type' => 'tid'), + ), + ), + 'primary key' => array( + 'vid' + ), + 'indexes' => array( + 'nid' => array('nid'), + 'md5' => array('biblio_md5'), + 'year' => array('biblio_year'), + 'title_sort' => array('biblio_sort_title'), + 'date' => array('biblio_date') + ), + ); + + $schema['biblio_fields'] = array( + 'fields' => array( + 'fid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{biblio_fields}.fid of the node', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'default' => '' + ), + 'type' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'default' => 'textfield' + ), + 'size' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 60, + ), + 'maxsize' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 255, + ), + ), + 'primary key' => array('fid'), + ); + + $schema['biblio_field_type'] = array( + 'description' => 'Relational table linking {biblio_fields} with {biblio_field_type_data}', + 'fields' => array( + 'tid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{biblio_types}.tid of the node', + ), + 'fid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{biblio_fields}.fid of the node', + ), + 'ftdid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{biblio_field_type_data}.ftdid of the node, points to the current data, default or custom', + ), + 'cust_tdid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'This always points to the custom data for this field. Stored so we can switch back an forth between default and custom', + ), + 'common' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + ), + 'vtab' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + ), + 'autocomplete' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + ), + 'required' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Is input required for this field' + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The weight (location) of the field on the input form' + ), + 'visible' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Determines if the field is visible on the input form' + ), + + ), + 'primary key' => array('tid', 'fid'), + 'indexes' => array( + 'tid' => array('tid') + ), + ); + + $schema['biblio_field_type_data'] = array( + 'description' => 'Data used to build the form elements on the input form', + 'fields' => array( + 'ftdid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{biblio_field_type_data}.ftdid of the node', + ), + 'title' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'default' => '', + 'description' => 'The title, which will be displayed on the form, for a given field' + ), + 'hint' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => FALSE, + 'description' => 'The hint text printed below the input widget' + ), + ), + 'primary key' => array('ftdid'), + ); + + $schema['biblio_types'] = array( + 'fields' => array( + 'tid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => '{biblio_types}.tid of the publication type', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => '64', + 'not null' => TRUE, + 'default' => '', + 'description' => 'The name of the publication type' + ), + 'description' => array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '255', + 'description' => 'Description of the publication type' + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Controls the order the types are listed in' + ), + 'visible' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 1, + 'description' => 'Determines if the publication type is visible in the list' + ), + ), + 'primary key' => array('tid'), + + ); + + + + $schema['biblio_contributor'] = array( + 'description' => 'Relational table linking authors to biblio entries', + 'fields' => array( + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{node}.nid of the node', + ), + 'vid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{node}.vid of the node', + ), + 'cid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '{biblio_contributor_data}.cid of the node', + ), + 'auth_type' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 1, + 'description' => '{biblio_contributor_type}.auth_type of the node', + ), + 'auth_category' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 1, + 'description' => '', + ), + 'rank' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Position of the author name on the publication (first,second,third...)', + ), + 'merge_cid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => '', + ), + ), + 'foreign keys' => array( + 'node_revision' => array( + 'table' => 'node_revision', + 'columns' => array('vid' => 'vid'), + ), + 'node' => array( + 'table' => 'node', + 'columns' => array('nid' => 'nid'), + ), + 'biblio_contributor_data' => array( + 'table' => 'biblio_contributor_data', + 'columns' => array('cid' => 'cid'), + ), + 'biblio_contributor_type' => array( + 'table' => 'biblio_contributor_type', + 'columns' => array('auth_type' => 'auth_type'), + ), + 'biblio_contributor_category' => array( + 'table' => 'biblio_contributor_type', + 'columns' => array('auth_category' => 'auth_category'), + ), + ), + 'primary key' => array( + 'vid', + 'cid', + 'auth_type', + 'rank' + ), + ); + + $schema['biblio_contributor_data'] = array( + 'description' => 'Contains Author information for each publication', + 'fields' => array( + 'cid' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'description' => 'Primary Key: Author ID', + ), + 'aka' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'Also known as, links this author entry with others so you can have variation on the name, but listing by cid will find all other (aka) author entries', + ), + 'alt_form' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'Alternate form of an author name, this value points to the desired form (cid), this form is kept in the database so that future imports of the same name will not create a new author.', + ), + 'drupal_uid' => array( + 'type' => 'int', + 'not null' => FALSE, + 'unsigned' => TRUE, + 'description' => 'Drupal User ID', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'default' => '', + 'description' => 'Full name', + ), + 'lastname' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'default' => '', + 'description' => 'Author last name', + ), + 'firstname' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => FALSE, + 'default' => '', + 'description' => 'Author first name', + ), + 'prefix' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => FALSE, + 'default' => '', + 'description' => 'Author name prefix', + ), + 'suffix' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => FALSE, + 'default' => '', + 'description' => 'Author name suffix', + ), + 'initials' => array( + 'type' => 'varchar', + 'length' => '10', + 'not null' => FALSE, + 'default' => '', + 'description' => 'Author initials (including first name initial)', + ), + 'affiliation' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => FALSE, + 'default' => '', + 'description' => 'Author affiliation or address', + ), + 'literal' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'Determines if the author name is allowed to be reformated by the variaous styles or should be used literally.', + ), + 'md5' => array( + 'type' => 'varchar', + 'length' => '32', + 'not null' => FALSE, + 'description' => '', + ) + ), + 'foreign keys' => array( + 'biblio_contributor' => array( + 'table' => 'biblio_contributor', + 'columns' => array('cid' => 'cid'), + ), + 'node_author' => array( + 'table' => 'users', + 'columns' => array('drupal_uid' => 'uid'), + ), + ), + 'primary key' => array('cid'), + 'indexes' => array( + 'lastname' => array('lastname'), + 'firstname' => array('firstname'), + 'initials' => array('initials') + ) + ); + $schema['biblio_contributor_type'] = array( + 'description' => 'Contains definitions of the contributor types', + 'fields' => array( + 'auth_category' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'There are 5 catagoies of author: Primary, Secondary, Tertiery, Subsidary and Corporate ', + ), + 'biblio_type' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '', + ), + 'auth_type' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'This is the pulication type specific verion of a particular catagory', + ), + ), + 'foreign keys' => array( + 'biblio' => array( + 'table' => 'biblio', + 'columns' => array('biblio_type' => 'biblio_type'), + ), + 'biblio_contributor_type' => array( + 'table' => 'biblio_contributor', + 'columns' => array('auth_type' => 'auth_type'), + ), + 'biblio_contributor_category' => array( + 'table' => 'biblio_contributor', + 'columns' => array('auth_category' => 'auth_category'), + ), + ), + 'primary key' => array('auth_category', 'biblio_type', 'auth_type'), + ); + + $schema['biblio_contributor_type_data'] = array( + 'description' => 'Data used to build the form elements on the input form', + 'fields' => array( + 'auth_type' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'description' => '{biblio_contributor_type_data} ctdid of the node', + ), + 'title' => array( + 'type' => 'varchar', + 'length' => '128', + 'not null' => TRUE, + 'default' => '', + 'description' => 'The title, which will be displayed on the form, for a given field' + ), + 'hint' => array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => FALSE, + 'description' => 'The hint text printed below the input widget' + ), + ), + 'primary key' => array('auth_type'), + ); + + $schema['biblio_keyword'] = array( + 'description' => 'Relational table linking keywords to biblio nodes', + 'fields' => array( + 'kid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Primary Key: The {biblio_keyword_data}.kid of the keyword of the node.', + ), + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'The {node}.nid of the node.', + ), + 'vid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Primary Key: The {node}.vid of the node.', + ), + ), + 'foreign keys' => array( + 'node_revision' => array( + 'table' => 'node_revision', + 'columns' => array('vid' => 'vid'), + ), + 'node' => array( + 'table' => 'node', + 'columns' => array('nid' => 'nid'), + ), + 'keyword' => array( + 'table' => 'biblio_keyword_data', + 'columns' => array('kid' => 'kid'), + ), + ), + 'primary key' => array('kid', 'vid'), + 'indexes' => array( + 'vid' => array('vid'), + 'nid' => array('nid'), + ), + ); + + $schema['biblio_keyword_data'] = array( + 'description' => 'Stores the keywords related to nodes.', + 'fields' => array( + 'kid' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'description' => 'Primary Key: The id of the keyword assigned to the node', + ), + 'word' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The keyword', + ), + ), + 'primary key' => array('kid'), + 'indexes' => array( + 'kword' => array('word'), + ), + ); + $schema['biblio_collection'] = array( + 'description' => 'Relational table grouping biblio nodes into collections', + 'fields' => array( + 'cid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Primary Key: The {biblio_collection_data}.cid of the collection', + ), + 'vid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Primary Key: The {node}.vid of the node.', + ), + 'pid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'The parent id of the collection', + ), + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'The {node}.nid of the node.', + ), + ), + 'primary key' => array('cid', 'vid'), + 'indexes' => array( + 'pid' => array('pid'), + 'nid' => array('nid'), + ), + ); + $schema['biblio_collection_type'] = array( + 'description' => 'Descriptions of the collections.', + 'fields' => array( + 'cid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Primary Key: The id of the collection', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The name of the collection', + ), + 'description' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The description of the collection', + ), + ), + 'primary key' => array('cid'), + 'indexes' => array( + 'name' => array('name'), + ), + ); + $schema['biblio_duplicates'] = array( + 'description' => 'Relational table linking possible duplicate biblio nodes', + 'fields' => array( + 'vid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'Primary Key: The {biblio}.nid of the original node', + ), + 'did' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'The {biblio}.nid of the newly imported node which may be a duplicate.', + ), + 'type' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => 'The type of duplicate 0=biblio, 1=author.', + ), + ), + 'primary key' => array('vid', 'did'), + 'indexes' => array( + 'did' => array('vid'), + ), + ); + + $schema['biblio_import_cache'] = array( + 'description' => 'tables used for caching data imported from file and then batch processed', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'unsigned' => TRUE), + 'session_id' => array( + 'type' => 'varchar', + 'length' => 45, + 'not null' => TRUE), + 'data' => array( + 'description' => 'A collection of data to cache.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big'), + ), + 'primary key' => array('id')); + + $schema['biblio_type_maps'] = array( + 'description' => 'Table used to store the mapping information between various file formats and the biblio schema', + 'fields' => array( + 'format' => array( + 'description' => 'The import/export file format', + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE), + 'type_map' => array( + 'description' => 'The mapping between the publication types in the file format and biblio', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big'), + 'type_names' => array( + 'description' => 'The human readable names of the publication types', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big'), + 'field_map' => array( + 'description' => 'The mapping between the fields in the file format and biblio', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big'), + 'export_map' => array( + 'description' => 'which fields are exported', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big'), + ), + 'primary key' => array('format')); + + $schema['biblio_vtabs'] = array( + 'description' => 'Table used to store the information to create the vertical tabs on the input form', + 'fields' => array( + 'tab_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '' + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + 'description' => '' + ), + 'title' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The title of the tab', + ), + 'description' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The description of the tab', + ), + ), + 'primary key' => array('tab_id')); + + return ($schema); + +} + +function biblio_reset_types() { + $result = array(); + + db_drop_table('biblio_field_type_data'); + db_drop_table('biblio_field_type'); + db_drop_table('biblio_fields'); + db_drop_table('biblio_contributor_type'); + db_drop_table('biblio_contributor_type_data'); + + db_query('DELETE FROM {biblio_types} WHERE tid>999'); + db_query('UPDATE {biblio_types} SET visible=1 WHERE visible=0'); + + $schema = biblio_schema(); + db_create_table('biblio_field_type_data', $schema['biblio_field_type_data']); + db_create_table('biblio_field_type', $schema['biblio_field_type']); + db_create_table('biblio_fields', $schema['biblio_fields']); + db_create_table('biblio_contributor_type', $schema['biblio_contributor_type']); + db_create_table('biblio_contributor_type_data', $schema['biblio_contributor_type_data']); + + variable_set('biblio_last_ftdid', 100); // reset custom field type id too + //_add_db_field_data_XML(); + _add_db_field_data(); + _add_custom_field_data(); +} + +function _biblio_add_vtabs() { + $vtabs = array( + array('tab_id' => 1, 'weight' => 1, 'title' => 'Authors', 'description' => ''), + array('tab_id' => 2, 'weight' => 2, 'title' => 'Publication', 'description' => ''), + array('tab_id' => 3, 'weight' => 3, 'title' => 'Publisher', 'description' => ''), + array('tab_id' => 4, 'weight' => 4, 'title' => 'Identifiers', 'description' => ''), + array('tab_id' => 5, 'weight' => 5, 'title' => 'Locators', 'description' => 'URL\'s etc'), + array('tab_id' => 6, 'weight' => 6, 'title' => 'Keywords', 'description' => ''), + array('tab_id' => 7, 'weight' => 7, 'title' => 'Notes', 'description' => ''), + array('tab_id' => 8, 'weight' => 8, 'title' => 'Alternate Titles', 'description' => ''), + array('tab_id' => 9, 'weight' => 9, 'title' => 'Other', 'description' => ''), + ); + + foreach ($vtabs as $record) { + db_insert('biblio_vtabs')->fields($record)->execute(); + } + + +} + +function _add_publication_types() { + $types[] = array(100, 'Book', NULL, 1); + $types[] = array(101, 'Book Chapter', NULL, 2); + $types[] = array(102, 'Journal Article', NULL, 3); + $types[] = array(131, 'Journal', NULL, 3); + $types[] = array(103, 'Conference Paper', NULL, 4); + $types[] = array(104, 'Conference Proceedings', NULL, 5); + $types[] = array(105, 'Newspaper Article', NULL, 6); + $types[] = array(106, 'Magazine Article', NULL, 7); + $types[] = array(107, 'Web Article', NULL, 8); + $types[] = array(132, 'Website', NULL, 8); + $types[] = array(133, 'Web service', NULL, 8); + $types[] = array(134, 'Web project page', NULL, 8); + $types[] = array(108, 'Thesis', NULL, 9); + $types[] = array(109, 'Report', NULL, 10); + $types[] = array(110, 'Film', NULL, 11); + $types[] = array(111, 'Broadcast', NULL, 12); + $types[] = array(112, 'Artwork', NULL, 13); + $types[] = array(113, 'Software', NULL, 14); + $types[] = array(114, 'Audiovisual', NULL, 15); + $types[] = array(115, 'Hearing', NULL, 16); + $types[] = array(116, 'Case', NULL, 17); + $types[] = array(117, 'Bill', NULL, 18); + $types[] = array(118, 'Statute', NULL, 19); + $types[] = array(119, 'Patent', NULL, 20); + $types[] = array(120, 'Personal', NULL, 21); + $types[] = array(121, 'Manuscript', NULL, 22); + $types[] = array(122, 'Map', NULL, 23); + $types[] = array(123, 'Chart', NULL, 24); + $types[] = array(124, 'Unpublished', NULL, 25); + $types[] = array(125, 'Database', NULL, 26); + $types[] = array(126, 'Government Report', NULL, 27); + $types[] = array(127, 'Classical' , NULL, 28); + $types[] = array(128, 'Legal Ruling', NULL, 29); + $types[] = array(129, 'Miscellaneous', NULL, 30); + $types[] = array(130, 'Miscellaneous Section', NULL, 31); + $types[] = array(135,'Presentation', NULL, 8); + + foreach ($types as $record) { + db_insert('biblio_types')->fields(array( + 'tid' => $record[0], + 'name' => $record[1], + 'description' => $record[2], + 'weight' => $record[3], + ))->execute(); + + } + return; +} + +function _add_db_field_data_XML() { + $next_ctdid=10; //first contributor_type_data id + $schema = biblio_schema(); + $fieldnames = array_keys($schema['biblio_fields']['fields']); + $field_type_fieldnames = array_keys($schema['biblio_field_type']['fields']); + $field_type_data_fieldnames = array_keys($schema['biblio_field_type_data']['fields']); + db_query("/*!40000 ALTER TABLE {biblio_field_type_data} DISABLE KEYS */;"); + db_query("/*!40000 ALTER TABLE {biblio_fields} DISABLE KEYS */;"); + for ($type = 1; $type <= 5; $type++) { + for ($biblio_type = 100; $biblio_type <= 130; $biblio_type++) { + db_query("INSERT INTO {biblio_contributor_type} (auth_category, biblio_type, auth_type) VALUES (%d, %d, %d)", $type, $biblio_type, $type); + } + } + _id_by_name(NULL, NULL, NULL, array('tablename' => 'biblio_field_type_data', 'name_column' => 'title', 'id_column' => 'ftdid')); + + $xml_file = drupal_get_path('module', 'biblio') . '/field_data.xml'; + $xml = simplexml_load_file($xml_file); + foreach ($xml->field as $field) { + $link_data = array(0, $field['fid'], $field['fid'], $field['fid'], $field->common, $field->autocomplete, $field->required, $field->weight, $field->visible); + db_query("INSERT INTO {biblio_field_type} (" . implode(", ", $field_type_fieldnames) . ") + VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d)", $link_data); + for ($t = 100; $t <= 130; $t++) { + $values = array($t, $field['fid'], $field['fid'], $field['fid'], $field->common, $field->autocomplete, $field->required, $field->weight, $field->visible); + db_query("INSERT INTO {biblio_field_type} (" . implode(", ", $field_type_fieldnames) . ") + VALUES('" . implode("', '", $values) . "')"); + } + $ftd = array($field['fid'], $field->default_name, $field->hint); + db_query("INSERT INTO {biblio_field_type_data} (" . implode(", ", $field_type_data_fieldnames) . ") + VALUES('" . implode("', '", $ftd) . "')"); + $field_data = array($field['fid'], $field->field_name, $field->type, $field->width, $field->maxlength); + db_query("INSERT INTO {biblio_fields} (" . implode(", ", $fieldnames) . ") + VALUES('" . implode("', '", $field_data) . "')"); + foreach ($field->name as $name) { + if ($name != "~" ) { //&& $field->type != 'contrib_widget') { + $ftd[0] = ($existing_id = _id_by_name('biblio_field_type_data', $name)) ? $existing_id : variable_get('biblio_last_ftdid', 100); // ftdid + $ftd[1] = trim($name); // title + $ftd[2] = ""; // hint + db_query("UPDATE {biblio_field_type} + SET ftdid = %d, cust_tdid = %d, visible = %d + WHERE tid = %d AND fid = %d ", $ftd[0], $ftd[0], 1, $name['tid'], $field['fid'] ); + if (!$existing_id) { + // if this title doesn't alreay exist, then insert it into the table + db_query("INSERT INTO {biblio_field_type_data} (" . implode(", ", $field_type_data_fieldnames) . ") + VALUES (%d, '%s', '%s')", $ftd); + _id_by_name('biblio_field_type_data', $name, $ftd[0]); // cache the new id value for future use + variable_set('biblio_last_ftdid', $ftd[0] +1); //increment the field type data id by one. + } + + } + elseif ($name == "~" ) { + // turn the visibility off for this (~) type + db_query("UPDATE {biblio_field_type} + SET visible = 0 + WHERE tid = %d AND fid = %d ", $name['tid'], $field['fid'] ); + } + if ($field->type == 'contrib_widget' && $name != "~" ) { + db_query("UPDATE {biblio_contributor_type} SET auth_type=%d where auth_category=%d and biblio_type=%d", $ftd[0], $field->contrib_type, $name['tid']); + } + } + } + + + + db_query("/*!40000 ALTER TABLE {biblio_field_type_data} ENABLE KEYS */;"); + db_query("/*!40000 ALTER TABLE {biblio_fields} ENABLE KEYS */;"); + + return $result; +} + +function _add_db_field_data() { + if (db_driver() == 'mysql' or db_driver() == 'mysqli') { + db_query("/*!40000 ALTER TABLE {biblio_field_type_data} DISABLE KEYS */;"); + db_query("/*!40000 ALTER TABLE {biblio_fields} DISABLE KEYS */;"); + } + $csv_file = drupal_get_path('module', 'biblio') . '/misc/biblio.field.link.data.csv'; + + if ($handle = fopen($csv_file, "r")) { + $header = fgetcsv($handle, 10000, ","); // the first line has the field names + while (($row = fgetcsv($handle, 10000, ",")) !== FALSE) { + $column = 0; + // add link data for default biblio type (0) and all other defined types (100-130) + foreach (array_merge(array(0), range(100, 136)) as $t) { + db_insert('biblio_field_type')->fields(array( + 'tid' => $t, + 'fid' => $row[0], + 'ftdid' => $row[0], + 'cust_tdid' => $row[0], + 'common' => $row[3], + 'autocomplete' => $row[4], + 'required' => $row[5], + 'weight' => $row[6], + 'visible' => $row[7], + 'vtab' => $row[12] + ))->execute(); + } + + db_insert('biblio_field_type_data')->fields(array( + 'ftdid' => $row[0], + 'title' => $row[1], + 'hint' => $row[2] + ))->execute(); + + db_insert('biblio_fields')->fields(array( + 'fid' => $row[0], + 'name' => $row[8], + 'type' => $row[9], + 'size' => $row[10], + 'maxsize' => $row[11] + ))->execute(); + + // add contributor type data + if ($row[9] == 'contrib_widget') { + // use field name without trailing 's' as initial guess for author type + $auth_type = (substr($row[1], -1, 1) == 's') ? substr($row[1], 0, -1) : $row[1]; + + db_insert('biblio_contributor_type_data')->fields(array( + 'auth_type' => $row[0], + 'title' => $auth_type + ))->execute(); + + db_insert('biblio_contributor_type')->fields(array( + 'auth_category' => $row[0], + 'biblio_type' => 0, + 'auth_type' => $row[0] + ))->execute(); + } + } + fclose($handle); + $result = array('success' => TRUE, 'query' => 'Added field titles and default values'); + + } + else { + $result = array('success' => FALSE, 'query' => 'Could not open ' . $csv_file); + } + + if (db_driver() == 'mysql' or db_driver() == 'mysqli') { + db_query("/*!40000 ALTER TABLE {biblio_field_type_data} ENABLE KEYS */;"); + db_query("/*!40000 ALTER TABLE {biblio_fields} ENABLE KEYS */;"); + } + return $result; +} + +function _add_custom_field_data() { + + $next_ctdid=10; //first contributor_type_data id + $schema = biblio_schema(); + $fieldnames = array_keys($schema['biblio_field_type_data']['fields']); + + $query = "SELECT fid, name FROM {biblio_fields} "; + $res = db_query($query); + foreach ($res as $row) { + $fieldmap[$row->name] = $row->fid; + } + + $csv_file = drupal_get_path('module', 'biblio') . '/misc/biblio.field.type.data.csv'; + + if ($handle = fopen($csv_file, "r")) { + $header = fgetcsv($handle, 10000, ","); // the first line has the field names + $generic = fgetcsv($handle, 10000, ","); // the second line has the default titles if none given + // build cache lookups + _id_by_name(NULL, NULL, NULL, array('tablename' => 'biblio_field_type_data', 'name_column' => 'title', 'id_column' => 'ftdid')); + _id_by_name(NULL, NULL, NULL, array('tablename' => 'biblio_contributor_type_data', 'name_column' => 'title', 'id_column' => 'auth_type')); + // map contributor field titles to field ids + $res = db_query("SELECT fid,name FROM {biblio_fields} WHERE type='contrib_widget'"); + $contributor_categories = array(); + foreach ($res as $row ) { + $contributor_categories[$row->name] = $row->fid; + } + // process all rows of the file + while (($row = fgetcsv($handle, 10000, ",")) !== FALSE) { + $column = 0; + if (empty($row[1])) continue; + + foreach ($header as $key => $field_name) { + if (!empty($field_name) && $field_name != 'tid') { + if (!empty($row[$column]) && $row[$column] != "~" && isset($fieldmap[$field_name])) { + $ftd[0] = ($existing_id = _id_by_name('biblio_field_type_data', $row[$column])) ? $existing_id : variable_get('biblio_last_ftdid', 100); // ftdid + $ftd[1] = trim($row[$column]); // title + $ftd[2] = ""; // hint + + db_update('biblio_field_type') + ->fields(array( 'ftdid' => $ftd[0], 'cust_tdid' => $ftd[0], 'visible' => 1)) + ->condition(db_and()->condition('tid', $row[1])->condition('fid', $fieldmap[$field_name])) + ->execute(); + + if (!$existing_id) { + // if this title doesn't alreay exist, then insert it into the table + db_insert('biblio_field_type_data') + ->fields(array('ftdid' => $ftd[0], 'title' => $ftd[1], 'hint' => $ftd[2])) + ->execute(); + + _id_by_name('biblio_field_type_data', $row[$column], $ftd[0]); // cache the new id value for future use + variable_set('biblio_last_ftdid', $ftd[0] +1); //increment the field type data id by one. + } + + // also populate biblio_contributor_type tables + if ((substr($field_name, -7, 7) == 'authors') && $row[$column] != '~' ) { + $type = $contributor_categories[$field_name]; + $title = trim($row[$column]); + $biblio_type = $row[1]; + $ctdid = ($eid = _id_by_name('biblio_contributor_type_data', $title)) ? $eid : $next_ctdid; + db_update('biblio_contributor_type') + ->fields(array( + 'auth_type' => $ctdid)) + ->condition(db_and()->condition('auth_category', $type)->condition('biblio_type', $biblio_type)) + ->execute(); + + if (!$eid) { + db_insert('biblio_contributor_type_data') + ->fields(array('auth_type' => $ctdid, 'title' => $title)) + ->execute(); + + _id_by_name('biblio_contributor_type_data', $title, $ctdid); // cache the new id value for future use + $next_ctdid++; + } + } + } + elseif ($row[$column] == "~" && isset($fieldmap[$field_name])) { + // turn the visibility off for this (~) type + + db_update('biblio_field_type') + ->fields(array('visible' => 0)) + ->condition(db_and()->condition('tid', $row[1])->condition('fid', $fieldmap[$field_name])) + ->execute(); + + } + elseif (empty($row[$column]) && isset($fieldmap[$field_name])) { + // use the default field title when the title is blank + db_update('biblio_field_type') + ->fields(array('visible' => 1)) + ->condition(db_and()->condition('tid', $row[1])->condition('fid', $fieldmap[$field_name])) + ->execute(); + } + } + $column++; + } + } + fclose($handle); + $result = array('success' => TRUE, 'query' => 'Added type specific field titles'); + } + else { + $result = array('success' => FALSE, 'query' => 'Could not open ' . $csv_file); + } + + return $result; +} +function _id_by_name($table, $name, $id = NULL, $build = NULL) { + static $result = NULL; + if (!empty($build)) { //refresh cache from table + unset($result[$build['tablename']]); + $res = db_query("SELECT " . $build['name_column'] . ", " . $build['id_column'] . " FROM {" . $build['tablename'] . "}", array(), array('fetch' => PDO::FETCH_ASSOC)); + foreach ($res as $row ) { + $result[$build['tablename']][$row[$build['name_column']]] = $row[$build['id_column']]; + } + return; + } + $name = trim($name); + if (isset($result[$table][$name])) return $result[$table][$name]; + if ($id) $result[$table][$name] = $id; + return FALSE; +} +/* + * Removed updates 1 - 27 since they were from 5.x biblio + */ + +/* + * Removed updates 6000 - 6023 only upgrades from biblio 6.x-1.9 are supported + */ +/* add the new field -refereed- on the biblio table +*/ +function biblio_update_6024() { + + db_add_field('biblio', 'biblio_refereed', array('type' => 'varchar', 'length' => '20')); + + /* add the field data for -refereed- on the biblo_fields table + you need to get the last inserted record from biblio_fields and increment it by one + so you don't step on customized fields added via the user online interface */ + + $sql = 'SELECT fid FROM {biblio_fields} ORDER BY fid DESC'; + $lastfid = db_query_range($sql, 0, 1)->fetchField(); + $newfid = $lastfid + 1; + + db_query("INSERT INTO {biblio_fields} (fid, name, type, size, maxsize) VALUES + ($newfid, 'biblio_refereed', 'select', 0, 125)"); + + /*use the same fid and insert an entry in the biblio_field_type_data */ + db_query("INSERT INTO {biblio_field_type_data} + (ftdid, title, hint) VALUES ($newfid, 'Refereed Designation', NULL)"); + + /* get a list of unique tids from the biblio_field_type table. You want to + insert a tid,fid using the new fid for every available tid */ + + $newsql = "SELECT DISTINCT tid FROM {biblio_field_type} ORDER BY tid DESC"; + + $tidlist = db_query($newsql); + foreach ($tidlist as $tid) { + $newtid = $tid->tid ; + db_query('INSERT INTO {biblio_field_type} + (tid, fid, ftdid, cust_tdid, common, autocomplete, required, weight, visible) + VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d)', + $newtid, $newfid, $newfid, $newfid, 1, 1, 0, 1, 1); + } +} + +function biblio_update_6025() { + $schema = biblio_schema(); + db_create_table('biblio_type_maps', $schema['biblio_type_maps']); +} + +function biblio_update_6026() { + // move custom block titles stored in variable "biblio_block_title" to the block table if the title has not already been overriden + $custom_title = variable_get('biblio_block_title', ''); + if (!empty($custom_title)) { + $db_result = db_query("SELECT bid,title FROM {blocks} b where module='biblio' "); + foreach ($db_result as $block) { + if (empty ($block->title)) { + $block->title = $custom_title; + db_query("UPDATE {blocks} SET title='" . $block->title . "' WHERE bid=" . $block->bid); + } + } + variable_del('biblio_block_title'); + } +} + +function biblio_update_6027() { + // renunmber the author rank such that it is zero based accross all categories + // this only needs to be done for entries that actually have auth_categories other than 1 + require_once(drupal_get_path('module', 'biblio') . '/includes/biblio.contributors.inc'); + $result = array(); + $count = 0; + $db_result = db_query("SELECT DISTINCT(vid),nid FROM {biblio_contributor} WHERE auth_category IN (2,3,4,5) "); + $count_success = db_query("SELECT COUNT(DISTINCT(vid)) FROM {biblio_contributor} WHERE auth_category IN (2,3,4,5) ")->fetchField(); + foreach ($db_result as $node) { + $contributors = biblio_load_contributors($node->vid); + _save_contributors($contributors, $node->nid, $node->vid, $update = FALSE) ; + $count++; + } +} + +function biblio_update_6028() { +/* + * Caching is not used in 7.x CiteProc + * + $table = drupal_get_schema_unprocessed('system', 'cache'); + $table['description'] = 'Cache table for biblio to store pre-built csl objects'; + $table['fields']['serialized']['default'] = 1; +*/ +} + +function biblio_update_6029() { + $spec = array( + 'type' => 'blob', + 'not null' => FALSE, + 'default' => NULL, + 'size' => 'big', + 'description' => 'Stores the mapping between biblio fields and external file formats', + ); + db_add_field('biblio_type_maps', 'export_map', $spec); +} + +function biblio_update_6030() { + + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'Determines if the author name is allowed to be reformated by the variaous styles or should be used literally.', + ); + db_add_field('biblio_contributor_data', 'literal', $spec); + +} + +function biblio_update_6031() { + $result = array(); + $types[] = array(131, 'Journal', NULL, 3); + $types[] = array(132, 'Web site', NULL, 8); + $types[] = array(133, 'Web service','e.g. Google, Yahoo', 8); + $types[] = array(134, 'Web project page', NULL, 8); + $types[] = array(135, 'Presentation', NULL, 8); + $types[] = array(136, 'Newspaper', NULL, 8); + + foreach ($types as $record) { + db_query("INSERT INTO {biblio_types} (tid, name, description, weight) VALUES ('" . implode("', '", $record) . "')"); + } + + db_query("DELETE FROM {biblio_types} WHERE tid=-1"); + + return $result; +} + +function biblio_update_6032() { + $spec = array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'default' => '', + 'description' => 'Full name', + ); + db_change_field('biblio_contributor_data', 'name', 'name', $spec); +} +function biblio_update_6033() { + $spec = array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '64', + 'description' => 'A normalized version of the title, used for sorting on titles. (only first 64 characters saved)', + ); + db_add_field('biblio', 'biblio_sort_title', $spec); +} + +function biblio_update_7000() { + + _biblio_helper_modules('install'); + +} +/** + * Add new column to biblio_contributor_data table + */ +function biblio_update_7001() { + if (!db_field_exists('biblio_contributor_data', 'literal')) { + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'Determines if the author name is allowed to be reformated by the variaous styles or should be used literally.', + ); + db_add_field('biblio_contributor_data', 'literal', $spec); + } +} +/** + * + * Adds export_map column to biblio_type_maps table + */ +function biblio_update_7002() { + if (!db_field_exists('biblio_type_maps', 'export_map')) { + biblio_update_6029(); + } +} +/** + * + * Adds some new publication types + */ +function biblio_update_7003() { + $result = db_query('SELECT tid FROM {biblio_types} WHERE tid = :tid', array(':tid' => 136))->fetchField(); + if (!$result) { + biblio_update_6031(); + } +} + +function _biblio_update_field_link_data($range, $vtabs = FALSE) { + $csv_file = drupal_get_path('module', 'biblio') . '/misc/biblio.field.link.data.csv'; + + if ($handle = fopen($csv_file, "r")) { + $header = fgetcsv($handle, 10000, ","); // the first line has the field names + while (($row = fgetcsv($handle, 10000, ",")) !== FALSE) { + if ($vtabs) { + // add link data for default biblio type (0) and all other defined types (100-130) + foreach (array_merge(array(0), range($range[0], $range[1])) as $t) { + db_update('biblio_field_type') + ->fields(array( + 'vtab' => $row[12] + )) + ->condition(db_and()->condition('tid', $t)->condition('fid', $row[0])) + ->execute(); + } + } + else { + foreach (range($range[0], $range[1]) as $t) { + db_insert('biblio_field_type')->fields(array( + 'tid' => $t, + 'fid' => $row[0], + 'ftdid' => $row[0], + 'cust_tdid' => $row[0], + 'common' => $row[3], + 'autocomplete' => $row[4], + 'required' => $row[5], + 'weight' => $row[6], + 'visible' => $row[7], + 'vtab' => $row[12] + ))->execute(); + } + } + } + } + +} + +/** + * Add information to manage vtabs on input form. +*/ +function biblio_update_7005() { + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'unsigned' => TRUE, + 'default' => 0, + ); + db_add_field('biblio_field_type', 'vtab', $spec); + _biblio_update_field_link_data(array(100, 130), TRUE); + cache_clear_all(); +} + +function biblio_update_7006() { + $result = db_query('SELECT fid FROM {biblio_field_type} WHERE tid = :tid', array(':tid' => 136))->fetchField(); + if (!$result) { + _biblio_update_field_link_data(array(131, 136)); + cache_clear_all(); + } +} +/** + * Increases the size of the {biblio_contributor_data}.name column to 255 characters +*/ +function biblio_update_7007() { + $spec = array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'default' => '', + 'description' => 'Full name', + ); + db_change_field('biblio_contributor_data', 'name', 'name', $spec); +} +/** + * Adds new "biblio_sort_title" column to the biblio table, which is used for title sorting. +*/ +function biblio_update_7008() { + if (!db_field_exists('biblio', 'biblio_sort_title')) { + biblio_update_6033(); + } +} + +/** + * Populates the new "biblio_sort_title" column, which is used for title sorting. +*/ +function biblio_update_7009(&$sandbox) { + $sandbox['#finished'] = 0; + module_load_include('inc', 'biblio', '/includes/biblio.util'); + + if (!isset($sandbox['max'])) { + $sandbox['max'] = db_query('SELECT COUNT(DISTINCT vid) FROM {node} n WHERE n.type = :type', array(':type' => 'biblio'))->fetchField(); + $sandbox['current_vid'] = 0; + } + + $nodes = db_select('node', 'n') + ->fields('n', array('vid', 'title')) + ->condition('vid', $sandbox['current_vid'], '>') + ->condition('type', 'biblio') + ->range(0, 20) + ->orderBy('vid', 'ASC') + ->execute(); + + foreach ($nodes as $node) { + $node->biblio_sort_title = biblio_normalize_title($node->title); + db_update('biblio') + ->fields(array('biblio_sort_title' => $node->biblio_sort_title)) + ->condition('vid', $node->vid) + ->execute(); + + $sandbox['progress']++; + $sandbox['current_vid'] = $node->vid; + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); +} +/** + * Removes "biblio_inlinemode_in_links" variable + */ +function biblio_update_7010() { + variable_del('biblio_inlinemode_in_links'); +} +/** + * Add a body field instance + */ +function biblio_update_7011() { + $content_type = node_type_load('biblio'); + node_add_body_field($content_type, 'Full text'); +} +/** + * Update biblio_field_type table + */ +function biblio_update_7012() { + + // there was a problem with update 7006 and it might no have done anything so lets try again. + biblio_update_7006(); +} + +function biblio_update_7013() { + db_add_index('biblio', 'title_sort', array('biblio_sort_title')); + db_add_index('biblio', 'date', array('biblio_date')); + +} + +/** + * + * Widen the biblio_date column to 64 characters + */ +function biblio_update_7014() { + $spec = array( + 'type' => 'varchar', + 'not null' => FALSE, + 'length' => '64', + 'description' => '', + ); + db_change_field('biblio', 'biblio_date', 'biblio_date', $spec); + +} +/** + * + * Add biblio_formats column to biblio table to hold the format information for each text area + */ +function biblio_update_7015() { + if (!db_field_exists('biblio', 'biblio_formats')) { + $spec = array( + 'type' => 'blob', + 'not null' => FALSE, + 'description' => '', + 'serialize' => TRUE, + ); + db_add_field('biblio', 'biblio_formats', $spec); + } +} +/** + * Convert textarea fields to text_format + */ +function biblio_update_7016() { + db_update('biblio_fields') + ->fields(array('type' => 'text_format')) + ->condition('type', 'textarea') + ->execute(); +} + +/** + * Remove views export handlers in sub-modules (if they still exist) + */ +function biblio_update_7017() { + $dirs = array('bibtexParse', 'endnote', 'RIS', 'rtf'); + foreach ($dirs as $dir) { + $path = drupal_get_path('module', 'biblio') . '/modules/' . $dir . '/views'; + if (is_dir($path)) { + $message = t('You have an inconsistancy in your installation, the directory: @path, should not exist!', array('@path' => $path)); + drupal_set_message($message, 'error'); + } + } + if (module_exists('views')) { // rebuild the data tables + views_invalidate_cache(); + } +} +/** + * + * Widen the biblio_contributor_data.lastname column to 255 characters + */ +function biblio_update_7018() { + $spec = array( + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + 'default' => '', + 'description' => 'Author last name', + ); + db_change_field('biblio_contributor_data', 'lastname', 'lastname', $spec); + +} +/** + * + * Change biblio_contributor_data.cid to a signed int + */ + +function biblio_update_7019() { + +} + +/** + * + * Change the primary key of the biblio_contributor_data table + */ +function biblio_update_7020() { + $driver = db_driver(); + if ($driver == 'mysql') { + db_query('ALTER TABLE `biblio_contributor_data` DROP PRIMARY KEY , ADD PRIMARY KEY ( `cid` )'); + } + if ($driver == 'pgsql') { + db_drop_primary_key('biblio_contributor_data'); + db_add_primary_key('biblio_contributor_data', array('cid')); + } +} +/** + * + * Add alt_form column to the biblio_contributor_data table + */ +function biblio_update_7021() { + if (!db_field_exists('biblio_contributor_data', 'alt_form')) { + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => 'Alternate form of an author name, this value points to the desired form (cid), this form is kept in the database so that future imports of the same name will not create a new author.', + ); + db_add_field('biblio_contributor_data', 'alt_form', $spec); + } +} +/** + * + * Add merge_cid column to the biblio_contributor_data table + */ +function biblio_update_7022() { + if (!db_field_exists('biblio_contributor', 'merge_cid')) { + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'unsigned' => TRUE, + 'description' => '', + ); + db_add_field('biblio_contributor', 'merge_cid', $spec); + } +} + +/** + * + * Re-apply update 7014 in case the install happened after 7014 was implemented. The main scheme definition was not changed at that time to match the update resulting a schema mismatch. + * + */ +function biblio_update_7023() { + biblio_update_7014(); +} +/** + * + * Re-apply update 7022 in case the install happened after 7022 was implemented. The main scheme definition was not changed at that time to match the update resulting a schema mismatch. + * + */ + +function biblio_update_7024() { + biblio_update_7022(); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/biblio.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/biblio.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,2259 @@ +auth_category][$row->biblio_type][] = $row->auth_type; + } + } + // fall back to defaults, if no author types are defined for this biblio_type + $result = isset($auth_types[$auth_category][$biblio_type])?$auth_types[$auth_category][$biblio_type]:$auth_types[$auth_category][0]; + return $result; +} +function _biblio_get_auth_type($auth_category, $biblio_type) { + $result = (array)_biblio_get_auth_types($auth_category, $biblio_type); + // return first element of the array + return empty($result) ? NULL : current($result); +} + +/** + * Translate field titles and hints through the interface translation system, if + * the i18nstrings module is enabled. + */ +function _biblio_localize_fields(&$fields) { + if (function_exists('i18n_string')) { + foreach ($fields as $key => $row) { + $fields[$key]['title'] = i18n_string("biblio:field:{$row['ftdid']}:title", $fields[$key]['title']); + $fields[$key]['hint'] = i18n_string("biblio:field:{$row['ftdid']}:hint", $fields[$key]['hint']); + } + } +} + +/** + * Translate a publication type through the interface translation system, if + * the i18nstrings module is enabled. + * + * @param integer $tid + * The biblio publication type identifier. + * + * @param string $value + * The string to translate. + * + * @param string $field + * The publication type field to translate (either 'name' or 'description'). + * + * @return + * Translated value. + */ +function _biblio_localize_type($tid, $value, $field = 'name') { + if (function_exists('i18n_string')) { + return i18n_string("biblio:type:$tid:$field", $value); + } + return $value; +} + +/** + * Implementation of hook_locale(). + */ +function biblio_locale($op = 'groups', $group = NULL) { + switch ($op) { + case 'groups': + return array('biblio' => t('Biblio')); + + case 'refresh': + if ($group == 'biblio') { + biblio_locale_refresh_fields(); + biblio_locale_refresh_types(); + } + break; + } +} + +/** + * Refresh all translatable field strings. + * + * @param integer $tid + * Biblio publication type id whose field strings are to be refreshed. If not + * specified, strings for all fields will be refreshed. + */ +function biblio_locale_refresh_fields($tid = NULL) { + if (function_exists('i18n_string')) { + if (isset($tid)) { + $result = db_query('SELECT d.* FROM {biblio_field_type} b INNER JOIN {biblio_field_type_data} d ON b.ftdid = d.ftdid WHERE tid = :tid', array(':tid' => $tid)); + } + else { + $result = db_query('SELECT * FROM {biblio_field_type_data}'); + } + $options = array('translate' => FALSE, 'update' => TRUE); + foreach ($result as $row) { + i18n_string("biblio:field:{$row->ftdid}:title", $row->title, $options); + i18n_string("biblio:field:{$row->ftdid}:hint", $row->hint, $options); + } + } +} + +/** + * Refresh all publication type strings. + * + * @param integer $tid + * Biblio publication type id whose field strings are to be refreshed. If not + * specified, strings for all fields will be refreshed. + */ +function biblio_locale_refresh_types($tid = NULL) { + if (function_exists('i18n_string')) { + if (isset($tid)) { + $result = db_query('SELECT * FROM {biblio_types} WHERE tid = :tid', array(':tid' => $tid)); + } + else { + $result = db_query('SELECT * FROM {biblio_types} WHERE tid > 0'); + } + $options = array('translate' => FALSE, 'update' => TRUE); + foreach ($result as $row ) { + i18n_string("biblio:type:{$row->tid}:name", $row->name, $options); + i18n_string("biblio:type:{$row->tid}:description", $row->description, $options); + } + } +} + +function biblio_init() { + global $user, $conf; + drupal_add_css(drupal_get_path('module', 'biblio') . '/biblio.css'); + + if ($user->uid === 0) { // Prevent caching of biblio pages for anonymous users so session variables work and thus filering works + $base = variable_get('biblio_base', 'biblio'); + if (drupal_match_path($_GET['q'], "$base\n$base/*")) + $conf['cache'] = FALSE; + } +} + +function biblio_cron() { + require_once(drupal_get_path('module', 'biblio') .'/includes/biblio.contributors.inc'); + require_once(drupal_get_path('module', 'biblio') .'/includes/biblio.keywords.inc'); + + $interval = variable_get('biblio_orphan_clean_interval', 24*60*60); //defaults to once per day + + if (time() >= variable_get('biblio_orphan_clean_next_execution', 0)) { + biblio_delete_orphan_authors(); + biblio_delete_orphan_keywords(); + variable_set('biblio_orphan_clean_next_execution', time() + $interval); + } +} + +function biblio_theme($existing, $type, $theme, $path) { + $path = drupal_get_path('module', 'biblio'); + return array( + 'biblio_alpha_line' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'type' => 'author', + 'current' => NULL, + 'path' => NULL), + ), + 'biblio_admin_keyword_edit_form' => array( + 'file' => '/includes/biblio.admin.inc', + 'render element' => 'form', + ), + 'biblio_admin_author_types_form' => array( + 'file' => '/includes/biblio.admin.inc', + 'render element' => 'form', + ), + 'biblio_admin_type_mapper_form' => array( + 'file' => '/includes/biblio.admin.inc', + 'render element' => 'form', + ), + 'biblio_admin_io_mapper_form'=> array( + 'file' => '/includes/biblio.admin.inc', + 'render element' => 'form', + ), + 'biblio_admin_io_mapper_add_form'=> array( + 'file' => '/includes/biblio.admin.inc', + 'render element' => 'form', + ), + 'biblio_admin_field_mapper_form' => array( + 'file' => '/includes/biblio.admin.inc', + 'render element' => 'form', + ), + 'biblio_admin_types_edit_form' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'biblio_admin_types_form' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'biblio_field_tab' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'biblio_admin_author_edit_form' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'biblio_admin_author_edit_merge_table' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'biblio_openurl' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array('openURL'), + ), + 'biblio_style' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'node' => NULL, + 'style_name' => 'classic', + ), + ), + 'biblio_long' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'node' => NULL, + 'base' => 'biblio', + 'style_name' => 'classic'), + ), + 'biblio_tabular' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'node' => NULL, + 'base' => 'biblio', + 'teaser' => FALSE), + ), + 'biblio_entry' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'node', + 'style_name' => 'classic', + ), + ), + 'biblio_format_authors' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'contributors' => NULL, + 'options' => array(), + ), + ), + 'biblio_page_number' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'orig_page_info' => NULL, + 'page_range_delim' => "-", + 'single_page_prefix' => '', + 'page_range_prefix' => '', + 'total_pages_prefix' => '', + 'single_page_suffix' => '', + 'page_range_suffix' => '', + 'total_pages_suffix' => '', + 'shorten_page_range_end' => FALSE), + ), + 'biblio_author_link' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'author'), + ), + 'biblio_filters' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'form_filter' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'biblio_export_links' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array( + 'node' => NULL, + 'filter' => array()), + ), + 'biblio_download_links' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array('node'), + ), + 'google_scholar_link' => array( + 'file' => '/includes/biblio_theme.inc', + 'variables' => array('node'), + ), + 'biblio_contributors' => array( + 'file' => '/includes/biblio_theme.inc', + 'render element' => 'form', + ), + 'biblio_sort_tabs' => array( + 'file' => 'includes/biblio_theme.inc', + 'variables' => array(), + ) + ); +} + +function biblio_autocomplete($field, $string = '') { + $matches = array(); + switch ($field) { + case 'contributor': + case 'a': + $result = db_query_range("SELECT name FROM {biblio_contributor_data} WHERE LOWER(lastname) LIKE LOWER(:name) OR LOWER(firstname) LIKE LOWER(:firstname) ORDER BY lastname ASC ", 0, 10, array(':name' => $string . '%', ':firstname' => $string . '%')); + foreach ($result as $data ) { + $matches[$data->name] = check_plain($data->name); + } + break; + case 'biblio_keywords': + case 'k': + $sep = check_plain(variable_get('biblio_keyword_sep', ',')); + $sep_pos = strrpos($string, $sep); //find the last separator + $start = trim(drupal_substr($string, 0, $sep_pos)); // first part of the string upto the last separator + $end_sep = ($sep_pos) ? $sep_pos + 1 :$sep_pos; + $end = trim(drupal_substr($string, $end_sep)) . '%'; // part of the string after the last separator + $result = db_query_range("SELECT * FROM {biblio_keyword_data} WHERE LOWER(word) LIKE LOWER(:end) ORDER BY word ASC ", 0, 10, array(':end' => $end)); + foreach ($result as $data) { + // now glue the word found onto the end of the original string... + $keywords = ($sep_pos) ? $start . ', ' . check_plain($data->word) : check_plain($data->word); + $matches[$keywords] = $keywords; + } + break; + + default: + $field = drupal_strtolower($field); + $string = '%' . drupal_strtolower($string) . '%'; + $query = db_select('biblio', 'b') + ->fields('b', array($field)) + ->condition($field, $string, 'LIKE') + ->orderBy($field, 'ASC') + ->range(0, 10); + $result = $query->execute(); + + foreach ($result as $data) { + $matches[$data->$field] = check_plain($data->$field); + } + } + print drupal_json_encode($matches); + exit(); +} + +function biblio_help_page() { + $base = variable_get('biblio_base', 'biblio'); + $text = "

    " . t('General:') . "

    "; + $text .= "

    " . t('By default, the !url page will list all of the entries in the database sorted by Year in descending order. If you wish to sort by "Title" or "Type", you may do so by clicking on the appropriate links at the top of the page. To reverse the sort order, simply click the link a second time.', array( + '!url' => l('', + $base + ))) . "

    "; + $text .= "

    " . t('Filtering Search Results:') . "

    "; + $text .= "

    " . t('If you wish to filter the results, click on the "Filter" tab at the top of the page. To add a filter, click the radio button to the left of the filter type you wish to apply, then select the filter criteria from the drop down list on the right, then click the filter button.') . "

    "; + $text .= "

    " . t('It is possible to create complex filters by returning to the Filter tab and adding additional filters. Simply follow the steps outlined above and press the "Refine" button.') . "

    "; + $text .= "

    " . t('All filters can be removed by clicking the Clear All Filters link at the top of the result page, or on the Filter tab they can be removed one at a time using the Undo button, or you can remove them all using the Clear All button.') . "

    "; + $text .= "

    " . t('You may also construct URLs which filter. For example, /biblio/year/2005 will show all of the entries for 2005. /biblio/year/2005/author/smith will show all of entries from 2005 for smith.') . "

    "; + $text .= "

    " . t('Exporting Search Results:') . "

    "; + $text .= "

    " . t('Assuming this option has been enabled by the administrator, you can export search results directly into EndNote. The link at the top of the result page will export all of the search results, and the links on individual entries will export the information related to that single entry.') . "

    "; + $text .= "

    " . t('The information is exported in EndNote "Tagged" format similar to this...') . "

    " . t('
    +                  %0  Book
    +                  %A  John Smith
    +                  %D  1959
    +                  %T  The Works of John Smith
    +                  ...') . '

    '; + $text .= "

    " . t('Clicking on one of the export links should cause your browser to ask you whether you want to Open, or Save To Disk, the file endnote.enw. If you choose to open it, Endnote should start and ask you which library you would like store the results in. Alternatively, you can save the file to disk and manually import it into EndNote.') . "

    "; + return ($text); +} +/** + * Implementation of hook_help(). + * + * Throughout Drupal, hook_help() is used to display help text at the top of + * pages. Some other parts of Drupal pages get explanatory text from these hooks + * as well. We use it here to provide a description of the module on the + * module administration page. + */ + +function biblio_help($path, $arg) { + switch ($path) { + case 'admin/help#biblio' : + return biblio_help_page(); + case 'admin/modules#description' : + // This description is shown in the listing at admin/modules. + return t('Manages a list of scholarly papers on your site'); + case 'node/add#biblio' : + // This description shows up when users click "create content." + return t('This allows you to add a bibliographic entry to the database'); + } +} + +function biblio_node_info() { + return array( + 'biblio' => array( + 'name' => t('Biblio'), + 'base' => 'biblio', + 'description' => t('Use Biblio for scholarly content, such as journal papers and books.'), + ) + ); +} + +function biblio_node_access($node, $op, $account) { + if (is_string($node)) { + return NODE_ACCESS_IGNORE; + } + + if ($node->type != 'biblio') { // we only care about biblio nodes + return NODE_ACCESS_IGNORE; + } + switch ($op) { + case 'view': + if (((variable_get('biblio_view_only_own', 0)) && $account->uid != $node->uid) || + !user_access('access biblio content')){ + return NODE_ACCESS_DENY; + } + break; + case 'update': + case 'delete': + if (user_access('edit by all biblio authors') && isset($node->biblio_contributors) && is_array($node->biblio_contributors)) { + foreach ($node->biblio_contributors as $key => $author) { + if ((isset($author['drupal_uid']) && $author['drupal_uid'] == $account->uid) || + (isset($account->data['biblio_contributor_id']) && $author['cid'] == $account->data['biblio_contributor_id'])) { + return NODE_ACCESS_ALLOW; + } + } + } + break; + default: + } + return NODE_ACCESS_IGNORE; +} + +function biblio_access($op, $node = '') { + global $user; + + switch ($op) { + case 'admin': + return user_access('administer biblio'); + case 'import': + return user_access('import from file'); + case 'export': + return user_access('show export links'); + case 'edit_author': + if (user_access('administer biblio') || user_access('edit biblio authors')) return NODE_ACCESS_ALLOW; + break; + case 'download': + if (user_access('show download links') || (user_access('show own download links') && ($user->uid == $node->uid))) return NODE_ACCESS_ALLOW; + break; + case 'rss': + return variable_get('biblio_rss', 0); + default: + } + return NODE_ACCESS_IGNORE; +} +/** + * Implementation of hook_permission(). + * + * Since we are limiting the ability to create new nodes to certain users, + * we need to define what those permissions are here. We also define a permission + * to allow users to edit the nodes they created. + */ +function biblio_permission() { + return array( + 'administer biblio' => array( + 'title' => t('Administer Biblio'), + 'description' => t('Allows full control (create, update, delete) of all Biblio nodes'), + ), + 'access biblio content' => array( + 'title' => t('Access Biblio content'), + 'description' => t('Allows the user to view Biblio nodes'), + ), +// 'create biblio' => array( +// 'title' => t('Create Biblio'), +// 'description' => t('Allows the user to create new Biblio nodes'), +// ), +// 'edit all biblio entries' => array( +// 'title' => t('Edit all Biblio entries'), +// 'description' => t('Allows the user to edit ALL biblio entries regardless of who "owns" them, otherwise they are restricted to on'), +// ), + 'edit by all biblio authors' => array( + 'title' => t('Edit by all Biblio authors'), + 'description' => t('Allows any/all of the authors associated with a biblio entry to edit the biblio entry. This requires the Drupal UserID be mapped to a Biblio author ID'), + ), + 'edit biblio authors' => array( + 'title' => t('Edit Biblio authors'), + 'description' => t('Allows the user to edit author information'), + ), + 'import from file' => array( + 'title' => t('Import from file'), + 'description' => t('Allows the user to import bibliographic data from files such as BibTex, RIS, EndNote'), + ), + 'show export links' => array( + 'title' => t('Show export links'), + 'description' => t('Allows users to see links which allow export of bibliographic data for an individual entry or the entire result set'), + ), + 'show download links' => array( + 'title' => t('Show download links'), + 'description' => t('Allows users to see links to any attachements associated with the Biblio entry'), + ), + 'show own download links' => array( + 'title' => t('Show own download links'), + 'description' => t('Allows user to only see download links on entries for which they are the owner.'), + ), + 'show filter tab' => array( + 'title' => t('Show filter tab'), + 'description' => t('This determines if the "Filter" tab on the Biblio list page will be shown to the user'), + ), + 'show sort links' => array( + 'title' => t('Show sort links'), + 'description' => t('This determines if the "Sort" links on the Biblio list page will be shown to the user'), + ), + 'view full text' => array( + 'title' => t('Show full text'), + 'description' => t('This determines if the user will be able to access the "Full Text" of the article if it is available'), + ), + ); +} + +/** + * Implementation of hook_user(). + */ +function biblio_form_user_profile_form_alter(&$form, &$form_state, $form_id) { + if ($form['#user_category'] == 'account') { + $account = $form['#user']; + include_once drupal_get_path('module', 'biblio') . '/includes/biblio.admin.inc'; + $show_form = variable_get('biblio_show_user_profile_form', '1') || + variable_get('biblio_show_crossref_profile_form', '1') || + variable_get('biblio_show_openurl_profile_form', '1'); + + $admin_show_form = ($account->uid == 1 || (user_access('administer users') && user_access('administer biblio'))) ? TRUE : FALSE; + if ($admin_show_form || $show_form) { + $form['biblio_fieldset'] = array( + '#type' => 'fieldset', + '#title' => t('Biblio settings'), + '#weight' => 5, + '#collapsible' => TRUE, + '#collapsed' => FALSE, + ); + if ($admin_show_form || variable_get('biblio_show_user_profile_form', '1')) { + $form['biblio_fieldset'] += _biblio_get_user_profile_form($account); + } + else { + $form['biblio_fieldset'] += _biblio_drupal_author_user_map($account); + } + if ($admin_show_form || variable_get('biblio_show_openurl_profile_form', '1')) { + $form['biblio_fieldset'] += _biblio_get_user_openurl_form($account); + } + if ($admin_show_form || variable_get('biblio_show_crossref_profile_form', '1')) { + $form['biblio_fieldset'] += _biblio_get_user_doi_form($account); + } + } + } +} + +function biblio_forms() { + $forms['biblio_admin_author_types_form_new'] = array( + 'callback' => 'biblio_admin_author_types_form', + ); + $forms['biblio_admin_author_types_form_edit'] = array( + 'callback' => 'biblio_admin_author_types_form', + ); + return $forms; + +} +/** + * Implementation of hook_menu(). + * + * Here we define some built in links for the biblio module, links exposed are: + * + * + */ +function biblio_menu() { + global $user; + $items = array(); + $base = variable_get('biblio_base', 'biblio'); + $base_title = check_plain(variable_get('biblio_base_title', 'Biblio')); + $items["$base"] = array( + 'title' => $base_title, + 'page callback' => 'biblio_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + ); + $items["$base/authors"] = array( + 'title' => 'Authors', + 'page callback' => 'biblio_author_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + 'weight' => 1, + ); + $items["$base/keywords"] = array( + 'title' => 'Keywords', + 'page callback' => 'biblio_keyword_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', +// 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + $items["$base/import"] = array( + 'title' => 'Import', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_import_form'), + 'file' => '/includes/biblio.import.export.inc', + 'access callback' => 'user_access', + 'access arguments' => array('import from file'), + // 'type' => MENU_LOCAL_TASK, + 'weight' => 10, + ); + $items["$base/user/%"] = array( + 'title' => 'My publications', + 'page callback' => 'biblio_profile_page', + 'page arguments' => array(2), + 'access callback' => '_biblio_profile_access', + 'access arguments' => array(2, 'menu'), + 'parent' => '', + 'file' => '/includes/biblio.pages.inc', + ); + /* + $items["$base/backup"] = array( + 'title' => '', + 'page callback' => 'biblio_backup', + 'access callback' => 'user_access', + 'access arguments' => array('access content'), + 'file' => 'biblio.import.export.inc', + 'type' => MENU_CALLBACK + ); + */ + $items["$base/pot"] = array( + 'title' => '', + 'page callback' => 'biblio_dump_db_data_for_pot', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'type' => MENU_CALLBACK + ); + + $wildcard = 2 + (count(explode("/", $base)) - 1); + + $items["$base/authors/%/edit"] = array( + 'title' => 'Edit author information', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_author_edit_form', $wildcard), + 'access callback' => 'biblio_access', + 'access arguments' => array('edit_author'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK + ); + $items["$base/keywords/%/edit"] = array( + 'title' => '', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_keyword_edit_form', $wildcard), + 'access callback' => 'user_access', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK + ); + $items["$base/keyword/%/delete"] = array( + 'title' => 'Delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_keyword_delete_confirm', $wildcard), + 'access callback' => 'user_access', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'weight' => 1, + 'type' => MENU_CALLBACK + ); + $items["$base/view/%"] = array( + 'page callback' => 'biblio_view_node', + 'page arguments' => array($wildcard), + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + 'type' => MENU_CALLBACK + ); + + $items["user/%user/$base"] = array( + 'title' => 'Publications', + 'page callback' => 'biblio_profile_page', + 'page arguments' => array(1, 'profile', 'no_filters'), + 'access callback' => '_biblio_profile_access', + 'access arguments' => array(1, 'profile'), + 'file' => '/includes/biblio.pages.inc', + 'type' => MENU_LOCAL_TASK + ); + // The next two "LOCAL TASKS" are for the admin/config/content/biblio page + $items['admin/config/content/biblio'] = array( + 'title' => 'Biblio settings', + 'description' => 'Configure default behavior of the Biblio module.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_settings'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + ); + + $items['admin/config/content/biblio/basic'] = array( + 'title' => 'Preferences', + 'description' => 'Configure default behavior of the Biblio module.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_settings'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -10 + ); + $items['admin/config/content/biblio/import'] = array( + 'title' => 'Data import', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_import_form'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.import.export.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 1 + ); + $items['admin/config/content/biblio/export'] = array( + 'title' => 'Export', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_export_form'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.import.export.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 2 + ); + $items['admin/config/content/biblio/fields'] = array( + 'title' => 'Fields', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_types_edit_form'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -9 + ); + $items['admin/config/content/biblio/fields/common'] = array( + 'title' => 'Common', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_types_edit_form'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10 + ); + $items['admin/config/content/biblio/iomap'] = array( + 'title' => 'Import/Export Mapping', + 'page callback' => 'biblio_admin_io_mapper_page', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -1 + ); + $items['admin/config/content/biblio/iomap/formats'] = array( + 'title' => 'Import/Export Mapping', + 'page callback' => 'biblio_admin_io_mapper_page', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -100 + ); + $formats = module_invoke_all('biblio_mapper_options'); + foreach ($formats as $key => $format) { + $items['admin/config/content/biblio/iomap/edit/' . $key] = array( + 'title' => $format['title'], + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_io_mapper_form', 6), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'tab_parent' => 'admin/config/content/biblio/iomap', + 'type' => MENU_LOCAL_TASK, + 'weight' => -1 + ); + } + $items['admin/config/content/biblio/iomap/%/%/add'] = array( + 'title' => '', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_io_mapper_add_form', 5, 6), + 'access arguments' => array('administer biblio'), + 'tab_parent' => 'admin/config/content/biblio/iomap', + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK, + 'weight' => -1 + ); + $items['admin/config/content/biblio/pubtype'] = array( + 'title' => 'Publication types', + 'page callback' => 'biblio_admin_types_form', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -9 + ); + $items['admin/config/content/biblio/pubtype/list'] = array( + 'title' => 'List', + 'page callback' => 'biblio_admin_types_form', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10 + ); + $items['admin/config/content/biblio/pubtype/delete/%'] = array( + 'title' => '', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_types_delete_form', 6), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK + ); + $items['admin/config/content/biblio/pubtype/new'] = array( + 'title' => 'Add New Type', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_types_add_form'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -9 + ); + $items['admin/config/content/biblio/pubtype/reset'] = array( + 'page callback' => 'biblio_admin_types_reset', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK, + ); + $items['admin/config/content/biblio/fields/reset'] = array( + 'title' => 'Reset all types to defaults', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_types_reset_form'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK + ); + $items['admin/config/content/biblio/pubtype/hide'] = array( + 'title' => '', + 'page callback' => 'biblio_admin_types_hide', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK + ); + $items['admin/config/content/biblio/pubtype/show'] = array( + 'title' => '', + 'page callback' => 'biblio_admin_types_show', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK + ); + $items['admin/config/content/biblio/author'] = array( + 'title' => 'Authors', + 'page callback' => 'biblio_author_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -7 + ); + $items['admin/config/content/biblio/author/list'] = array( + 'title' => 'List', + 'page callback' => 'biblio_author_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -7 + ); + $items['admin/config/content/biblio/author/%/edit'] = array( + 'title' => 'Edit author information', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_author_edit_form', 5), + 'access callback' => 'biblio_access', + 'access arguments' => array('edit_author'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK, + 'weight' => -6 + ); + $items['admin/config/content/biblio/author/orphans'] = array( + 'title' => 'Orphaned Authors', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_orphans_form'), + 'access arguments' => array('administer biblio'), + 'description' => 'Delete orphaned biblio authors.', + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -6 + ); + $items['admin/config/content/biblio/author/type'] = array( + 'title' => 'Author Types', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_author_types_form', 7, 6), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -5 + ); + + $items['admin/config/content/biblio/author/type/new'] = array( + 'title' => 'Add New Author Type', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_author_types_form_new', 'new'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -9 + ); + $items['admin/config/content/biblio/author/type/%/edit'] = array( + 'title' => 'Edit Author Type', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_author_types_form_edit', 'edit', 6), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK, + 'weight' => -9 + ); + $items['admin/config/content/biblio/author/type/%/delete'] = array( + 'title' => 'Delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_author_type_delete_confirm', 6), + 'access callback' => 'user_access', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'weight' => 1, + 'type' => MENU_CALLBACK + ); + $items['admin/config/content/biblio/keywords'] = array( + 'title' => 'Keywords', + 'page callback' => 'biblio_keyword_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -7 + ); + $items['admin/config/content/biblio/keywords/list'] = array( + 'title' => 'List', + 'page callback' => 'biblio_keyword_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -7 + ); + $items['admin/config/content/biblio/keywords/%/edit'] = array( + 'title' => 'Edit keyword information', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_keyword_edit_form', 5), + 'access callback' => 'user_access', + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_CALLBACK, + 'weight' => -6 + ); + $items['admin/config/content/biblio/keywords/orphans'] = array( + 'title' => 'Orphaned Keywords', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_keyword_orphans_form'), + 'access arguments' => array('administer biblio'), + 'description' => 'Delete orphaned biblio keywords.', + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => -6 + ); + /* $items['admin/config/content/biblio/authors/reset'] = array( + 'title' => t('Reset all Author types to defaults'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_author_type_reset_form'), + 'access arguments' => array('administer biblio'), + 'file' => '/includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK + ); + */ + $items['biblio/autocomplete'] = array( + 'title' => 'Autocomplete ', + 'page callback' => 'biblio_autocomplete', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'type' => MENU_CALLBACK + ); +/* $items["$base/list"] = array( + 'title' => 'List', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10 + ); + $items["$base/filter"] = array( + 'title' => 'Filter', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_form_filter'), + 'access callback' => 'user_access', + 'access arguments' => array('show filter tab'), + 'type' => MENU_LOCAL_TASK, + 'file' => '/includes/biblio.pages.inc', + 'weight' => -9 + ); +*/ + $items["$base/filter/clear"] = array( + 'title' => '', + 'page callback' => 'biblio_filter_clear', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'type' => MENU_CALLBACK + ); + $items["$base/help"] = array( + 'title' => 'Help', + 'page callback' => 'biblio_help_page', + 'access callback' => 'user_access', + 'access arguments' => array('access biblio content'), + 'type' => MENU_CALLBACK + ); + $items["$base/export"] = array( + 'title' => '', + 'page callback' => 'biblio_export', + 'access callback' => 'user_access', + 'access arguments' => array('show export links'), + 'file' => '/includes/biblio.import.export.inc', + 'type' => MENU_CALLBACK + ); + $items["$base/citekey"] = array( + 'title' => '', + 'page callback' => 'biblio_citekey_view', + 'access arguments' => array('access biblio content'), + 'file' => '/includes/biblio.pages.inc', + 'type' => MENU_CALLBACK + ); + $items["$base/recent/rss.xml"] = array( + 'title' => 'RSS feed', + 'page callback' => 'biblio_recent_feed', + 'access callback' => 'biblio_access', + 'access arguments' => array('rss'), + 'type' => MENU_CALLBACK + ); + return $items; +} + +function biblio_filter_clear() { + $options = array(); + $_SESSION['biblio_filter'] = array(); + $base = variable_get('biblio_base', 'biblio'); + if (isset($_GET['sort'])) { + $options['sort'] = $_GET['sort']; + } + if (isset($_GET['order'])) { + $options['order'] = $_GET['order']; + } + drupal_goto($base, $options); +} + +function biblio_remove_brace($title_string) { + //$title_string = utf8_encode($title_string); + $matchpattern = '/\{\$(?:(?!\$\}).)*\$\}|(\{[^}]*\})/'; + $output = preg_replace_callback($matchpattern, 'biblio_remove_brace_callback', $title_string); + return $output; +} + +function biblio_remove_brace_callback($match) { + if (isset($match[1])) { + $braceless = str_replace('{', '', $match[1]); + $braceless = str_replace('}', '', $braceless); + return $braceless; + } + return $match[0]; +} + +function biblio_node_revision_delete($node) { + if ($node->type == 'biblio') { + db_delete('biblio') + ->condition('vid', $node->vid) + ->execute(); + + db_delete('biblio_contributor') + ->condition(db_and()->condition('nid', $node->nid)->condition('vid', $node->vid)) + ->execute(); + + db_delete('biblio_keyword') + ->condition(db_and()->condition('nid', $node->nid)->condition('vid', $node->vid)) + ->execute(); + } +} + +function biblio_node_insert($node) { + if ($node->type == 'biblio') { + if (variable_get('biblio_index', 0)) { + _node_index_node($node); + search_update_totals(); + } + } +} + +function biblio_node_update($node) { + if ($node->type == 'biblio') { + if (variable_get('biblio_index', 0)) { + // _node_index_node performs a node_load without resetting the node_load cache, + // so it would index the old version. We reset the cache here. + // Don't assign node_load to $node because node_load resets e.g. the menus mlid etc. + $mynode = node_load($node->nid, NULL, TRUE); + _node_index_node($mynode); + search_update_totals(); + } + } +} + +function biblio_node_view($node, $view_mode) { + if ($node->type == 'biblio') { + switch ($view_mode) { + case 'full': + if (variable_get('biblio_hide_bibtex_braces', 0) && !empty($a4)) { + drupal_set_title(filter_xss($node->title, biblio_get_allowed_tags())); + } + //fall through + case 'teaser': + $show_link = variable_get('biblio_lookup_links', array('google' => TRUE)); + if (!empty($show_link['google'])) { + $node->content['links']['biblio_google_scholar'] = array( + '#links' => array(theme('google_scholar_link', array('node' => $node))), + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } + } +} + +function biblio_query_node_access_alter(QueryAlterableInterface $query) { + global $user; + if (user_access('access biblio content', $user)) return; + $tables = $query->getTables(); + foreach ($tables as $alias => $table_info) { + if (!($table_info instanceof SelectQueryInterface)) { + if ( $table_info['table'] == 'node') { + $query->condition($alias .'.type', 'biblio', '<>'); + break; + } + } + } + +} + +function biblio_user_presave(&$edit, $account, $catagory) { + $keys = array_keys($edit); + foreach ($keys as $key) { + if (strpos($key, 'biblio_') !== FALSE && isset($edit[$key])) { + if (isset($account->data['biblio_id_change_count']) && $account->data['biblio_id_change_count'] > 2 + && $key == 'biblio_contributor_id' && $edit[$key] != 0 ) { + $edit[$key] = 0; + } + $edit['data'][$key] = $edit[$key]; + if ($key == 'biblio_contributor_id' ) { + if ($edit[$key] != 0 && $edit[$key] != $account->data[$key]) { + $edit['biblio_id_change_count']++; + } + db_update('biblio_contributor_data')->condition('drupal_uid', $account->uid)->fields(array('drupal_uid' => 0))->execute(); + db_update('biblio_contributor_data')->condition('cid', $edit['biblio_contributor_id'])->fields(array('drupal_uid' => $account->uid))->execute(); + } + } + } +} + +/** + * Implementation of hook_form(). + * + * Create the form for collecting the information + * specific to this node type. This hook requires us to return some HTML + * that will be later placed inside the form. + */ +function biblio_form($node, &$form_state) { + global $user; + $path = drupal_get_path('module', 'biblio'); + if (variable_get('biblio_button_hide', 1) == 1) { + drupal_add_js($path . '/misc/biblio.nodeformbuttonhide.js', 'file'); + } + $fields = array(); + $form['biblio_tabs'] = $tabs = array(); + + $tid = !empty($form_state['biblio_type']) ? $form_state['biblio_type'] : + ( isset($node->biblio_type) ? $node->biblio_type : 0); + + $step_two = !empty($tid); + + /* publication type */ + $param['options'] = array("enctype" => "multipart/form-data"); + $result = db_query('SELECT t.* FROM {biblio_types} as t WHERE tid > -2 AND visible = 1'); + foreach ($result as $option) { + $results[$option->tid] = $option->name; + } + asort($results); + $options[0] = t('Select Type...'); + $options += $results; + $form['biblio_type'] = array( + '#type' => 'select', + '#title' => t('Publication Type'), + '#default_value' => $tid, + '#options' => $options, + '#description' => NULL, + '#weight' => 2, + '#attributes' => array('onchange' => 'document.getElementById(\'edit-biblio-next\').click()'), + '#executes_submit_callback' => TRUE, + '#limit_validation_errors' => array(), + '#multiple' => FALSE, + '#required' => TRUE + ); + + $form['biblio_next'] = array( + '#type' => 'submit', + '#value' => $step_two ? t('Change Publication Type') : t('Next'), + '#limit_validation_errors' => array(), + '#weight' => -10, + '#submit' => array(), + ); + if (isset($_COOKIE['has_js']) && !$_COOKIE['has_js']) { + unset($form['biblio_next']['#attributes']); + } + + if ($step_two) { + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Title'), + '#required' => TRUE, + '#default_value' => trim((isset($form_state['values']['title'])?$form_state['values']['title']: $node->title)), + '#maxlength' => 255, + '#size' => 120, + '#weight' => 1 + ); + // Build the field array used to make the form + $result = db_query("SELECT * FROM {biblio_fields} b + INNER JOIN {biblio_field_type} bt ON b.fid = bt.fid + INNER JOIN {biblio_field_type_data} btd ON btd.ftdid=bt.ftdid + WHERE bt.tid=:tid ORDER BY bt.weight ASC", array(':tid' => $tid), array('fetch' => PDO::FETCH_ASSOC)); + + foreach ($result as $row) { + $fields[$row['name']] = $row; + } + _biblio_localize_fields($fields); + + $tabs = array( + '#type' => 'vertical_tabs', + '#weight' => 10, + ); + $tabs += biblio_node_form_vtabs(); + + $tabs['biblio_authors'] = array( + '#type' => 'fieldset', + '#group' => 'biblio_tabs', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => 'Authors', + '#description' => t('Enter a single name per line using a format such as "Smith, John K" or "John K Smith" or "J.K. Smith"'), + ); + + $tabs['biblio_authors'] += biblio_contributor_widget($node, $form_state); + + $form_state['biblio_fields'] = $fields; + + foreach ($fields as $key => $fld) { + $options = ''; + if ($key == 'biblio_keywords' ) { + $sep = check_plain(variable_get('biblio_keyword_sep', ',')); + // is the kewords are in array form, then implode them into a string. + if (isset($form_state['values']['biblio_keywords']) && + is_array($form_state['values']['biblio_keywords'])) { + require_once(drupal_get_path('module', 'biblio') . '/includes/biblio.keywords.inc'); + $form_state['values']['biblio_keywords'] = biblio_implode_keywords($form_state['values']['biblio_keywords']); + } + if (!empty($node->$key) && is_array($node->$key)) { + require_once(drupal_get_path('module', 'biblio') . '/includes/biblio.keywords.inc'); + $node->$key = biblio_implode_keywords($node->$key); + } + if (empty($fld['hint'])) { + $fld['hint'] = t('Separate keywords using the " @sep " character', array('@sep' => $sep)); + } + } + + $element = array( + '#default_value' => (isset($form_state['values'][$key]) ? $form_state['values'][$key] : (isset($node->$key)?$node->$key:'')), + '#type' => $fld['type'], + '#title' => check_plain($fld['title']), + '#size' => $fld['size'], + '#rows' => 10, +// '#required' => $fld['required'], + '#maxlength' => $fld['maxsize'], + '#weight' => $fld['weight'] / 10, + '#autocomplete_path' => ($fld['autocomplete']) ? 'biblio/autocomplete/' . $fld['name'] : '', + '#description' => check_plain($fld['hint']), + '#format' => isset($node->biblio_formats[$key]) ? $node->biblio_formats[$key] : filter_default_format(), + ); + + if ($key == 'biblio_refereed' ) { + $element['#options'] = array( + '' => t('None'), + 'Refereed' => t('Refereed'), + 'Non-Refereed' => t('Non-Refereed'), + 'Does Not Apply' => t('Does Not Apply'), + 'Unknown' => t('Unknown'), + ); + $element['#description'] = t('If you are not sure, set this to Unknown or Does Not Apply'); + } + + if ( $fld['common'] || $fld['visible'] ) { + $tabs[$fld['vtab']][$key] = $element; + } + + } + } + foreach (element_children($tabs) as $key) { + $tab_children = element_children($tabs[$key]); + if (empty($tab_children) && $key != 'biblio_full_text') { + unset($tabs[$key]); + } + } + // $form['format'] = filter_form($node->format, 20); + //$biblio_form['#tree'] = TRUE; + $form['#validate']= array('biblio_node_form_validate'); + $form['#cache'] = TRUE; + $form['biblio_tabs'] += $tabs; + + return $form; +} + +function biblio_node_form_vtab_info() { + return array( + array('tab_id' => 1, 'weight' => 10, 'title' => 'Abstract', 'description' => ''), + array('tab_id' => 'biblio_full_text', 'weight' => 11, 'title' => 'Full text', 'description' => ''), + array('tab_id' => 2, 'weight' => 12, 'title' => 'Publication', 'description' => ''), + array('tab_id' => 3, 'weight' => 13, 'title' => 'Publisher', 'description' => ''), + array('tab_id' => 4, 'weight' => 14, 'title' => 'Identifiers', 'description' => ''), + array('tab_id' => 5, 'weight' => 15, 'title' => 'Locators', 'description' => 'URL\'s etc'), + array('tab_id' => 6, 'weight' => 16, 'title' => 'Keywords', 'description' => ''), + array('tab_id' => 7, 'weight' => 17, 'title' => 'Notes', 'description' => ''), + array('tab_id' => 8, 'weight' => 18, 'title' => 'Alternate Titles', 'description' => ''), + array('tab_id' => 9, 'weight' => 19, 'title' => 'Other', 'description' => ''), + ); +} + +function biblio_node_form_vtabs() { + $vtabs = biblio_node_form_vtab_info(); + + foreach ($vtabs as $tab) { + $form[$tab['tab_id']] = array( + '#type' => 'fieldset', + '#group' => 'biblio_tabs', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t($tab['title']), + '#description' => '', + '#weight' => $tab['weight'] + ); + } + return $form; +} +function biblio_contributor_widget($node, &$form_state) { + $init_count = variable_get('biblio_init_auth_count', 4); + $contributor_count = 0; + if (isset($form_state['values']['biblio_contributors'])) { + $contributors = $form_state['values']['biblio_contributors']; + } + elseif (isset($node->biblio_contributors)) { + $contributors = $node->biblio_contributors; + } + else { + $contributors = array(); + } + + $ctypes = db_query('SELECT * FROM {biblio_contributor_type_data}'); + + foreach ($ctypes as $ctype ) { + $options['roles'][$ctype->auth_type] = $ctype->title; + } + $options['categories'] = array( + 1 => t('Primary'), + 2 => t('Secondary'), + 3 => t('Tertiary'), + 4 => t('Subsidiary'), + 5 => t('Corporate/Institutional') + ); + + // Container for just the contributors. + $wrapper = array(); + $wrapper['biblio_contributors'] = array( + '#tree' => TRUE, + '#theme' => 'biblio_contributors', + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + foreach ($contributors as $author) { + $wrapper['biblio_contributors'][] = biblio_contributor_form($author, $options); + $contributor_count++; + } + + if (isset($form_state['biblio_contrib_count'])) { + $form_count = max(max($init_count, $contributor_count), $form_state['biblio_contrib_count']); + } + else { + $form_count = max($init_count, $contributor_count); + $form_state['biblio_contrib_count'] = $form_count; + } + if ($form_count > $contributor_count) { + $author = array(); + for ($i = 0; $i < ($form_count - $contributor_count); $i++) { + $wrapper['biblio_contributors'][] = biblio_contributor_form($author, $options); + } + } + $wrapper['add_more'] = array( + '#type' => 'submit', + '#value' => t('More contributors'), + '#description' => t("If there aren't enough boxes above, click here to add more."), + '#weight' => 1, + '#submit' => array('biblio_contributors_add_more'), // If no javascript action. + '#limit_validation_errors' => array(), + '#ajax' => array( + 'callback' => 'biblio_contributors_add_more_callback', + 'wrapper' => 'biblio-contributors-wrapper', + ), + ); + + return $wrapper; +} + +function biblio_contributor_form($contributor, $options) { + $form = array('#tree' => TRUE); + + $form['name'] = array( + '#type' => 'textfield', + '#maxlength' => 255, + '#autocomplete_path' => 'biblio/autocomplete/contributor', + '#default_value' => isset($contributor['name']) ? $contributor['name'] : '', + ); + $form['auth_category'] = array( + '#type' => 'select', + '#default_value' => isset($contributor['auth_category']) ? $contributor['auth_category'] : '', + '#options' => $options['categories'], + '#multiple' => FALSE, + ); + $form['auth_type'] = array( + '#type' => 'select', + '#default_value' => isset($contributor['auth_type']) ? $contributor['auth_type'] : '', + '#options' => $options['roles'], + '#multiple' => FALSE, + ); + $form['cid'] = array( + '#type' => 'hidden', + '#default_value' => isset($contributor['cid']) ? $contributor['cid'] : '', + ); + $form['rank'] = array( + '#type' => 'textfield', + '#size' => 6, + '#default_value' => isset($contributor['rank']) ? $contributor['rank'] : '', + ); + return $form; +} + +function biblio_contributors_add_more_callback($form, &$form_state) { + return $form['biblio_tabs']['biblio_authors']['biblio_contributors']; +} + +function biblio_contributors_add_more($form, &$form_state) { + $form_state['biblio_contrib_count'] += variable_get('biblio_contrib_fields_delta', 2); + $form_state['rebuild'] = TRUE; +} + +function biblio_form_node_form_alter(&$form, &$form_state, $form_id) { + if ($form_id == 'biblio_node_form') { + $form['#pre_render'][] = 'biblio_node_form_pre_render'; + if (!isset($form['biblio_tabs']['biblio_full_text']) && isset($form['body'])) { + unset($form['body']); + } + if (empty($form_state['biblio_type']) && + empty($form['vid']['#value'])) { + foreach (element_children($form) as $form_element) { + if (strstr($form_element, 'biblio_')) continue; + if (strstr($form_element, 'form_')) continue; + if (isset($form[$form_element]['#type']) && $form[$form_element]['#type'] != 'value') { + $form[$form_element]['#access'] = FALSE; + } + } + } + + } +} +function biblio_node_form_pre_render($form) { + if (isset($form['biblio_tabs']['biblio_full_text']) && isset($form['body'])) { + $form['biblio_tabs']['biblio_full_text']['body'] = $form['body']; + unset($form['body']); + } + return $form; +} +/** + * Implementation of hook_validate(). + * + * + * Errors should be signaled with form_set_error(). + */ +function biblio_node_form_validate($form, &$form_state) { + if ($form_state['triggering_element']['#value'] == t('Next') + || $form_state['triggering_element']['#value'] == t('Change Publication Type')) { + $form_state['rebuild'] = TRUE; + $form_state['biblio_type'] = $form_state['values']['biblio_type']; + if ($form_state['values']['biblio_type'] == 0) { + form_set_error('biblio_type', t('Please select a publication type.')); + } + return; + } + $format = new stdClass(); + foreach (_biblio_get_formatted_fields() as $field) { + if (isset($form_state['values'][$field]['format'])) { + $format->format = $form_state['values'][$field]['format']; + if (!filter_access($format)) { + form_set_error($field, t('You do not have access to the !format format', array('!format' => $format->format))); + } + } + } + if (isset($form_state['values']['biblio_keywords'])) { + require_once(drupal_get_path('module', 'biblio') . '/includes/biblio.keywords.inc'); + if (!is_array($form_state['values']['biblio_keywords'])) { + $form_state['values']['biblio_keywords'] = biblio_explode_keywords($form_state['values']['biblio_keywords']); + } + foreach ($form_state['values']['biblio_keywords'] as $keyword) { + if (strlen($keyword) > 255) { + form_set_error('biblio_keywords', t('No single keyword can be greater than 255 characters in length, the word: @kw exceeds this length', array('@kw' => $keyword ))); + } + } + } + + + if (isset($form_state['biblio_fields'])) { + $vtabs = biblio_node_form_vtab_info(); + foreach($vtabs as $tab) { + $tabs[$tab['tab_id']] = $tab['title']; + } + + foreach ($form_state['biblio_fields'] as $key => $fld) { + if ($fld['required'] && isset($form_state['values'][$key]) && empty($form_state['values'][$key])) { + $tab = $tabs[$fld['vtab']]; + form_set_error($key, t('The @fld field (on the @tab tab) is required', array('@fld' => $fld['title'], '@tab' => $tab))); + } + } + } + +} + +function _biblio_numeric_year($year) { + if (!is_numeric($year)) { + if (drupal_strtoupper($year) == drupal_strtoupper(t("In Press"))) return 9998; + if (drupal_strtoupper($year) == drupal_strtoupper(t("Submitted"))) return 9999; + } + else { + return $year; + } +} + +function _biblio_text_year($year) { + if ($year == 9998) return check_plain(variable_get('biblio_inpress_year_text', t('In Press'))); + if ($year == 9999) return check_plain(variable_get('biblio_no_year_text', t('Submitted'))); + return $year; +} + +function _biblio_get_formatted_fields() { + $fields = &drupal_static(__FUNCTION__); + if (!isset($fields)) { + $query = db_select('biblio_fields', 'bf'); + $result = $query->fields('bf', array('name')) + ->condition('type', 'text_format') + ->execute(); + foreach ($result as $field) { + $fields[] = $field->name; + } + } + return (array) $fields; +} +/** + * Prepare a node for submit to database. Contains code common to insert and update. + * @param $node + * @return none + */ +function _biblio_prepare_submit(&$node) { + $node->biblio_sort_title = biblio_normalize_title($node->title); + if(!isset($node->biblio_year)) $node->biblio_year = 9999; + $node->biblio_year = _biblio_numeric_year($node->biblio_year); + + if (variable_get('biblio_auto_citekey', 1) && empty($node->biblio_citekey)) { + $node->biblio_citekey = biblio_citekey_generate($node); + } + foreach (_biblio_get_formatted_fields() as $field) { + if (isset($node->$field) && is_array($node->$field)) { + $node->biblio_formats[$field] = $node->{$field}['format']; + $node->$field = $node->{$field}['value']; + } + else { + $node->biblio_formats[$field] = filter_default_format(); + } + } +} +/** + * Implementation of hook_insert(). + * + * As a new node is being inserted into the database, we need to do our own + * database inserts. + */ +function biblio_insert($node) { + module_load_include('inc', 'biblio', 'includes/biblio.util'); + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + module_load_include('inc', 'biblio', 'includes/biblio.keywords'); + _biblio_prepare_submit($node); + biblio_insert_contributors($node); + biblio_insert_keywords($node); + $node->biblio_coins = biblio_coins($node); + $duplicate = biblio_hash($node); + drupal_write_record('biblio', $node); + if (isset($duplicate) && $duplicate != $node->nid) { // if this is a potential duplcate, write the nids of the pre-existing and new nodes + $dup_map = array('vid' => $duplicate, 'did' => $node->nid); + drupal_write_record('biblio_duplicates', $dup_map); + } + +} +/** + * Implementation of hook_update(). + * + * As an existing node is being updated in the database, we need to do our own + * database updates. + */ +function biblio_update($node) { + module_load_include('inc', 'biblio', 'includes/biblio.util'); + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + module_load_include('inc', 'biblio', 'includes/biblio.keywords'); + _biblio_prepare_submit($node); + biblio_update_contributors($node); + biblio_update_keywords($node); + $node->biblio_coins = biblio_coins($node); + + // Update the node in the database: + if (!empty($node->revision)) { + drupal_write_record('biblio', $node); + } + else { + drupal_write_record('biblio', $node, 'vid'); + } + + +} +/** + * Implementation of hook_delete(). + * + * When a node is deleted, we need to clean up related tables. + */ +function biblio_delete($node) { + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + module_load_include('inc', 'biblio', 'includes/biblio.keywords'); + //first remove data from the biblio table + + db_delete('biblio') + ->condition('nid', $node->nid) + ->execute(); + //now remove the entries from the contributor linking table + biblio_delete_contributors($node); + biblio_delete_keywords($node); +} +/** + * Implementation of hook_load(). + * + * This hook is called + * every time a node is loaded, and allows us to do some loading of our own. + * + */ +function biblio_load($nodes) { + module_load_include('inc', 'biblio', 'includes/biblio.util'); + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + module_load_include('inc', 'biblio', 'includes/biblio.keywords'); + $vids = array(); + foreach ($nodes as $nid => $node) $vids[] = $node->vid; + + $result = db_query('SELECT b.*, bt.name as biblio_type_name + FROM {biblio} b + LEFT JOIN {biblio_types} bt on b.biblio_type = bt.tid + WHERE b.vid IN (:vids)', array(':vids' => $vids), array('fetch' => PDO::FETCH_ASSOC)); + $contributors = biblio_load_contributors_multiple($vids); + $keywords = biblio_load_keywords_multiple($vids); + + foreach ($result as $record) { + if ((isset($record['biblio_url']) || isset($record['biblio_accession_number'])) && + variable_get('biblio_fix_isi_links', 0)) { + biblio_fix_isi_links($record); + } + + foreach ($record as $key => $value) { + $nodes[$record['nid']]->$key = $value; + } + + $nodes[$record['nid']]->biblio_year = _biblio_text_year($record['biblio_year']); + $nodes[$record['nid']]->biblio_contributors = isset($contributors[$record['vid']]) ? $contributors[$record['vid']] : array(); + $nodes[$record['nid']]->biblio_keywords = isset($keywords[$record['vid']]) ? $keywords[$record['vid']] : array(); + + if (empty($record['biblio_coins'])) { + $nodes[$record['nid']]->biblio_coins = biblio_coins($nodes[$record['nid']]); + } + if ($record['biblio_formats'] != NULL) { + $nodes[$record['nid']]->biblio_formats = unserialize($record['biblio_formats']); + } + else { + $nodes[$record['nid']]->biblio_formats = array(); + } + + } +} +function biblio_citekey_generate($node) { + $php = check_plain(variable_get('biblio_citekey_phpcode', '')); + if (empty($php)) { + $prefix = variable_get('biblio_citekey_prefix', ''); + $primary_field = variable_get('biblio_citekey_field1', 'nid'); + $secondary_field = variable_get('biblio_citekey_field2', 'nid'); + $citekey = (!empty($node->$primary_field)) ? $node->$primary_field : ((!empty($node-> $secondary_field)) ? $node-> $secondary_field : $node->nid); + return check_plain($prefix . $citekey); + } + else { + ob_start(); + $return = eval($php); + ob_end_clean(); + return check_plain(strip_tags((string)$return)); + } +} + +/** + * Implementation of hook_view(). + * + */ +function biblio_view($node, $buildmode = 'full') { + global $user; + $links = array(); + $style = biblio_get_style(); + $base = variable_get('biblio_base', 'biblio'); + $base_title = check_plain(variable_get('biblio_base_title', 'Biblio')); + + switch ($buildmode) { + case 'full': + case 'print': + if (variable_get('biblio_hide_bibtex_braces', 0) && !isset($node->view )) { + $node->title = biblio_remove_brace($node->title); + drupal_set_title(filter_xss($node->title, biblio_get_allowed_tags())); + } // fall through... + case 'search_index': + switch (variable_get('biblio_node_layout', 'tabular')) { + case 'orig' : + case 'ft' : + $node->content['body']['#markup'] = theme('biblio_long', array('node' => $node, 'base' => $base, 'style_name' => $style)); + break; + case 'cite': + $node->content['body']['#markup'] = theme('biblio_style', array('node' => $node, 'base' => $base, 'style_name' => $style)); + break; + case 'tabular' : + default : + $node->content['body']['#markup'] = theme('biblio_tabular', array('node' => $node, 'base' => $base)); + break; + } + break; + case 'teaser': + $node->content['teaser']['#markup'] = theme('biblio_style', array('node' => $node, 'base' => $base, 'style_name' => $style)); + break; + } + + return $node; +} +/** + * Implementation of hook_block(). + * + * Generates a block containing the latest poll. + */ +function biblio_block_info() { + $blocks['recent'] = array( + 'info' => t('Recent publications'), + ); + return $blocks; +} + +function biblio_block_configure($delta = '') { + $form = array(); + $form['block'] = array( + '#type' => 'fieldset', + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#title' => t('Options'), + '#description' => '', + ); + $form['block']['biblio_rowsperblock'] = array( + '#type' => 'textfield', + '#title' => t('Number of results in the "Recent Publications" block'), + '#default_value' => variable_get('biblio_rowsperblock', 4), + '#size' => 2, + '#maxlength' => 2, + '#description' => t('This sets the number of results that will be displayed in the "New Publications" block.') + ); + $form['block']['biblio_block_order'] = array( + '#type' => 'radios', + '#title' => t('Order by'), + '#default_value' => variable_get('biblio_block_order', 'n.created'), + '#options' => array( + 'n.created' => t('Date Created'), + 'b.biblio_year' => t('Year Published') + ) + ); + return $form; +} + +function biblio_block_save($delta = '', $edit = array()) { + if ($delta == 'recent') { + variable_set('biblio_rowsperblock', $edit['biblio_rowsperblock']); + variable_set('biblio_block_order', $edit['biblio_block_order']); + } +} + +function biblio_block_view($delta = '') { + switch ($delta) { + case 'recent': + $num_in_block = variable_get('biblio_rowsperblock', 4); + $block_order = variable_get('biblio_block_order', 'n.created'); + + $query = db_select('node', 'n') + ->fields('n', array('nid', 'title')) + ->condition(db_and() + ->condition('n.type', 'biblio') + ->condition('n.status', 1)) + ->orderBy($block_order, 'DESC') + ->range(0, $num_in_block); + if ($block_order == 'b.biblio_year') { + $query->leftJoin('biblio', 'b', 'n.vid=b.vid'); + } + $result = $query->execute(); + + $base = variable_get('biblio_base', 'biblio'); + $block['subject'] = t('Recent Publications'); + $block['content'] = '
      '; + $options['html'] = TRUE; + + foreach ($result as $pub) { + $block['content'] .= '
    • ' . l(filter_xss($pub->title, biblio_get_allowed_tags()), "node/$pub->nid", $options) . '
    • '; + } + $block['content'] .= '
    '; + if (variable_get('biblio_rss', 0)) { + $block['content'] .= theme('feed_icon', array( + 'url' => url("$base/recent/rss.xml", array('absolute' => TRUE)), + 'title' => t('Recent Publications'))); + } + $block['content'] .= l(t('More...'), $base); + $block['content'] .= '
    '; + + return $block; + break; + } +} + +function biblio_recent_feed() { + $numberInFeed = variable_get('biblio_rss_number_of_entries', 10); + + $query = db_select('node', 'n') + ->fields('n', array('nid', 'title')) + ->condition(db_and() + ->condition('n.type', 'biblio') + ->condition('n.status', 1)) + ->orderBy('n.created', 'DESC') + ->range(0, $numberInFeed); + $result = $query->execute(); + + $siteName = variable_get('site_name', 'Drupal'); + $base = variable_get('biblio_base', 'biblio'); + + $channel['title'] = $siteName . ' - ' . t("Recently Added Publications"); + $channel['link'] = url($base, array('absolute' => TRUE)); + $channel['description'] = t("This feed lists the %num most recently added publications on %site", array('%num' => $numberInFeed, '%site' => $siteName)); + $nids = array(); + + foreach ($result as $row) { + $nids[] = $row->nid; + } + node_feed($nids, $channel); +} + +function biblio_filter_feed($rss_info, $nids) { + $base = variable_get('biblio_base', 'biblio'); + $channel['title'] = $rss_info['title']; + $channel['link'] = url($base . $rss_info['link'], array('absolute' => TRUE)); + $channel['description'] = $rss_info['description']; + node_feed($nids, $channel); +} + +function biblio_get_db_fields() { + $fields = array(); + $fields[] = 'nid'; + $fields[] = 'vid'; + $fields[] = 'biblio_type'; + $result = db_query('SELECT name FROM {biblio_fields} ', array(), array('fetch' => PDO::FETCH_ASSOC)); + foreach ($result as $field ) { + $fields[] = $field['name']; + } + return $fields; +} + +/******************************************* + * Filter + * Largely inspired from the footnote module + * + *******************************************/ +function _biblio_citekey_print($citekey) { + + $nid = db_query("SELECT nid FROM {biblio} WHERE biblio_citekey = :key", array(':key' => $citekey))->fetchField(); + if ($nid) { + $style = biblio_get_style(); + $base = variable_get('biblio_base', 'biblio'); + $node = node_load($nid); + return theme('biblio_style', array('node' => $node, 'base' => $base, 'style' => $style)); + } + else { + return t("Citekey @cite not found", array('@cite' => $citekey)); + } +} +function biblio_filter_info() { + $filters['biblio_filter_reference'] = array( + 'title' => t('Biblio module references <bib> or [bib]'), + 'description' => t('Use <bib>citekey</bib> or [bib]citebkey[/bib]to insert automatically numbered references.'), + 'prepare callback' => '_biblio_filter_reference_prepare', + 'process callback' => '_biblio_filter_reference_process', + 'tips callback' => '_biblio_filter_reference_tips', + ); + $filters['biblio_filter_inline_reference'] = array( + 'title' => t('Biblio module inline references <ibib> or [ibib]'), + 'description' => t('Use <bib>citekey</bib> or [bib]citebkey[/bib]to insert automatically numbered references.'), + 'prepare callback' => '_biblio_filter_inline_reference_prepare', + 'process callback' => '_biblio_filter_inline_reference_process', + 'tips callback' => '_biblio_filter_inline_reference_tips', + ); + return $filters; +} + +function _biblio_filter_reference_tips($filter, $format, $long = FALSE) { + if (!$long) { + // This string will be shown in the content add/edit form + return t('Use <bib>citekey</bib> or [bib]citekey[/bib] to insert automatically numbered references.'); + } + else { + return t('You can cite references directly into texts with <bib>citekey</bib> or [bib]citekey[/bib]. This will be replaced with a running number (the publication reference) and the publication referenced by the citekey within the <bib> tags will be printed at the bottom of the page (the reference).'); + } +} + +function _biblio_filter_inline_reference_tips($filter, $format, $long = FALSE) { + return t('This creates an in line reference to another publication.'); +} + +function _biblio_filter_reference_prepare($text, $filter, $format, $langcode, $cache, $cache_id) { + return $text; +} + +function _biblio_filter_inline_reference_prepare($text, $filter, $format, $langcode, $cache, $cache_id) { + return $text; +} + +function _biblio_filter_reference_process($text, $filter, $format, $langcode, $cache, $cache_id) { + $pattern = array('|\[bib](.*?)\[/bib]|s', '|(.*?)|s'); + if (variable_get('biblio_footnotes_integration', 0) && module_exists('footnotes')) { // this is used with footnote module integration to replace the tags with tags + $text = preg_replace_callback($pattern, '_biblio_filter_footnote_callback', $text); + return $text; + } + else { + $text = preg_replace_callback($pattern, '_biblio_filter_replace_callback', $text); + //Replace tag with the list of footnotes. + //If tag is not present, by default add the footnotes at the end. + //Thanks to acp on drupal.org for this idea. see http://drupal.org/node/87226 + $footer = ''; + $footer = _biblio_filter_replace_callback(NULL, 'output footer'); + if (preg_match('//', $text) > 0) { + $text = preg_replace('//', $footer, $text, 1); + return $text; + } + else { + return $text . "\n\n" . $footer; + } + } +} + +function _biblio_filter_inline_reference_process($text, $filter, $format, $langcode, $cache, $cache_id) { + $pattern = array('|\[ibib](.*?)\[/ibib]|s', '|(.*?)|s'); + $text = preg_replace_callback($pattern, '_biblio_inline_filter_replace_callback', $text); + return $text; +} + +function _biblio_inline_filter_replace_callback($matches) { + $text = _biblio_citekey_print($matches[1]) ; + return $text; +} + +function _biblio_filter_footnote_callback($matches, $square_brackets = FALSE) { + if ($square_brackets) { + $text = '[fn]' . _biblio_citekey_print($matches[1]) . ""; + } + else { + $text = '' . _biblio_citekey_print($matches[1]) . ""; + } + return $text; +} +/** + * Helper function called from preg_replace_callback() above + * + * Uses static vars to temporarily store footnotes found. + * In my understanding, this is not threadsafe?! + */ +function _biblio_filter_replace_callback($matches, $op = '') { + static $n = 0; + static $store_matches = array(); + $str = ''; + if ($op == 'output footer') { + if ($n > 0) { + $str = '

    ' . t('References') . '

    '; + $str .= '
      '; + for ($m = 1; $m <= $n; $m++) { + $str .= '
    1. ' . _biblio_citekey_print($store_matches[$m -1]) . "
    2. \n\n"; + } + $str .= '
    '; + } + $n = 0; + $store_matches = array(); + return $str; + } + //default op: act as called by preg_replace_callback() + $ref = array_search($matches[1], $store_matches); + if ($ref === FALSE) { + $n++; + array_push($store_matches, $matches[1]); //$stores_matches[$matches[1]] = $n; + $ref = $n; + } + else { + $ref++; + } + $allowed_tags = array(); + $title = filter_xss($matches[1], biblio_get_allowed_tags()); + //html attribute cannot contain quotes + $title = str_replace('"', """, $title); + //remove newlines. Browsers don't support them anyway and they'll confuse line break converter in filter.module + $title = str_replace("\n", " ", $title); + $title = str_replace("\r", "", $title); + //return ''. $n .''; + //$text = '['. $n .'] '; + //$text = '['. $n .']'; + //$text .= ''._biblio_citekey_print($title) .''; + $text = '[' . $ref . ']'; + if (module_exists('hovertip')) { + $text = '[' . $ref . ']'; + $text .= '' . _biblio_citekey_print($title) . ''; + } + else { + $text = '[' . $ref . ']'; + + } + return $text; +} + +function hook_taxonomy_vocabulary_delete($vocabulary) { + if ($vocabulary->vid == variable_get('biblio_keyword_vocabulary', FALSE)) { + variable_del('biblio_keyword_freetagging'); + variable_del('biblio_keyword_vocabulary'); + } +} + +function biblio_term_path($term) { + $base = variable_get('biblio_base', 'biblio'); + if ($term->vid == variable_get('biblio_collection_vocabulary', 0) ) { + return ("$base/collection/$term->name"); + } + elseif ($term->vid == variable_get('biblio_keyword_vocabulary', 0) ) { + return ("$base/term_id/$term->tid"); + } + else return; +} + +function biblio_hash($node) { + static $sums = array(); + $duplicate = NULL; + if (empty($sums)) { + $res = db_query("SELECT nid, biblio_md5 FROM {biblio} "); + foreach ($res as $md5) { + $sums[$md5->biblio_md5] = $md5->nid; + } + } + + $hash_string = str_replace(' ', '', drupal_strtolower($node->title)); + if (isset($node->biblio_contributors[0]['lastname'])) { + $hash_string .= str_replace(' ', '', drupal_strtolower($node->biblio_contributors[0]['lastname'])); + } + $hash_string .= $node->biblio_year; + + $sum = md5($hash_string); + + if (isset ($sums[$sum])) { + $duplicate = $sums[$sum]; + } + else { + $sums[$sum] = $node->nid; + } + $node->biblio_md5 = $sum; + return $duplicate; //return the nid of the potential duplicate +} + +function _biblio_profile_access($user, $type = 'profile') { + if ($type == 'profile') { + $key = 'biblio_show_profile'; + } + elseif ($type == 'menu' && !empty($user) && $user->uid > 0) { + $key = 'biblio_my_pubs_menu'; + } + else { + return FALSE; + } + // if user cannot override site settings or user hasn't yet made its selection, we use site default + if (!variable_get('biblio_show_user_profile_form', '1') || !isset($user->data[$key])) { + return variable_get($key, '0'); // return site default + } + else { + return $user->data[$key]; // return user setting + } +} +/* + * Helper function to get either the user or system style + */ +function biblio_get_style() { + global $user; + if (isset($user->data['biblio_user_style']) && $user->data['biblio_user_style'] != "system") { + return $user->data['biblio_user_style']; + } + return module_exists('biblio_citeproc') ? variable_get('biblio_citeproc_style', 'ieee.csl') : variable_get('biblio_style', 'cse'); +} + +function biblio_get_styles() { + $styles = array(); + if (module_exists('biblio_citeproc')) { + $result = db_select('biblio_citeproc_styles', 'csl') + ->fields('csl', array('filename', 'title')) + ->orderBy('title', 'ASC') + ->execute(); + foreach ($result as $style) { + $styles[$style->filename] = $style->title; + } + } + else { + $dir = drupal_get_path('module', 'biblio') . '/styles'; + $files = file_scan_directory($dir, '/biblio_style_..*.inc$/'); + foreach ($files as $file) { + include_once $file->uri; + $function = $file->name . '_info'; + if (function_exists($function)) { + $styles = array_merge($styles, call_user_func($function)); //build and array of the short and long names + } + } + ksort($styles); + } + return $styles; +} + +/** + * Implementation of hook_views_api(). + */ +function biblio_views_api() { + return array( + 'api' => 2, + 'path' => drupal_get_path('module', 'biblio') . '/views', + ); +} + +function biblio_fix_isi_links(&$node) { + $isi = check_plain(variable_get('biblio_isi_url', 'http://apps.isiknowledge.com/InboundService.do?Func=Frame&product=WOS&action=retrieve&SrcApp=EndNote&Init=Yes&SrcAuth=ResearchSoft&mode=FullRecord&UT=')); + if (isset($node['biblio_url']) && preg_match ('/Go\s*to\s*ISI/', $node['biblio_url'])) { + $node['biblio_url'] = str_replace('://', $isi, $node['biblio_url']); + } + if (isset($node['biblio_accession_number']) && preg_match ('/^ISI:/', $node['biblio_accession_number'])) { + $node['biblio_accession_number'] = str_replace("ISI:", $isi, $node['biblio_accession_number']); + } +} + +function biblio_get_allowed_tags() { + return array('a', 'b', 'i', 'u', 'sub', 'sup', 'span'); +} + +function biblio_get_title_url_info($node) { + return array('link' => ((variable_get('biblio_link_title_url', 0) && !empty($node->biblio_url)) ? $node->biblio_url : "node/$node->nid" ), + 'options' => + array('attributes' => (variable_get('biblio_links_target_new_window', FALSE)) ? array('target' => '_blank') : array(), + 'html' => TRUE), + ); +} + + +/** + * @param string $type (can be one of "type_names", "type_map" or "field_map") + * @param string $format (tagged, ris, endnote_xml8 etc...) + * @return array $map + */ +function biblio_get_map($type, $format) { + $result = db_select('biblio_type_maps', 'btm') + ->fields('btm', array($type)) + ->condition('format', $format) + ->execute() + ->fetchField(); + + $map = unserialize($result); + + if ($type == 'export_map' && empty($map)) { + $schema = drupal_get_schema('biblio'); + $fieldnames = array_keys($schema['fields']); + asort($fieldnames); + $map = array_fill_keys($fieldnames, 1); + } + + drupal_alter('biblio_map', $map, $type, $format); + return $map; +} + +function biblio_save_map($maps) { + db_insert('biblio_type_maps') + ->fields($maps) + ->execute(); +} +/** + * @param string $type (can be one of "type_names", "type_map" or "field_map") + * @param string $format (tagged, ris, endnote_xml8 etc...) + * @param array $map + */ +function biblio_set_map($type, $format, $map) { + $map[$type] = serialize($map); + $map['format'] = $format; + drupal_write_record('biblio_type_maps', $map, 'format'); +} + +function biblio_reset_map($type, $format) { + module_invoke_all($format . '_map_reset', $type); +} + +function biblio_field_extra_fields() { + module_load_include('inc', 'biblio', 'includes/biblio.fields'); + return _biblio_field_extra_fields(); +} + +//Fixes node export importing issue #826506 +function biblio_node_export_node_alter($node, $original_node) { + if ($node->type == 'biblio' && isset($node->biblio_contributors) && is_array($node->biblio_contributors)) { + foreach ($node->biblio_contributors as $n => $value) { + unset($node->biblio_contributors[$n]['cid']); + } + } +} + +function biblio_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { + module_load_include('inc', 'biblio', 'includes/biblio.feeds'); + return _biblio_feeds_processor_targets_alter($targets, $entity_type, $bundle_name); +} + +function biblio_feeds_importer_default() { + module_load_include('inc', 'biblio', 'includes/biblio.feeds'); + $defaults = array(); + if (module_exists('feeds_oai_pmh')) { + $defaults += _biblio_feeds_oai_importer_default(); + } + + return $defaults; + } + +function biblio_ctools_plugin_api() { + list($module, $api) = func_get_args(); + if ($module == "feeds" && $api == "feeds_importer_default") { + return array("version" => 1); + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/biblio.tokens.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/biblio.tokens.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,89 @@ + '', 'vid' => '', 'biblio_formats' => '')); + $node_token['biblio']= array( + 'name' => t('Biblio'), + 'description' => t('Tokens related to the Biblio content type.'), + 'type' => 'biblio' + ); + foreach ($fields as $key => $value) { + $name = str_replace('biblio_', '', $key); + $name = str_replace('_', ' ', $name); + $name = ucwords($name); + $biblio_tokens[$key] = array( + 'name' => t($name), + 'description' => (isset($value['description'])) ? t("!desc" , array('!desc' => $value['description'])) : '', + ); + } + + $biblio_tokens['biblio_first_author'] = array( + 'name' => t("Author - First"), + 'description' => 'First author of the publication', + ); + $biblio_tokens['biblio_type_name'] = array( + 'name' => t("Type Name"), + 'description' => t('The name of the publication type (i.e. Journal, Book, etc.'), + ); + + + $types['biblio'] = array( + 'name' => t('Biblio'), + 'description' => t('Tokens related to Biblio node type.'), + 'needs-data' => 'node', + ); + +/* + $types['biblio-authors'] = array( + 'name' => t('Biblio Authors'), + 'description' => t('Tokens related to Biblio node type.'), + 'needs-data' => 'node', + ); + $types['biblio-keywords'] = array( + 'name' => t('Biblio Keywords'), + 'description' => t('Tokens related to Biblio node type.'), + 'needs-data' => 'node', + ); +*/ + + return array( + 'types' => $types, + 'tokens' => array( + 'biblio' => $biblio_tokens, + 'node' => $node_token), + ); +} + +/** + * @param unknown_type $type + * @param unknown_type $tokens + * @param unknown_type $data + * @param unknown_type $options + * @return multitype:NULL + */ +function biblio_tokens($type, $tokens, $data = array(), $options = array()) { + $replacements = array(); + if ($type == 'node' && !empty($data['node']) && $data['node']->type == 'biblio') { + $sanitize = !empty($options['sanitize']); + $node = $data['node']; + foreach (token_find_with_prefix($tokens, 'biblio') as $name => $original) { + switch ($name) { + case 'biblio_first_author': + $replacements[$original] = $sanitize ? check_plain($node->biblio_contributors[0]['lastname']) : $node->biblio_contributors[0]['lastname']; + break; + case 'biblio_type_name': + $type = db_query('SELECT name FROM {biblio_types} as t WHERE t.tid = :tid', array(':tid' => $node->biblio_type))->fetchField(); + $replacements[$original] = $sanitize ? check_plain($type) : $type; + break; + default: + $replacements[$original] = $sanitize ? check_plain($node->$name) : $node->$name; + } + } + } + + return $replacements; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/changelog.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/changelog.html Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,326 @@ + + + + +Biblio Change Log + + + +

    7.x-dev

    +New features... +
      +
    • Added MARC format export to the Biblio MARC module.
    • +
    • #1853022 added option to display file attachments as text or icons.
    • +
    • Added cron process to Biblio PubMed to periodically check for updates to existing PubMed entries.
    • +
    • Added edit and upload functions for CSL style files on the "admin/config/content/biblio/citeproc/styles" page.
    • +
    • #1485856 Contributor and Keyword orphan cleanup is now done using cron rather than on biblio update.
    • +
    • Added #1443250 Formatted text fields now respect the format setting when rendered in Views
    • +
    • Added a field mapper GUI for the CiteProc module at admin/config/content/biblio/citeproc/map
    • +
    • Added a "highlight" feature to the "authors" page which allows privileged users to toggle highlighting of potential duplicate authors
    • +
    • #1140366 Patch: PubMed XML Bulk Import
    • +
    + +Bugs Fixed... +
      +
    • #1888776 Fixed: Warning message when importing a journal article via DOI
    • +
    • #1889886 Fixed: property of non-object in biblio_handler_citation->render()
    • +
    • #1890094 Fixed: Keyword - Taxonomy integration. Taxonomy term reference were not being created.
    • +
    • #1815032 Fixed: Array to string conversion in biblio_token_info() in 7.x-1.0-dev
    • +
    • #1862788 Fixed: Can not add additional authors to link list
    • +
    • #1806214 Fixed: Feeds module needs input to be an array or it cuts input to first string $value[0]
    • +
    • #1802264 Fixed: Views filter "Biblio: Drupal UserID" does not list any Drupal users
    • +
    • #1202840 Fixed: Shouldn't print "p. " when the pages field is left empty (CiteProc module)
    • +
    • #1798244 Fixed: Publisher field not rendered in CiteProc
    • +
    • #1781126 Fixed: Superfluous quotation marks (CiteProc module)
    • +
    • #1794636 Fixed: Missing t() in page title
    • +
    • #1780414 Fixed: Enforce RIS permitted character set during RIS import - Remove non-printing characters and other "gremlins"
    • +
    • #1786014 Fixed: Undefined index: biblio_notes.
    • +
    • #1509872 Fixed: PubMed import fails for long lastnames.
    • +
    • #1497040 Fixed: Views "Author lastname" sort handler so you can select the author "rank" (1st, 2nd, 3rd) to sort on.
    • +
    • #1449612 Fixed: Class attribute passed to theme not an array
    • +
    • #741212 Fixed: Wrong node count on author/keyword overview pages, when using revisions
    • +
    • #1206278 Fixed: Author order not saved when updating an existing Biblio
    • +
    • #972312 Fixed: casting issue in biblio_save_node() (implmented node_object_prepare)
    • +
    • #1138636 Fixed: Show filefield attachment link in list view
    • +
    • #1049218 Fixed: Bad URL for sort by links
    • +
    • #1177038 Fixed: Call to undefined function mb_regex_encoding()
    • +
    • #1166300 Fixed: Notice: Undefined index: class in _biblio_sort_tab() (line 569
    • +
    • #1145172 Fixed: Call to undefined function curl_init()
    • +
    • #1138636 Fixed: Show filefield attachment link in list view
    • +
    • #1140366 Patch: PubMed XML Bulk Import
    • +
    • #1139432 Fixed: Biblio Incompatible With Field-fix Branch of Token Module
    • +
    • #1135326 Fixed: Notice: Undefined offset
    • +
    • #1127514 Patch removes issue numbers from (non journal) chicago style listings.
    • +
    • #830770 Fixed: Biblio list output not showing filefield path aliases
    • +
    • #1104928 Fixed: PubMed Link Setting
    • +
    • #1104718 Fixed: PubMed Import multi-paragraph abstracts
    • +
    • #1055494 Fixed: Undefined variable: export_map
    • +
    • #1037350 Fixed: Notice re: biblio_contributor_id on user edit page
    • +
    • #1030254 Fixed: Undefined index: tagged in biblio_tagged_biblio_export_link()
    • +
    • #1015100 Fixed: Small nit about $output in biblio_style_classic()
    • +
    • #999776 Fixed: various errors with endnote (XML) import)
    • +
    • #951124 Fixed: RIS import / implemented import from node form
    • +
    • #987556 Fixed: CSE citation style no delimiter for first author's initials
    • +
    • #978022 Fixed: Parentheses should be removed from Google Scholar link
    • +
    • #978096 Fixed: Missing Magazine Article in tagged endnote publication type mapping
    • +
    • #946362 Fixed: Encoding issue in author formatter
    • +
    • #946492 Fixed: Subsidiary authors not imported by tagged EndNote parser
    • +
    • #288957RIS parser handles BT tag incorrect for types other than BOOK
    • +
    • #734682Theming error - $base not supplied
    • +
    • updated German translation (biblio.de.po) by Niels Hackius
    • +
    • #502562 "CrossRef OpenURL Account ID" descriptive text on user profile page enhanced to avoid confusion between different types of CrossRef accounts
    • +
    • #681730Rendering biblio as citation style for views omits status
    • +
    • #670394fixed MLA Style issue: missing punctuation for Book Chapter type
    • +
    • #616754 style_vancouver warning
    • +
    • #614970 fixed extra "biblio-title-vancouver" spans in Vancouver style
    • +
    • #614598 fixed Missing } in biblio.module causes errors using multisite
    • +
    + +

    6.x-dev

    +New features... + +Bugs Fixed... +
      +
    • # row titles in theme_biblio_tabular() not translated
    • +
    • #146758 fixed missing initialization of the keyword separator string
    • +
    • # fixed spans being filtered out of biblio listings
    • +
    • #611712 fixed Empty href attributes in OpenURL links
    • +
    • # fixed "keyword" page so that keywords from "unpublished" nodes are not listed (with the exception of the Admin user UID=1, who will still see them )
    • +
    • # fixed "authors" page so that authors from "unpublished" nodes are not listed (with the exception of the Admin user UID=1, who will still see them)
    • +
    • # fixed extra period after title in some styles
    • +
    • #146760proper handling of name suffixes
    • +
    • # fixed Import: Invalid argument supplied for foreach () in biblio.import.export.inc on line 267
    • +
    • # fixed falformed paragraph tag
    • +
    • #597286searching bibligraphy adds around result
    • +
    • #584616Import: Invalid argument supplied for foreach () in biblio.import.export.inc
    • +
    • #592750Error when Block Settings is ordered by Year Published following upgrade from 6.x-1.6 to 6.x-1.7
    • +
    + +

    6.x-1.7

    +New features... +
      +
    • New settings available in "Styling" to allow admin to change the text that is displayed when there is no year of publication available (9999) or the "In Press" text (9998)
    • +
    • Reworked the SQL query used for the list display to improve speed at which the list display is sorted. This is especially noticable on large datasets.
    • +
    • Reworked export functions such that the data is streamed to the client node by node thus reducing the memory footprint required to export large datasets.
    • +
    • Thanks to Filipe Rocha for the Portuguese translation file (biblio.pt-pt.po)
    • +
    • Added links to file attachments in the Tagged, bibtex and EndNote 8 export file formats
    • +
    • Thanks to Turczi Attil for the Hungarian translation file (biblio.hu.po)
    • +
    +Bugs Fixed... +
      +
    • fixed potential XSS vulnerability
    • +
    • #585332 fixed Can't use the print module in the biblio list page
    • +
    • disabled default views so they do not over ride some of the built in paths
    • +
    • #570640 fixed author ordering problem when "More Authors" are added on the input form
    • +
    • #561722 fixed Order Vocabularies by Weight on Import Screen(s)
    • +
    • #561800 fixed Incorrectly placed curly bracket } around table name in db_query
    • +
    • #551362 fixed problem with author and keyword edit links when base url has more than one level
    • +
    • #502140 fixed typo which prevented removal of BibTex braces in the title with Views display
    • +
    • #536062 fixed bug which would overwrite page title if the page had a "View" containing a biblio node
    • +
    • fixed bug which prevented the "Alpha" line from being displayed on the "Authors" page when there were no authors to display
    • +
    +
    +

    6.x-1.6

    +New features... +
      +
    • Added configurable "type" mapping #520828. This feature allows mapping between various input file format publication types and Biblio publication types.
    • +
    • Added a per-user setting for OpenURL information #528930
    • +
    • EndNote XML files are now formated in the latest XML format by default, good for versions 8 and newer of EndNote.
    • +
    • Added new setting in the OpenURL section to set the Site ID (sid) of the OpenURL link
    • +
    +Bugs Fixed... +
      +
    • fixed potential XSS vulnerability
    • +
    • #514882 fixed MLA style looks for incorrect year field
    • +
    • #------ fixed incorrect link in keyword edit page
    • +
    • #513930fixed Missing '</ul>' tag in biblio_theme.inc, and also refactored the entire link building process so that the links on the "node" display are built by the same code as the links on the "bibliographic" display
    • +
    • #504702fixed The first [bib] causes a line break with hovertips enabled
    • +
    • #501932fixed warning generated when new "type" is created and no fields are set "visible", also change the default for new types so that ALL fields will be visible rather than invisible.
    • +
    • #497594removed the manditory requirement for the year of publication to be supplied
    • +
    • #502140fixed Views citation handler does not remove BibTeX brackets
    • +
    • #501932fixed Biblio Search returns error
    • +
    • #477438fixed issue with CrossRef XML parser where it would truncate data with embedded HTML tags, also added the retention of font style (bold, italic, underline, subscript, superscript) information.
    • +
    • #477438fixed issue with EndNote XML parser where it would truncate data with embedded styles, also added the retention of font style information.
    • +
    • #------ fixed issue with author formatting which could cause extra periods before and after initials
    • +
    • #------ removed some debug code which was left in the Crossref xml parser by mistake
    • +
    • #492214by scottrigby: biblio: authors views field sql error
    • +
    • #486462 Fixed: Arrow images not showing when Drupal is not installed at DocumentRoot
    • +
    • #486790 "Edit" links on admin/settings/biblio/author/list are broken
    • +
    • #488214 theme_biblio_long makes bad author links
    • +
    • #486312 User cannot edit biblio entries unless it has 'administer nodes' permission
    • +
    • #477878 Fixed Views biblio_year field with "In Press" and "Submitted" values
    • +
    • #483650 Google Scholar links not shown in "node" view
    • +
    • #467928 Wrong tool tip on Google Scholar link
    • +
    +
    +

    6.x-1.5

    +New features... +
      +
    • Added Views handlers for sorting, filtering and arguments on most fields.
    • +
    • Added new settings in the ISI Web of Knowledge section on the admin/settings/biblio page. Turning this on automatically converts EndNote "Go to ISI" links to valid ISI search links. (ISI subscription required)
    • +
    • Added new settings in the links section on the admin/settings/biblio page. You choose to carry "inline" mode through to all subsequent links. Inline mode is used primarily by people accessing biblio data from custom code, so the average user will not need this.
    • +
    • Added new settings in the links section on the admin/settings/biblio page. You can now toggle the export links on/off individually as well as the Google Scholar link
    • +
    • Added links from each entry to Google Scholar. Following the link does a Google Scholar search using the title and first authors name.
    • +
    • Added a new feature (and setting to turn it on/off) which will allow you to retain BibTex protected capitalization braces in title strings, they will be stripped out on display, but will still be in place when exported in BibTex format.
    • +
    • Added a new permission called "access biblio content" which will allow you to restrict access to any of the biblio pages (biblio, biblio/authors, biblio/keywords etc.) A user MUST have this permission to see anything from the biblio module!
    • +
    • Added a section to the user profile page to allow entry of CrossRef login information used for DOI lookup
    • +
    • Added a new option when selecting taxonomy terms from a vocabulary, you can now choose to copy them to the biblio keyword database or not. There is a check box on the input and import forms and there is also a new check box in the "Keywords" section of the admin/settings page which sets the default for this option.
    • +
    • Added a setting in the links section to toggle the hyperlinks on the author names.
    • +
    +Bugs Fixed... +
      +
    • #150002 Go to ISI Link
    • +
    • #477502 Unescape XML Entities in import from crossref
    • +
    • #477432 crossref_unix_parser pulls in extra dois
    • +
    • #477448 crossref_unix_parser on multiple isbns
    • +
    • #282996 FileField support for Biblio?
    • +
    • #469502 problem with viewinline
    • +
    • #465834 issn and section fields too small for patent type
    • +
    • #472062 Biblio and i18n - Problem with assigned language when importing nodes
    • +
    • #471436 Problem with Taxonomy and Biblio Keywords
    • +
    • #469328 What to do if Author is an Organization (CSE and APA styles only)
    • +
    • #464572 Undefined function taxonomy_vocabulary_load()-installation error
    • +
    • #461474 BibTex - ISSN field
    • +
    • #461464 BibTex - Inproceedings and inbook - booktitle replaced by series
    • +
    • #461426 BibTex - @incollection interpreted as @book
    • +
    • #461422 BibTex - export - no doi
    • +
    • #456750 Content Access Permissions are ignored
    • +
    • #459374 Use of $form_state['storage'] to hold a string/integer (as opposed to an array)
    • +
    • #459684 DOI import fails, CrossRef registration required now
    • +
    • #455216 Compilation failed: POSIX named classes.. in biblio_theme.inc
    • +
    • #456360 Export and Import in Bibtext format changes the citekey
    • +
    • #455402 Changes to Biblio Filter
    • +
    • #455022 Extend admin pages to set the 'biblio_author_links' variable
    • +
    • fixed problem with missing sort arrow images when filtering
    • +
    • #460240fixed keyword export in BibTex and EndNote Tagged formats
    • +
    • #453708 Duplicate bibtex export
    • +
    • #453298 "Number of items in the RSS feed" not working...
    • +
    • #450522 Limit on author initials is not respected in Vancouver style
    • +
    • #450030 Minor error on edit authors
    • +
    +
    +

    6.x-1.4

    +Bugs Fixed... +
      +
    • #448732 can't save any biblio: "Call to undefined function biblio_explode_keywords()"
    • +
    • #448306 preg_replace() error
    • +
    +
    +

    6.x-1.3

    +New features... +
      +
    • Added "DOI Lookup" option on the input form. Entering a valid DOI name (number) here will query crossref.org and poplulate the input form with all the data it gets. You are then presented with the populated form to make further additions/changes prior to saving it to the database.
    • +
    • Added "BibTex cut and paste" option to the input form. Pasting a valid bibTex string into the box will populate the input form with the data and similar to the DOI lookup above, allows you to preview and make changes prior to saving it in the database.
    • +
    • Added a new user permission called "edit biblio authors" which allows the user to edit biblio author information but not other biblio settings, unlike "administer biblio" which gives them the right to edit all biblio settings.
    • +
    • Changed "Views" integration such that Blblio is no longer a "Base" type of view. You should now use the "Node" view type to create a view containing biblio information.
    • +
    • Added an option to toggle sort links between text and tab type display.
    • +
    • Changed Keyword/Taxonomy freetag handling... The Taxonomy freetag input box is no longer displayed on the input form. All keywords/taxonomy freetags should be entered into the biblio Keywords input box and (if required) the taxonomy freetags will be updated when the keywords are saved. This keeps the keywords and freetags in sync and avoids the confustion of having two input boxes (one for keywords and one for freetags).
    • +
    +Bugs Fixed... +
      +
    • #444428 simple mispelled words
    • +
    • #437440 Citation Key cannot be longer than 16
    • +
    • #435058 Keywords field length exceeded
    • +
    • #439152 Export by keyword
    • +
    • #356322 Biblio nodes not XHTML-compliant
    • +
    • #355572 Filtering by author should be more lenient / "smart"
    • +
    • #340570 inline biblio_db_search layout is broken
    • +
    • #442916 Shortening forenames does not use betweenInitialsDelim option
    • +
    • #440248 Typo in HTML in _biblio_filter_replace_callback()
    • +
    • #438930 edit_authors permission
    • +
    • #441104 schema module reports a mismatch
    • +
    • #439086 Biblio Keywords and Biblio free tags
    • +
    • #435058 Keywords field length exceeded
    • +
    • #433198 Shortening forename of authors containing UTF-8 special characters does not work
    • +
    • #431688 Interaction between url filter and standard sorting filters
    • +
    • #425852 Biblio page title setting ignored
    • +
    • #429226 Quotes around title in Vancouver style
    • +
    • #421546 Keywords vanish when previewing
    • +
    +
    +

    6.x-1.2

    +New features... +
      +
    • Added option to automatically delete authors "orphaned" by the update or deletion of an entry
    • +
    • User selectable bibliographic styles, authorized users can choose a style other than the system default on their "edit user" page
    • +
    • Author "Link" allows 2 or more authors to be linked together (intended to deal with name changes due to marriage, but could be used for other purposes)
    • +
    +Bugs Fixed... +
      +
    • #376808 Delete orphaned authors automatically?
    • +
    • #415478 Sorting by type without bars
    • +
    • #414870 "more" link in the Block for new publications
    • +
    • #414380 Only one keyword is shown
    • +
    • #412720 Broken download links when filtering with clean URLs (e.g. /biblio/year/2009)
    • +
    • #410610 "Hyperlink titles using supplied URL if available" feature does not seem to work.
    • +
    • #409952 All Fields appear (even when unchecked) after upgrade from Biblio 5x to 6x
    • +
    • #376802 Search index not updated when editing a node
    • +
    • #404842 Tabs and styling
    • +
    • #404522 Redirection after batch import
    • +
    • #397104 Search function sucks :-)
    • +
    • #403484 Slightly malformed RIS files are not imported
    • +
    • #404080 Typo in hook_menu prevents author type editing
    • +
    • #402468 Filter by author, sort both first and secondary authors
    • +
    • #401938 First name / Last name order switched
    • +
    +
    +

    6.x-1.1

    +New features... +
      +
    • MARC format file import
    • +
    • New bibliographic output sytles (AMA, Chicago, MLA, Vancouver)
    • +
    • Authors page (biblio/authors) which displays all authors in the database linked to publication lists filtered by that author. ("edit" links are provided for privileged users)
    • +
    • Author edit/merge form (merge allows you to merge all instances of the same author which have been entered slightly +differently. i.e. Smith, J and John Smith are the same person so you merge the two entries into one.)
    • +
    • Orphaned author page(admin/settings/biblio/author/orphans), allows you to remove authors who are not associated with any publications from the database
    • +
    • Keywords page (biblio/keywords) which displays all keywords in the database linked to publication list filtered by that keyword. ("edit" links are provided for privileged users)
    • +
    • Orphaned keyword page (admin/settings/biblio/keyword/orphans) allows you to find and remove orphaned keywords (keywords which are no longer associated with any publications)
    • +
    • added 'tabledrag' to admin/settings/biblio/defaults and admin/settings/biblio/types/edit/xxx which allows you to change the order of the fields on the input form by just dragging them to the desired location
    • +
    • input your own PHP code to generate cite keys
    • +
    +Bugs Fixed... +
      +
    • #398472 Drupal UserID-Author Mapping problems
    • +
    • #396654 Export single reference fails - SQL error
    • +
    • #398226 Fatal error: Cannot break/continue 1 level in biblio.module on line 277
    • +
    • #396096 Caching problem: no filters for anonymous users
    • +
    +
    +

    6.x-1.0

    +Bugs Fixed... +
      +
    • [#141602]
    • +
    • [#381190]
    • +
    • [#385830]
    • +
    • [#387788]
    • +
    • [#388722]
    • +
    • [#183517]
    • +
    • [#376852]
    • +
    • [#372820]
    • +
    • [#381070]
    • +
    • [#376852]
    • +
    • [#372038]
    • +
    • [#375085]
    • +
    • innumerable D5 -> D6 upgrade fixes provided by Robert Haschke (http://drupal.org/user/409889)
    • +
    • [#371867]
    • +
    • [#372770]
    • +
    • [#373107]
    • +
    • [#373129]
    • +
    • [#372915]
    • +
    • [#372238]
    • +
    • [#372024]
    • +
    • [#354676] fixed inconsistency between page title and breadcrumb
    • +
    • [#355480] fixed issue with setting user id on import by none admin users
    • +
    • [#357298] fixed another userID related issue
    • +
    • [#336344] fixed problem with taxonomy tags not being added to imported entries
    • +
    • [#356322] added danep's patch to correct XHTML validation warnings
    • +
    • [#355621] fixed PHP4 compatibility problem
    • +
    • [#275410] fixed issue where "My Publications" tab on profile pages could not be turned off
    • +
    • [#359752] fixed issue related to profile and inline modes
    • +
    • [#360391] fixed problem creating database on install with aliased
    • +
    • [#365425] fixed Importing bibtex omits Conference title
    • +
    • [#365435] fixed URL search doesn't show search keywords
    • +
    • [#362696] RIS Import omits Journal Abbreviation field (JA)
    • +
    + + \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/Name.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/Name.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,102 @@ +setStr($str); + } + + /** + * Checks encoding, normalizes whitespace/punctuation, and sets the name string. + * + * @param String $str a utf8-encoding string. + * @return Bool True on success + */ + public function setStr($str) + { + if (!drupal_validate_utf8($str)){ + throw new Exception("Name is not encoded in UTF-8"); + } + $this->str = $str; + $this->norm(); + return true; + } + + public function getStr() + { + return $this->str; + } + + + /** + * Uses a regex to chop off and return part of the namestring + * There are two parts: first, it returns the matched substring, + * and then it removes that substring from $this->str and normalizes. + * + * @param string $regex matches the part of the namestring to chop off + * @param integer $submatchIndex which of the parenthesized submatches to use + * @param string $regexFlags optional regex flags + * @return string the part of the namestring that got chopped off + */ + public function chopWithRegex($regex, $submatchIndex = 0, $regexFlags = '') + { + $regex = $regex . "ui" . $regexFlags; // unicode + case-insensitive + preg_match($regex, $this->str, $m); + $subset = (isset($m[$submatchIndex])) ? $m[$submatchIndex] : ''; + + if ($subset){ + $this->str = preg_replace($regex, ' ', $this->str, -1, $numReplacements); + if ($numReplacements > 1){ + throw new Exception("The regex being used to find the name: '$this->str' has multiple matches."); + } + $this->norm(); + return $subset; + } + else { + return ''; + } + } + + /* + * Flips the front and back parts of a name with one another. + * Front and back are determined by a specified character somewhere in the + * middle of the string. + * + * @param String $flipAroundChar the character(s) demarcating the two halves you want to flip. + * @return Bool True on success. + */ + public function flip($flipAroundChar) + { + $substrings = preg_split("/$flipAroundChar/u", $this->str); + if (count($substrings) == 2){ + $this->str = $substrings[1] . " " . $substrings[0]; + $this->norm(); + } + else if (count($substrings) > 2) { + throw new Exception("Can't flip around multiple '$flipAroundChar' characters in: '$this->str'."); + } + return true; // if there's 1 or 0 $flipAroundChar found + } + + /** + * Removes extra whitespace and punctuation from $this->str + * Strips whitespace chars from ends, strips redundant whitespace, converts whitespace chars to " ". + * + * @return Bool True on success + */ + private function norm() + { + $this->str = preg_replace( "#^\s*#u", "", $this->str ); + $this->str = preg_replace( "#\s*$#u", "", $this->str ); + $this->str = preg_replace( "#\s+#u", " ", $this->str ); + $this->str = preg_replace( "#,$#u", " ", $this->str ); + return true; + } +} +?> diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/Parser.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/Parser.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,214 @@ +getLast() . ", " . $parser->getFirst(); + * //returns "Smith, John" + * + * + */ +class HumanNameParser_Parser { + private $name; + private $nameParts = array(); + private $leadingInit; + private $first; + private $nicknames; + private $middle; + private $last; + private $suffix; + private $category; + private $type; + private $literal; + + private $suffixes; + private $prefixes; + + /* + * Constructor + * + * @param mixed $name Either a name as a string or as a Name object. + */ + public function __construct($name = NULL) + { + $this->suffixes = array('esq','esquire','jr','sr','2','ii','iii','iv'); + $this->prefixes = array('bar','ben','bin','da','dal','de la', 'de', 'del','der','di', + 'ibn','la','le','san','st','ste','van', 'van der', 'van den', 'vel','von'); + $this->setName($name); + } + + public function parseName($name = NULL, $category = NULL) { + $this->literal = 0; + $this->category = 1; + $this->type = 1; + if (is_array($name) && isset($name['name'])) { + if (isset($name['auth_category']) && !empty($name['auth_category']) && empty($category)) { + $this->category = $name['auth_category']; + } + elseif (!empty($category)) { + $this->category = $category; + } + if (isset($name['auth_type']) && !empty($name['auth_type'])) { + $this->type = $name['auth_type']; + } + $this->nameParts = $name; + $this->setName($name['name'], $category); + } + else { + $this->nameParts['name'] = $name; + $this->setName($name, $category); + } + + return $this->getArray(); + } + /** + * Sets name string and parses it. + * Takes Name object or a simple string (converts the string into a Name obj), + * parses and loads its constituant parts. + * + * @param mixed $name Either a name as a string or as a Name object. + */ + public function setName($name = NULL, $category = NULL){ + if ($name) { + $this->category == $category; + + if (is_object($name) && get_class($name) == "HumanNameParser_Name") { // this is mostly for testing + $this->name = $name; + } + elseif (is_array($name) && isset($name['name'])) { + $this->name = new HumanNameParser_Name($name['name']); + $this->nameParts = $name; + } + else { + $this->name = new HumanNameParser_Name($name); + } + + $this->leadingInit = ""; + $this->first = ""; + $this->nicknames = ""; + $this->middle = ""; + $this->last = ""; + $this->suffix = ""; + + if ($this->category == 5 || $this->type == 5) { + $this->last = $name; + $this->literal = TRUE; + } + else { + $this->parse(); + } + + } + } + + public function getleadingInit() { + return $this->leadingInit; + } + public function getFirst() { + return $this->first; + } + public function getNicknames() { + return $this->nicknames; + } + + public function getMiddle() { + return $this->middle; + } + + public function getLast() { + return $this->last; + } + + public function getSuffix() { + return $this->suffix; + } + public function getName(){ + return $this->name; + } + + /** + * returns all the parts of the name as an array + * + * @param String $arrType pass 'int' to get an integer-indexed array (default is associative) + * @return array An array of the name-parts + */ + public function getArray($arrType = 'assoc') { + $arr = array(); + $arr['prefix'] = $this->leadingInit; + $arr['firstname'] = $this->first; + $arr['nicknames'] = $this->nicknames; + $arr['initials'] = substr($this->middle, 0, 10); + $arr['lastname'] = $this->last; + $arr['suffix'] = $this->suffix; + $arr['md5'] = md5(json_encode($arr)); + $arr['literal'] = $this->literal; + + if ($arrType == 'assoc') { + return array_merge($this->nameParts, $arr); + } + else if ($arrType == 'int'){ + return array_values($arr); + } + else { + throw new Exception("Array must be associative ('assoc') or numeric ('num')."); + } + } + + /* + * Parse the name into its constituent parts. + * + * Sequentially captures each name-part, working in from the ends and + * trimming the namestring as it goes. + * + * @return boolean true on success + */ + private function parse() + { + $suffixes = implode("\.*|", $this->suffixes) . "\.*"; // each suffix gets a "\.*" behind it. + $prefixes = implode(" |", $this->prefixes) . " "; // each prefix gets a " " behind it. + + // The regex use is a bit tricky. *Everything* matched by the regex will be replaced, + // but you can select a particular parenthesized submatch to be returned. + // Also, note that each regex requres that the preceding ones have been run, and matches chopped out. + $nicknamesRegex = "/ ('|\"|\(\"*'*)(.+?)('|\"|\"*'*\)) /"; // names that starts or end w/ an apostrophe break this + $suffixRegex = "/,* *($suffixes)$/"; + $lastRegex = "/(?!^)\b([^ ]+ y |$prefixes)*[^ ]+$/"; + $leadingInitRegex = "/^(.\.*)(?= \p{L}{2})/"; // note the lookahead, which isn't returned or replaced + $firstRegex = "/^[^ ]+/"; // + + // get nickname, if there is one + $this->nicknames = $this->name->chopWithRegex($nicknamesRegex, 2); + + // get suffix, if there is one + $this->suffix = $this->name->chopWithRegex($suffixRegex, 1); + + // flip the before-comma and after-comma parts of the name + $this->name->flip(","); + + // get the last name + $this->last = $this->name->chopWithRegex($lastRegex, 0); + if (!$this->last){ + throw new Exception("Couldn't find a last name in '{$this->name->getStr()}'."); + } + + // get the first initial, if there is one + $this->leadingInit = $this->name->chopWithRegex($leadingInitRegex, 1); + + // get the first name + $this->first = $this->name->chopWithRegex($firstRegex, 0); + if (!$this->first && $this->category != 5){ + throw new Exception("Couldn't find a first name in '{$this->name->getStr()}'"); + } + + // if anything's left, that's the middle name + $this->middle = $this->name->getStr(); + return true; + } + + + + + +} +?> diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.admin.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,2925 @@ + 'vertical_tabs', + '#weight' => 0, + ); + + $form['general'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('General'), + '#group' => 'biblio_settings', + '#weight' => 0, + '#description' => '' + ); + + $form['general']['biblio_rev'] = array( + '#markup' => $version, + + ); + + $form['general']['biblio_base'] = array( + '#type' => 'textfield', + '#title' => t('Base URL'), + '#size' => 20, + '#default_value' => variable_get('biblio_base', 'biblio'), + '#description' => t('This sets the base URL used to access the biblio module (e.g. /biblio ).') + ); + $form['#biblio_base'] = $form['general']['biblio_base']['#default_value']; + $form['general']['biblio_base_title'] = array( + '#type' => 'textfield', + '#title' => t('Biblio page title'), + '#size' => 20, + '#default_value' => variable_get('biblio_base_title', 'Biblio'), + '#description' => t('The page title shown on the base URL.') + ); + + $form['general']['biblio_rowsperpage'] = array( + '#type' => 'textfield', + '#title' => t('Number of results per page'), + '#default_value' => variable_get('biblio_rowsperpage', 25), + '#size' => 6, + '#maxlength' => 6, + '#description' => t('This sets the number of results that will be displayed per page.') + ); + $form['general']['biblio_view_only_own'] = array( + '#type' => 'checkbox', + '#title' => t('Restrict users such that they can only view their own biblio entries'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_view_only_own', 0), + '#description' => t('This option restricts the users capability to view biblio entries. They will only be able to see the entries which they have created and own.') + ); + $form['general']['biblio_button_hide'] = array( + '#type' => 'checkbox', + '#title' => t('Hide next button in node form'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_button_hide', 1), + '#description' => t('If checkbox is set a javascript adds the accesible system class "element-invisible" to the next button in node form. The next button is not needed if javascript is available and listening on changes.') + ); + $form['authors'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Authors'), + '#group' => 'biblio_settings', + '#weight' => 10, + '#description' => '' + ); + $form['authors']['biblio_auto_orphaned_author_delete'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically delete orphaned authors'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_auto_orphaned_author_delete', 0), + '#description' => t('Orphaned authors are those which are no longer linked to any entries as the result of a biblio update or delete. This requires a functioning "cron" process.') + ); + $form['authors']['biblio_orphan_clean_interval'] = array( + '#type' => 'radios', + '#title' => t('Orphaned author cleaning frequency'), + '#default_value' => variable_get('biblio_orphan_clean_interval', 24 * 60 * 60), + '#options' => array( + 0 => t('Every CRON run'), + 3600 => t('Hourly'), + 86400 => t('Daily'), + 604800 => t('Weeekly'), + ), + '#description' => t('How frequently should we check for and delete orphans.'), + '#states' => array( + 'invisible' => array( + 'input[name="biblio_auto_orphaned_author_delete"]' => array('checked' => FALSE), + ), + ), + ); + $form['authors']['biblio_init_auth_count'] = array( + '#type' => 'textfield', + '#title' => t('Number of contributor fields to initially display on the input form'), + '#size' => 2, + '#maxlength' => 2, + '#default_value' => variable_get('biblio_init_auth_count', 4), + '#description' => t('Increasing this value will increase the number of input fields displayed in the contributors section of the input form') + ); + $form['authors']['biblio_contrib_fields_delta'] = array( + '#type' => 'textfield', + '#title' => t('Number of fields added by the "Add more" button on the contributor input form'), + '#size' => 2, + '#maxlength' => 2, + '#default_value' => variable_get('biblio_contrib_fields_delta', 2), + '#description' => t('This number of blank fields will be added to the contributors section of the input form each time the "Add more" button is pressed.') + ); + $form['bibtex'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('BibTex settings'), + '#group' => 'biblio_settings', + '#weight' => 20, + '#description' => '' + ); + $form['bibtex']['biblio_hide_bibtex_braces'] = array( + '#type' => 'checkbox', + '#title' => t('Retain bibtex\'s {Protected} capitalization in the title string, but hide the braces on display'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_hide_bibtex_braces', 0), + '#description' => '' + ); + + $result = db_query("SELECT b.name, bftd.title FROM {biblio_fields} b + INNER JOIN {biblio_field_type} bt ON bt.fid=b.fid + INNER JOIN {biblio_field_type_data} bftd ON bftd.ftdid=bt.ftdid + WHERE bt.tid=0 ORDER by bftd.title ASC "); + $schema = drupal_get_schema('biblio'); + $keys = array_keys($schema['fields']); + $options = array(); + $options['nid'] = t('Node ID'); + foreach ($result as $row) { + $options[$row->name] = $row->title; + } + $form['citekey'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Citekey'), + '#description' => t('You can alter citekey related settings here.'), + '#group' => 'biblio_settings', + '#weight' => 30, + + ); + $form['citekey']['biblio_display_citation_key'] = array( + '#type' => 'checkbox', + '#title' => t('Show citation key in results'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_display_citation_key', 0), + '#description' => t('This will output the citekey as the first item in the citation string') + ); + $form['citekey']['biblio_auto_citekey'] = array( + '#type' => 'checkbox', + '#title' => t('Auto generate citekeys if not given'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_auto_citekey', 1), + '#description' => t('This option will cause "citekey" entries to be automatically generated if a value is not provided.') + ); + $form['citekey']['biblio_citekey_prefix'] = array( + '#type' => 'textfield', + '#title' => t('Citekey prefix'), + '#default_value' => variable_get('biblio_citekey_prefix', ''), + '#size' => 10, + '#maxlength' => 10, + '#description' => t('This text will be combined with the field choosen below to form the auto generated citekey.') + ); + $form['citekey']['biblio_citekey_field1'] = array( + '#type' => 'select', + '#title' => t('Primary Citekey field'), + '#default_value' => variable_get('biblio_citekey_field1', 'nid'), + '#options' => $options, + '#description' => t('Select the field to be used when generating citekeys.') + ); + $form['citekey']['biblio_citekey_field2'] = array( + '#type' => 'select', + '#title' => t('Secondary Citekey field'), + '#default_value' => variable_get('biblio_citekey_field2', 'nid'), + '#options' => $options, + '#description' => t('If the field above has no value this field will be used.') + ); + // Allow only users to modify PHP code which have PHP block visibility permissions + if (user_access('use PHP for block visibility')) { + $form['citekey']['biblio_citekey_phpcode'] = array( + '#type' => 'textarea', + '#title' => t('PHP code for citekey generation'), + '#default_value' => variable_get('biblio_citekey_phpcode', ''), + '#description' => t('Advanced usage only: PHP code that returns the citekey. Should not include <?php ?> delimiters.') + ); + } + + $form['biblio_crossref'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('CrossRef Login Information'), + '#group' => 'biblio_settings', + '#weight' => 40, + ); + $link_attrs = array('attributes' => array('target' => '_blank'), + 'absolue' => TRUE); + $form['biblio_crossref']['biblio_show_crossref_profile_form'] = array( + '#type' => 'checkbox', + '#title' => t('Allow users to override these settings on their "My account" page') , + '#return_value' => 1, + '#description' => t('If this is selected, a form similar to this section will be available to the user when they edit their own account information. This will allow them to override the global preferences set here.') , + '#default_value' => variable_get('biblio_show_crossref_profile_form', '1') + ); + $form['biblio_crossref']['biblio_crossref_pid'] = array( + '#type' => 'textfield', + '#title' => t('CrossRef OpenURL Account ID'), + '#default_value' => variable_get('biblio_crossref_pid', ''), + '#description' => t('Enter your complimentary CrossRef OpenURL account ID which you can obtain here !url, OR enter your full CrossRef (colon separated) account:password combination.', array('!url' => l(t('OpenURL Account Request Form'), 'http://www.crossref.org/requestaccount/', $link_attrs))) + ); + + $form['footnotes'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Footnotes'), + '#description' => t('You can integrate with the !url module here.', array('!url' => l('footnotes', url("http://www.drupal.org/project/footnotes", array('query' => NULL, 'fragment' => NULL, 'absolute' => TRUE))))), + '#group' => 'biblio_settings', + '#weight' => 50, + ); + if (!module_exists('footnotes')) { + $additional_text = '
    ' . t('Depends on') . ': ' . t('Footnotes') . ' (' . t('disabled') . ')
    '; + $disabled = TRUE; + variable_set('biblio_footnotes_integration', 0); + } + else { + $additional_text = '
    ' . t('Depends on') . ': ' . t('Footnotes') . ' (' . t('enabled') . ')
    '; + $disabled = FALSE; + } + $form['footnotes']['biblio_footnotes_integration'] = array( + '#type' => 'checkbox', + '#title' => t('Integration with the footnotes module') . $additional_text, + '#disabled' => $disabled, + '#return_value' => 1, + '#default_value' => variable_get('biblio_footnotes_integration', 0), + '#description' => t('This will convert <bib> tags into <fn> tags. This will cause intermingled <bib> and <fn> tags to be sequentially numbered. For this to work, you must put the <bib> filter ahead of the <fn> filter in the filter chain. If this option is not set, <bib> and <fn> tags will be handled separately.') + ); + $form['isi_wok'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('ISI Web of Knowledge'), + '#description' => '', + '#group' => 'biblio_settings', + '#weight' => 60, + ); + $form['isi_wok']['biblio_fix_isi_links'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically replace "Go to ISI" links with the URL below'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_fix_isi_links', 0), + '#description' => t('This option automatically replaces any fake "Go to ISI" links with the supplied URL to ISI Web of Knowledge. Note a subscription with ISI is required for these links to function.') + ); + $form['isi_wok']['biblio_isi_url'] = array( + '#type' => 'textfield', + '#title' => t('ISI Web of Knowledge URL'), + '#size' => 128, + '#maxlength' => 512, + '#default_value' => variable_get('biblio_isi_url', 'http://apps.isiknowledge.com/InboundService.do?Func=Frame&product=WOS&action=retrieve&SrcApp=EndNote&Init=Yes&SrcAuth=ResearchSoft&mode=FullRecord&UT='), + '#description' => t('Enter the URL which will replace the "Go to ISI" fake links imported from EndNote') + ); + $form['keywords'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Keywords'), + '#description' => '', + '#group' => 'biblio_settings', + '#weight' => 70, + ); + $form['keywords']['biblio_keyword_sep'] = array( + '#type' => 'textfield', + '#title' => t('Keyword separator'), + '#size' => 2, + '#default_value' => variable_get('biblio_keyword_sep', ','), + '#description' => t('Enter the character which will be used to separate multiple keywords in the keyword field') + ); + $form['keywords']['biblio_keyword_orphan_autoclean'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically remove orphaned keywords'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_keyword_orphan_autoclean', 1), + '#description' => t('This option automatically deletes keywords which are no longer associated with any publications (primarily due to the due to the removal of a publication or editing a keyword).') + ); + $taxo_mesg = '
    ' . t('Depends on') . ': ' . t('Taxonomy') . ' (' . t('disabled') . ')
    '; + $form['keywords']['biblio_copy_taxo_terms_to_keywords'] = array( + '#type' => 'checkbox', + '#title' => t('Copy any selected taxonomy terms to the biblio keyword database'), + '#return_value' => 1, + '#default_value' => module_exists('taxonomy') ? variable_get('biblio_copy_taxo_terms_to_keywords', 0) : 0, + '#disabled' => (!module_exists('taxonomy')), + '#description' => module_exists('taxonomy') ? t('If this option is selected, the selected taxonomy terms will be copied to the @base_title keyword database and be displayed as keywords (as well as taxonomy terms) for this entry.', array('@base_title', variable_get('biblio_base_title', 'Biblio'))) : $taxo_mesg + ); + $form['keywords']['biblio_keyword_freetagging'] = array( + '#type' => 'checkbox', + '#title' => t('Use keywords from biblio entries as taxonomy "free tags"'), + '#return_value' => 1, + '#default_value' => module_exists('taxonomy') ? variable_get('biblio_keyword_freetagging', 0) : 0, + '#disabled' => (!module_exists('taxonomy')), + '#description' => module_exists('taxonomy') ? t('This option allows user to add keywords (free tags) to describe their documents. These keywords will be registered as taxonomy.') : $taxo_mesg + ); + $vocabularies = module_invoke('taxonomy', 'get_vocabularies'); + // ... and print a form to select the terms in each of them + $taxo_options = array(); + $taxo_options[0]= '<' . t('none') . '>'; + if (count($vocabularies)) { + foreach ($vocabularies as $voc) { + $taxo_options[$voc->vid] = $voc->name; + } + $form['keywords']['biblio_keyword_vocabulary'] = array( + '#type' => 'select', + '#title' => t('Vocabulary'), + '#default_value' => variable_get('biblio_keyword_vocabulary', 0), + '#options' => $taxo_options, + '#description' => t('Select vocabulary (category) to use for free tags.'), + '#multiple' => FALSE, + // '#disabled' => (!variable_get('biblio_keyword_freetagging', 0)), + // '#size' => $multiple ? min(9, count($taxo_options)) : 0, + '#weight' => 15, + '#states' => array( + 'invisible' => array( + 'input[name="biblio_keyword_freetagging"]' => array('checked' =>FALSE), + ), + ), + ); + } + + $form['links'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Links'), + '#group' => 'biblio_settings', + '#weight' => 80, + ); + $form['links']['export'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t('Export Links') + ); + $options = array(); + $export_defaults = array(); + $options = module_invoke_all('biblio_export_options'); + if (!empty($options)) { + $export_defaults = array_combine(array_keys($options), array_keys($options)); + } + $form['links']['export']['biblio_export_links'] = array( + '#type' => 'checkboxes', + '#title' => t('Show export links'), + '#default_value' => array_merge($export_defaults, variable_get('biblio_export_links', $export_defaults)), + '#options' => $options, + '#description' => t('You can select which export links to display here.') + ); + $form['links']['file_attachments'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t('File Attachments') + ); + $form['links']['file_attachments']['biblio_file_link_type'] = array( + '#type' => 'radios', + '#title' => t('File attachment display'), + '#default_value' => variable_get('biblio_file_link_type', 'text'), + '#options' => array( + 'text' => t('Text'), + 'icon' => t('Icon'), + ), + '#description' => t('Display file attachments as either the file name or an icon') + ); + + $form['links']['google'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t('Lookup Links') + ); + + $options = module_invoke_all('biblio_lookup_link_settings'); + $options += array('google' => 'Google Scholar'); + $lookup_defaults = array_combine(array_keys($options), array_keys($options)); + + $form['links']['google']['biblio_lookup_links'] = array( + '#type' => 'checkboxes', + '#title' => t('Show lookup links'), + '#default_value' => array_merge($lookup_defaults, variable_get('biblio_lookup_links', $lookup_defaults)), + '#options' => $options, + '#description' => t('You can select which lookup links to display here.') + ); + + $form['links']['biblio_download_links_to_node'] = array( + '#type' => 'checkbox', + '#title' => t('Download links in "List" view link to "Node" view'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_download_links_to_node', 0), + '#description' => t('If selected, the download links in the "List" view will link to the full "Node" view rather than the file itself, the file can then be downloaded from the node view') + ); + $form['links']['biblio_links_target_new_window'] = array( + '#type' => 'checkbox', + '#title' => t('Links open in new browser'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_links_target_new_window', 0), + '#description' => t('This causes related URLs to open in a new browser window') + ); + + $form['links']['biblio_link_title_url'] = array( + '#type' => 'checkbox', + '#title' => t('Hyperlink titles using supplied URL if available'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_link_title_url', 0), + '#description' => t('Selecting this links the titles to the supplied URL (if available) rather than the "node" view.') + ); + $form['links']['author'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#title' => t('Author Links') + ); + $form['links']['author']['biblio_author_links'] = array( + '#type' => 'checkbox', + '#title' => t('Hyperlink author names'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_author_links', 1), + '#description' => t('This creates a hyperlink on author names which, when clicked, will select entries which contain that author') + ); + $form['links']['author']['biblio_author_link_profile'] = array( + '#type' => 'checkbox', + '#title' => t('Hyperlink author names to author profile page'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_author_link_profile', 0), + '#description' => t('This creates a hyperlink on author names which, when clicked, will take the user to the authors profile page') + ); + $form['links']['author']['biblio_author_link_profile_path'] = array( + '#type' => 'textfield', + '#title' => t('Path to profile page'), + '#default_value' => variable_get('biblio_author_link_profile_path', 'user/[user:uid]'), + '#description' => t('Do not include a leading "/"') + ); + + $form['links']['author']['token_tree'] = array( + '#theme' => 'token_tree', + '#token_types' => array('user'), + ); + + $form['openurl'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('OpenURL'), + '#description' => t('You can set an openurl link here'), + '#group' => 'biblio_settings', + '#weight' => 90, + ); + $form['openurl']['biblio_show_openurl_profile_form'] = array( + '#type' => 'checkbox', + '#title' => t('Allow users to override these settings on their "My account" page') , + '#return_value' => 1, + '#description' => t('If this is selected, a form similar to this section will be available to the user when they edit their own account information. This will allow them to override the global preferences set here.') , + '#default_value' => variable_get('biblio_show_openurl_profile_form', '1') + ); + + $form['openurl']['biblio_baseopenurl'] = array( + '#type' => 'textfield', + '#title' => t('OpenURL Base URL'), + '#size' => 95, + '#default_value' => variable_get('biblio_baseopenurl', ''), + '#description' => t('This sets your institution\'s base OpenURL gateway, which is used to generate OpenURL links. To implement a "Universal" OpenURL system, try using OCLC\'s OpenURL Resolver Registry gateway: http://worldcatlibraries.org/registry/gateway') + ); + $sid = "Biblio:" . variable_get('site_name', 'Drupal'); + $form['openurl']['biblio_openurl_sid'] = array( + '#type' => 'textfield', + '#title' => t('OpenURL Site ID'), + '#size' => 95, + '#default_value' => variable_get('biblio_openurl_sid', $sid), + '#description' => t('This sets your institution\'s site name, some link resolvers will require a specific Site ID in order to process your requests.') + ); + $form['openurl']['biblio_openurlimage'] = array( + '#type' => 'textfield', + '#title' => t('OpenURL Image'), + '#size' => 95, + '#default_value' => variable_get('biblio_openurlimage', ''), + '#description' => t('Enter a path to your image here, this image will be used as button which when clicked will find the entry via the OpenURL link') + ); + // Add profile page settings... this is done in a fucntion so it can be reused elsewhere + $form['biblio_profile'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Profile pages'), + '#group' => 'biblio_settings', + '#weight' => 100, + ); + $form['biblio_profile'] += _biblio_get_user_profile_form(); + // + if (!module_exists('search')) { + $search_text = '
    ' . t('Depends on') . ': ' . t('Search') . ' (' . t('disabled') . ')
    '; + $search_disabled = TRUE; + variable_set('biblio_footnotes_integration', 0); + } + else { + $search_text = '
    ' . t('Depends on') . ': ' . t('Search') . ' (' . t('enabled') . ')
    '; + $search_disabled = FALSE; + } + $form['search'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#description' => $search_text, + '#title' => t('Search'), + '#group' => 'biblio_settings', + '#weight' => 110, + ); + $form['search']['biblio_search'] = array( + '#type' => 'checkbox', + '#disabled' => $search_disabled, + '#title' => t('Enable a search box on the biblio page.'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_search', 0), + '#description' => t('Shows a search box on the biblio page that returns drupal search results in the biblio style.') + ); + $form['search']['biblio_search_button_text'] = array( + '#type' => 'textfield', + '#title' => t('Search button text'), + '#disabled' => $search_disabled, + '#size' => 95, + '#default_value' => variable_get('biblio_search_button_text', t('Biblio search')), + '#description' => t('This allows you to customize the text on the search button, it defaults to "Biblio search".') + ); + $form['search']['biblio_index'] = array( + '#type' => 'checkbox', + '#disabled' => $search_disabled, + '#title' => t('Re-/Index a biblio node when creating or updating.'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_index', 0), + '#description' => t('A biblio node must be indexed for the drupal search to know its content. You need to check this option if you want to search for a biblio node that you just created or updated. Otherwise you must wait for the cron job to reindex nodes.') + ); + $form['sort'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Sorting'), + '#description' => t('You can set the default sorting and ordering for the /biblio page here.'), + '#group' => 'biblio_settings', + '#weight' => 120, + ); + $form['sort']['biblio_sort'] = array( + '#type' => 'select', + '#title' => t('Sort by'), + '#default_value' => variable_get('biblio_sort', 'year'), + '#options' => array( + 'author' => t('Author'), + 'keyword' => t('Keyword'), + 'title' => t('Title'), + 'type' => t('Type'), + 'year' => t('Year') + ), + '#description' => t('This is the initial default sort.'), + ); + $stop_words = 'a,an,is,on,the'; + $form['sort']['biblio_stop_words'] = array( + '#type' => 'textfield', + '#title' => t('Words to remove from the beginning of titles prior to sorting'), + '#size' => 60, + '#default_value' => variable_get('biblio_stop_words', $stop_words), + '#description' => t('A comma separated list of (case insensitive) words to strip from the title for sorting purposes. NOTE: quotation and punctuation are stripped automatically.') + ); + + $form['sort']['biblio_sort_tabs'] = array( + '#type' => 'checkboxes', + '#title' => t('Show sort links'), + '#default_value' => variable_get('biblio_sort_tabs', array('author', 'title', 'type', 'year')), + '#options' => array( + 'author' => t('Author'), + 'keyword' => t('Keyword (Warning: sorting by keyword may produce many duplicates due to the fact that an entry is listed for each keyword attached to it.)'), + 'title' => t('Title'), + 'type' => t('Type'), + 'year' => t('Year') + ), + '#description' => t('You turn the sorting links at the top of the /biblio page here.') + ); + $form['sort']['biblio_sort_tabs_style'] = array( + '#type' => 'checkbox', + '#title' => t('Show sort links as "tabs"'), + '#default_value' => variable_get('biblio_sort_tabs_style', 0), + '#return_value' => 1, + '#description' => t('This changes the sort links from text links to tabs') + ); + $form['sort']['biblio_order'] = array( + '#type' => 'radios', + '#title' => t('Order'), + '#default_value' => variable_get('biblio_order', 'DESC'), + '#options' => array( + 'DESC' => t('Descending'), + 'ASC' => t('Ascending') + ) + ); + $form['style'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Styling'), + '#description' => t('You can set the default style for the /biblio page here.'), + '#group' => 'biblio_settings', + '#weight' => 130, + ); + $form['style']['biblio_no_year_text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display if no year of publication is available'), + '#size' => 95, + '#default_value' => variable_get('biblio_no_year_text', t('Submitted')), + '#description' => t('The text that is displayed when no date of publication is given or it is deliberately set to 9999, it defaults to "Submitted".') + ); + $form['style']['biblio_inpress_year_text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display if year of publication is set to 9998'), + '#size' => 95, + '#default_value' => variable_get('biblio_inpress_year_text', t('In Press')), + '#description' => t('The text that is displayed when the date of publication is deliberately set to 9998, it defaults to "In Press".') + ); + if (module_exists('biblio_citeproc')) { + $form['style']['biblio_citeproc_style'] = array( + '#type' => 'select', + '#title' => t('Style'), + '#default_value' => variable_get('biblio_citeproc_style', 'ieee.csl'), + '#options' => biblio_get_styles(), + '#description' => t('Set the bibliographic style of the "list" view.') + ); + } + else { + $form['style']['biblio_style'] = array( + '#type' => 'select', + '#title' => t('Style'), + '#default_value' => variable_get('biblio_style', 'cse'), + '#options' => biblio_get_styles(), + '#description' => t('Set the bibliographic style of the "list" view.') + ); + } + $form['style']['biblio_node_layout'] = array( + '#type' => 'radios', + '#title' => t('Node Layout'), + '#default_value' => variable_get('biblio_node_layout', 'tabular'), + '#options' => array( + 'orig' => t('Original'), + 'ft' => t('Only Fulltext if available'), + 'tabular' => t('Tabular'), + 'cite' => t('Bibliographic style choosen above') + ), + '#description' => t('This alters the layout of the "node" (full) view.') + ); + $form['style']['biblio_annotations'] = array( + '#type' => 'select', + '#title' => t('Annotations'), + '#default_value' => variable_get('biblio_annotations', 'none'), + '#options' => array( + 'none' => t('none'), + 'biblio_notes' => t('notes'), + 'biblio_custom1' => t('custom1'), + 'biblio_custom2' => t('custom2'), + 'biblio_custom3' => t('custom3'), + 'biblio_custom4' => t('custom4'), + 'biblio_custom5' => t('custom5'), + 'biblio_custom6' => t('custom6'), + 'biblio_custom7' => t('custom7') + ), + '#description' => t('Select a field from which an annotation will be displayed below biblo entry in "short" listings'), + '#multiple' => FALSE, + '#size' => 0 + ); + $form['syndication'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Syndication'), + '#description' => t('You can set the RSS defaults here.'), + '#group' => 'biblio_settings', + '#weight' => 140, + ); + $form['syndication']['biblio_rss'] = array( + '#type' => 'checkbox', + '#title' => t('Allow RSS feeds of new biblio entries'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_rss', 0), + '#description' => t('This will create an rss feed of the 10 most recent biblio entries. It will be available at /biblio/rss.xml') + ); + $form['syndication']['biblio_rss_number_of_entries'] = array( + '#type' => 'textfield', + '#title' => t('Number of items in the RSS feed.'), + '#default_value' => variable_get('biblio_rss_number_of_entries', 10), + '#size' => 6, + '#maxlength' => 6, + '#description' => t('Limits the number of items in the /biblio/rss.xml feed to this number.') + ); + + $form = system_settings_form($form); + $form['#submit'][] = 'biblio_admin_settings_form_submit'; + // our submit handler is added after the call to system settings form so that it gets + // called after system_settings_form_submit, and thus the variables have been stored already + // and the menu will be rebuilt correctly. + return ($form); +} +/** + * Form handler for biblio_admin_settings + * + */ +function biblio_admin_settings_form_submit($form, &$form_state) { +// if ($form_state['values']['biblio_keyword_freetagging'] && $form_state['values']['biblio_keyword_vocabulary']) { +// if ($vocabulary = taxonomy_vocabulary_load(variable_get('biblio_keyword_vocabulary', 0))) { +// $vocabulary = (array) $vocabulary; +// $vocabulary['nodes']['biblio'] = 1; +// taxonomy_save_vocabulary($vocabulary); +// } +// } + + if (($form['#biblio_base'] != $form_state['values']['biblio_base']) || + ($form['biblio_profile']['#biblio_show_profile'] != $form_state['values']['biblio_show_profile']) || + ($form['biblio_profile']['#biblio_my_pubs_menu'] != $form_state['values']['biblio_my_pubs_menu']) ) + { + menu_rebuild(); + } +} +/** + * Form constructor for the Publication type edit form. + * + * @param int $tid + * The publication type. + * @return array $form + */ +function biblio_admin_types_edit_form($form, &$form_state, $tid = 0) { + // $form['#theme'] = 'biblio_admin_types_edit_form'; + // $form['#tree'] = TRUE; + $form['#cache'] = TRUE; + $tid = (isset($form_state['pub_type']) ? $form_state['pub_type'] : (int)$tid); + $msg = '
    ' . t('On this page you can set type specific "Titles" and "Hints" which will display on the input form.'); + if ($tid) { + $msg .= ' ' . t('Checking the "Visible" box will add the field to the input form, checking "Required" will force the user to supply a value for this field and the weight value changes the order which it is rendered on the form with smaller values floating to the top of the form. +

    Fields which are grayed out on this page have been set to "common" on the !linktobiblioadmin page.

    ', array('!linktobiblioadmin' => l("admin/config/content/biblio/fields", "admin/config/content/biblio/fields"))); + + } + else { + $msg .= ' ' . t('Checking the "Common" box will add the field to all the different publication types. Checking "Required" will force the user to supply a value for the field, checking "Autocomplete" will enable AJAX type auto complete look up for the field when the user is entering data and the weight value changes the order which it is rendered on the form with smaller values floating to the top of the form.'); + } + $msg .= t('Finally, for each author field you can choose a set of author roles. Assigning different roles to authors within the same field, e.g. primary and secondary authors within the authors field, allows to theme them differently.'); + $msg .= '
    '; + + $form['#redirect'] = 'admin/config/content/biblio/fields'; + $form['help'] = array( + '#type' => 'fieldset', + '#title' => t('Help'), + '#collapsible' => TRUE, + '#collapsed' => TRUE + ); + $form['help']['message'] = array( + '#markup' => $msg + ); + // get author types + $result = db_query("SELECT * FROM {biblio_contributor_type_data} b"); + $contrib_options = array(); + foreach ($result as $contrib_type) { + $contrib_options[$contrib_type->auth_type] = $contrib_type->title; + $contrib_hints[$contrib_type->auth_type] = $contrib_type->hint; + } + + // first get all of the field info + if ($tid) { + $result = db_query("SELECT bf.*, bftd.*, bft.vtab, bft.common, bft.visible, bft.autocomplete, bft.weight, bft.required, bt.name AS type_name + FROM {biblio_fields} AS bf + INNER JOIN {biblio_field_type} AS bft ON bft.fid=bf.fid + INNER JOIN {biblio_field_type_data} AS bftd ON bftd.ftdid=bft.ftdid + INNER JOIN {biblio_types} AS bt ON bt.tid=bft.tid + WHERE bft.tid=:tid", array(':tid' => $tid), array('fetch' => PDO::FETCH_ASSOC)); + } + else { + $result = db_query("SELECT bf.*, bftd.*, bft.vtab, bft.common, bft.visible, bft.autocomplete, bft.weight, bft.required + FROM {biblio_fields} AS bf + INNER JOIN {biblio_field_type} AS bft ON bft.fid=bf.fid + INNER JOIN {biblio_field_type_data} AS bftd ON bftd.ftdid=bft.ftdid + WHERE bft.tid=:tid", array(':tid' => $tid), array('fetch' => PDO::FETCH_ASSOC)); + } + + foreach ($result as $row) { + $fields[$row['fid']] = $row; + } + + $types = db_query("SELECT * from {biblio_types} WHERE visible=1 ORDER BY name ASC"); + $types_options[0] = t('Common'); + foreach ($types as $type) { + $types_options[$type->tid] = $type->name; + } + + $form['pub_type'] = array( + '#title' => t('Publication type'), + '#type' => 'select', + '#options' => $types_options, + '#attributes' => array('onchange' => 'document.getElementById(\'edit-change-type\').click()'), + '#default_value' => $tid, + ); + + $no_js = (!isset($_COOKIE['has_js']) || empty($_COOKIE['has_js'])); + + $form['change_type'] = array( + '#type' => 'submit', + '#value' => t('Change Publication Type'), + '#weight' => -10, + '#prefix' => $no_js ? '' : '
    ', + '#suffix' => $no_js ? '' : '
    ', + ); + + $form['biblio_tabs'] = array( + '#type' => 'vertical_tabs', + '#weight' => 0, + ); + + $form['biblio_tabs'] += biblio_node_form_vtabs(); + + // $form['configured_flds'] = array('#tree' => 1); + if ($tid) { // show an existing type + $typename = $fields[1]['type_name']; + $form['type_name'] = array( + '#title' => t('Publication type name'), + '#type' => 'textfield', + '#maxlength' => 64, + '#default_value' => $typename, + '#required' => TRUE, + ); + $form['top_message'] = array( + '#value' => t('Field settings related to @type publications', array('@type' => $typename)) + ); + $form['type_id'] = array( + '#type' => 'value', + '#title' => 'tid', + '#value' => $tid + ); + } + else { + $form['top_message'] = array( + '#value' => t('Field settings common to all publication types') + ); + + } + uasort($fields, "biblio_form_sort"); // resort the fields since the weight may have changed + $vis_comm = $tid ? 'visible' : 'common'; + $options["$vis_comm"] = ''; + $options['required'] = ''; + if ($tid == 0) { + $options['autocomplete'] = ''; + } + foreach ($fields as $key => $fld) { + if ($fld['type'] == 'contrib_widget') { + continue; + } + $def_values[$fld['name']] = array(); + if ($tid) { + if ($fld['visible'] ) { + array_push($def_values[$fld['name']], 'visible'); + } + if ($fld['required']) { + array_push($def_values[$fld['name']], 'required'); + } + } + else { + if ($fld['common']) { + array_push($def_values[$fld['name']], 'common'); + } + if ($fld['required']) { + array_push($def_values[$fld['name']], 'required'); + } + if ($fld['autocomplete']) { + array_push($def_values[$fld['name']], 'autocomplete'); + } + } + $disabled = $tid ? ($fld['common'] ? 1 : 0) : 0; + $form_state['tab_defaults'][$key]['common'] = $disabled; + + $tab_data[$fld['vtab']][$key]['#parents'] = array('biblio_tabs', $key); + $tab_data[$fld['vtab']][$key]['name'] = array( + '#type' => 'markup', + '#markup' => check_plain($fld['name']), + '#weight' => $fld['weight'] + ); + $tab_data[$fld['vtab']][$key]['title'] = array( + '#type' => 'textfield', + '#default_value' => $fld['title'], + '#size' => 15, + '#weight' => $fld['weight'], + '#disabled' => $disabled + ); + $form_state['tab_defaults'][$key]['title']['default'] = $fld['title']; //we need store the default to see if it's changed later + $form_state['tab_defaults'][$key]['title']['disabled'] = $disabled; + + $tab_data[$fld['vtab']][$key]['hint'] = array( + '#type' => 'textfield', + '#default_value' => $fld['hint'], + '#size' => 20, + '#weight' => $fld['weight'], + '#disabled' => $disabled + ); + $form_state['tab_defaults'][$key]['hint']['default'] = $fld['hint']; + $form_state['tab_defaults'][$key]['hint']['disabled'] = $disabled; + + $tab_data[$fld['vtab']][$key]['weight'] = array( + '#type' => 'textfield', + '#default_value' => $fld['weight'], + '#size' => 2, + '#weight' => $fld['weight'], + //'#disabled' => $disabled + ); + $tab_data[$fld['vtab']][$key]['checkboxes'] = array( + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => $def_values[$fld['name']], + '#weight' => $fld['weight'], + //'#disabled' => $disabled + ); + $tab_data[$fld['vtab']][$key]['ftdid'] = array( + '#type' => 'value', + '#value' => $fld['ftdid'] + ); + + } + if ($tid) { + $header = array('', t('Field Name'), t('Title'), t('Hint'), t('Visible'), t('Required'), ''); + } + else { + $header = array('', t('Field Name'), t('Default Title'), t('Hint'), t('Common'), t('Required'), t('Autocomplete'), ''); + } + + foreach ($tab_data as $key => $rows) { + $form['biblio_tabs'][$key][] = array( + '#theme' => 'biblio_field_tab', + '#header' => $header, + 'rows' => $rows, + ); + } + + $form['biblio_tabs']['#tree'] = TRUE; + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save') + ); + + return $form; +} + +/** + * Form handler for biblio_admin_types_edit_form + */ +function biblio_admin_types_edit_form_submit($form, &$form_state) { + if ($form_state['triggering_element']['#value'] == t('Change Publication Type')) { + $form_state['rebuild'] = TRUE; + $form_state['pub_type'] = $form_state['values']['pub_type']; + $form_state['input'] = array(); + return; + } + $tid = isset($form_state['values']['type_id']) ? $form_state['values']['type_id'] : 0; + + if ($tid) { + // save publication type name + $type_name_record = array( + 'tid' => $tid, + 'name' => $form_state['values']['type_name'], + ); + + db_update('biblio_types') + ->fields($type_name_record) + ->condition('tid', $tid) + ->execute(); + biblio_locale_refresh_types($tid); + } + + $hide_other_fields = isset($form_state['values']['hide_other_fields']) ? $form_state['values']['hide_other_fields'] : 0; + variable_set('biblio_hide_other_fields', $hide_other_fields); + + // save other field data + foreach ($form_state['values']['biblio_tabs'] as $fid => $values) { + $update = FALSE; + $val = array(); + $link = array(); + if (is_int($fid)) { + $common = $form_state['tab_defaults'][$fid]['common']; + $link['fid'] = $fid; + $link['tid'] = $tid; + foreach ($values as $name => $value) { + $disabled = !empty($form_state['tab_defaults'][$fid][$name]['disabled']) ? TRUE : FALSE; + if (!$disabled) { + $default_value = isset($form_state['tab_defaults'][$fid][$name]['default']) ? $form_state['tab_defaults'][$fid][$name]['default'] : ''; + if ($name == 'checkboxes') { + $link['visible'] = (!empty($value['visible'])) ? 1 : 0; + $link['required'] = (!empty($value['required'])) ? 1 : 0; + if ($tid == 0) { + $link['common'] = (!empty($value['common'])) ? 1 : 0; + $link['autocomplete'] = (!empty($value['autocomplete'])) ? 1 : 0; + } + } + elseif ($name == 'weight') { + $link['weight'] = $value; + } + else { + $val[$name] = $common ? $default_value : $value; + if (!$update) { + $update = ($default_value != $val[$name]) ? $form_state['values']['biblio_tabs'][$fid]['ftdid'] : FALSE; + } + } + } + } + if ($update == $fid && $link['tid']) { // we just changed a default value, so create a new entry in biblio_field_type_data + $new_ftdid = variable_get('biblio_last_ftdid', 100); + variable_set('biblio_last_ftdid', $new_ftdid + 1); + $val['ftdid'] = $new_ftdid; + $link['ftdid'] = $new_ftdid; + $link['cust_tdid'] = $new_ftdid; + drupal_write_record('biblio_field_type_data', $val); + + } + elseif ($update >= 100 && $link['tid']) { // we are updating an existing entry + $val['ftdid'] = $form_state['values']['biblio_tabs'][$fid]['ftdid']; + drupal_write_record('biblio_field_type_data', $val, 'ftdid'); + + } + elseif ($update == $fid) { // changing the defaults + $val['ftdid'] = $fid; + drupal_write_record('biblio_field_type_data', $val, 'ftdid'); + } + + drupal_write_record('biblio_field_type', $link, array('fid', 'tid')); + + if ($tid == 0) { + if ($link['common']) { + // $query = "UPDATE {biblio_field_type} SET ftdid = %d, common = %d, visible = %d WHERE fid = %d"; + // db_query($query, array($fid, 1, 1, $fid)); + db_update('biblio_field_type') + ->fields(array('ftdid' => $fid, 'common' => 1, 'visible' => 1)) + ->condition('fid', $fid) + ->execute(); + } + else { // not common, so change pointer back to customizations if available + $query = "UPDATE {biblio_field_type} SET ftdid = cust_tdid, common = :comm WHERE fid = :fid"; + db_query($query, array(':comm' => 0, ':fid' => $fid)); + // db_update('biblio_field_type') + // ->fields(array('ftdid' => $fid, 'common' => 0, 'visible' => 1)) + // ->condition('fid', $fid) + // ->execute(); + } + // set the autocomplete bit on this field for all the types + // $query = "UPDATE {biblio_field_type} SET autocomplete = %d WHERE fid = %d"; + // db_query($query, array($link['autocomplete'], $fid)); + db_update('biblio_field_type') + ->fields(array('autocomplete' => $link['autocomplete'])) + ->condition('fid', $fid) + ->execute(); + } + } + } + drupal_set_message(t("The changes have been saved.")); + + // Clear the cached pages and menus: + //menu_rebuild(); + + // Refresh translatable field strings. + biblio_locale_refresh_fields($tid); +} + +/** + * Outputs a page containing "edit" links for each enabled import/export module. + * + * Each link goes to a page allowing the user to modifiy the field mapping for + * each of the import/export formats. + */ +function biblio_admin_io_mapper_page() { + $formats = module_invoke_all('biblio_mapper_options'); + asort($formats); + + foreach ($formats as $key => $value) { + $rows[] = array( + $value['title'], + l(t('edit'), 'admin/config/content/biblio/iomap/edit/' . $key), + ); + } + + if (count($rows) == 0) { + drupal_set_message(t('There are no import/export modules enabled. Please go to the !l page and enable at least one.', + array('!l' => l(t('modules'), "admin/modules")))); + } + + $header = array(t('Format'), t('Action')); + return theme('table', array('header' => $header, 'rows' => $rows)); +} + +/** + * @param unknown_type $format + * @param unknown_type $exportable + * @return Ambigous Ambigous , multitype:string > + */ +function biblio_admin_io_mapper_form($form, $form_state, $format, $exportable = TRUE) { + $formats = module_invoke_all('biblio_mapper_options'); + $form = array(); + if (isset($formats[$format])) { + $form['title'] = array( + '#prefix' => '

    ', + '#markup' => check_plain($formats[$format]['title']) . ' ' . t('file format mapping'), + '#suffix' => '

    ', + ); + $form['fileformat_title'] = array( + '#type' => 'hidden', + '#value' => $formats[$format]['title'], + ); + + } + $form['fileformat'] = array( + '#type' => 'hidden', + '#value' => $format, + ); + $form['fileformat_export'] = array( + '#type' => 'hidden', + '#value' => isset($formats[$format]['export']) ? $formats[$format]['export'] : $exportable, + ); + + $form['typemap'] = array( + '#type' => 'fieldset', + '#title' => t('Publication types'), + '#collapsible' => TRUE, + '#collapsed' => FALSE + ); + $form['typemap']['#theme'] = 'biblio_admin_type_mapper_form'; + $form['typemap']['#tree'] = TRUE; + $form['typemap'] += biblio_admin_type_mapper_form($format); + + $form['fieldmap'] = array( + '#type' => 'fieldset', + '#title' => t('Data fields'), + '#collapsible' => TRUE, + '#collapsed' => FALSE + ); + $form['fieldmap']['#theme'] = 'biblio_admin_field_mapper_form'; + $form['fieldmap']['#tree'] = TRUE; + $form['fieldmap'] += biblio_admin_field_mapper_form($format, $exportable); + + return $form; +} +/** + * @param unknown_type $variables + * @return unknown + */ +function theme_biblio_admin_io_mapper_form($variables) { + $form = $variables['form']; + $header = $rows = array(); + $output = drupal_render($form['title']); + $rows[] = array('data' => array( + array('data' => drupal_render($form['typemap']), 'style' => 'vertical-align:top'), + array('data' => drupal_render($form['fieldmap']), 'style' => 'vertical-align:top'), + ) + ); + $output .= theme('table', array('header' => $header, 'rows' => $rows)); + $output .= drupal_render_children($form); + + return $output; +} + +/** + * @param unknown_type $format + * @param unknown_type $type + * @return multitype:string NULL multitype:string + */ +function biblio_admin_io_mapper_add_form($form, &$form_state, $format, $type) { + $formats = module_invoke_all('biblio_mapper_options'); + + if ($type == 'pubtype') { + $title = 'Publication Type'; + $desc = t('This is the name of the type identifier, exactly as it appears in the file'); + $submit = array('biblio_admin_io_mapper_add_form_pubtype_submit'); + } + if ($type == 'field') { + $title = 'Field name'; + $desc = t('This is the name of the field identifier, exactly as it appears in the file'); + $submit = array('biblio_admin_io_mapper_add_form_field_submit'); + } + $form['fileformat'] = array( + '#type' => 'hidden', + '#value' => $format, + ); + $form['fileformat_title'] = array( + '#type' => 'hidden', + '#value' => $formats[$format]['title'], + ); + $form['type_name'] = array( + '#type' => 'textfield', + '#title' => 'Publication Type', + '#required' => TRUE, + '#description' => t('This is the name of the type identifier, exactly as it appears in the file'), + ); + $form['type_desc'] = array( + '#type' => 'textfield', + '#title' => 'Description' + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Add'), + '#submit' => $submit, + ); + + return $form; +} + +/** + * @param unknown_type $variables + * @return unknown + */ +function theme_biblio_admin_io_mapper_add_form($variables) { + $output = ''; + $form = $variables['form']; + $title = $form['fileformat_title']['#value']; + drupal_set_title(t('Add new publication type to %title file type', array('%title' => $title))); + $output .= drupal_render($form['type_name']); + $output .= drupal_render($form['type_desc']); + $output .= drupal_render($form['submit']); + $output .= drupal_render_children($form); + return $output; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_io_mapper_add_form_pubtype_submit($form, &$form_state) { + $names = biblio_get_map('type_names', $form_state['values']['fileformat']); + $names[$form_state['values']['type_name']] = $form_state['values']['type_desc']; + biblio_set_map('type_names', $form_state['values']['fileformat'], $names); + $form_state['redirect'] = 'admin/config/content/biblio/iomap/edit/' . $form_state['values']['fileformat']; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_io_mapper_add_form_field_submit($form, &$form_state) { + $names = biblio_get_map('field_map', $form_state['values']['fileformat']); + $names[$form_state['values']['type_name']] = ''; + biblio_set_map('field_map', $form_state['values']['fileformat'], $names); +} + +/** + * @param unknown_type $format + * @return Ambigous + */ +function biblio_admin_type_mapper_form($format) { + $formats = module_invoke_all('biblio_mapper_options'); + $form['#file_format_title'] = isset($formats[$format]) ? $formats[$format]['title'] : ''; + $form['#file_format'] = $format; + $names = biblio_get_map('type_names', $format); + $map = biblio_get_map('type_map', $format); + ksort($names); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save publication type mapping'), + '#submit' => array('biblio_admin_type_mapper_form_submit'), + ); + $form['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset publication type mapping to default'), + '#submit' => array('biblio_admin_type_mapper_form_reset_submit'), + ); + + $result = db_query('SELECT t.* FROM {biblio_types} as t WHERE t.tid > 0'); + + foreach ($result as $type) { + $biblio_type_options[$type->tid] = $type->name; + } + + $biblio_type_options[0] = t('-none-'); + asort($biblio_type_options); + + $biblio_type_select = array( + '#type' => 'select', + '#options' => $biblio_type_options, + ); + foreach ($names as $key => $value) { + $biblio_type_select['#default_value'] = (isset($map[$key]) ? $map[$key] : 0); + $form['type'][$key] = array( + 'format' => array('#markup' => "" . check_plain($key) . " (" . check_plain($value) . ")"), + 'biblio' => $biblio_type_select, + ); + + } + return $form; +} + +/** + * @param unknown_type $variables + * @return unknown + */ +function theme_biblio_admin_type_mapper_form($variables) { + $form = $variables['form']; + $title = $form['#file_format_title']; + + foreach (element_children($form['type']) as $key ) { + $rows[] = array( + drupal_render($form['type'][$key]['format']), + drupal_render($form['type'][$key]) + ); + } + $rows[] = array( + l('[' . t('Add New') . " $title " . t('Publication Type') . ']', 'admin/config/content/biblio/iomap/' . $form['#file_format'] . '/pubtype/add') , + l('[' . t('Add New Biblio Publication Type') . ']', 'admin/config/content/biblio/pubtype/new') + ); + $header = array( + $title . ' ' . t('Publication Types'), + t('Biblio Publication Type') + ); + $output = theme('table', array('header' => $header, 'rows' => $rows)); + + $output .= drupal_render($form['submit']); + $output .= drupal_render($form['reset']); + + $output .= drupal_render_children($form); + return $output; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_type_mapper_form_submit($form, $form_state) { + foreach ($form_state['values']['typemap']['type'] as $key => $value) { + if (is_array($value)) { + $map[$key]=$value['biblio']; + } + } + biblio_set_map('type_map', $form_state['values']['fileformat'], $map); +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_type_mapper_form_reset_submit($form, $form_state) { + biblio_reset_map('type_map', $form_state['values']['fileformat']); + biblio_reset_map('type_names', $form_state['values']['fileformat']); +} + +/** + * @param unknown_type $format + * @param unknown_type $exportable + * @return Ambigous Ambigous , multitype:string > + */ +function biblio_admin_field_mapper_form($format, $exportable = TRUE) { + $formats = module_invoke_all('biblio_mapper_options'); + $form['#file_format_title'] = isset($formats[$format]) ? $formats[$format]['title'] : ''; + $form['#file_format'] = $format; + $exportable = isset($formats[$format]['export']) ? $formats[$format]['export'] : $exportable; + $form['#file_format_export'] = $exportable; + $map = biblio_get_map('field_map', $format); + $export_map = biblio_get_map('export_map', $format); + ksort($map); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save field mapping'), + '#submit' => array('biblio_admin_field_mapper_form_submit'), + ); + $form['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset field mapping to default'), + '#submit' => array('biblio_admin_field_mapper_form_reset_submit'), + ); + + $schema = drupal_get_schema('biblio'); + $fieldnames = array_keys($schema['fields']); + asort($fieldnames); + $biblio_field_options[''] = t('-none-'); + $biblio_field_options['title'] = 'title'; + foreach ($fieldnames as $field) { + $biblio_field_options[$field] = $field; + } + + $biblio_field_select = array( + '#type' => 'select', + '#options' => $biblio_field_options, + ); + + foreach ($map as $key => $value) { + $biblio_field_select['#default_value'] = $map[$key]; + $form['type'][$key] = array( + 'format' => array('#markup' => "" . $key . ""), + 'biblio' => $biblio_field_select, + '#tree' => 1, + ); + if ($exportable) { + $form['type'][$key]['export'] = array( + '#type' => 'checkbox', + '#default_value' => isset($export_map[$value]) ? $export_map[$value] : '' + ); + } + + } + return $form; +} + +/** + * @param unknown_type $variables + * @return unknown + */ +function theme_biblio_admin_field_mapper_form($variables) { + $form = $variables['form']; + $title = $form['#file_format_title']; + + foreach (element_children($form['type']) as $key ) { + $row = array(); + $row[] = drupal_render($form['type'][$key]['format']); + $row[] = drupal_render($form['type'][$key]['biblio']); + if ($form['#file_format_export']) { + $row[] = drupal_render($form['type'][$key]); + } + $rows[] = $row; + } + $header = array( + $title . ' ' . t('field identifier'), + t('Biblio schema field'), + ); + if ($form['#file_format_export']) { + $header[] = t('Export'); + } + $output = theme('table', array('header' => $header, 'rows' => $rows)); + //$output .= l('[' . t('Add New') . " $title " . t('Publication Type') . ']','admin/config/content/biblio/fields/typemap/' . $form['#file_format'] . '/add') . ' '; + //$output .= l('[' . t('Add New Biblio Publication Type') . ']','admin/config/content/biblio/fields/type/new') . '
    '; + $output .= drupal_render_children($form); + + return $output; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_field_mapper_form_submit($form, $form_state) { + foreach ($form_state['values']['fieldmap']['type'] as $key => $value) { + if (is_array($value)) { + $map[$key] = $value['biblio']; + if ($form_state['values']['fileformat_export']) { + $export_map[$value['biblio']] = $value['export']; + } + } + } + biblio_set_map('field_map', $form_state['values']['fileformat'], $map); + if ($form_state['values']['fileformat_export']) { + biblio_set_map('export_map', $form_state['values']['fileformat'], $export_map); + } + + // elseif ($form_state['values']['op'] == t('Add')) { + // $names = biblio_get_map('type_names', $form_state['values']['fileformat']); + // $names[$form_state['values']['type_name']] = $form_state['values']['type_desc']; + // variable_set('biblio_'.$form_state['values']['fileformat'] .'_type_names', $names); + // } + +} +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_field_mapper_form_reset_submit($form, $form_state) { + biblio_reset_map('field_map', $form_state['values']['fileformat']); + biblio_reset_map('export_map', $form_state['values']['fileformat']); +} + +/** + * @return unknown + */ +function biblio_admin_types_form() { + $output = ''; + $result = db_query('SELECT t.* FROM {biblio_types} as t WHERE t.tid > 0 ORDER BY name ASC'); + //$rows[] = array('',t('Fields Common To All Types'),l('edit', 'admin/config/content/biblio/types/edit'),''); + foreach ($result as $row ) { + if ($row->tid < 999) { + $rows[] = array( + $row->tid, + check_plain($row->name), + ($row->visible) ? l(t('edit'), 'admin/config/content/biblio/fields/' . $row->tid) : '', ($row->visible) ? l(t('hide'), 'admin/config/content/biblio/pubtype/hide/' . $row->tid) : l(t('show'), 'admin/config/content/biblio/pubtype/show/' . $row->tid) + ); + } + else { + $rows[] = array( + $row->tid, + check_plain($row->name), + l(t('edit'), 'admin/config/content/biblio/fields/' . $row->tid), l(t('delete'), 'admin/config/content/biblio/pubtype/delete/' . $row->tid) + ); + } + } + $header = array( + t('Type Id'), + t('Type Name'), array( + 'data' => t('Operations'), + 'colspan' => '2' + ) + ); + $output .= theme('table', array('header' => $header, 'rows' => $rows)); + $output .= l(t('Reset to defaults'), 'admin/config/content/biblio/pubtype/reset'); + return $output; +} +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @return multitype:string number NULL + */ +function biblio_admin_types_add_form($form, &$form_state) { + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Type Name'), + '#size' => 20, + '#weight' => 1, + '#required' => TRUE, + '#maxlength' => 64 + ); + $form['description'] = array( + '#type' => 'textfield', + '#title' => t('Description'), + '#size' => 60, + '#weight' => 2, + '#maxlength' => 255 + ); + $form['type_button'] = array( + '#type' => 'submit', + '#value' => t('Create New Type'), + '#weight' => 3 + ); + + + return $form; +} +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_types_add_form_submit($form, &$form_state) { + $values['name'] = $form_state['values']['name']; + $values['description'] = $form_state['values']['description']; + $values['tid'] = variable_get('biblio_max_user_tid', '999') + 1; + + db_insert('biblio_types') + ->fields($values) + ->execute(); + + variable_set('biblio_max_user_tid', $values['tid']); + $result = db_query('SELECT * FROM {biblio_field_type} WHERE tid=0', array(), array('fetch' => PDO::FETCH_ASSOC)); + foreach ($result as $row) { + $row['tid'] = $values['tid']; + $row['visible'] = 1; + db_insert('biblio_field_type') + ->fields($row) + ->execute(); + } + // Fill contributor types. Use the first 4 defaults. + for ($type = 1; $type <= 4; $type++) { + $ct_vals = array( + 'auth_category' => $type, + 'biblio_type' => $values['tid'], + 'auth_type' => $type, + ); + + db_insert('biblio_contributor_type') + ->fields($ct_vals) + ->execute(); + } + + // Refresh publication type string for translation. + biblio_locale_refresh_types($values['tid']); + $form_state['redirect'] = 'admin/config/content/biblio/pubtype'; + +} + +function biblio_admin_types_reset() { + module_load_include('install', 'biblio'); + + db_delete('biblio_types') + ->execute(); + _add_publication_types(); + drupal_goto('admin/config/content/biblio/pubtype'); +} +/** + * + */ +function biblio_admin_types_hide() { + $args = func_get_args(); + if ($args[0] > 0 && is_numeric($args[0])) { + db_update('biblio_types') + ->fields(array( + 'visible' => 0)) + ->condition('tid', $args[0]) + ->execute(); + } + drupal_goto('admin/config/content/biblio/pubtype'); +} +/** + * + */ +function biblio_admin_types_show() { + $args = func_get_args(); + if ($args[0] > 0 && is_numeric($args[0])) { + db_update('biblio_types') + ->fields(array( + 'visible' => 1)) + ->condition('tid', $args[0]) + ->execute(); + } + drupal_goto('admin/config/content/biblio/pubtype'); +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $tid + * @return unknown + */ +function biblio_admin_types_delete_form($form, &$form_state, $tid) { + $existing_msg = ''; + if (isset($tid) && is_numeric($tid)) { + $row = db_query('SELECT t.* FROM {biblio_types} as t WHERE t.tid = :tid ', array(':tid'=> $tid))->fetchObject(); + $num_rows = db_query('SELECT COUNT(*) FROM {biblio} as b WHERE b.biblio_type = :btype', array(':btype' => $row->tid))->fetchField(); + if ($num_rows) { + $existing_msg = t('There are @count biblio entries of this type, you should change the type of these entries before proceeding otherwise they will be deleted', array( + '@count' => $num_rows + )); + } + $form['tid'] = array( + '#type' => 'value', + '#value' => $row->tid + ); + $output = confirm_form($form, t('Are you sure you want to delete the custom biblio type: %title ?', array( + '%title' => $row->name + )) . $existing_msg, isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/content/biblio/pubtype', t('This action cannot be undone.'), t('Delete'), t('Cancel')); + return $output; + } + else { + drupal_goto('admin/config/content/biblio/pubtype'); + } +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_types_delete_form_submit($form, &$form_state) { + $query = db_select('biblio_field_type', 'bft'); + $result = $query->fields('bft', array('ftdid')) + ->condition('tid', $form_state['values']['tid']) + ->condition('ftdid', 100, '>') + ->execute(); + foreach ($result as $field) { + $ftdids[] = $field->ftdid; + } + + db_delete('biblio_field_type_data') + ->condition('ftdid', $ftdids, 'IN') + ->execute(); + + db_delete('biblio_types') + ->condition('tid', $form_state['values']['tid']) + ->execute(); + + db_delete('biblio_field_type') + ->condition('tid', $form_state['values']['tid']) + ->execute(); + + $form_state['redirect'] = 'admin/config/content/biblio/pubtype'; +} + +/** + * @return unknown + */ +function biblio_admin_types_reset_form() { + $form['reset'] = array( + '#type' => 'value', + '#value' => 'reset' + ); + $output = confirm_form($form, t('Are you sure you want to reset ALL the field definitions to the defaults?'), isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/content/biblio/fields', t('By doing this, you will loose all customizations you have made to the field titles, this action cannot be undone!'), t('Reset!'), t('Cancel')); + return $output; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_types_reset_form_submit($form, & $form_state) { + module_load_include('install', 'biblio'); + biblio_reset_types(); + $form_state['redirect'] = 'admin/config/content/biblio/fields'; +} +/* + * This functin is used by both the admin/config/content/biblio page and user profile page + * - if $user is set, then it is being called from the user profile page + */ +function _biblio_get_user_profile_form($profile_user = FALSE) { + $form = array(); + $allow_edit = variable_get('biblio_show_user_profile_form', '1'); + $allow_edit = $allow_edit || user_access('administer biblio'); + + if (!$profile_user) { + $form['biblio_show_user_profile_form'] = array( + '#type' => 'checkbox', + '#title' => t('Allow users to override these settings on their "My account" page') , + '#return_value' => 1, + '#description' => t('If this is selected, a form similar to this section will be available to the user when they edit their own account information. This will allow them to override the global preferences set here.') , + '#default_value' => variable_get('biblio_show_user_profile_form', '1') + ); + } + $form['biblio_show_profile'] = array( + '#type' => 'checkbox', + '#title' => ($profile_user) ? t('Show my publications on my profile page') : t('Show publications on users profile pages'), + '#return_value' => 1, + '#disabled' => !$allow_edit, + '#description' => ($profile_user) ? t('Selecting this will create a listing of your publications on your profile page') : t('This sets the site wide default, users may change this in their profile') + ); + if ($profile_user) { + $form['biblio_show_profile']['#default_value'] = (isset($profile_user->data['biblio_show_profile'])) ? $profile_user->data['biblio_show_profile'] : variable_get('biblio_show_profile', '0'); + } + else { + $form['biblio_show_profile']['#default_value'] = variable_get('biblio_show_profile', '0'); + } + $form['#biblio_show_profile'] = $form['biblio_show_profile']['#default_value']; + $form['biblio_my_pubs_menu'] = array( + '#type' => 'checkbox', + '#title' => t('Show "My publications" item in the navigation menu'), + '#disabled' => !$allow_edit, + '#return_value' => 1, + '#description' => '' + ); + if ($profile_user) { + $form['biblio_my_pubs_menu']['#default_value'] = ((isset($profile_user->data['biblio_my_pubs_menu'])) ? $profile_user->data['biblio_my_pubs_menu'] : variable_get('biblio_my_pubs_menu', '0')); + } + else { + $form['biblio_my_pubs_menu']['#default_value'] = variable_get('biblio_my_pubs_menu', '0'); + } + $form['#biblio_my_pubs_menu'] = $form['biblio_my_pubs_menu']['#default_value']; + + if ($profile_user) { + $form += _biblio_drupal_author_user_map($profile_user, $allow_edit); + } + + $options = array(); + $options['system'] = t('System default'); + $options = array_merge($options, biblio_get_styles() ); + $form['biblio_user_style'] = array( + '#type' => 'select', + '#title' => t('Style'), + '#default_value' => (isset($profile_user->data['biblio_user_style']) ? $profile_user->data['biblio_user_style'] : 'system'), + '#options' => $options, + '#description' => t('Set the bibliographic style of the "list" view.') + ); + + return $form; +} + +function _biblio_drupal_author_user_map($profile_user, $allow_edit = TRUE) { + global $user; + + $db_result = db_query("SELECT cd.lastname, cd.firstname, cd.initials, cd.cid FROM {biblio_contributor_data} cd + ORDER by cd.lastname ASC "); + $options = array(); + $options[0] = t('(None)'); + + foreach ($db_result as $row) { + $options[$row->cid] = "$row->lastname, $row->firstname $row->initials "; + } + if (isset($profile_user->data['biblio_id_change_count']) && $profile_user->data['biblio_id_change_count'] > 2) { + $allow_edit = 0; + $msg = t('This control has been disabled because the author mapping has been changed more than 3 times, please see your system administrator to have it reset.'); + } + else { + $msg = t('This will link your profile to the selected author from the biblio database, then all publications containing this author to be displayed on your "Publications" tab.'); + } + $form['biblio_contributor_id'] = array( + '#type' => 'select', + '#title' => t('Link My Profile with this Author from the Biblio database:'), + '#default_value' => isset($profile_user->data['biblio_contributor_id']) ? $profile_user->data['biblio_contributor_id'] : 0, + '#disabled' => ($user->uid == 1 || user_access('administer biblio')) ? FALSE : !$allow_edit, + '#options' => $options, + '#description' => $msg, + ); + $form['biblio_id_change_count'] = array( + '#type' => 'textfield', + '#size' => 2, + '#title' => t('Drupal UserID to Biblio AuthorID mapping change count:'), + '#default_value' => isset($profile_user->data['biblio_id_change_count']) ? $profile_user->data['biblio_id_change_count'] : 0, + '#disabled' => ($user->uid == 1 || user_access('administer biblio')) ? FALSE : TRUE, + '#description' => 'When this value is >= 3, you will no longer be able to change your author id mapping. Contact your system administrator to reset this value.', + ); + + return $form; +} +/** + * @param unknown_type $user + * @return multitype:string number NULL + */ +function _biblio_get_user_doi_form($user) { + $form['biblio_doi'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('CrossRef Login Information') + ); + $link_attrs = array('attributes' => array('target' => '_blank'), + 'absolue' => TRUE); + $form['biblio_doi']['biblio_crossref_pid'] = array( + '#type' => 'textfield', + '#title' => t('CrossRef OpenURL Account ID'), + '#default_value' => isset($user->data['biblio_crossref_pid']) ? $user->data['biblio_crossref_pid'] : '', + '#return_value' => 1, + '#description' => t('Enter your complimentary CrossRef OpenURL account ID which you can obtain here !url, OR enter your full CrossRef (colon separated) account:password combination.', array('!url' => l(t('OpenURL Account Request Form'), 'http://www.crossref.org/requestaccount/', $link_attrs))) + ); + return $form; +} +/** + * @param unknown_type $user + * @return multitype:string number NULL + */ +function _biblio_get_user_openurl_form($user) { + $form['openurl'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('OpenURL'), + '#description' => t('You can set an openurl link here') + ); + $form['openurl']['biblio_baseopenurl'] = array( + '#type' => 'textfield', + '#title' => t('OpenURL Base URL'), + '#size' => 95, + '#default_value' => !empty($user->data['biblio_baseopenurl']) ? $user->data['biblio_baseopenurl'] : '', + '#description' => t('This sets your base OpenURL gateway, which is used to generate OpenURL links. To implement a "Universal" OpenURL system, try using OCLC\'s OpenURL Resolver Registry gateway: http://worldcatlibraries.org/registry/gateway') + ); + $sid = "Biblio:" . variable_get('site_name', 'Drupal'); + $form['openurl']['biblio_openurl_sid'] = array( + '#type' => 'textfield', + '#title' => t('OpenURL Site ID'), + '#size' => 95, + '#default_value' => !empty($user->data['biblio_openurl_sid']) ? $user->data['biblio_openurl_sid'] : '', + '#description' => t('This sets your site name, some link resolvers will require a specific Site ID in order to process your requests.') + ); + return $form; +} +/* This function parses the module directory for 'style' files, loads them and + * calls the info fuction to get some basic information like the short and long + * names of the style +*/ +function biblio_form_sort($a, $b) { + $a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0; + $b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0; + if ($a_weight == $b_weight) { + return 0; + } + return ($a_weight < $b_weight) ? -1 : 1; +} +/** + * @param unknown_type $name + * @return string + */ +function biblio_admin_get_query($name) { + switch ($name) { + case "author_dup" : + return ('SELECT lastname, firstname, initials, COUNT(lastname) as cnt FROM {biblio_contributor_data} GROUP BY lastname, firstname, initials HAVING COUNT(*)>1 ORDER BY lastname ASC, firstname ASC, initials ASC '); + break; + case "author_by_name" : + return ("SELECT lastname, cid FROM {biblio_contributor_data} where lastname LIKE '%s'"); + break; + case 'author_pub_count' : +// return ('SELECT bd.lastname, b.cid,COUNT(*) AS cnt FROM {biblio_contributor} b, {biblio_contributor_data} bd WHERE bd.cid=b.cid GROUP BY b.cid HAVING cnt > 0 ORDER BY lastname ASC'); + return ('SELECT bd.lastname, b.cid,COUNT(*) AS cnt FROM {biblio_contributor} b, {biblio_contributor_data} bd WHERE bd.cid=b.cid GROUP BY b.cid, bd.lastname HAVING COUNT(*) > 0 ORDER BY lastname ASC'); + break; + } +} + + +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $op + * @param unknown_type $id + * @return void|multitype:string multitype:string number NULL multitype:string number multitype:string NULL multitype:string number boolean NULL NULL + */ +function biblio_admin_author_types_form($form, &$form_state, $op = NULL, $id = NULL) { + + switch ($op) { + case 'edit': + $type = db_query("SELECT * FROM {biblio_contributor_type_data} WHERE auth_type=:atype", array(':atype' => $id))->fetchObject(); + $form['auth_type'] = array( + '#type' => 'hidden', + '#value' => $type->auth_type); + //fall through and use the same form used for a new entry + case 'new': + $form['title'] = array( + '#type' => 'textfield', + '#title' => t('Type Name'), + '#size' => 20, + '#weight' => 1, + '#default_value' => ($op == 'new' ? '' : $type->title), + '#required' => TRUE, + '#maxlength' => 64 + ); + $form['hint'] = array( + '#type' => 'textfield', + '#title' => t('Description'), + '#size' => 60, + '#weight' => 2, + '#default_value' => ($op == 'new' ? '' : $type->hint), + '#maxlength' => 255 + ); + $form['type_button'] = array( + '#type' => 'submit', + '#value' => ($op == 'new')? t('Create New Type') : t('Save'), + '#weight' => 3, + '#submit' => array('biblio_admin_author_types_form_submit') + ); + $form['cancel_button'] = array( + '#type' => 'submit', + '#value' => t('Cancel'), + '#weight' => 4, + ); + $form['#theme'] = ''; + return $form; + break; + case 'hide': + break; + default: + return; + } +} + +/** + * @param unknown_type $variables + * @return string + */ +function theme_biblio_admin_author_types_form($variables) { + $form = $variables['form']; + // We need this complex query to realize author_types which are not in use (cid is NULL) + $query = db_select('biblio_contributor_type_data', 'ctd'); + $query->fields('ctd', array('auth_type', 'title', 'hint')) + ->leftJoin('biblio_contributor', 'bc', 'ctd.auth_type = bc.auth_type'); + $db_result = $query->groupBy('ctd.auth_type') + ->groupBy('ctd.title') + ->groupBy('ctd.hint') + ->orderBy('ctd.auth_type') + ->execute(); + + foreach ($db_result as $row) { + $ops = l(t('edit'), 'admin/config/content/biblio/author/type/' . $row->auth_type . '/edit' ); + if (!isset($row->cid) && $row->auth_type >= 10) { // allow delete only if type not in use + $ops .= '  '; + $ops .= l(t('delete'), 'admin/config/content/biblio/author/type/' . $row->auth_type . '/delete/'); + } + $rows[] = array( + $row->auth_type, + check_plain($row->title), + check_plain($row->hint), + $ops, + ); + } + $header = array( + t('Type Id'), + t('Contributor Type'), + t('Description'), + array('data' => t('Operations'), 'colspan' => '2') + ); + $output = '

    [ ' . l(t('Add New Type'), 'admin/config/content/biblio/author/type/new') . ' ]'; + $output .= theme('table', array('header' => $header, 'rows' => $rows)); + $output .= '

    [ ' . l(t('Add New Type'), 'admin/config/content/biblio/author/type/new') . ' ]'; + // $output .= ' [ ' . l(t('Reset all types to defaults'), 'admin/config/content/biblio/authors/reset') . ' ]'; + + return $output; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_author_types_form_submit($form, &$form_state) { + + if ($form_state['triggering_element']['#value'] == t('Save') || $form_state['triggering_element']['#value'] == t('Create New Type')) { + $record->title = $form_state['values']['title']; + $record->hint = $form_state['values']['hint']; + switch ($form['#id']) { + case 'biblio-admin-author-types-form-new': + $record->title = $form_state['values']['title']; + $record->hint = $form_state['values']['hint']; + drupal_write_record('biblio_contributor_type_data', $record); + break; + case 'biblio-admin-author-types-form-edit': + $record->auth_type = $form_state['values']['auth_type']; + drupal_write_record('biblio_contributor_type_data', $record, 'auth_type'); + break; + } + } + $form_state['redirect'] = 'admin/config/content/biblio/author/type'; + +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $type_id + */ +function biblio_admin_author_type_delete_confirm($form, &$form_state, $type_id) { + $base = variable_get('biblio_base', 'biblio'); + $type_data = db_query('SELECT * FROM {biblio_contributor_type_data} bctd WHERE bctd.auth_type = :tid ', array(':tid' => $type_id))->fetchObject(); + $form['auth_type'] = array( + '#type' => 'value', + '#value' => $type_data->auth_type, + ); + + return confirm_form($form, + t('Are you sure you want to delete the author type: %title ?', array('%title' => $type_data->title)), + isset($_GET['destination']) ? $_GET['destination'] : 'admin/config/content/biblio/author/type', + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); + +} +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_author_type_delete_confirm_submit($form, &$form_state) { + + db_delete('biblio_contributor_type_data') + ->condition('auth_type', $form_state['values']['auth_type']) + ->execute(); + + db_delete('biblio_contributor_type') + ->condition('auth_type', $form_state['values']['auth_type']) + ->execute(); + + $form_state['redirect'] = 'admin/config/content/biblio/author/type'; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $author_id + * @return void|multitype:string NULL + */ +function biblio_admin_author_edit_form($form, &$form_state, $author_id) { + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + $merge_options = $linked = array(); + if (!isset($form_state['biblio_add_merge_author'])) { + $form_state['biblio_add_merge_author'] = array(); + } + $base = variable_get('biblio_base', 'biblio'); + + $author = db_query('SELECT * FROM {biblio_contributor_data} b WHERE b.cid = :aid ', array(':aid' => $author_id))->fetchObject(); + if (!$author) { + drupal_not_found(); + return; + } + $base = variable_get('biblio_base', 'biblio'); + $menu = menu_get_active_title(); + $path = (strpos($_GET['q'], 'config'))? 'admin/config/content/biblio/author/' : $base . '/authors/'; + + // $form['#tree'] = TRUE; + $form['cid'] = array( + '#type' => 'value', + '#value' => $author_id, + ); + $users = db_query('SELECT uid,name FROM {users} WHERE uid>0 ORDER BY name'); + $options[0] = t('(None)'); + foreach ($users as $user) { + $options[$user->uid] = $user->name; + } + $form['drupal_uid'] = array( + '#type' => 'select', + '#title' => t('Drupal User ID'), + '#options' => $options, + '#default_value' => $author->drupal_uid, + '#weight' => 12, + '#required' => FALSE, + '#description' => t('If this author has a an account (Drupal User ID) on this web site, you may select it here. This will help to link the authors publications with other content.') + ); + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Complete Name'), + '#default_value' => $author->name, + '#size' => 100, + '#weight' => 1, + '#required' => TRUE, + '#disabled' => TRUE, + '#maxlength' => 255, + '#description' => t('The value in this box will be constructed from the individual name parts fields above.') + ); + $form_state['current_name'] = $author->name; + + $form['literal'] = array( + '#type' => 'checkbox', + '#title' => t('Do not reformat'), + '#default_value' => $author->literal, + '#weight' => 1.5, + '#required' => FALSE, + '#description' => t('Selecting this will prevent the styles from trying to reformat the contributor name. The text in the "Complete Name" field will be used as is.') + ); + $form['prefix'] = array( + '#type' => 'textfield', + '#title' => t('Prefix'), + '#default_value' => $author->prefix, + '#size' => 20, + '#weight' => 2, + '#required' => FALSE, + '#maxlength' => 128 + ); + $form['firstname'] = array( + '#type' => 'textfield', + '#title' => t('First Name'), + '#default_value' => $author->firstname, + '#size' => 20, + '#weight' => 3, + '#required' => FALSE, + '#maxlength' => 128 + ); + $form['initials'] = array( + '#type' => 'textfield', + '#title' => t('Initials'), + '#default_value' => $author->initials, + '#size' => 20, + '#weight' => 4, + '#required' => FALSE, + '#maxlength' => 10 + ); + $form['lastname'] = array( + '#type' => 'textfield', + '#title' => t('Last Name'), + '#default_value' => $author->lastname, + '#size' => 20, + '#weight' => 5, + '#required' => FALSE, + '#maxlength' => 255 + ); + $form['suffix'] = array( + '#type' => 'textfield', + '#title' => t('Suffix'), + '#default_value' => $author->suffix, + '#size' => 20, + '#weight' => 6, + '#required' => FALSE, + '#maxlength' => 128 + ); + $form['affiliation'] = array( + '#type' => 'textfield', + '#title' => t('Affiliation'), + '#default_value' => $author->affiliation, + '#size' => 100, + '#weight' => 7, + '#required' => FALSE, + '#maxlength' => 256, + '#description' => t('University, Company or Organization that the author is affiliated with.') + ); + + $query = db_select('biblio_contributor_data', 'bcd'); + $authors = $query->fields('bcd') + ->condition('cid', $author_id, '<>') + ->orderBy('lastname') + ->execute(); + + $query = db_select('biblio_contributor', 'bc'); + $merged_authors = $query->fields('bc') + ->condition('merge_cid', 0, '>') + ->execute(); + foreach ($merged_authors as $auth) { + $merged[$auth->merge_cid] = $auth->cid; + } + + $linked = biblio_get_linked_contributors($author->cid); + + $radio_options = array('link' => '', 'merge' => ''); + $candidate = FALSE; + $candidates = array(); + + $this_soundx = soundex($author->lastname); + + foreach ($authors as $other_author) { + $merge_def = $link_def = 0; + $link_disable = $merge_disable = $retain_disable = FALSE; + + if ((soundex($other_author->lastname) == $this_soundx) || + (isset($merged[$other_author->cid]) && $merged[$other_author->cid] == $author->cid) || + in_array($other_author->cid, $form_state['biblio_add_merge_author'])) { + $candidate = TRUE; + $merge_def = ($other_author->alt_form && $other_author->aka) ? $author->cid : FALSE; + $retain_def = $other_author->alt_form ? $author->cid : FALSE; + $retain_disable = $other_author->alt_form ? TRUE : FALSE; + } + + if (in_array($other_author->cid, $linked)) { + $candidate = TRUE; + $link_def = $author->cid; + $retain_def = 0; + } + + if ($candidate) { + $candidate = FALSE; + $form_state['biblio_add_merge_author'][$other_author->cid] = $other_author->cid; + + $candidates[$other_author->cid]['name'] = array( + '#markup' => l($other_author->lastname . ($other_author->firstname ? ', ' . $other_author->firstname : ($other_author->initials?', ' . $other_author->initials:'')), $path . $other_author->cid . '/edit/'), + '#markup' => l($other_author->name , $path . $other_author->cid . '/edit/'), + ); + $candidates[$other_author->cid]['link'] = array( + '#type' => 'checkbox', + '#return_value' => $author->cid, + '#default_value' => $link_def, + '#disabled' => $link_disable, + '#parents' => array('candidates', $other_author->cid, 'link'), + '#states' => array( + 'disabled' => array( + ':input[name="candidates[' . $other_author->cid . '][merge]"]' => array('checked' => true), + ), + ), + ); + $candidates[$other_author->cid]['merge'] = array( + '#type' => 'checkbox', + '#return_value' => $author->cid, + '#default_value' => $merge_def, + '#disabled' => $merge_disable, + '#parents' => array('candidates', $other_author->cid, 'merge'), + '#states' => array( + 'disabled' => array( + ':input[name="candidates[' . $other_author->cid . '][link]"]' => array('checked' => true), + ), + ), + ); + $candidates[$other_author->cid]['retain'] = array( + '#type' => 'checkbox', + '#return_value' => $author->cid, + '#default_value' => $retain_def, + '#disabled' => $retain_disable, + '#parents' => array('candidates', $other_author->cid, 'retain'), + '#states' => array( + 'enabled' => array( + ':input[name="candidates[' . $other_author->cid . '][merge]"]' => array('checked' => true), + ), + 'unchecked' => array( + ':input[name="candidates[' . $other_author->cid . '][merge]"]' => array('unchecked' => true), + ), + ), + ); + } + } + + $form['merge'] = array( + '#type' => 'fieldset', + '#theme' => 'biblio_admin_author_edit_merge_table', + '#title' => t('Author Link / Merge'), + '#description' => t('Select other author names which will be linked or merged. Merging removes all the selected authors, then replaces any references to these authors with the author being edited above. You should only do this if you are sure the other authors represent the same author as the one being edited. IF you do not select "Retain as alternate form" then THIS CANNOT BE UNDONE!'), + '#weight' => 5, + '#collapsible' => TRUE, + '#collapsed' => !count($candidates), + '#header' => array(t('Author name'), t('Link'), t('Merge'), t('Retain as "alternate form"')), + 'candidates' => $candidates, + '#prefix' => '

    ', + '#suffix' => '
    ', + + ); + $form['merge']['more_authors_search'] = array( + '#type' => 'textfield', + '#title' => t('Other authors which could be linked to this author'), + '#autocomplete_path' => 'biblio/autocomplete/contributor', + '#weight' => 12, + '#required' => FALSE, + ); + $form['merge']['more_authors_add'] = array( + '#type' => 'submit', + '#value' => t('Add author'), + '#weight' => 15, + '#submit' => array('biblio_admin_author_edit_add_candidate'), + '#ajax' => array( + 'callback' => 'biblio_admin_author_edit_add_candidate_callback', + 'wrapper' => 'biblio-author-edit-merge-table', + ), + + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save') + ); + $form['cancel'] = array( + '#type' => 'submit', + '#value' => t('Cancel') + ); + + return $form; +} + +function biblio_admin_author_edit_add_candidate($form, &$form_state) { + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + if (!empty($form_state['values']['more_authors_search'])) { + if ($cid = biblio_get_cid_by_name($form_state['values']['more_authors_search'])) { + if ($cid == $form_state['values']['cid']) { // we don't want to merge with ourselves :-( + drupal_set_message(t('You cannot merge an author with itself'), 'error'); + } + elseif (in_array($cid, $form_state['biblio_add_merge_author'])) { + drupal_set_message(t('Author already exists in the merge list'), 'warning'); + } + else { + $form_state['biblio_add_merge_author'][$cid] = $cid; + } + } + else { + drupal_set_message(t('The Author was not found in the database. You must select an Author from the "auto-complete" list of the Keyword search box.'), 'error'); + } + } + else { + drupal_set_message(t('Cannot add an empty value'), 'error'); + } + + $form_state['rebuild'] = TRUE; + +} + +function biblio_admin_author_edit_add_candidate_callback($form, $form_state) { + return $form['merge']; +} + +/** + * @param array $form + * @param array $form_state + */ +function biblio_admin_author_edit_form_merge_validate($form, &$form_state) { +} + +/** + * @param array $form + * @param array $form_state + */ +function biblio_admin_author_edit_form_merge_link($form_state) { + $op = $form_state['triggering_element']['#value']; + $merge_authors = array(); + $cid = $form_state['values']['cid']; + + foreach ($form_state['values']['candidates'] as $ccid => $options) { + + if ($options['link']) { + db_update('biblio_contributor_data') + ->fields(array( + 'aka' => $options['link'], + 'alt_form' => 0, + )) + ->condition('cid', $ccid) + ->execute(); + } + else { + db_update('biblio_contributor_data') + ->fields(array( + 'aka' => 0, + 'alt_form' => 0, + )) + ->condition(db_or()->condition('cid', $cid)->condition('cid', $ccid)) + ->execute(); + } + + if ($options['merge']) { + db_update('biblio_contributor') + ->fields(array( + 'cid' => $cid, + 'merge_cid' => $ccid)) + ->condition('cid', $ccid) + ->execute(); + } + else { + db_update('biblio_contributor') + ->fields(array( + 'cid' => $ccid, + 'merge_cid' => 0)) + ->condition('merge_cid', $ccid) + ->execute(); + } + + if ($options['merge'] && $options['retain']) { + db_update('biblio_contributor_data') + ->fields(array( + 'alt_form' => $options['retain'] ? $options['retain'] : 0, + 'aka' => $options['retain'] ? $cid : 0)) + ->condition('cid', $ccid) + ->execute(); + } + elseif ($options['merge'] && !$options['retain']) { + db_delete('biblio_contributor_data') + ->condition('cid', $ccid) + ->execute(); + } + } +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_author_edit_form_link_validate($form, &$form_state) { + +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_author_edit_form_link_submit($form, &$form_state) { + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + $linked_authors = array(); + $link_authors = ''; + + if (isset($form_state['values']['linked_authors']) ) { + $linked_authors = $form_state['values']['linked_authors']; + } + + if (isset($form_state['values']['link_authors']) ) { + $link_authors = $form_state['values']['link_authors']; + } + + foreach ($linked_authors as $key => $value) { + if ($value == 0) { + db_update('biblio_contributor_data') + ->fields(array('aka' => $value)) + ->condition('cid', $key) + ->execute(); + } + } + + if (!empty($link_authors)) { + if ( ($cid = biblio_get_cid_by_name($link_authors))) { + db_update('biblio_contributor_data') + ->fields(array('aka' => $form_state['values']['cid'])) + ->condition('cid', $cid) + ->execute(); + } + } + +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_author_edit_form_validate($form, &$form_state) { + foreach ($form_state['values'] as $key => $value) { + if (is_string($value)) $form_state['values'][$key] = trim($value); + } +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_author_edit_form_submit($form, &$form_state) { + $op = $form_state['values']['op']; + switch ($op) { + case t('Save'): + if ($form_state['values']['drupal_uid'] == 0 ) { + $uid = $form['drupal_uid']['#default_value']; + $cid = 0; + } + else { + $uid = $form_state['values']['drupal_uid']; + $cid = $form_state['values']['cid'] ; + } + if ($uid) { + db_update('biblio_contributor_data') + ->fields(array('drupal_uid' => 0)) + ->condition('drupal_uid', $uid) + ->execute(); + $result = db_query('SELECT data FROM {users} WHERE uid = :uid', array(':uid' => $uid))->fetchField(); + $data = unserialize($result); + $data['biblio_contributor_id'] = $cid; + $v = serialize($data); + db_update('users') + ->fields(array('data' => $v)) + ->condition('uid', $uid) + ->execute(); + } + + $form_state['values']['name'] = + (!empty($form_state['values']['prefix']) ? $form_state['values']['prefix'] . ' ' :'') . + (!empty($form_state['values']['firstname']) ? $form_state['values']['firstname'] . ' ' :'') . + (!empty($form_state['values']['initials']) ? $form_state['values']['initials'] . ' ' :'') . + (!empty($form_state['values']['lastname']) ? $form_state['values']['lastname'] . ' ' :'') . + (!empty($form_state['values']['suffix']) ? $form_state['values']['suffix'] :'') ; + $form_state['values']['name'] = trim($form_state['values']['name']); + $form_state['values']['md5'] = md5($form_state['values']['name']); + + drupal_write_record('biblio_contributor_data', $form_state['values'], 'cid'); + + if (isset($form_state['values']['candidates']) && !empty($form_state['values']['candidates'])) { + biblio_admin_author_edit_form_merge_link($form_state); + } + break; + case t('Cancel'): + break; + } +} + + +/** + * + * @param $form + * @param $form_state + * @return array + */ +function biblio_admin_orphans_form($form, &$form_state) { + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + $orphans = $options = $names = array(); + + $base = variable_get('biblio_base', 'biblio'); + + $orphans = biblio_get_orphan_authors(); + + foreach ($orphans as $author) { + $options[$author->cid] = array( + 'author' => array( + 'data' => array( + '#type' => 'link', + '#title' => $author->name, + '#href' => $base . '/authors/' . $author->cid . '/edit', + ), + ), + 'affiliation' => check_plain($author->affiliation), + ); + $names[$author->cid] = $author->name; + } + $form['names'] = array('#type' => 'hidden', '#value' => $names); + + $header = array( + 'author' => t('Author name'), + 'affiliation' => t('Author affiliation') + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete Selected'), + '#disabled' => (!count($options)), + '#submit' => array('biblio_admin_orphans_form_submit'), + ); + $form['delete_all'] = array( + '#type' => 'submit', + '#value' => t('Delete ALL'), + '#disabled' => (!count($options)), + '#submit' => array('biblio_admin_orphans_form_submit'), + ); + $form['authors'] = array( + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + '#empty' => t('No orphaned authors.'), + ); + + return $form; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_orphans_form_validate($form, &$form_state) { + $check_count = array_filter($form_state['values']['authors']); + if ($form_state['triggering_element']['#value'] == t('Delete Selected') && count($check_count) == 0) { + form_set_error('', t('No items selected.')); + } +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_orphans_form_submit($form, &$form_state) { + $authors = $names = array(); + // Filter out unchecked authors + if ($form_state['triggering_element']['#value'] == t('Delete Selected')) { + $authors = array_filter($form_state['values']['authors']); + } + elseif ($form_state['triggering_element']['#value'] == t('Delete ALL')) { + $authors = drupal_map_assoc(array_keys($form_state['values']['authors'])); + } + + $names = array_intersect_key($form_state['values']['names'], $authors); + $del_names = implode('; ', $names); + + db_delete('biblio_contributor_data') + ->condition('cid', $authors, 'IN') + ->execute(); + + drupal_set_message(t('The orphaned authors (@author_list) have been deleted.', array('@author_list' => $del_names))); + +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @return multitype:NULL + */ +function biblio_admin_keyword_orphans_form($form, $form_state) { + $orphans = $keywords = $options = array(); + + $base = variable_get('biblio_base', 'biblio'); + + $header = array( + 'keyword' => t('Keyword') + ); + + $query = db_select('biblio_keyword', 'bk'); + $active_kids = $query + ->fields('bk', array('kid')) + ->groupBy('kid') + ->execute() + ->fetchCol(); + + $query = db_select('biblio_keyword_data', 'bkd'); + $all_kids = $query + ->fields('bkd', array('kid')) + ->groupBy('kid') + ->execute() + ->fetchCol(); + + $orphans = array_diff($all_kids, $active_kids); + + if (count($orphans)) { + $query = db_select('biblio_keyword_data', 'bkd')->extend('PagerDefault')->limit(50); + $query->fields('bkd'); // SELECT * FROM {biblio_contributor_data} + $query->condition('kid', $orphans, 'IN'); + $result = $query->execute(); //pager_query('SELECT * FROM {biblio_keyword_data} WHERE kid NOT IN (SELECT kid FROM {biblio_keyword} GROUP BY kid)', 50); + foreach ($result as $keyword) { + $options[$keyword->kid] = array( + 'keyword' => array( + 'data' => array( + '#type' => 'link', + '#title' => $keyword->word, + '#href' => $base . '/keywords/' . $keyword->kid . '/edit', + ), + ), + ); + } + } + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Delete'), + '#disabled' => (!count($options)), + '#submit' => array('biblio_admin_keyword_orphans_form_submit'), + ); + $form['words'] = array( + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + '#empty' => t('No orphaned keywords.'), + ); + + $form['pager'] = array('#markup' => theme('pager')); + + return $form; +} +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_keyword_orphans_form_validate($form, &$form_state) { + $check_count = array_filter($form_state['values']['words']); + if (count($check_count) == 0) { + form_set_error('', t('No items selected.')); + } +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_keyword_orphans_form_submit($form, &$form_state) { + + $keywords = array_filter($form_state['values']['words']); + db_delete('biblio_keyword_data') + ->condition('kid', $keywords, 'IN') + ->execute(); + + drupal_set_message(t('%count orphaned keywords have been deleted.', array('%count' => count($keywords)))); + +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $keyword_id + * @return multitype:string number NULL + */ +function biblio_admin_keyword_edit_form($form, &$form_state, $keyword_id) { + $options = array(); + $keywords = array(); + $destination = NULL; + + $keyword = db_query('SELECT * FROM {biblio_keyword_data} bkd WHERE bkd.kid = :kid ', array(':kid' => $keyword_id))->fetchObject(); + + $base = variable_get('biblio_base', 'biblio'); +// $path = (strpos($_GET['q'], 'config')) ? 'admin/config/content/biblio/keywords' : $base . '/keywords'; + + if (isset($_GET['destination'])) { + $destination = $_GET['destination']; + } + if (!$destination && isset($form_state['complete form']['#action'])) { + $action = drupal_parse_url($form_state['complete form']['#action']); + $destination = isset($action['query']['destination']) ? $action['query']['destination'] : NULL; + } + if (!$destination) $destination = $base . '/keywords'; + + $form_state['redirect'] = $destination; + + $form['kid'] = array( + '#type' => 'value', + '#value' => $keyword_id + ); + $form['word'] = array( + '#type' => 'textfield', + '#title' => t('Keyword'), + '#default_value' => $keyword->word, + '#size' => 100, + '#weight' => -10, + '#required' => TRUE, + '#maxlength' => 255 + ); + + + $result = db_query("SELECT kd.word, kd.kid, count(*) as use_count FROM {biblio_keyword_data} kd + LEFT JOIN {biblio_keyword} bk on bk.kid = kd.kid + WHERE LOWER(word) LIKE LOWER(:word) + AND kd.kid <> :kid + GROUP BY kd.kid, kd.word", array(':word' => '%%' . drupal_substr($keyword->word, 0, 5) . '%%', ':kid' => $keyword_id)); + + foreach ($result as $keyword) { + $keywords[] = $keyword; + } + + if (isset($form_state['biblio_add_merge_keywords'])) { + $keywords = array_merge($keywords, $form_state['biblio_add_merge_keywords']); + } + + foreach ($keywords as $keyword) { + $options[$keyword->kid] = array( + 'keyword' => array( + 'data' => array( + '#type' => 'link', + '#title' => $keyword->word . ' (' . $keyword->use_count .')', + '#href' => $destination . '/'. $keyword->kid . '/edit', + '#options' => array('query' => array('destination' => $destination)), + ), + ), + ); + } + + $form['merge'] = array( + '#type' => 'fieldset', + '#title' => t('Keyword Merge'), + '#description' => t('If you wish to consolodate references to multiple keywords into single reference to: !kw, select the others from the list below. This will remove the selected keywords from the database and replace references to them with a reference to: !kw. You should only do this if you are sure the other keywords represent the same keyword as the one being edited.', array('!kw' => $keyword->word)), + '#weight' => 5, + '#collapsible' => TRUE, + '#collapsed' => FALSE, + // '#theme' => 'biblio_admin_keyword_merge_fieldset' + ); + + $header = array('keyword' => t('Similar keywords ')); + + $form['merge']['merge_words'] = array( + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + '#empty' => t('No similar keywords automatically detected, use the search box below to manually add others.'), + '#prefix' => '
    ', + '#suffix' => '
    ', + + ); + $form['merge']['search'] = array( + '#type' => 'textfield', + '#title' => t('Keyword search'), + '#autocomplete_path' => 'biblio/autocomplete/biblio_keywords', + '#description' => t('Use this field to find other keywords you would like to merge with: !kw, then click the "Add to merge list" button. (The merge will not happen until the "Save" button is clicked)', array('!kw' => $keyword->word)) + ); + $form['merge']['add'] = array( + '#type' => 'submit', + '#value' => t('Add to merge list'), + '#submit' => array('biblio_add_merge_keyword'), + '#ajax' => array( + 'callback' => 'biblio_add_merge_keyword_callback', + 'wrapper' => 'biblio-keyword-merge-table', + ), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 6, + ); + $form['delete'] = array( + '#type' => 'submit', + '#value' => t('Delete'), + '#weight' => 7, + ); + $form['cancel'] = array( + '#type' => 'submit', + '#value' => t('Cancel'), + '#weight' => 8, + ); + + return $form; +} +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_add_merge_keyword_callback($form, &$form_state) { + return $form['merge']['merge_words']; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_add_merge_keyword($form, &$form_state) { + + if (!empty($form_state['values']['search'])) { + module_load_include('inc', 'biblio', 'includes/biblio.keywords'); + if ($word = biblio_get_keyword_by_name($form_state['values']['search'])) { + if ($word->kid == $form_state['values']['kid']) { // we don't want to merge with ourselves :-( + drupal_set_message(t('You cannot merge a keyword with itself'), 'error'); + } + elseif (isset($form_state['biblio_add_merge_keywords'][$word->kid])) { + drupal_set_message(t('Keyword already exists in the merge list'), 'warning'); + } + else { + $form_state['biblio_add_merge_keywords'][$word->kid] = $word; + } + } + else { + drupal_set_message(t('The keyword was not found in the database. You must select a keyword from the "auto-complete" list of the Keyword search box.'), 'error'); + } + } + else { + drupal_set_message(t('Cannot add an empty value'), 'error'); + } + + $form_state['rebuild'] = TRUE; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $keyword_id + */ +function biblio_admin_keyword_delete_confirm($form, &$form_state, $keyword_id) { + $base = variable_get('biblio_base', 'biblio'); + $keyword = db_query('SELECT * FROM {biblio_keyword_data} bkd WHERE bkd.kid = :kid ', array(':kid' => $keyword_id))->fetchObject(); + $form['kid'] = array( + '#type' => 'value', + '#value' => $keyword_id, + ); + + return confirm_form($form, + t('Are you sure you want to delete the keyword: %word from ALL publications?', array('%word' => $keyword->word)), + isset($_GET['destination']) ? $_GET['destination'] : $base . '/keywords', + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); + +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_keyword_delete_confirm_submit($form, &$form_state) { + $base = variable_get('biblio_base', 'biblio'); + module_load_include('inc', 'biblio', 'includes/biblio.keywords'); + biblio_delete_keyword($form_state['values']['kid']); + $form_state['redirect'] = $base . '/keywords'; +} + +/** + * @param unknown_type $form + * @param unknown_type $form_state + */ +function biblio_admin_keyword_edit_form_submit($form, &$form_state) { + switch ($form_state['values']['op']) { + + case t('Save'): + drupal_write_record('biblio_keyword_data', $form_state['values'], 'kid'); + if (isset($form_state['values']['merge_words'])) { + $kids = array_filter($form_state['values']['merge_words']); + if (count($kids)) { + db_update('biblio_keyword') + ->fields(array('kid' => $form_state['values']['kid'])) + ->condition('kid', $kids, 'IN') + ->execute(); + + db_delete('biblio_keyword_data') + ->condition('kid', $kids, 'IN') + ->execute(); + } + } + break; + case t('Delete'): + $base = variable_get('biblio_base', 'biblio'); + unset($_GET['destination']); + $form_state['redirect'] = "$base/keyword/" . $form_state['values']['kid'] . '/delete'; + break; + case t('Cancel'): + $base = variable_get('biblio_base', 'biblio'); + $form_state['redirect'] = $base . '/keywords'; + break; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.contributors.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.contributors.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,625 @@ + $aid))->fetchField(); + if ($aka == 0) { + $query = db_select('biblio_contributor_data', 'bcd'); + $cids = $query->fields('bcd', array('cid')) + ->condition(db_and()->condition('bcd.aka', $aid, '=')->condition('bcd.alt_form', 0, '=')) + ->execute() + ->fetchCol(); + } + else { + $query = db_select('biblio_contributor_data', 'bcd'); + $and = db_and()->condition('bcd.aka', $aka, '=')->condition('bcd.alt_form', 0, '=')->condition('bcd.cid', $aid, '<>'); + $cids = $query->fields('bcd', array('cid')) + ->condition(db_or()->condition('bcd.cid', $aka)->condition('bcd.aka', $aid)->condition($and)) + ->execute() + ->fetchCol(); + } + $related[$aid] = $cids; + } + return $related[$aid]; +} + +function biblio_get_contributor($aid) { + $contributor = &drupal_static(__FUNCTION__); + + if (!isset($contributor[$aid])) { + $contributor[$aid] = db_query('SELECT * FROM {biblio_contributor_data} WHERE cid = :cid', array(':cid' => $aid))->fetchObject(); + } + + return $contributor[$aid]; +} + +/** + * @param array $contributors + * @param int $category + * @return array $authors + */ +function biblio_get_contributor_category($contributors, $category) { + $authors = array(); + foreach ($contributors as $author) { + if ($author['auth_category'] == $category) { + $authors[] = $author; + } + } + return $authors; +} + +/** + * Get an author id from the database using the "name" field as a key. + * If the "cid" value is negative, this means we found an "alternate" form of + * the name, which should have an "aka" value which points to the preferred form. + * + * @param string $name + * @return int + */ +function biblio_get_cid_by_name($name) { + $cid = 0; + $auth = biblio_get_contributor_by_name($name); + if ($auth) { + if ($auth->cid > 0) { + $cid = $auth->cid; + } + if ($auth->aka > 0 && $auth->alt_form > 0) { + $cid = $auth->aka; + } + } + return $cid; +} + +/** + * @param unknown_type $name + * @return unknown + */ +function biblio_get_contributor_by_name($name) { + $contributors = &drupal_static(__FUNCTION__); + $name = trim($name); + if (!isset($contributors[$name]) || $contributors[$name] === FALSE) { + $query = db_select('biblio_contributor_data', 'bcd'); + $contributors[$name] = $query->fields('bcd')->condition('bcd.name', $name)->execute()->fetchObject(); + } + + return $contributors[$name]; +} + +/** + * @param unknown_type $vid + * @return Ambigous + */ +function biblio_get_first_contributor($vid) { + static $contributor = array(); + if (!isset($contributor[$vid])) { + $query = db_select('biblio_contributor', 'bc'); + $query->join('biblio_contributor_data', 'bcd', 'bc.cid=bcd.cid'); + $query->fields('bcd'); + $query->condition('bc.vid', $vid); + $query->condition('bc.rank', 0); + $contributor[$vid] = $query->execute()->fetchObject(); + } + + return $contributor[$vid]; +} + +/** + * @param $vid + * @return unknown_type + */ +function biblio_load_contributors($vid) { + $vids = (isset($vid) ? array($vid) : array()); + return biblio_load_contributors_multiple($vids); +} + +/** + * @param unknown_type $vids + * @param unknown_type $auth_category + * @return multitype:|Ambigous + */ +function biblio_load_contributors_multiple($vids = array(), $auth_category = 0) { + $contributors = array(); + if (empty($vids)) return $contributors; + + $query = db_select('biblio_contributor', 'bc'); + $query->innerJoin('biblio_contributor_data', 'bcd', 'bcd.cid = bc.cid'); + $query->fields('bc'); + $query->fields('bcd'); + $query->orderby('bc.vid'); + $query->orderby('bc.rank'); + if (count($vids) == 1) { + $query->condition('bc.vid', $vids[0]); + } + else { + $query->condition('bc.vid', $vids, 'IN'); + } + if ($auth_category) { + $query->condition('bc.auth_category', $auth_category); + } + $query->addMetaData('base_table', 'biblio_contributor'); + $query->addTag('node_access'); + $result = $query->execute(); + + foreach ($result as $creator) { + $contributors[$creator->vid][] = (array)$creator; + } + + return $contributors; +} +/** + * Add separate author named "et al" to the end of the author array + * + * @param $authors - author array to augment + * @param $type - auth_type + * @return TRUE if author was added, FALSE if "etal" was already there + */ +function biblio_authors_add_etal(&$authors, $type) { + $etal = "et al"; $max_rank = 0; + foreach ($authors as $author) { // et al author should be added only once per type + if ($author['auth_type'] != $type) continue; + if ($author['name'] == $etal) { + return FALSE; + } + $max_rank = max($max_rank, $author['rank']); + } + $authors[] = biblio_parse_author(array('name' => $etal, 'auth_type' => $type, 'lastname' => $etal, 'rank' => $max_rank + 1)); + return TRUE; +} + +/** + * @param unknown_type $node + * @return unknown + */ +function biblio_delete_contributors($node) { + $count = db_delete('biblio_contributor') + ->condition('nid', $node->nid) + ->execute(); + return $count; +} + +/** + * @param unknown_type $node + * @return unknown + */ +function biblio_delete_contributors_revision($node) { + $count = db_delete('biblio_contributor') + ->condition('vid', $node->vid) + ->execute(); + return $count; +} + +/** + * @param unknown_type $cid + */ +function biblio_delete_contributor($cid) { + db_delete('biblio_contributor') + ->condition('cid', $cid) + ->execute(); + + return db_delete('biblio_contributor_data') + ->condition('cid', $cid) + ->execute(); + +} +/** + * @param unknown_type $cid + * @param unknown_type $vid + */ +function biblio_delete_contributor_revision($cid, $vid) { + return db_delete('biblio_contributor') + ->condition('cid', $cid) + ->condition('vid', $vid) + ->execute(); + +} + +/** + * Get the number of orphaned authors in the database + * + * @return number + */ +function biblio_count_orphan_authors() { + $cids = biblio_get_orphan_author_ids(); + return count($cids); +} + +/** + * Get an array of authors which are not associated with any biblio entires. + * @return array + */ +function biblio_get_orphan_authors() { + $authors = array(); + $cids = biblio_get_orphan_author_ids(); + + if (count($cids)) { + $query = db_select('biblio_contributor_data', 'bcd'); + $result = $query->fields('bcd') + ->condition('cid', $cids, 'IN') + ->orderBy('lastname') + ->execute(); + + foreach ($result as $author) { + $authors[] = $author; + } + } + return $authors; +} + +/** + * Get an array of author id's which are not associated with any biblio entries + * + * @return array + */ +function biblio_get_orphan_author_ids() { + $orphans = array(); + $active_cids = array(); + $all_cids = array(); + + $query = db_select('biblio_contributor', 'bc'); + $active_cids = $query + ->fields('bc', array('cid')) + ->groupBy('cid') + ->execute() + ->fetchCol(); + + $query = db_select('biblio_contributor_data', 'bcd'); + $all_cids = $query + ->fields('bcd', array('cid')) + ->condition(db_and()->condition('bcd.cid', 0, '>')->condition('bcd.alt_form', 0, '=')) + ->execute() + ->fetchCol(); + + $orphans = array_diff($all_cids, $active_cids); + + return $orphans; +} + +/** + * @param unknown_type $force + */ +function biblio_delete_orphan_authors($force = FALSE) { + if (variable_get('biblio_auto_orphaned_author_delete', 0) || $force) { + $orphans = biblio_get_orphan_author_ids(); + if (!empty($orphans)) { + db_delete('biblio_contributor_data') + ->condition('cid', $orphans, 'IN') + ->execute(); + } + } +} + +/** + * @param unknown_type $node + * @return Ambigous + */ +function biblio_insert_contributors($node) { + if (!empty($node->biblio_contributors)) { + return _save_contributors($node->biblio_contributors, $node->nid, $node->vid); + } +} + +/** + * @param unknown_type $node + */ +function biblio_update_contributors($node) { + if (!empty($node->biblio_contributors)) { + _save_contributors($node->biblio_contributors, $node->nid, $node->vid, TRUE); + } + return; + +} + +/** + * @param unknown_type $author + */ +function biblio_save_contributor(&$author) { + foreach ($author as $key => $value) { + $author[$key] = trim($value); + } + return drupal_write_record('biblio_contributor_data', $author); +} + +/** + * @param unknown_type $author + * @return boolean + */ +function biblio_update_contributor(&$author) { + if (!isset($author['cid'])) return FALSE; + return drupal_write_record('biblio_contributor_data', $author, 'cid'); +} + +/** + * @param unknown_type $authors + */ +function _biblio_contributor_sort(&$authors) { + foreach ($authors as $key => $author) { + if (!isset($author['rank']) || empty($author['rank'])) { + $authors[$key]['rank'] = $key; + } + } + usort($authors, '_biblio_contributor_usort'); +} + +/** + * @param unknown_type $a + * @param unknown_type $b + * @return number + */ +function _biblio_contributor_usort($a, $b) { + if (empty($a['name'])) return 1; + if (empty($b['name'])) return -1; + return ($a['rank'] < $b['rank']) ? -1 : 1; +} +/** + * Save contributors to the database + * @param $authors + * @param $nid + * @param $vid + * @param $update + * @return success of database operations + */ +function _save_contributors(&$contributors, $nid, $vid, $update = FALSE) { + $rank = 0; + + db_delete('biblio_contributor') + ->condition(db_and()->condition('nid', $nid)->condition('vid', $vid)) + ->execute(); + + // re-sort the authors by rank because the rank may have changed due to tabledrag on the input form + _biblio_contributor_sort($contributors); + + $name_parser = new HumanNameParser_Parser(); + + foreach ($contributors as $key => $author) { + if (!empty($author['name'])) { + if (isset($author['cid']) && !empty($author['cid'])) { + // check to make sure the name wasn't changed + // this should only happen via the node/add/biblio input form + $auth = biblio_get_contributor($author['cid']); + if (!empty($auth) && isset($auth->name) && $auth->name != $author['name']) { + //if the name has changed, NULL the cid so a new entry is created + $author['cid'] = NULL; + } + else { + $contributors[$key] = array_merge($author, (array)$auth); + } + } + // if we don't have a cid, lets see if we can find and exact match + // to the name and use that cid + if (!isset($author['cid']) || empty($author['cid'])) { + $auth = biblio_get_contributor_by_name($author['name']); + if (!empty($auth) && isset($auth->cid)) { + $author['cid'] = $auth->cid; + $contributors[$key] = array_merge($author, (array)$auth); + } + } + + // if we still don't have a cid, then create a new entry in the biblio_contirbutor_data table + if (empty($author['cid'])) { + try { + $author = $name_parser->parseName($author); + } + catch (Exception $e) { + $link = l('in node ' . $nid, 'node/' . $nid); + $message = $e->getMessage() . ' ' . $link; + drupal_set_message($message , 'error'); + watchdog('biblio', $message, array(), WATCHDOG_ERROR); + } + + $contributors[$key] = $author; + biblio_save_contributor($author); + } + + // we should now have a cid, if not we are in big trouble... + if (empty ($author['cid'])) { + //throw error that author was not saved + } + + $link_array = array( + 'nid' => $nid, + 'vid' => $vid, + 'cid' => $author['cid'], + 'rank' => $rank++, + 'auth_type' => !empty($author['auth_type']) ? $author['auth_type'] : $author['auth_category'], + 'auth_category' => $author['auth_category'], + ); + + if (!drupal_write_record('biblio_contributor', $link_array)) return FALSE; + } + } + //TODO check if it is necessary to reset aka here... + // db_query("UPDATE {biblio_contributor_data} SET aka = cid WHERE aka = 0 OR aka IS NULL"); + // db_update('biblio_contributor_data') + // ->fields(array('aka', ) + return TRUE; // successfully saved all contributors +} +/* + Released through http://bibliophile.sourceforge.net under the GPL licence. + Do whatever you like with this -- some credit to the author(s) would be appreciated. + + A collection of PHP classes to manipulate bibtex files. + + If you make improvements, please consider contacting the administrators at bibliophile.sourceforge.net so that your improvements can be added to the release package. + + Mark Grimshaw 2004/2005 + http://bibliophile.sourceforge.net + + 28/04/2005 - Mark Grimshaw. + Efficiency improvements. + + 11/02/2006 - Daniel Reidsma. + Changes to preg_matching to account for Latex characters in names such as {\"{o}} + */ +// For a quick command-line test (php -f PARSECREATORS.php) after installation, uncomment these lines: +/*********************** + $authors = "Mark \~N. Grimshaw and Bush III, G.W. & M. C. H{\\'a}mmer Jr. and von Frankenstein, Ferdinand Cecil, P.H. & Charles Louis Xavier Joseph de la Vallee P{\\\"{o}}ussin"; + $creator = new PARSECREATORS(); + $creatorArray = $creator->parse($authors); + print_r($creatorArray); + ***********************/ +/* Create writer arrays from bibtex input. + 'author field can be (delimiters between authors are 'and' or '&'): + 1. + 2. , + 3. , , + */ +/** + * @param $author_array + * @return unknown_type + */ +function biblio_parse_author($author_array, $cat = 0) { + + if (isset($author_array['auth_category']) && $author_array['auth_category'] == 5) { + $author_array['firstname'] = ''; + $author_array['initials'] = ''; + $author_array['lastname'] = trim($author_array['name']); + $author_array['prefix'] = ''; + $author_array['literal'] = 1; + } + else { + $value = trim($author_array['name']); + $appellation = $prefix = $surname = $firstname = $initials = ''; + $prefix = ""; + $value = preg_replace("/\s{2,}/", ' ', $value); // replace multiple white space by single space + $author = explode(",", $value); + $size = sizeof($author); + // No commas therefore something like Mark Grimshaw, Mark Nicholas Grimshaw, M N Grimshaw, Mark N. Grimshaw + if ($size == 1) { + // Is complete surname enclosed in {...}, unless the string starts with a backslash (\) because then it is + // probably a special latex-sign.. + // 2006.02.11 DR: in the last case, any NESTED curly braces should also be taken into account! so second + // clause rules out things such as author="a{\"{o}}" + // + if (preg_match("/(.*) {([^\\\].*)}/", $value, $matches) && !(preg_match("/(.*) {\\\.{.*}.*}/", $value, $matches2))) { + $author = explode(" ", $matches[1]); + $surname = $matches[2]; + } + else { + $author = explode(" ", $value); + // last of array is surname (no prefix if entered correctly) + $surname = array_pop($author); + } + } + // Something like Grimshaw, Mark or Grimshaw, Mark Nicholas or Grimshaw, M N or Grimshaw, Mark N. + else + if ($size == 2) { + // first of array is surname (perhaps with prefix) + list ($surname, $prefix) = _grabSurname(array_shift($author)); + } + // If $size is 3, we're looking at something like Bush, Jr. III, George W + else { + // middle of array is 'Jr.', 'IV' etc. + $appellation = implode(' ', array_splice($author, 1, 1)); + // first of array is surname (perhaps with prefix) + list ($surname, $prefix) = _grabSurname(array_shift($author)); + } + $remainder = implode(" ", $author); + list ($firstname, $initials, $prefix2) = _grabFirstnameInitials($remainder); + if (!empty ($prefix2)) + $prefix .= $prefix2; + //var_dump($prefix); + //$surname = $surname . ' ' . $appellation; + $author_array['firstname'] = trim($firstname); + $author_array['initials'] = (strlen(trim($initials)) > 10) ? drupal_substr(trim($initials), 0, 10) : trim($initials); + $author_array['lastname'] = trim($surname); + $author_array['prefix'] = trim($prefix); + $author_array['suffix'] = trim($appellation); + } + $author_array['md5'] = _md5sum($author_array); + return $author_array; +} +/** + * @param $creator + * @return unknown_type + */ +function _md5sum($creator) { + $string = $creator['firstname'] . $creator['initials'] . $creator['prefix'] . $creator['lastname']; + $string = str_replace(' ', '', drupal_strtolower($string)); + return md5($string); +} +// grab firstname and initials which may be of form "A.B.C." or "A. B. C. " or " A B C " etc. +/** + * @param $remainder + * @return unknown_type + */ +function _grabFirstnameInitials($remainder) { + $prefix = array(); + $firstname = $initials = ''; + $array = explode(" ", $remainder); + foreach ($array as $value) { + $first_char = drupal_substr($value, 0, 1); + if ((ord($first_char) >= 97) && (ord($first_char) <= 122)) { + $prefix[] = $value; + } + elseif (preg_match("/[a-zA-Z]{2,}/", trim($value))) { + $firstname_array[] = trim($value); + } + else { + $initials_array[] = trim(str_replace(".", " ", trim($value))); + } + } + if (isset ($initials_array)) { + $initials = implode(" ", $initials_array); + } + if (isset ($firstname_array)) { + $firstname = implode(" ", $firstname_array); + } + if (!empty ($prefix)) { + $prefix = implode(" ", $prefix); + } + return array($firstname, $initials, $prefix); +} +// surname may have title such as 'den', 'von', 'de la' etc. - characterised by first character lowercased. Any +// uppercased part means lowercased parts following are part of the surname (e.g. Van den Bussche) +/** + * @param $input + * @return unknown_type + */ +function _grabSurname($input) { + $no_prefix = FALSE; + $surname = FALSE; + $prefix = FALSE; + + $surname_array = explode(" ", $input); + + foreach ($surname_array as $value) { + $first_char = substr($value, 0, 1); + if (!$no_prefix && (ord($first_char) >= 97) && (ord($first_char) <= 122)) { + $prefix[] = $value; + } + else { + $surname[] = $value; + $no_prefix = TRUE; + } + } + if (!empty($surname)) { + $surname = implode(" ", $surname); + } + if (!empty ($prefix)) { + $prefix = implode(" ", $prefix); + } + return array($surname, $prefix); +} +/** + * @return unknown_type + */ +function _loadMD5() { + static $md5 = array(); + static $count = 0; + $db_count = db_query("SELECT COUNT(*) FROM {biblio_contributor_data}")->fetchField(); + if ($db_count != $count) { //refresh the cached data as some new authors may have been added or removed + $count = $db_count; + $md5 = array(); + $result = db_query('SELECT md5,cid FROM {biblio_contributor_data} '); + foreach ($result as $row ) { + $md5[$row->cid] = $row->md5; + } + } + return (count($md5)) ? $md5 : NULL; +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.feeds.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.feeds.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,211 @@ + $spec) { + if (strstr($field, 'biblio_')) { + $type = $spec['type']; + $length = isset($spec['length']) ? ' ['. $spec['length'] . ']': ''; + $targets[$field] = array( + 'name' => $field . ' (' . $type . $length . ')', + 'description' =>'', + 'callback' => '_biblio_feeds_set__simple_target', + ); + } + } + $targets['biblio_type']['callback'] = '_biblio_feeds_set__type_target'; + $targets['biblio_contributor'] = array( + 'name' => t('biblio_contributor'), + 'description' => t('This is a contributor (author) contained in a biblio entry.'), + 'callback' => '_biblio_feeds_set__contrib_target', + // 'real_target' => 'my_node_field_two', // Specify real target field on node. + ); + $targets['biblio_keyword'] = array( + 'name' => t('biblio_keyword'), + 'description' => t('This is a keyword contained in a biblio entry.'), + 'callback' => '_biblio_feeds_set__keyword_target', + // 'real_target' => 'my_node_field_two', // Specify real target field on node. + ); + } +} + +function _biblio_feeds_set__type_target($source, $entity, $target, $value) { + static $types = array(); + + if (empty($value)) { + return; + } + + // Handle non-multiple value fields. + if (!is_array($value)) { + $value = array($value); + } + + if (isset($value[0]) && !empty($value[0])) { + if (intval($value[0]) > 0) { + // value[0] is the bibio type ID + if (empty($types)) { + $result = db_query('SELECT t.* FROM {biblio_types} as t WHERE t.tid > 0'); + foreach ($result as $row) { + $types[$row->tid] = $row->tid; + } + } + + $type_id = $value[0]; + $entity->biblio_type = (isset($types[$type_id])) ? $type_id : 129; + } + elseif (is_string($value[0])) { + // value[0] is the bibio type name + if (empty($types)) { + $result = db_query('SELECT t.* FROM {biblio_types} as t WHERE t.tid > 0'); + foreach ($result as $row) { + $types[$row->tid] = str_replace(" ","_", strtolower($row->name)); + } + } + + $type = array_search($value[0], $types); + $entity->biblio_type = (!empty($type)) ? $type : 129; + } + } +} + +function _biblio_feeds_set__simple_target($source, $entity, $target, $value) { + if (empty($value)) { + return; + } + + // Handle non-multiple value fields. + if (!is_array($value)) { + $value = array($value); + } + + if (isset($value[0]) && !empty($value[0])) { + $entity->$target = $value[0]; + if ($target == 'biblio_abst_e') { + $entity->biblio_formats[$target] = 'full_html'; + } + } +} + +function _biblio_feeds_set__contrib_target($source, $entity, $target, $value) { + if (is_string($value)) { + $value = explode('||', $value); + } + foreach ($value as $author) { + $entity->biblio_contributors[] = array( + 'name' => $author, + 'auth_category' => 1, + ); + } +} + +function _biblio_feeds_set__keyword_target($source, $entity, $target, $value) { + if (!empty($value)) { + $entity->biblio_keywords = $value; + } +} + +function _biblio_feeds_oai_importer_default() { + + $feeds_importer = new stdClass; + $feeds_importer->disabled = FALSE; /* Edit this to true to make a default feeds_importer disabled initially */ + $feeds_importer->api_version = 1; + $feeds_importer->id = 'biblio_oai_pmh'; + $feeds_importer->config = array( + 'name' => 'Biblio OAI-PMH', + 'description' => 'Import an OAI-PMH feed into the Biblio node type.', + 'fetcher' => array( + 'plugin_key' => 'FeedsOAIHTTPFetcher', + 'config' => array( + 'auto_detect_feeds' => FALSE, + 'use_pubsubhubbub' => FALSE, + 'last_fetched_timestamp' => '', + 'earliest_timestamp' => '', + 'use_dates' => FALSE, + 'to' => array(), + 'from' => array(), + ), + ), + 'parser' => array( + 'plugin_key' => 'FeedsOAIParser', + 'config' => array(), + ), + 'processor' => array( + 'plugin_key' => 'FeedsNodeProcessor', + 'config' => array( + 'content_type' => 'biblio', + 'expire' => '-1', + 'author' => 0, + 'mappings' => array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + 'unique' => 0, + ), + 1 => array( + 'source' => 'publisher', + 'target' => 'biblio_publisher', + 'unique' => FALSE, + ), + 2 => array( + 'source' => 'subject', + 'target' => 'biblio_keyword', + 'unique' => FALSE, + ), + 3 => array( + 'source' => 'source', + 'target' => 'biblio_secondary_title', + 'unique' => FALSE, + ), + 4 => array( + 'source' => 'guid', + 'target' => 'guid', + 'unique' => 1, + ), + 5 => array( + 'source' => 'creator', + 'target' => 'biblio_contributor', + 'unique' => FALSE, + ), + 6 => array( + 'source' => 'description', + 'target' => 'biblio_abst_e', + 'unique' => FALSE, + ), + 7 => array( + 'source' => 'contributor', + 'target' => 'biblio_contributor', + 'unique' => FALSE, + ), + 8 => array( + 'source' => 'identifier', + 'target' => 'biblio_url', + 'unique' => FALSE, + ), + 9 => array( + 'source' => 'date', + 'target' => 'biblio_year', + 'unique' => FALSE, + ), + 10 => array( + 'source' => 'setspec_raw', + 'target' => 'biblio_type', + 'unique' => FALSE, + ), + ), + 'update_existing' => '0', + 'input_format' => 'plain_text', + ), + ), + 'content_type' => '', + 'update' => 0, + 'import_period' => '1800', + 'expire_period' => 3600, + 'import_on_create' => 1, + 'process_in_background' => 0, + ); + + return array( + 'biblio_oai' => $feeds_importer + ); +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.fields.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.fields.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,51 @@ + 0), array('fetch' => PDO::FETCH_ASSOC)); + + foreach ($result as $row) { + $fields[$row['name']] = $row; + } + _biblio_localize_fields($fields); + + $extras['node']['biblio']['form']['biblio_type'] = array( + 'label' => t('Publication Type'), + 'description' => t('Biblio module form.'), + 'weight' => -4 + ); + + foreach ($fields as $key => $fld) { + $label = check_plain($fld['title']); + if ($fld['type'] == 'textarea' || $fld['type'] == 'contrib_widget') { + $key = $key . '_field'; + $label = $label . ' (' . t('Fieldset') . ')'; + } + $extras['node']['biblio']['form'][$key] = array( + 'label' => $label, + 'description' => t('Biblio module form.'), + 'weight' => $fld['weight'] / 10 + ); + } + $extras['node']['biblio']['form']['other_fields'] = array( + 'label' => t('Other Biblio Fields') . ' (' . t('Fieldset') . ')', + 'description' => t('Biblio module form.'), + 'weight' => 0 + ); + + $extras['user']['user'] = array( + 'form' => array( + 'biblio_fieldset' => array( + 'label' => t('User specific Biblio settings'), + 'description' => t('Biblio module account form elements.'), + 'weight' => -10, + ), + ), + ); + + return $extras; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.import.export.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.import.export.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,873 @@ + 0 '; + $result = db_query($sql); + foreach ($result as $user) { + $users[$user->uid] = $user->name . " ($user->mail)"; + } + asort($users); + $select = array( + '#type' => 'select', + '#title' => t("Set user ID of entries in this file to"), + '#options' => $users, + '#default_value' => $my_uid, + '#disabled' => (user_access('administer biblio')) ? FALSE : TRUE + ); + return $select; +} +/** + * Return a form used to import files into biblio. + * + * @return + * An array which will be used by the form builder to build the import form + */ +function biblio_import_form($form, &$form_state) { + global $user; + $msg = ''; + $biblio_vocabs = array(); + + if (biblio_access('import')) { // && !user_access('administer nodes')) { + $form['#attributes']['enctype'] = 'multipart/form-data'; + $form['biblio_import_file'] = array( + '#type' => 'file', + '#title' => t('Import file'), + '#default_value' => '', + '#size' => 60 + ); + + $import_options = module_invoke_all('biblio_import_options'); + if (count($import_options) > 1) { + $form['filetype'] = array( + '#type' => 'select', + '#title' => t('File Type'), + '#default_value' => 0, + '#options' => array( + '0' => t('Select type'), + ) + ); + + $form['filetype']['#options'] = array_merge($form['filetype']['#options'], $import_options); + asort($form['filetype']['#options']); + } + elseif (count($import_options) == 1) { + $form['biblio_import_file']['#description'] = t('Import type: @option', array('@option' => current($import_options))); + $form['filetype'] = array( + '#type' => 'value', + '#value' => key($import_options), + ); + } + elseif (count($import_options) == 0) { + $form['biblio_import_file']['#disabled'] = TRUE; + drupal_set_message(t("You won't be able to select a file until you enable at least one import module."), 'error'); + + } + + $form['batch_process'] = array( + '#type' => 'checkbox', + '#title' => t('Batch Process'), + '#default_value' => 1, + '#description' => t('You should use batch processing if your import file contains more than about 20 records, or if you are experiencing script timeouts during import'), + ); + $form ['userid'] = _biblio_admin_build_user_select($user->uid); + // Get the vocabularies attached to the biblio node type ... + foreach (field_info_instances('node', 'biblio') as $instance) { + $field = field_info_field_by_id($instance['field_id']); + if ($field['type'] == 'taxonomy_term_reference') { + foreach ($field['settings']['allowed_values'] as $delta => $tree) { + $biblio_vocabs[$tree['vocabulary']] = array('instance' => $instance, 'field' => $field); + } + } + } + + // ... and print a form to select the terms in each of them + $form['import_taxonomy'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('Taxonomy Settings'), + '#description' => t('Typically you don\'t have to do anything here, however if you wish, you may select terms to be assigned to imported records. This effectively adds a keyword to all entries being imported.')); + if (count($biblio_vocabs)) { + $vocabularies = module_invoke('taxonomy', 'get_vocabularies', 'biblio'); + if (variable_get('biblio_keyword_freetagging', 0)) { + $freetag_vocab = $vocabularies[variable_get('biblio_keyword_vocabulary', 0)]; + unset($vocabularies[variable_get('biblio_keyword_vocabulary', 0)]); + $msg = t('NOTE: Keyword "free tagging" is turned on, consequently all incomming keywords will be added to the @name vocabulary as specified in the "Keyword" section of the !url page.', array('@name' => $freetag_vocab->name, '!url' => l(t('admin/config/content/biblio'), 'admin/config/content/biblio'))); + } + else { + $msg = t('NOTE: Keyword "free tagging" is turned off, consequently keywords will NOT be added to the vocabulary as specified in the Taxonomy section of the !url page.', array('!url' => l(t('admin/config/content/biblio'), 'admin/config/content/biblio'))); + } + $i = 0; + $form += array('#parents' => array()); + $form['import_taxonomy']['vocabularies'] = array(); + $term_refs = array(); + + foreach ($vocabularies as $vocabulary) { + if (in_array($vocabulary->machine_name, array_keys($biblio_vocabs))) { + $term_refs[] = $biblio_vocabs[$vocabulary->machine_name]['instance']['field_name']; + $entity = new stdClass(); + $entity->type = 'biblio'; + $field = $biblio_vocabs[$vocabulary->machine_name]['field']; + $instance = $biblio_vocabs[$vocabulary->machine_name]['instance']; + $items = array(); + $form['import_taxonomy']['vocabularies'] += field_default_form('node', $entity, $field, $instance, 'und', $items, $form, $form_state); + } + } + if (!empty($term_refs)) { + $form['term_refs'] = array( + '#type' => 'hidden', + '#value' => $term_refs, + ); + $form['import_taxonomy']['copy_to_biblio'] = array( + '#type' => 'checkbox', + '#title' => t('Copy these terms to the biblio keyword database'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_copy_taxo_terms_to_keywords', 0), + '#description' => t('If this option is selected, the selected taxonomy terms will be copied to the ' . check_plain(variable_get('biblio_base_title', 'Biblio')) . ' keyword database and be displayed as keywords (as well as taxonomy terms) for this entry.') + ); + } + } + else { + if (module_exists('taxonomy')) { + $vocab_msg = t('There are currently no "Term references" attached to the biblio node type. If you would like to associate a Taxonomy vocabulary with the Biblio node type, go the the !url page and add one or more "Term reference" fields.', array('!url' => l(t('admin/structure/types/manage/biblio/fields'), 'admin/structure/types/manage/biblio/fields'))); + } + else{ + $vocab_msg = '
    ' . t('Depends on') . ': ' . t('Taxonomy') . ' (' . t('disabled') . ')
    '; + + } + $form['import_taxonomy']['vocabulary_message'] = array( + '#markup' => '

    ' . $vocab_msg . '

    ' + ); + } + $form['import_taxonomy']['freetagging_information'] = array( + '#markup' => '

    ' . $msg . '

    ' + ); + $form['button'] = array('#type' => 'submit', '#value' => t('Import')); + return $form; + } + else { + drupal_set_message(t("You are not authorized to access the biblio import page"), 'error'); + } +} + +/** + * Implementation of hook_validate() for the biblio_import_form. + */ +function biblio_import_form_validate($form, & $form_state) { + $op = $form_state['values']['op']; + $filetype = isset($form_state['values']['filetype']) ? $form_state['values']['filetype'] : 0; + if ($error = isset($_FILES['files']) ? $_FILES['files']['error']['biblio_import_file'] : '') { + switch ($error) { + case 1: form_set_error('biblio_import_form', t("The uploaded file exceeds the upload_max_filesize directive in php.ini.")); + break; + case 2: form_set_error('biblio_import_form', t("The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.")); + break; + case 3: form_set_error('biblio_import_form', t("The uploaded file was only partially uploaded.")); + break; + case 4: form_set_error('biblio_import_form', t("No file was uploaded.")); + break; + case 6: form_set_error('biblio_import_form', t("Missing a temporary folder.")); + break; + case 7: form_set_error('biblio_import_form', t("Failed to write file to disk.")); + break; + case 8: form_set_error('biblio_import_form', t("File upload stopped by extension.")); + } + } + + if ($op == t('Import') && $filetype == "0") { + form_set_error('biblio_import_form', t("You did not select the file type")); + } +} +/** + * Implementation of hook_submit() for the biblio_import_form. + */ +function biblio_import_form_submit($form, & $form_state) { + global $user; + $batch_proc = FALSE; + $extensions = 'xml bib enw mrc ris txt'; + $validators['file_validate_extensions'] = array(); + $validators['file_validate_extensions'][0] = $extensions; + if ($form_state['values']['op'] == t('Import') && isset ($form_state['values']['filetype'])) { + if ($import_file = file_save_upload('biblio_import_file', $validators)) { + if ($form_state['values']['batch_process'] == 1) { + $batch_proc = TRUE; // we will use batch import for larger files. + } + // Concatenate all the terms of the different vocabularies + // in a single array to be sent to biblio_import + $terms = array(); + if (isset($form_state['values']['term_refs'])) { + foreach ($form_state['values']['term_refs'] as $key) { + if (isset($form_state['values'][$key]) && !empty($form_state['values'][$key])) { + foreach ($form_state['values'][$key]['und'] as $id => $item) { + if (!is_array($item) || (empty($item['tid']) && (string) $item['tid'] !== '0')) { + unset($form_state['values'][$key]['und'][$id]); + } + } + if (empty($form_state['values'][$key]['und'])) { + unset($form_state['values'][$key]); + } + else { + $terms[$key] = $form_state['values'][$key]; + } + } + // } + + //FIXME if (count($terms) && $key == 'copy_to_biblio') $terms['copy_to_biblio'] = $form_state['values'][$key]; + } + } + + // Added the $terms argument + // the array of terms to be attached to the node(s) + $userid = (isset($form_state['values']['userid'])) ? $form_state['values']['userid'] : $user->uid; + $filetype = $form_state['values']['filetype']; + $filesize = sprintf("%01.1f", $import_file->filesize / 1000); + $filesize = " ($filesize KB)"; + if ($batch_proc) { + $session_id = microtime(); + $batch_op = array( + 'title' => t('Importing @filename', array('@filename' => $import_file->filename . $filesize)), + 'operations' => array( + array('biblio_import', array($import_file, $filetype, $userid, $terms, $batch_proc, $session_id)), + array('biblio_import_batch_operations', array($session_id, $user, $userid, $terms)) + ), + 'progressive' => TRUE, + 'finished' => 'biblio_import_batch_finished', + 'init_message' => t('Parsing file...'), + 'progress_message' => t('Saving nodes...'), + 'file' => './' . drupal_get_path('module', 'biblio') . '/includes/biblio.import.export.inc' + ); + batch_set($batch_op); + + $base = variable_get('biblio_base', 'biblio'); + batch_process("$base/import"); + + } + else{ //not batch processing the file + $session_id = microtime(); + $context = array(); + biblio_import($import_file, $filetype, $userid, $terms, $batch_proc, $session_id, $context); + biblio_import_finalize(TRUE, $context['results']); + } + file_delete($import_file); + } + else { + drupal_set_message(t("File was NOT successfully uploaded"), 'error'); + } + } +} + +function biblio_import_batch_operations($session_id, $user, $userid, $terms, &$context) { + $limit = 10; + if (empty($context['sandbox'])) { + // Initiate multistep processing. + $context['results']['session_id'] = $session_id; + $context['results']['userid'] = $userid; + $context['results']['user'] = $user; + $context['results']['terms'] = $terms; + $context['sandbox']['progress'] = 0; + $context['sandbox']['current_id'] = 0; + $context['results']['nids'] = array(); + $context['sandbox']['max'] = db_query("SELECT COUNT(DISTINCT(id)) FROM {biblio_import_cache} WHERE session_id = :sessid", array(':sessid' => $session_id))->fetchField(); + $context['sandbox']['itters'] = $context['sandbox']['max'] / $limit; + $context['sandbox']['eta'] = 0; + } + // Bail out if the cache is empty. + if ($context['sandbox']['max'] == 0) { + return; + } + + // Process the next 20 nodes. + timer_start('biblio_import'); + + $result = db_query_range("SELECT id, data FROM {biblio_import_cache} WHERE id > :id AND session_id = :sessid ORDER BY id ASC", 0, $limit, array(':id' => $context['sandbox']['current_id'], ':sessid' => $session_id)); + foreach ($result as $row) { + if ($node = unserialize(base64_decode($row->data))) { + biblio_save_node($node, $terms); + $context['results']['nids'][] = $node->nid; + } + $context['sandbox']['progress']++; + $context['sandbox']['current_id'] = $row->id; + } + $looptime = timer_stop('biblio_import'); + $context['sandbox']['eta'] += $looptime['time']; + $itters = $context['sandbox']['progress'] / $limit; + if ($itters) { + $average_time = $context['sandbox']['eta'] / $itters; + $eta = (($context['sandbox']['itters'] * $average_time) - ($average_time * $itters)) / 1000; + if ($eta >= 60) { + $min = (int) $eta / 60; + } + else { + $min = 0; + } + $sec = $eta % 60; + $eta = sprintf("%d:%02d", $min, $sec); + $progress = sprintf("%d / %d", $context['sandbox']['progress'], $context['sandbox']['max'] ); + $context['message'] = t('
    Nodes saved: %progress
    Time remaining: %eta min.
    ' , array('%progress' => $progress, '%eta' => $eta)); + + } + // Multistep processing : report progress. + if ($context['sandbox']['progress'] <= $context['sandbox']['max']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; + } +} +function biblio_import_batch_finished($success, $results, $operations) { + + biblio_import_finalize($success, $results); + //clean up import cache... + db_delete('biblio_import_cache') + ->condition('session_id', $results['session_id']) + ->execute(); +} + +function biblio_import_finalize($success, $results) { + global $user; + $format = $results['format']; + $nids = $results['nids']; + $dups = $results['dups']; + $total = count($nids) + count($dups); + // drupal_set_message(t("%count of %total nodes were successfully imported.", array('%count' => count($nids), '%total' => $total)), (count($nids) != $total)?'warning':'status'); + + if ($success && (count($nids) || count($dups))) { + $message = t("The file @file was successfully uploaded.", array('@file' => $results['file']->filename)); + drupal_set_message($message, 'status'); + watchdog($format, $message); + $count = count($nids); + $message = format_plural($count, 'One of @total node imported.', '@count of @total nodes imported.', array('@total' => $total)); + drupal_set_message($message, 'status'); + watchdog($format, $message, array('@count' => $count, '@total' => $total), WATCHDOG_INFO); + if (count($dups)) { + $count = count($dups); + $message = format_plural($count, 'One duplicate node skipped.', '@count duplicate nodes skipped.'); + drupal_set_message($message, 'status'); + watchdog($format, $message, array('@count' => $count), WATCHDOG_INFO); + foreach ($dups as $nid) { + $message = ''; + $message = t('The item you are trying to import already exists in the database, see'); + $message .= ' ' . l('node/' . $nid, 'node/' . $nid); + + drupal_set_message($message, 'status'); + watchdog($format, $message, array(), WATCHDOG_ERROR); + } + } + } + else { + $count = count($nids); + $message = t('Import finished with an error!') . ' ' . format_plural($count, 'One node imported.', '@count nodes imported.'); + drupal_set_message($message, 'error'); + watchdog($format, $message, array('@count' => $count), WATCHDOG_ERROR); + } + + $userid = isset($results['userid']) ? $results['userid'] : $user->uid; + + if (user_access('administer biblio') && count($nids) && $user->uid != $userid) { + db_update('node') + ->fields(array('uid' => $results['userid'])) + ->condition('nid', $nids, 'IN') + ->execute(); + db_update('node_revision') + ->fields(array('uid' => $results['userid'])) + ->condition('nid', $nids, 'IN') + ->execute(); + } + +} + +function biblio_import_from_url($URL) { + $handle = fopen($URL, "r"); // fetch data from URL in read mode + $data = ""; + if ($handle) { + while (!feof($handle)) { + $data .= fread($handle, 4096); // read data in chunks + } + fclose($handle); + } + else { + $errorMessage = t("Error occurred: Failed to open %url", array('%url', $URL)); // network error + drupal_set_message($errorMessage, 'error'); + } + + return $data; +} + +function biblio_export_form() { + $form['pot'] = array( + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#title' => t('POT Export'), + '#description' => t('Here you may export a ".pot" file which contains the titles and hints from the database which are not normally captured by translation extractors)') + ); + $form['pot']['button'] = array( + '#type' => 'submit', + '#value' => t('Export translation data') + ); + + return $form; +} +function biblio_export_form_submit($form, & $form_state) { + if ($form_state['values']['op'] == t('Export translation data')) { + biblio_dump_db_data_for_pot(); + } + +} + +/** + * Import data from a file and return the node ids created. + * + * @param $userid + * The user id that will be assigned to each node imported + * @param $filename + * The name of the file containing the data to import + * @param $type + * The format of the file to be imported (tagged, XML, RIS, bibTEX) + * @param $terms + * the vocabulary that the imported nodes will be associated with + * @return + * An array of node id's of the items imported + */ +function biblio_import($import_file, $type, $userid = 1, $terms = NULL, $batch = FALSE, $session_id = NULL, &$context ) { + global $user; + $parsed = 0; + $nids = array(); + $dups = array(); + + if (isset($context['message'])) $context['message'] = t('Parsing file'); + switch ($type) { + case 'csv' : // comma separated variable file + // $file_content = @ file_get_contents($import_file->uri); + // $parsed = biblio_csv_import($file_content, $node_template, $node_array); + break; + case 'biblio_backup' : // a complete backup of all biblio information + $file_content = @ file_get_contents($import_file->uri); + $parsed = biblio_restore($file_content, $node_template, $node_array); + break; + default: + list($nids, $dups) = module_invoke($type, 'biblio_import', $import_file, $terms, $batch, $session_id); + break; + } + $context['results']['nids'] = $nids; + $context['results']['dups'] = $dups; + $context['results']['format'] = $type; + $context['results']['userid'] = $userid; + $context['results']['user'] = $user; + $context['results']['file'] = $import_file; + + return $batch ? NULL : $nids; +} +/** + * Export nodes in a given file format. + * + * @param $format + * The file format to export the nodes in (tagged, XML, bibTEX) + * @param $nid + * If not NULL, then export only the given nodeid, else we will + * use the session variable which holds the most recent query. If neither + * $nid or the session variable are set, then nothing is exported + * @param $version + * The version of EndNote XML to use. There is one format for ver. 1-7 and + * a different format for versions 8 and greater. + * @return + * none + */ +function biblio_export($format = "tagged", $nid = NULL, $popup = FALSE) { + $params = array(); + $nids = array(); + $arg_list = array(); + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + if ($nid === NULL ) { + module_load_include('inc', 'biblio', 'includes/biblio.pages'); + $uri = drupal_parse_url(request_uri()); + $arg_list += $uri['query']; + $arg_list['page_limit'] = 0; + list($nids,,) = biblio_build_query($arg_list); + } + elseif (!empty ($nid)) { + $nids[] = $nid; + } + elseif (!count($nids)) { + return; + } + + module_invoke('biblio_' . $format, 'biblio_export', $nids); + +} + +/** + * Save node imported from a file. + * + * @param $node_array + * a 2 dimensional array containing all the node information + * @return + * The node ids of the saved nodes + */ +function biblio_save_imported_nodes(& $node_array) { + $dup_count = 0; + if (function_exists('node_save')) { + foreach ($node_array as $imp_node) { + $node_ids[] = biblio_save_node($imp_node); + } + } +/* if ($dup_count) + drupal_set_message(t("Detected @dupcount duplicate node(s) when importing", array('@dupcount' => $dup_count)), 'error'); + + drupal_set_message(t("Succesfully imported @count entries.", array('@count' => count($node_ids))), 'status'); +*/ + return $node_ids; +} +function biblio_save_node($node, $terms = array(), $batch = FALSE, $session_id = NULL, $save_node = TRUE) { + global $user; + + if ($batch && $session_id) { // we are batch processing some import data + $cache['session_id'] = $session_id; + $cache['data'] = base64_encode(serialize($node));// base64_encode to avoid problems unserializing strings with embeded quotes. + drupal_write_record('biblio_import_cache', $cache); + return; + } + + $node->type = 'biblio'; + // Persist the node revision log since it will be overridden by + // node_object_prepare(). + $created = !empty($node->created)? $node->created : NULL; + $revision_log = !empty($node->log)? $node->log : NULL; + node_object_prepare($node); + $node->created = $created; + $node->log = $revision_log; + + if (!empty($terms)) { + foreach ($terms as $key => $value) { + $node->{$key} = $value; + } + } + + $node->language = 'und'; //start by setting the language to undefined and then try to refine it. + if (module_exists('locale')) { + $node->language = locale_language_url_fallback(); + } + if (module_exists('i18n') && variable_get('i18n_node_biblio', 0) && variable_get('language_content_type_biblio', 0) ) { + $node->language = module_invoke('i18n', 'default_language'); + } + + if (!isset($node->biblio_type)) { + $node->biblio_type = 129; // default to misc if not set. + } + + + if ($save_node) { // $save_node = TRUE, the normal save path + $validation_errors = array(); + $validation_errors = biblio_save_node_validate($node); + node_save($node); + if (!empty($validation_errors)) { + foreach ($validation_errors as $field => $value) { + $message = $field . ' ' . t('was truncated to fit into the database column'); + $link = l('node/' . $node->nid, 'node/' . $node->nid); + drupal_set_message($message . '; ' . $link, 'warning'); + watchdog('biblio - import', $message, array(), WATCHDOG_ALERT, $link); + } + } + return; // (isset($node->nid)) ? $node->nid : 0; + } + else { // $save_node = FALSE, primarily used to parse data and return it to the input form + return (array)$node; + } +} + +function biblio_save_node_validate($node) { + static $schema = array(); + $error = array(); + + if (empty($schema)) { + $schema['biblio'] = drupal_get_schema('biblio'); + } + if (isset($node->title) && strlen($node->title) > 255) { + $error['title'] = $node->title; + $node->title = substr($node->title, 0, 255); + } + if (isset($node->biblio_keywords) && is_array($node->biblio_keywords)) { + foreach ($node->biblio_keywords as $key => $word) { + if (strlen($word) > 255) { + $error['keyword'] = $word; + $node->biblio_keywords[$key] = substr($word, 0, 255); + } + } + } + if (isset($node->biblio_contributors) && is_array($node->biblio_contributors)) { + foreach ($node->biblio_contributors as $key => $author) { + if (strlen($author['name']) > 255) { + $error['author'] = $author['name']; + $node->biblio_contributors[$key]['name'] = substr($author['name'], 0, 255); + } + } + } + foreach ($schema['biblio']['fields'] as $field => $spec) { + if (isset($node->$field)) { + switch ($spec['type']) { + case 'varchar': + if (strlen($node->$field) > $spec['length']) { + $error[$field] = $node->$field; + $node->$field = substr($node->$field, 0, $spec['length']); + } + break; + } + } + } + return $error; +} + + +function biblio_csv_export_2($result, $bfields) { + // $query_biblio_fields = 'SELECT name, title FROM {biblio_fields}'; + // $res_biblio_fields = db_query($query_biblio_fields); + // while ($rec = db_fetch_object($res_biblio_fields)) { + // $bfields[$rec->name] = $rec->title; + // } + $bfields = biblio_get_db_fields('all'); + $query_biblio_types = 'SELECT tid, name FROM {biblio_types}'; + $res_biblio_types = db_query($query_biblio_types); + foreach ($res_biblio_types as $rec) { + $btypes[$rec->tid] = $rec->name; + } + switch (variable_get('biblio_csv_field_sep', 'tab')) { + case 'tab' : + $filedsep = "\t"; + break; + case 'comma' : + $filedsep = ','; + break; + } + switch (variable_get('biblio_csv_text_sep', 'dquote')) { + case 'dquote' : + $textsep = '"'; + break; + case 'quote' : + $textsep = '\''; + break; + } + $label = (variable_get('biblio_csv_col_head', 'label') == 'label' ? 1 : 0); // or 'col_name' + $linebreak = variable_get('biblio_linebreak_exp', 1); + foreach ($result as $rec) { + $node_id = $rec->nid; + $node_array[$node_id]['type'] = $btypes[$rec->biblio_type]; // there is no "label" for "type" + $col_array['type'] = 'Type'; + foreach (array_keys($bfields) as $fieldname) { + if (!empty ($rec-> $fieldname) && !in_array($fieldname, array( + 'biblio_citekey', + 'biblio_coins' + ))) { + $col_array[$fieldname] = $bfields[$fieldname]; // mark field as in use + $text = strtr($rec-> $fieldname, $textsep, "$textsep$textsep"); + if ($linebreak) { + $text = strtr($text, ';', "\n"); + } + $node_array[$node_id][$fieldname] = trim($text); + } + } + } //end while + if ($label) { // head line containing column names + $csv = $textsep . join("$textsep$filedsep$textsep", array_values($col_array)) . "$textsep\n"; + } + else { // original DB field names + $csv = $textsep . join("$textsep$filedsep$textsep", array_keys($col_array)) . "$textsep\n"; + } + // Enclosing text in "" is neccessary to enshure + // multi line fields (like author) are handled correctly. + // Therefore existing " must be excaped before. + $csv = '"' . join("\"\t\"", array_keys($col_array)) . "\"\n"; + foreach ($node_array as $line_array) { + $csv_line = ''; + foreach (array_keys($col_array) as $col) { + $csv_line .= "$filedsep$textsep" . $line_array[$col] . $textsep; + } + $csv .= substr($csv_line, 1) . "\n"; // cut off leading fieldsep and append EOL + } + drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8'); + drupal_add_http_header('Content-Disposition', 'attachment; filename=biblio_export.csv'); + return $csv; +} +//function _biblio_cck_join($biblio_fields = array()) { // works not with php4 +function _biblio_cck_join(& $biblio_fields) { + $cck_join = ''; + $biblio_fields['nid'] = 'Node-ID'; // identify records for update operations + $query_cck_fields = "SELECT field_name, label from {node_field_instance} where type_name='biblio' and not (widget_type='image')"; + $res_cck_fields = db_query($query_cck_fields); + foreach ($$res_cck_fields as $rec) { + $cck_table = 'content_' . $rec->field_name; + $cck_field = $rec->field_name . '_value'; + $biblio_fields[$cck_field] = $rec->label; + $cck_join .= ' left join {' . $cck_table . '} on b.vid=' . $cck_table . '.vid'; + } + return $cck_join; +} + +function biblio_backup() { + + $csv_function = (!function_exists('fputcsv')) ? 'biblio_fputcsv' : 'fputcsv'; + $count_sql = "SELECT COUNT(*) + FROM {biblio} b, {node} n, {node_revision} nr + WHERE b.vid = n.vid and nr.vid = n.vid;"; + $field_type_sql = "SELECT * FROM {biblio_field_type} "; + $field_type_data_sql = "SELECT * FROM {biblio_field_type_data} "; + $field_fields_sql = "SELECT * FROM {biblio_fields} "; + $types_sql = "SELECT * FROM {biblio_types} "; + $sql = "SELECT b.*, + n.type, n.language, n.title, n.uid, n.status, n.created, + n.changed, n.comment, n.promote, n.moderate, n.sticky, + n.tnid, n.translate, + nr.title, nr.body, nr.teaser, nr.log, nr.timestamp, nr.format + FROM {biblio} b, {node} n, {node_revision} nr + WHERE b.vid = n.vid and nr.vid = n.vid;"; + + $biblio_count = db_result(db_query($count_sql)); + if ($biblio_count) { + drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8'); + drupal_add_http_header('Content-Disposition', 'attachment; filename=Biblio-export.csv'); + $biblio_nodes = db_query($sql); + while ($node = db_fetch_array($biblio_nodes)) { + $results[] = $node; + } + print biblio_csv_export($results); + unset($results); + $result = db_query($field_type_data_sql, 'biblio_field_type_data.csv'); + while ($data = db_fetch_array($result)) { + $results[] = $data; + } + print biblio_csv_export($results); + unset($results); + $result = db_query($field_fields_sql, 'biblio_fields.csv'); + while ($data = db_fetch_array($result)) { + $results[] = $data; + } + print biblio_csv_export($results); + unset($results); + $result = db_query($types_sql, 'biblio_types.csv'); + while ($data = db_fetch_array($result)) { + $results[] = $data; + } + print biblio_csv_export($results); + unset($results); + $result = db_query($field_type_sql, 'biblio_field_type.csv'); + while ($data = db_fetch_array($result)) { + $results[] = $data; + } + print biblio_csv_export($results); + } +} + +function biblio_restore(& $csv_content, $mode = 'create') { + +} + +function biblio_csv_export($results) { + $csv = ''; + if (!is_array($results)) { + $result_array[] = (array) $results; + } + else { + $result_array = $results; + } + $fieldnames = NULL; + foreach ((array)$result_array as $rec) { + if (empty($fieldnames)) { + $fieldnames = array_keys($rec); + $csv .= biblio_strcsv($fieldnames); + } + $csv .= biblio_strcsv($rec); + } + return $csv; +} + +function biblio_strcsv($fields = array(), $delimiter = ',', $enclosure = '"') { + $str = ''; + $escape_char = '\\'; + foreach ($fields as $value) { + if (strpos($value, $delimiter) !== FALSE || strpos($value, $enclosure) !== FALSE || strpos($value, "\n") !== FALSE || strpos($value, "\r") !== FALSE || strpos($value, "\t") !== FALSE || strpos($value, ' ') !== FALSE) { + $str2 = $enclosure; + $escaped = 0; + $len = strlen($value); + for ($i = 0; $i < $len; $i++) { + if ($value[$i] == $escape_char) { + $escaped = 1; + } + else + if (!$escaped && $value[$i] == $enclosure) { + $str2 .= $enclosure; + } + else { + $escaped = 0; + } + $str2 .= $value[$i]; + } + $str2 .= $enclosure; + $str .= $str2 . $delimiter; + } + else { + $str .= $value . $delimiter; + } + } + $str = substr($str, 0, -1); + $str .= "\n"; + return $str; +} +function biblio_dump_db_data_for_pot() { + $query = "SELECT name, description FROM {biblio_types} "; + $result = db_query($query); + $strings = array(); + foreach ($result as $type) { + $strings[] = $type->name; + if (!empty($type->description)) $strings[] = $type->description; + } + $query = "SELECT title, hint FROM {biblio_field_type_data} "; + $result = db_query($query); + foreach ($result as$type_data) { + $strings[] = $type_data->title; + if (!empty($type_data->hint)) $strings[] = $type_data->hint; + } + $query = "SELECT title, hint FROM {biblio_contributor_type_data} "; + $result = db_query($query); + foreach ($result as $type_data ) { + $strings[] = $type_data->title; + if (!empty($type_data->hint)) $type_data->hint; + } + $strings = array_unique($strings); + foreach ($strings as $string) { + $output .= "t(\"$string\"\);\n"; + } + + drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8'); + drupal_add_http_header('Content-Disposition', 'attachment; filename=biblio_db_values.pot'); + print $output; +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.keywords.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.keywords.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,486 @@ +kid; + } +} +/** + * @param $name + * @return array of keywords + */ +function biblio_get_keyword_by_name($name) { + static $keywords = array(); + if (!$kid = array_search($name, $keywords)) { + $term = db_query("SELECT kd.kid, kd.word, COUNT(*) as use_count FROM {biblio_keyword_data} kd + LEFT JOIN {biblio_keyword} bk on bk.kid = kd.kid + WHERE LOWER(kd.word) = LOWER(:name) + GROUP BY kd.kid, kd.word", array(':name' => trim($name)))->fetchObject(); + if ($term) { + $keywords[$term->kid] = $term; + return $keywords[$term->kid]; + } + else { + return FALSE; + } + } + + return $keywords[$kid]; +} + +/** + * @param $kid + * @return unknown_type + */ +function biblio_get_keyword_by_id($kid) { + static $keywords = array(); + + if (!isset($keywords[$kid])) { + $keywords[$kid] = db_query('SELECT * FROM {biblio_keyword_data} WHERE kid = :kid', array(':kid' => $kid))->fetchObject(); + } + + return $keywords[$kid]; + +} + +/** + * Load keywords for a single node + * + * @param $vid + * @return keywords for a given node->vid + */ +function biblio_load_keywords($vid) { + $vids = (isset($vid) ? array($vid) : array()); + return biblio_load_keywords_multiple($vids); +} + +/** + * Load keywords for multiple nodes + * + * @param array $vids + * @return multitype: + */ +function biblio_load_keywords_multiple($vids = array()) { + $keywords = array(); + if (empty($vids)) return $keywords; + + $query = db_select('biblio_keyword', 'bk'); + $query->innerJoin('biblio_keyword_data', 'bkd', 'bk.kid = bkd.kid'); + $query->addField('bk', 'vid'); + $query->fields('bkd', array('kid', 'word')); + $query->orderby('bk.vid'); + $query->orderby('bkd.word'); + if (count($vids) == 1) { + $query->condition('bk.vid', $vids[0]); + } + else { + $query->condition('bk.vid', $vids, 'IN'); + } + $query->addMetaData('base_table', 'biblio_keyword'); + $query->addTag('node_access'); + $result = $query->execute(); + + foreach ($result as $keyword) { + $keywords[$keyword->vid][$keyword->kid] = $keyword->word; + } + + return $keywords; +} +/** + * Update the keyword database from the supplied node + * + * @param stdClass $node + * @return + * An array of keyword ID's + */ +function biblio_update_keywords($node) { + $kids = biblio_insert_keywords($node, TRUE); + return $kids; +} + +/** + * Insert keywords into the database + * + * @param $node + * A node with keywords attached + * @param $update + * Set to TRUE if you are updating an existing node + * @return + * An array of keyword ID's from this node + */ +function biblio_insert_keywords($node, $update = FALSE) { + $kw_vocab = variable_get('biblio_keyword_vocabulary', 0); + $taxo_terms = $typed_keywords = array(); + $freetag_vocab = FALSE; + if (!is_array($node->biblio_keywords)) { + $typed_keywords = biblio_explode_keywords($node->biblio_keywords); + } + else { + $typed_keywords = $node->biblio_keywords; + } + + if ($update) { + $and = db_and()->condition('nid', $node->nid) + ->condition('vid', $node->vid); + db_delete('biblio_keyword') + ->condition($and) + ->execute(); + } + + if (empty($node->biblio_keywords)) return; + + $vocabularies = module_invoke('taxonomy', 'get_vocabularies'); + $vid = variable_get('biblio_keyword_vocabulary', 0); + + if (variable_get('biblio_keyword_freetagging', 0) && $vid) { + $freetag_vocab = $vocabularies[variable_get('biblio_keyword_vocabulary', 0)]; + } + + if (isset($node->taxonomy) && is_array($node->taxonomy) && variable_get('biblio_copy_taxo_terms_to_keywords', 0)) { //add any taxonomy terms to our keyword list + foreach ($node->taxonomy as $vid => $term) { + if ($vid == 'copy_to_biblio' && $term == 0 ) {// don't copy if user overrides the default to copy, just set the $taxo_terms to an empty array and break out of the for loop + $taxo_terms = array(); + break; + } + if (is_array($term) && !empty($term)) { + foreach ($term as $tid) { + if ($tid) { + $term_obj = taxonomy_term_load($tid); + $taxo_terms[$term_obj->tid] = $term_obj->name; + } + } + } + elseif ($term) { + $term_obj = taxonomy_term_load($term); + $taxo_terms[$term_obj->tid] = $term_obj->name; + } + } + } + + $keywords = array_merge($typed_keywords, $taxo_terms); + + foreach ($keywords as $keyword) { + $word = (is_object($keyword)) ? trim($keyword->word) : trim($keyword); + if (!strlen(trim($word))) continue; //skip if we have a blank + $kid = FALSE; + // See if the term exists + if ( ($kw = biblio_get_keyword_by_name($word)) ) { + $kid = $kw->kid; + } + if (!$kid) { + $kw = array('word' => trim($word)); + $status = biblio_save_keyword($kw); + $kid = $kw['kid']; + } + // Defend against duplicate, differently cased tags + if (!isset($inserted[$kid])) { + db_merge('biblio_keyword') + ->key(array('kid' => $kid, 'vid' => $node->vid)) + ->fields( + array( + 'kid' => $kid, + 'nid' => $node->nid, + 'vid' => $node->vid + ))->execute(); + $inserted[$kid] = TRUE; + } + } + + // now if we are saving keywords into a taxonomy freetagging vocabulary, then create the tags string and add it to the node object. + $vocabularies = module_invoke('taxonomy', 'get_vocabularies'); + $vid = variable_get('biblio_keyword_vocabulary', 0); + $freetagging = variable_get('biblio_keyword_freetagging', 0); + + if ($freetagging && $vid) { + $freetag_vocab = $vocabularies[$vid]; + $term_refs = biblio_get_term_ref_fields(); + $ft_field = 'field_' . $freetag_vocab->machine_name . '_ref'; + if (!isset($term_refs[$ft_field])) { + biblio_create_term_ref($freetag_vocab); + } + } + + + if ($freetagging && $freetag_vocab ) { + $tids = array(); + $lang = isset($freetag_vocab->language) ? $freetag_vocab->language : 'und'; + if (isset($node->{$ft_field}[$lang])) { + foreach ($node->{$ft_field}[$lang] as $tag) { + $tids[] = $tag['tid']; + } + } + + if (is_array($typed_keywords) && !empty($typed_keywords)) { + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'taxonomy_term') + ->propertyCondition('vid', $vid) + ->propertyCondition('name', $typed_keywords); + + $result = $query->execute(); + + if (isset($result['taxonomy_term'])) { + $existing_tids = array_keys($result['taxonomy_term']); + $existing_terms = taxonomy_term_load_multiple($existing_tids); + $existing_words = array(); + foreach ($existing_terms as $term) { + $existing_words[$term->name] = $term; + } + } + } + + foreach ($typed_keywords as $kw) { + if (isset($existing_words[$kw])) { + $term = $existing_words[$kw]; + } + else { + $term = new stdClass(); + $term->vid = $freetag_vocab->vid; + $term->name = $kw; + $term->vocabulary_machine_name = $freetag_vocab->machine_name; + taxonomy_term_save($term); + } + if (!in_array($term->tid, $tids)) { + $node->{$ft_field}[$lang][] = (array)$term; + } + } + } + + return array_keys($inserted); +} +/** + * @param $word + * @return + */ +function biblio_save_keyword(&$keyword) { + if (!empty($keyword['kid']) && $keyword['word']) { + drupal_write_record('biblio_keyword_data', $keyword, 'kid'); + $status = SAVED_UPDATED; + } + else { + drupal_write_record('biblio_keyword_data', $keyword); + $status = SAVED_NEW; + } + + return $status; +} + +/** + * @return multitype: + */ +function biblio_get_orphaned_keyword_ids() { + $orphans = $active_kids = $all_kids = array(); + + $query = db_select('biblio_keyword', 'bk'); + $active_kids = $query + ->fields('bk', array('kid')) + ->groupBy('kid') + ->execute() + ->fetchCol(); + + $query = db_select('biblio_keyword_data', 'bkd'); + $all_kids = $query + ->fields('bkd', array('kid')) + ->execute() + ->fetchCol(); + + $orphans = array_diff($all_kids, $active_kids); + + return $orphans; +} + +/** + * Deletes orphaned keywords + * + * Deletion only occurs if the Drupal Variable "biblio_keyword_orphan_autoclean" + * is true OR if the $force argument is TRUE + * + * @param bool $force + * Forces deletion to occur even if autoclean is turned off + * @return int $count + * The number of orphans removed + */ +function biblio_delete_orphan_keywords($force = FALSE) { + $count = 0; + + if (variable_get('biblio_keyword_orphan_autoclean', 0) || $force) { + $orphans = biblio_get_orphaned_keyword_ids(); + if (!empty($orphans)) { + $count = db_delete('biblio_keyword_data') + ->condition('kid', $orphans, 'IN') + ->execute(); + } + } + return $count; +} + +/** + * Delete all keywords references for a given node. + * + * The actual keywords remain in the biblio_keyword_data table + * + * @param $node + * @return + * The number of links removed + */ +function biblio_delete_keywords($node) { + $count = db_delete('biblio_keyword') + ->condition('nid', $node->nid) + ->execute(); + return $count; +} + +/** + * Delete "node revision to keyword" links from the biblio_keyword table + * + * @param $node + * @return + * The number of links removed + */ +function biblio_delete_revision_keywords($node) { + return db_delete('biblio_keyword') + ->condition('vid', $node->vid) + ->execute(); +} + +/** + * Delete multiple keywords. + * + * Removes from both the biblio_keyword and biblio_keyword_data tables + * This will remove the keywords referenced by the supplied ID's from + * ALL nodes which reference them. + * + * @param array $keywords + * An array of keyword id's to delete + * @return + * The number of keywords deleted + */ +function biblio_delete_multiple_keywords($keywords) { + $count = 0; + foreach ($keywords as $kid) { + $count += biblio_delete_keyword($kid); + } + return $count; +} +/** + * Delete a keyword from both the biblio_keyword and biblio_keyword_data tables + * This will remove the keyword referenced by the supplied ID from ALL nodes which reference them. + * + * @param $keyword_id + * The keyword id to delete + * @return + * The number of keywords deleted (should always be one) + */ +function biblio_delete_keyword($keyword_id) { + db_delete('biblio_keyword') + ->condition('kid', $keyword_id) + ->execute(); + + return db_delete('biblio_keyword_data') + ->condition('kid', $keyword_id) + ->execute(); +} + +/** + * @param unknown_type $string + * @return multitype:string + */ +function biblio_explode_keywords($string) { + $sep = variable_get('biblio_keyword_sep', ','); + $regexp = '%(?:^|' . $sep . '\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^"' . $sep . ']*))%x'; + preg_match_all($regexp, $string, $matches); + $keyword_array = array_unique($matches[1]); + + $keywords = array(); + foreach ($keyword_array as $keyword) { + // If a user has escaped a term (to demonstrate that it is a group, + // or includes a comma or quote character), we remove the escape + // formatting so to save the term into the database as the user intends. + $keyword = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $keyword))); + if ($keyword != "") { + $keywords[] = $keyword; + } + } + return $keywords; +} +/** + * @param unknown_type $keywords + * @param unknown_type $sep + * @return Ambigous + */ +function biblio_implode_keywords($keywords, $sep = '') { + + if (empty($sep)) $sep = variable_get('biblio_keyword_sep', ','); + $string = ''; + foreach ($keywords as $kid => $keyword) { + $string .= strlen($string)?"$sep ":''; + if (strpos($keyword, $sep) !== FALSE) { + $string .= '"' . $keyword . '"'; + } + else { + $string .= $keyword; + } + } + return $string; +} + +function biblio_get_term_ref_fields() { + $termfields = array(); + foreach (field_info_instances('node', 'biblio') as $instance) { + $field = field_info_field_by_id($instance['field_id']); + if ($field['type'] == 'taxonomy_term_reference') { + $termfields[$field['field_name']] = $instance['label']; + } + } + return $termfields; +} + +function biblio_create_term_ref($vocabulary) { + $field_name = 'field_' . $vocabulary->machine_name . '_ref'; + + $field = array( + 'field_name' => $field_name, + 'type' => 'taxonomy_term_reference', + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => $vocabulary->machine_name, + 'parent' => 0 + ), + ), + ), + ); + + field_create_field($field); + + + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'node', + 'label' => $vocabulary->name, + 'bundle' => 'biblio', + 'required' => FALSE, + 'widget' => array( + 'type' => 'options_select', + 'cardinality' => -1 + ), + 'display' => array( + 'default' => array('type' => 'hidden'), + 'teaser' => array('type' => 'hidden') + ) + ); + + field_create_instance($instance); +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.pages.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.pages.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1342 @@ +data['biblio_contributor_id']) && $user->data['biblio_contributor_id'] > 0 ) { + $arg_list = array( + 'f' => array( + 'author' => $user->data['biblio_contributor_id'], + ), + ); + } + elseif (isset($user->data['biblio_lastname']) && !empty($user->data['biblio_lastname'])) { + $arg_list = array( + 'f' => array( + 'author' => $user->data['biblio_lastname'], + ), + ); + } + else { + $arg_list = array( + 'f' => array( + 'uid' => $user->uid + ), + ); + } + $arg_list['page_limit'] = variable_get('biblio_rowsperpage', 25); + + list($nids, $extras, $rss_info) = biblio_build_query($arg_list); + + $render['biblio_page']['profile'] = biblio_page_content($nids, $extras); + + return $render; +} + +function biblio_page() { + $nodes = $nids = $extras = $rss_info = $info = $arg_list = $render = array(); + $base = variable_get('biblio_base', 'biblio'); + + $info['func_args'] = func_get_args(); + $info['uri'] = drupal_parse_url(request_uri()); + $arg_list = biblio_arg_handler($info); + $arg_list['page_limit'] = variable_get('biblio_rowsperpage', 25); + + list($nids, $extras, $rss_info) = biblio_build_query($arg_list); + if ($rss_info['feed']) { + biblio_filter_feed($rss_info, $nids); + return; + } + + if (variable_get('biblio_rss', 0)) { + drupal_add_html_head_link(array( + 'rel' => 'alternate', + 'type' => 'application/rss+xml', + 'title' => variable_get('site_name', 'Drupal') . ' RSS', + 'href' => url("$base/rss.xml") + )); + } + + drupal_set_title(t(check_plain(variable_get('biblio_base_title', 'Biblio')))); + $filter = (isset($arg_list['f'])) ? array('f' => $arg_list['f']) : array(); + + $render['biblio_page']['header'] = biblio_page_header($filter); + $render['biblio_page']['content'] = biblio_page_content($nids, $extras); + + // if no content AND we have a filter give a hint on what to do... + if (isset($render['biblio_page']['content']['section_0']) && !empty($render['biblio_page']['header']['filter_status'])) { + $render['biblio_page']['content']['hint']['#markup'] = t('!modify_link or !remove_link your filters and try again.', array('!modify_link' => l(t('Modify'), "$base/filter"), '!remove_link' => l(t('remove'), "$base/filter/clear"))); + } + + return $render; +} +function biblio_page_header($filter = array()) { + $header = array(); + $header = array( + '#prefix' => '
    ', + '#suffix' => '
    ', + '#weight' => -100, + ); + + // Search box. Has same permissions as the filter tab. + if (variable_get('biblio_search', 0) && user_access('show filter tab')) { + $header += array( + 'search_form' => drupal_get_form('biblio_search_form'), + ); + } + $header += array( + 'export_links' => array( + '#prefix' => '
    ', + '#node' => NULL, + '#filter' => $filter, + '#theme' => 'biblio_export_links', + '#suffix' => '
    ', + ), + ); + + if ( !biblio_access('export')) { + global $pager_total_items; + $header['export_links']['#markup'] = t('Found @count results', array('@count' => $pager_total_items[0])); + unset($header['export_links']['#theme']); + } + // Add some links to the top of the page to change the sorting/ordering... + if (user_access('show sort links')) { + $header += array( + 'sort_links' => array( + '#markup' => theme('biblio_sort_tabs') + ), + ); + } + $header += array( + 'filter_status' => array( + '#prefix' => '
    ', + '#suffix' => '
    ', + '#markup' => _biblio_filter_info_line($filter), + ), + ); + if (isset($_GET['s'])) { + if ( $_GET['s'] == 'title' || + $_GET['s'] == 'author' || + $_GET['s'] == 'keyword') { + if (strpos($_GET['q'], 'ag') || + strpos($_GET['q'], 'tg') || + strpos($_GET['q'], 'keyword')) { + $value = substr($_GET['q'], strrpos($_GET['q'], '/') + 1); + } + else { + $value = ''; + } + $header += array( + 'alpha_line' => array( + '#prefix' => '
    ', + '#suffix' => '
    ', + '#markup' => theme('biblio_alpha_line', array( + 'type' => $_GET['s'], + 'current' => $value, + 'path' => variable_get('biblio_base', 'biblio')) + ), + ), + ); + } + } + + return $header; +} + +function biblio_page_content($nids = array(), $extras = array()) { + $base = variable_get('biblio_base', 'biblio'); + $content = $raw_nodes = $nodes = array(); + $count = $section_id = 0; + + if (module_exists('popups')) { + popups_add_popups(); + } + + if (count($nids)) { + // $nids = array_unique($nids); + $raw_nodes = node_load_multiple($nids); + $langcode = $GLOBALS['language_content']->language; + field_attach_prepare_view('node', $raw_nodes, 'biblio_list', $langcode); + entity_prepare_view('node', $raw_nodes, $langcode); + + foreach ($nids as $key => $nid) { + if (!empty($extras)) { + $nodes[] = (object)array_merge((array)$raw_nodes[$nid], (array)$extras[$key]); + } + else { + $nodes[] = $raw_nodes[$nid]; + } + } + + } + + foreach ($nodes as $node) { + $count++; + if (is_array($node)) $node = (object)$node; + if (variable_get('biblio_hide_bibtex_braces', 0)) $node->title = biblio_remove_brace($node->title); + + // output new section if needed + if ($section = biblio_category_section($node)) { + $section_id++; + $content['section_'.$section_id] = $section; + } + + $content['section_'.$section_id][] = biblio_entry($node); + + } + + $content['pager']['#markup'] = theme('pager'); + if ($count == 0) { + $content['section_0']['#markup'] = "

    " . t("No items found") . "

    "; + } + return $content; +} + +function biblio_entry($node) { + $entry = array(); + $style = biblio_get_style(); + + /* + $select_box = array( + '#type' => 'checkbox', + '#return_value' => $node->nid, + '#default_value' => 0, + '#attributes' => array('class' => array('biblio-export-selector'),) + + ); +*/ + $prefix = '
    '; + $suffix = '
    '; + if (!$node->status) { + $prefix .= '
    '; + $suffix .= '
    '; + } +// $prefix .= theme('checkbox', array('element' => $select_box)); + + $entry = array( + '#prefix' => $prefix, + '#suffix' => $suffix, + ); + + $entry['entry']['#markup'] = theme('biblio_style', array('node' => $node, 'style_name' => $style)); + + $annotation_field = variable_get('biblio_annotations', 'none'); + if ($annotation_field != 'none' && $node-> $annotation_field) { + $entry['annotation'] = array( + '#prefix' => '
    ', + '#markup' => filter_xss($node->$annotation_field, biblio_get_allowed_tags()), + '#suffix' => '
    ', + ); + } + + $openurl_base = variable_get('biblio_baseopenurl', ''); + + if ($openurl_base) { + $entry['openurl'] = array( + '#markup' => theme('biblio_openurl', array('openURL' => biblio_openurl($node))), + ); + } + + if (biblio_access('export')) { + $base = variable_get('biblio_base', 'biblio'); + $entry['export_links'] = array( + '#markup' => theme('biblio_export_links', array('node' => $node)), + ); + } + + if (biblio_access('download', $node)) { + // add links to attached files (if any) + $entry['download_links'] = array( + '#markup' => theme('biblio_download_links', array('node' => $node)), + ); + } + + return $entry; +} + +/* + * biblio_db_search builds the SQL query which will be used to + * select and order "biblio" type nodes. The query results are + * then passed to biblio_show_results for output + * + * + */ +function biblio_build_query($arg_list) { + global $user; + static $bcc = 0; //biblio_contributor (bc) count , increase for every invocation + static $bkd = 0; + static $tcc = 0; //term counter, increase for every invocation + $rss_info['feed'] = FALSE; + $rss_info['title'] = variable_get('biblio_base_title', 'Biblio'); + $rss_info['link'] = ''; + $rss_info['description'] = ''; + + if ($arg_list['page_limit'] > 0) { + $query = db_select('node', 'n')->extend('PagerDefault'); + $query->limit($arg_list['page_limit']); + } + else { + $query = db_select('node', 'n'); + } + + //add a tag of "node_access" to ensure that only nodes to which the user has access are retrieved + $query->addTag('node_access'); + + $query->addField('n', 'nid'); + $type_name = $query->addField('bt', 'name', 'biblio_type_name'); + $query->leftJoin('biblio', 'b', 'n.vid=b.vid'); + $query->innerJoin('biblio_types', 'bt', 'b.biblio_type=bt.tid'); +// $query->distinct(); + + // POSIX regular expression matching, case insensitive + $match_op = (db_driver() == 'pgsql') ? '~*' : 'RLIKE'; + + $limit = ''; + if (variable_get('biblio_view_only_own', 0) ) { + $limit .= " AND n.uid = $user->uid "; + } + + if (isset($arg_list['s']) || isset($arg_list['sort'])) { + $sort = isset($arg_list['s']) ? $arg_list['s'] : $arg_list['sort']; + unset($arg_list['s']); + unset($arg_list['sort']); + } + else { + $sort = variable_get('biblio_sort', 'year'); + } + + if (isset($arg_list['o']) || isset($arg_list['order'])) { + if (isset($arg_list['o'])) { + $order = (strcasecmp($arg_list['o'], 'ASC')) ? 'DESC' : 'ASC'; + unset($arg_list['o']); + } + if (isset($arg_list['order'])) { + $order = (strcasecmp($arg_list['order'], 'ASC')) ? 'DESC' : 'ASC'; + unset($arg_list['order']); + } + } + else { + $order = strtolower(variable_get('biblio_order', 'DESC')); + } + +/* if (!isset($_SESSION['biblio_filter']) || !is_array($_SESSION['biblio_filter'])) { + $_SESSION['biblio_filter'] = array(); + } + + $session = &$_SESSION['biblio_filter']; + + if (!in_array('no_filters', $arg_list)) { + foreach ($session as $filter) { + $arg_list = array_merge($arg_list, $filter); + } + } +*/ + + + switch ($sort) { + case 'type': + //$sortby = "ORDER BY bt.name %s, b.biblio_year DESC "; + $query->addField('n', 'title'); + $query->orderBy($type_name, $order); + $query->orderBy('biblio_sort_title', $order); + break; + case 'title': + $query->addField('n', 'title'); + $query->orderBy('biblio_sort_title', $order); + break; + case 'author': + //$last_name = $query->addField('bcd', 'lastname'); + $query->innerJoin('biblio_contributor', 'bc', 'b.vid = bc.vid'); + $query->join('biblio_contributor_data', 'bcd', 'bc.cid = bcd.cid'); + $query->condition('bc.rank', 0); + $query->addField('bcd', 'lastname'); + $query->orderBy('bcd.lastname', $order); + // $query->condition('bc.auth_category', 1); + break; + case 'keyword': // added msh 070808 + $word = $query->addField('bkd', 'word', 'biblio_keyword'); + $query->orderBy($word, $order); + $query->innerJoin('biblio_keyword', 'bk', 'b.vid = bk.vid'); + $query->innerJoin('biblio_keyword_data', 'bkd', 'bk.kid = bkd.kid'); + break; + case 'year': + default: + $query->addField('b', 'biblio_year'); + $query->addField('b', 'biblio_date'); + $query->orderBy('biblio_year', $order); + $query->orderBy('biblio_sort_title'); + } //end switch + + + if (isset($arg_list['f']) && count($arg_list['f']) ) { + $fields = biblio_get_db_fields(); + foreach ($arg_list['f'] as $type => $value) { + $tables = array_keys($query->getTables()); + switch ($type) { + case 'no_filters': + break; + case 'rss.xml': + $rss_info['feed'] = TRUE; + $query->limit(variable_get('biblio_rss_number_of_entries', 10)); + break; + case 'term': + case 'term_id': + $query->innerJoin('taxonomy_index', "ti$tcc", "n.nid = ti$tcc.nid"); + if ($type == 'term') { + $query->innerJoin('taxonomy_term_data', 'td', "ti$tcc.tid = td.tid"); + $query->condition('td.name', $value); + } + elseif ($type == 'term_id') { + $query->condition("ti$tcc.tid", $value); + } + $tcc++; + break; + case 'tg': + $query->where("UPPER(substring(biblio_sort_title,1 ,1)) = :letter", array(':letter' => $value)); + break; + case 'ag': //selects entries whoose authors firstname starts with the letter provided + $query->where(" UPPER(substring(bcd.lastname,1,1)) = :letter ", array(':letter' => $value)); + //$where['bc-rank'] = "bc.rank=0"; + if ($sort != 'author') { + $query->innerJoin('biblio_contributor', 'bc', 'b.vid = bc.vid'); + $query->innerJoin('biblio_contributor_data', 'bcd', 'bc.cid = bcd.cid'); + } + break; + case 'cid': + case 'aid': + $bcc++; + $query->innerJoin('biblio_contributor', "bc$bcc", "n.vid = bc$bcc.vid"); + $query->condition("bc$bcc.cid", $value); + break; + case 'author': + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + $bcc++; + if (array_search('bc', $tables) === FALSE) { + $query->innerJoin('biblio_contributor', 'bc', 'n.vid = bc.vid'); + } + if (is_numeric($value)) { + $cids = biblio_get_linked_contributors($value); + $cids[] = $value; + $cid_count = 0; + $or = db_or(); + + foreach ($cids as $cid) { + $or->condition("bc.cid", $cid); + $cid_count++; + } + + if ($cid_count == 0) { + $query->condition("bc.cid", -1); + } + else { + $query->condition($or); + } + + } + else { + if (array_search('bcd', $tables) === FALSE) { + $query->innerJoin('biblio_contributor_data', 'bcd', 'bcd.cid = bc.cid'); + } + $query->condition('bcd.name', "[[:<:]]" . $value . "[[:>:]]", $match_op); + $rss_info['title'] = t("Publications by @value", array('@value' => $value)); + $rss_info['description'] = t("These publications by %author are part of the works listed at %sitename", array('%author' => $value, '%sitename' => variable_get('site_name', 'Drupal'))); + $rss_info['link'] = '/author/' . $value; + } + break; + case 'publisher': + $query->condition('b.biblio_publisher', "[[:<:]]" . $value . "[[:>:]]", $match_op); + break; + case 'year': + $query->condition('b.biblio_year', $value); + break; + case 'uid': + $query->addField('n', 'uid'); + $query->condition('n.uid', $value); + break; + case 'keyword': + $bkd++; + if (array_search('bk', $tables) === FALSE) { + $query->innerJoin('biblio_keyword', 'bk', 'n.vid = bk.vid'); + } + if (is_numeric($value)) { + $query->condition('bk.kid', $value); + } + else{ + if (array_search('bkd', $tables) === FALSE) { + $query->innerJoin('biblio_keyword_data', 'bkd', 'bkd.kid = bk.kid'); + } + if (strlen($value) == 1) { + // $query->condition('', $value, 'SUBSTR(bkd.word, 1, 1) ='); + $query->where(" UPPER(substring(bkd.word,1,1)) = :letter ", array(':letter' => $value)); + } + else { + $query->condition('bkd.word', "[[:<:]]" . $value . "[:>:]]", 'LIKE'); + } + $rss_info['title'] = t("Keyword @value", array('@value' => $value)); + $rss_info['description'] = t("These publications, containing the keyword: %keyword, are part of the works listed at %sitename", array('%keyword' => $value, '%sitename' => variable_get('site_name', 'Drupal'))); + $rss_info['link'] = '/keyword/' . $value; + } + break; + case 'citekey': + $query->condition('b.biblio_citekey', $value); + break; + case 'type': + $query->condition('b.biblio_type', $value); + break; + case 'search': + $search_nids = array(); + $search_nids = biblio_search_query($value); + if (empty($search_nids)) { + $search_nids[] = -1; // if we didn't find anything, then add one value of -1 since there will never be a node id == -1 + } + $query->condition('n.nid', $search_nids, 'IN'); + break; + default: + if (in_array("biblio_$type", $fields)) { + $query->condition("b.biblio_$type ", $value, 'LIKE'); + } + break; + } + } + } + + // show unpublished nodes to users with uid = 1 or with 'Administer Biblio' permissions + if ($user->uid != 1 && !biblio_access('admin')) { + $query->condition('n.status', 1); + } + + $result = $query->execute(); + $nids = array(); + $extras = array(); + + foreach ($result as $node) { + $nids[] = $node->nid; + if (isset($node->biblio_year)) unset($node->biblio_year); + $extras[] = $node; + } + + return array($nids, $extras, $rss_info); + +} + +function _biblio_filter_info_line($args = array()) { + $content = ''; + $filtercontent = ''; + $search_content = ''; + $base = variable_get('biblio_base', 'biblio'); + $session = &$_SESSION['biblio_filter']; + // if there are any filters in place, print them at the top of the list + $uri = drupal_parse_url(request_uri()); + $uri['path'] = variable_get('biblio_base', 'biblio'); + $filters = isset($uri['query']['f']) ? $uri['query']['f'] : (isset($args['f'])? $args['f']: array()); + if (count($filters)) { + $i = 0; + foreach ($filters as $type => $value) { + if ($type == 'search') { + $search_content = $value; + continue; + } + if ($type == 'term_id') { + $term = taxonomy_term_load($value); + $value = $term->name; + $type = t("Taxonomy term"); + } + if ($type == 'keyword') { + module_load_include('inc', 'biblio', 'includes/biblio.keywords'); + $type = t("Keyword"); + if (is_numeric($value)) { + $term = biblio_get_keyword_by_id($value); + if (isset($term->word)) { + $value = $term->word; + } + } + elseif (is_string($value) && strlen($value) == 1) { + $type = t("First letter of keyword"); + } + } + if ($type == 'uid' ) { + $user = user_load($value); + $value = $user->name; + $type = t("Drupal user"); + } + if ($type == 'aid' || ($type == 'author' && is_numeric($value))) { + module_load_include('inc', 'biblio', 'includes/biblio.contributors'); + $author = biblio_get_contributor($value); + $value = isset($author->name) ? $author->name : t('Unknown Author'); + $type = t("Author"); + } + if ($type == 'ag' ) { + //return; + $type = t("First letter of last name"); + } + if ($type == 'tg' ) { + //return; + $type = t("First letter of title"); + } + if ($type == 'type' && $value > 0) { + if (($pub_type = db_query('SELECT t.* FROM {biblio_types} as t WHERE t.tid=:tid', array(':tid' => $value))->fetchObject())) { + $value = drupal_ucfirst(_biblio_localize_type($pub_type->tid, $pub_type->name)); + $type = t("Type"); + } + } + $params = array('%a' => check_plain(ucwords($type)) , '%b' => check_plain($value) ); + $filtercontent .= ($i++ ? t(' and %a is %b', $params) : t('%a is %b', $params)) ; + } + if ($search_content) { + $content .= '
    ' . t('Search results for') . ''; + $content .= ' ' . check_plain($search_content) . ''; + if ($filtercontent) { + $content .= '
    ' . t('Filters') . ': '; + } + } + else { + $content .= '
    ' . t('Filters') . ': '; + } + $content .= $filtercontent; + + $link_options = array(); + if (isset($_GET['s'])) { + $link_options['query']['s'] = $_GET['s']; + } + if (isset($_GET['o'])) { + $link_options['query']['o'] = $_GET['o']; + } + unset($uri['query']['f']); + if ($search_content) { + $content .= '  ' . l('[' . t('Reset Search') . ']', "$base/filter/clear", $link_options); + } + else { + $content .= '  ' . l('[' . t('Clear All Filters') . ']', "$base/filter/clear", $uri); + } + $content .= '
    '; + } + + return $content; +} + +function _biblio_category_separator_bar($node, $reset = FALSE) { + $_text = &drupal_static(__FUNCTION__, ''); + if ($reset) { + $_text = &drupal_static(__FUNCTION__, '', TRUE); + return; + } + $content = ''; + + if (isset($_GET['s'])) { + $sort = $_GET['s']; + } + else { + $sort = variable_get('biblio_sort', 'year'); + } + + switch ($sort) { + case 'title': + $title = $node->biblio_sort_title; + $first = drupal_substr(drupal_ucfirst(ltrim($title)), 0, 1); + if ( $first != $_text) { + if ($_text != '' ) { + $content .= theme_biblio_end_category_section(); + } + $_text = $first ; + $content .= theme_biblio_separator_bar($_text); + } + break; + case 'author': + if ( (isset($node->biblio_contributors[0]['lastname'])) && + (drupal_substr(drupal_ucfirst(ltrim($node->biblio_contributors[0]['lastname'])), 0, 1) != $_text)) + { + if ($_text != '' ) { + $content .= theme_biblio_end_category_section(); + } + $_text = drupal_substr(drupal_ucfirst(ltrim($node->biblio_contributors[0]['lastname'])), 0, 1) ; + $content .= theme_biblio_separator_bar($_text); + } + break; + case 'type': + if ($node->biblio_type_name != $_text) { + if ($_text != '' ) { + $content .= theme_biblio_end_category_section(); + } + $_text = $node->biblio_type_name; + // $name = db_result(db_query("SELECT name FROM {biblio_types} as t where t.tid=%d", $node->biblio_type)) ; + $content .= theme_biblio_separator_bar(_biblio_localize_type($node->biblio_type, $_text)); + } + break; + case 'keyword': // added msh 08 aug 07 + // $kw = array_shift($node->biblio_keyword); + $tok = $node->biblio_keyword; + if (empty($tok)) { + $tok = t("No Keywords"); + } + if ($tok != $_text) { + if ($_text != '' ) { + $content .= theme_biblio_end_category_section(); + } + $_text = $tok; + if ($_text != '') { + $content .= theme_biblio_separator_bar($_text); + } + } + break; + case 'year': + default: + if ($node->biblio_year != $_text) { + if ($_text != '' ) { + $content .= theme_biblio_end_category_section(); + } + $_text = $node->biblio_year; + $content .= theme_biblio_separator_bar($_text); + } + } //end switch + return $content; +} +function biblio_category_section($node, $reset = FALSE) { + $_text = &drupal_static(__FUNCTION__, ''); + if ($reset) { + $_text = &drupal_static(__FUNCTION__, '', TRUE); + return; + } + + $section = array(); + + if (isset($_GET['s'])) { + $sort = $_GET['s']; + } + else { + $sort = variable_get('biblio_sort', 'year'); + } + + switch ($sort) { + case 'title': + $title = $node->biblio_sort_title; + $first = drupal_substr(drupal_ucfirst(ltrim($title)), 0, 1); + if ( $first != $_text) { + $_text = $first ; + $section['bar'] = biblio_section_bar($_text); + } + break; + case 'author': + if ( (isset($node->biblio_contributors[0]['lastname'])) && + (drupal_substr(drupal_ucfirst(ltrim($node->biblio_contributors[0]['lastname'])), 0, 1) != $_text)) + { + $_text = drupal_substr(drupal_ucfirst(ltrim($node->biblio_contributors[0]['lastname'])), 0, 1) ; + $section['bar'] = biblio_section_bar($_text); + } + break; + case 'type': + if ($node->biblio_type_name != $_text) { + $_text = $node->biblio_type_name; + // $name = db_result(db_query("SELECT name FROM {biblio_types} as t where t.tid=%d", $node->biblio_type)) ; + $section['bar'] = biblio_section_bar(_biblio_localize_type($node->biblio_type, $_text)); + } + break; + case 'keyword': // added msh 08 aug 07 + // $kw = array_shift($node->biblio_keyword); + $tok = $node->biblio_keyword; + if (empty($tok)) { + $tok = t("No Keywords"); + } + if ($tok != $_text) { + $_text = $tok; + if ($_text != '') { + $section['bar'] = biblio_section_bar($_text); + } + } + break; + case 'year': + default: + if ($node->biblio_year != $_text) { + $_text = $node->biblio_year; + $section['bar'] = biblio_section_bar($_text); + } + } //end switch + if (!empty($section)) { + $section += array( + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + } + return $section; +} + +function biblio_section_bar($text) { + return array( + '#prefix' => '
    ', + '#suffix' => '
    ', + '#markup' => check_plain($text), + ); +} + +/** + * Add a search field on the main biblio page. + */ +/** + * @param $form_state + * @return unknown_type + */ +function biblio_search_form($form, &$form_state) { + $base = variable_get('biblio_base', 'biblio'); + $searchform['biblio_search'] = array( + '#prefix' => '', + ); + $searchform['biblio_search']['keys'] = array( + '#type' => 'textfield', + '#title' => '', + '#default_value' => '', + '#size' => 25, + '#maxlength' => 255, + ); + $button_text = variable_get('biblio_search_button_text', 'Biblio search'); + $searchform['biblio_search']['submit'] = array( + '#type' => 'submit', + '#value' => t($button_text) + ); + + $form['search_form'] = array( + '#type' => 'fieldset', + '#title' => t('Search'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + 'searchform' => $searchform, + 'filterform' => biblio_form_filter(), + ); + + return $form; +} + +function biblio_search_form_submit($form, &$form_state) { + static $keys = ''; + $base = variable_get('biblio_base', 'biblio'); + $keys = trim($form_state['values']['keys']); + if (!empty($keys)) { + $uri = drupal_parse_url(request_uri()); + $uri['path'] = variable_get('biblio_base', 'biblio'); + $uri['query']['f']['search'] = $keys; + $form_state['redirect'] = array($uri['path'], $uri); + } + +} + +function biblio_search_query($keys) { + if (!empty($keys)) { + $query = db_select('search_index', 'i', array('target' => 'slave')) + ->extend('SearchQuery'); + // ->extend('PagerDefault'); + $query->join('node', 'n', 'n.nid = i.sid'); + $query->condition('n.status', 1) + ->addTag('node_access') + ->searchExpression($keys, 'node'); + + // Insert special keywords. + $query->setOption('type', 'n.type'); + $query->setOption('language', 'n.language'); + if ($query->setOption('term', 'ti.tid')) { + $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); + } + // Only continue if the first pass query matches. + if (!$query->executeFirstPass()) { + return array(); + } + + // Add the ranking expressions. + _node_rankings($query); + + // Load results. + $find = $query->execute(); + + $nids = array(); + foreach ($find as $item) { + $nids[] = $item->sid; + } + return $nids; + } +} + + /** + * @param $arg + * @return unknown_type + */ +function _get_biblio_search_filter($arg = 'keys') { + if (variable_get('biblio_search', 0) && + !empty($_SESSION['biblio_filter']) && + is_array($_SESSION['biblio_filter']) && + is_array($_SESSION['biblio_filter'][0]) && + in_array('search', $_SESSION['biblio_filter'][0]) + ) { + switch ($arg) { + case 'keys': return $_SESSION['biblio_filter'][0][2]; break; + case 'nodelist': return $_SESSION['biblio_filter'][0][1]; break; + } + } +} + + +function _get_biblio_filters() { + + $fields = " b.biblio_year, t.name , t.tid "; + $order = " b.biblio_year DESC"; + $taxo_fields = "td.name as termname, td.tid as taxid, v.name as vocab_name"; + $taxo_order = "vocab_name ASC, termname ASC"; + $table = "{node} as n inner join {biblio} as b on n.vid=b.vid "; + $join = "left join {biblio_types} as t on b.biblio_type = t.tid"; + $taxo_join = array("inner join {taxonomy_index} as ti on n.nid = ti.nid", + "left join {taxonomy_term_data} as td on ti.tid = td.tid", + "left join {taxonomy_vocabulary} as v on v.vid = td.vid"); + + $taxo_joins = implode(' ', $taxo_join); + + $result = db_query("SELECT $fields FROM $table $join ORDER BY $order"); + $authors = db_query("SELECT DISTINCT firstname, initials, lastname, bcd.cid + FROM {biblio_contributor_data} as bcd + INNER JOIN {biblio_contributor} as bc on bc.cid = bcd.cid + ORDER BY lastname ASC"); + $keywords = db_query("SELECT word, kid FROM {biblio_keyword_data} ORDER BY word ASC"); + $taxoresult = db_query("SELECT $taxo_fields FROM $table $taxo_joins ORDER BY $taxo_order"); + $pub_years['any'] = t('any'); + $pub_type['any'] = t('any'); + $pub_authors['any'] = t('any'); + $pub_keywords['any'] = t('any'); + $pub_taxo['any'] = t('any'); + foreach ($result as $option) { + if (isset ($option->biblio_year)) { + $option->biblio_year = _biblio_text_year($option->biblio_year); + } + $pub_years[$option->biblio_year] = $option->biblio_year; + $pub_type[$option->tid] = _biblio_localize_type($option->tid, $option->name); + } + + foreach ($authors as $auth) { + $pub_authors[$auth->cid] = $auth->lastname . ((!empty($auth->firstname) || !empty($auth->initials))?', ' . $auth->firstname . ' ' . $auth->initials :''); + } + foreach ($keywords as $keyword) { + $pub_keywords[$keyword->kid] = $keyword->word; + } + foreach ($taxoresult as $tax) { + $pub_taxo["$tax->taxid"] = "$tax->vocab_name - $tax->termname"; + } + $author_select = isset($pub_authors) ? array('title' => t('Author'), 'options' => $pub_authors) : NULL; + $years_select = isset($pub_years) ? array('title' => t('Year'), 'options' => array_unique($pub_years)) : NULL; + $type_select = isset($pub_type) ? array('title' => t('Type'), 'options' => array_unique($pub_type)) : NULL; + $tax_select = isset($pub_taxo) ? array('title' => t('Term'), 'options' => array_unique($pub_taxo)) : NULL; + $keyword_select = isset($pub_keywords) ? array('title' => t('Keyword'), 'options' => $pub_keywords) : NULL; + + $filters = array( + 'author' => $author_select, + 'type' => $type_select, + 'term_id' => $tax_select, + 'year' => $years_select, + 'keyword' => $keyword_select, + ); + + return $filters; +} + +/** + * @return unknown_type + */ +function biblio_form_filter() { + $session = isset($_SESSION['biblio_filter']) ? $_SESSION['biblio_filter'] : array(); + $filters = _get_biblio_filters(); + + $i = 0; + $form['filters'] = array( + '#type' => 'fieldset', + '#title' => t('Show only items where'), + '#theme' => 'exposed_filters__node', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + foreach ($session as $filter) { + $type = key($filter); + $value = $filter[$type]; + // list($type, $value) = $filter; + if ($type == 'search') { + $session = array(); + break; + } + if ($type == 'category') { + // Load term name from DB rather than search and parse options array. + $value = module_invoke('taxonomy', 'get_term', $value); + $value = $value->name; + } + else { + $value = $filters[$type]['options'][$value]; + } + $t_args = array('%property' => $filters[$type]['title'], '%value' => $value); + if ($i++) { + $form['filters']['current'][] = array('#markup' => t('and where %property is %value', $t_args)); + } + else { + $form['filters']['current'][] = array('#markup' => t('where %property is %value', $t_args)); + } + if (in_array($type, array('type', 'language'))) { + // Remove the option if it is already being filtered on. + unset($filters[$type]); + } + } + + $form['filters']['status'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('clearfix')), + '#prefix' => ($i ? '
    ' . t('and where') . '
    ' : ''), + ); + $form['filters']['status']['filters'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('filters')), + ); + foreach ($filters as $key => $filter) { + $form['filters']['status']['filters'][$key] = array( + '#type' => 'select', + '#options' => $filter['options'], + '#title' => $filter['title'], + '#default_value' => 'any', + ); + } + + $form['filters']['status']['actions'] = array( + '#type' => 'actions', + '#attributes' => array('class' => array('container-inline')), + ); + $form['filters']['status']['actions']['submit'] = array( + '#type' => 'submit', + '#value' => count($session) ? t('Refine') : t('Filter'), + '#submit' => array('biblio_form_filter_submit'), + ); + if (count($session)) { + $form['filters']['status']['actions']['undo'] = array( + '#type' => 'submit', + '#value' => t('Undo'), + '#submit' => array('biblio_form_filter_submit') + ); + $form['filters']['status']['actions']['reset'] = array( + '#type' => 'submit', + '#value' => t('Reset'), + '#submit' => array('biblio_form_filter_submit') + ); + } + + return $form; +} + + +/** + * @param $form + * @param $form_state + * @return unknown_type + */ +function biblio_form_filter_submit($form, &$form_state) { + // If the search filter was set, remove it now. + if (_get_biblio_search_filter()) { + $_SESSION['biblio_filter'] = array(); + } + $op = $form_state['values']['op']; + $filters = _get_biblio_filters(); + switch ($op) { + case t('Filter'): + case t('Refine'): + $uri = drupal_parse_url(request_uri()); + $uri['path'] = variable_get('biblio_base', 'biblio'); + foreach ($filters as $filter => $options) { + if (isset($form_state['values'][$filter]) && $form_state['values'][$filter] != 'any') { + // Flatten the options array to accommodate hierarchical/nested options. + $flat_options = form_options_flatten($filters[$filter]['options']); + // Only accept valid selections offered on the dropdown, block bad input. + if (isset($flat_options[$form_state['values'][$filter]])) { + $_SESSION['biblio_filter'][] = array($filter => $form_state['values'][$filter]); + $uri['query']['f'][$filter] = $form_state['values'][$filter]; + } + } + } + $form_state['redirect'] = array($uri['path'], $uri); + break; + case t('Undo'): + array_pop($_SESSION['biblio_filter']); + break; + case t('Reset'): + $_SESSION['biblio_filter'] = array(); + break; + } +} + + +/** + * @return unknown_type + */ +function biblio_citekey_view() { + $citekey = arg(2); + $nid = db_query("SELECT nid FROM {biblio} WHERE biblio_citekey = :citekey ORDER BY vid DESC", array(':citekey' => $citekey))->fetchObject(); + if ($nid->nid > 0) { + $node = node_load($nid->nid); + return node_page_view($node); + } + else { + return t("Sorry, citekey @cite not found", array('@cite' => $citekey)); + } + +} + + +function biblio_author_page() { + $path = drupal_get_path('module', 'biblio'); + drupal_add_js($path . '/misc/biblio.highlight.js', 'file'); + + $uri = drupal_parse_url(request_uri()); + $filter = isset($uri['query']['f']['author']) ? $uri['query']['f']['author'] : ''; + $authors = _biblio_get_authors($filter); + + return _biblio_format_author_page($uri['path'], $filter, $authors); +} + +function _biblio_get_authors($filter = NULL) { + global $user; + $where = array(); + $authors = array(); + $where_clause = ''; + $output = ''; + + if ($filter) { + $filter = strtoupper($filter); + $where['filter'] = "UPPER(SUBSTRING(lastname,1,1)) = :filter "; + $header_ext = t(' (whose last name starts with the letter "@letter") ', array('@letter' => $filter )); + } + else { + $query_ext = NULL; + $header_ext = NULL; + } + + if (!biblio_access('edit_author')) { + $where['access'] = 'n.status = 1 '; + }//show only published entries to everyone except admin + + if (count($where)) { + $where_clause = 'WHERE (' . implode(') AND (', $where) . ')'; + } + + $suspects = array(); + $query = db_select('biblio_contributor_data', 'bcd') + ->fields('bcd', array('lastname', 'firstname', 'initials', 'alt_form')) + ->groupBy('lastname') + ->groupBy('firstname') + ->groupBy('initials') + ->groupBy('alt_form') + ->having('COUNT(*) > 1'); + + if ($filter) { + $filter = strtoupper($filter); + $query->where("UPPER(SUBSTRING(lastname,1,1)) = :filter ", array(':filter' => $filter)); + } + $result = $query->execute(); + + foreach ($result as $author) { + $suspects[] = $author->lastname; + } + + $db_result = db_query('SELECT bd.cid, bd.drupal_uid, bd.name, bd.lastname, + bd.firstname, bd.prefix, bd.suffix, + bd.initials, bd.affiliation, bd.md5, bd.literal, + COUNT(*) AS cnt + FROM {biblio_contributor} b + LEFT JOIN {biblio_contributor_data} bd ON b.cid = bd.cid + INNER JOIN {node} n on n.vid = b.vid + ' . $where_clause . ' + GROUP BY bd.cid, bd.drupal_uid, bd.name, bd.lastname, + bd.firstname, bd.prefix, bd.suffix, + bd.initials, bd.affiliation, bd.md5, bd.literal + ORDER BY lastname ASC, SUBSTRING(firstname,1,1) ASC, + initials ASC', array(':filter' => $filter)); + + foreach ($db_result as $author) { + if (array_search($author->lastname, $suspects) !== FALSE) { + $author->suspect = TRUE; + } + $authors[] = $author; + } + + return $authors; +} + +function _biblio_format_author_page($path, $filter, $authors) { + $header_ext = $checkbox = ''; + $header = array(); + + if (biblio_access('edit_author')) { + if (!empty($filter)) { + $header_ext = ' ' . t('whose last name begins with the letter') . ': ' . $filter; + } + $checkbox = array( + '#title' => t('Highlight possible duplicates'), + '#type' => 'checkbox', + '#id' => 'biblio-highlight', + ); + $checkbox = '
    ' . drupal_render($checkbox) . '
    '; + $header = array(array('data' => t('There are a total of @count authors in the database!header_ext.', array('@count' => count($authors), '!header_ext' => $header_ext)), 'align' =>'center', 'colspan' => 3)); + } + + $rows[] = array( + array('data' => + theme('biblio_alpha_line', array('type' => 'authors', 'current' => $filter, 'path' => $path)) . $checkbox, 'colspan' => 3)); + if (count($authors)) { + for ($i=0; $i < count($authors); $i+=3) { + $rows[] = array( array('data' => _biblio_format_author($authors[$i]) ), + array('data' => isset($authors[$i+1])?_biblio_format_author($authors[$i+1]):'' ), + array('data' => isset($authors[$i+2])?_biblio_format_author($authors[$i+2]):'' )); + } + } + + + return array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + ); +} +/* + * Helper function to format the authors and add edit links if required + */ +function _biblio_format_author($author) { + if ($author->literal) { + $name = $author->name; + } + else { + $name = $author->lastname; + $name .= (!empty($author->firstname)) ? ', ' . drupal_substr($author->firstname, 0, 1) . '.' : + (!empty($author->initials) ? ', ' . $author->initials : ''); + } + + $uri['path'] = variable_get('biblio_base', 'biblio'); + $uri['query']['f']['author'] = $author->cid; + $uri['attributes'] = array(); + + if (isset($author->drupal_uid) && $author->drupal_uid > 0) { + $uri['attributes'] += array('class' => array('biblio-local-author')); + } + if (variable_get('biblio_links_target_new_window', null)){ + $uri['attributes'] += array('target'=>'_blank'); + $uri['html'] = TRUE; + } + + $name = l(trim($name), $uri['path'], $uri ); + + // $format = biblio_format_authors(array($author)); + $name .= ' (' . $author->cnt . ') ' . ((biblio_access('edit_author'))?_biblio_author_edit_links($author):''); + if (biblio_access('edit_author') && isset($author->suspect)) { + $name = '
    ' . $name . '
    '; + } + + return $name; +} + +function _biblio_author_edit_links($author) { + static $path = ''; + $destination = drupal_get_destination(); + if (empty($path)) { + $path = (ord(substr($_GET['q'], -1)) > 97) ? $_GET['q'] . "/" : substr($_GET['q'], 0, -1); + $path = (strpos($path, 'list/')) ? str_replace('list/', '', $path) : $path; + } + return l(' [' . t('edit') . ']', $path . $author->cid . "/edit" ); +} + +function biblio_keyword_page() { + $uri = drupal_parse_url(request_uri()); +// $uri['path'] = variable_get('biblio_base', 'biblio'); + $filter = isset($uri['query']['f']['keyword']) ? $uri['query']['f']['keyword'] : ''; + $keywords = _biblio_get_keywords($filter); + return _biblio_format_keyword_page($uri, $filter, $keywords); +} + +function _biblio_get_keywords($filter = NULL) { + global $user; + $keywords = array(); + $where = array(); + $where_clause = ''; + if ($filter) { + $filter = strtoupper($filter); + $where[] = "UPPER(SUBSTRING(word,1,1)) = :filter "; + $header_ext = t(' (which start with the letter "@letter") ', array('@letter' => $filter )); + } + else { + $query_ext = NULL; + $header_ext = NULL; + } + + if ($user->uid != 1 ) { + $where[] = 'n.status = 1 '; + }//show only published entries to everyone except admin + + if (count($where)) { + $where_clause = count($where) > 1 ? 'WHERE (' . implode(') AND (', $where) . ')': 'WHERE ' . $where[0]; + } + + $db_result = db_query('SELECT bkd.kid, bkd.word, COUNT(*) AS cnt + FROM {biblio_keyword} bk + LEFT JOIN {biblio_keyword_data} bkd ON bkd.kid = bk.kid + INNER JOIN {node} n ON n.vid = bk.vid + '. $where_clause . ' + GROUP BY bkd.kid, bkd.word + ORDER BY word ASC', array(':filter' => $filter)); + + foreach ($db_result as $keyword) { + $keywords[] = $keyword; + } + return $keywords; +} + +function _biblio_format_keyword_page($uri, $filter, $keywords) { + $rows[] = array(array('data' => theme('biblio_alpha_line', array('type' => 'keywords', 'current' => $filter, 'path' => $uri['path'])), 'colspan' => 3)); + for ($i=0; $i < count($keywords); $i+=3) { + $rows[] = array( array('data' => _biblio_format_keyword($uri, $keywords[$i]) ), + array('data' => isset($keywords[$i+1]) ? _biblio_format_keyword($uri, $keywords[$i+1]) : '' ), + array('data' => isset($keywords[$i+2]) ? _biblio_format_keyword($uri, $keywords[$i+2]) : '' )); + } + //$header = array(array('data' => t('There are a total of @count keywords !header_ext in the database',array('@count' => count($keywords), '!header_ext' => $header_ext)), 'align' =>'center', 'colspan' => 3)); + $output = theme('table', array('rows' => $rows)); + return $output; +} +function _biblio_format_keyword($uri, $keyword) { + $base = variable_get('biblio_base', 'biblio'); + $uri['path'] = $base; + $uri['query']['f']['keyword'] = $keyword->kid; + $format = l(trim($keyword->word), $base, $uri); + $format .= ' (' . $keyword->cnt . ') ' ; + $destination = drupal_get_destination(); + $path = (ord(substr($_GET['q'], -1)) > 97) ? $_GET['q'] . "/" : substr($_GET['q'], 0, -1); + $edit_link = ' [' . l(t('edit'), $path . $keyword->kid . "/edit", array('query' => $destination)) . '] '; + $format .= (user_access('administer biblio')) ? $edit_link : ''; + + return $format; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.search.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.search.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,303 @@ + 'Content', + 'path' => 'node', + 'conditions_callback' => 'sample_search_conditions_callback', + ); +} + +function sample_search_conditions_callback($keys) { + $conditions = array(); + + if (!empty($_REQUEST['keys'])) { + $conditions['keys'] = $_REQUEST['keys']; + } + if (!empty($_REQUEST['sample_search_keys'])) { + $conditions['sample_search_keys'] = $_REQUEST['sample_search_keys']; + } + if ($force_keys = variable_get('sample_search_force_keywords', '')) { + $conditions['sample_search_force_keywords'] = $force_keys; + } + return $conditions; +} + +function _biblio_search_access() { + return user_access('access content'); +} + +/** + * Take action when the search index is going to be rebuilt. + * + * Modules that use hook_update_index() should update their indexing + * bookkeeping so that it starts from scratch the next time + * hook_update_index() is called. + * + * @ingroup search + */ +function _biblio_search_reset() { + db_update('search_dataset') + ->fields(array('reindex' => REQUEST_TIME)) + ->condition('type', 'node') + ->execute(); +} + +/** + * Report the status of indexing. + * + * @return + * An associative array with the key-value pairs: + * - 'remaining': The number of items left to index. + * - 'total': The total number of items to index. + * + * @ingroup search + */ +function _biblio_search_status() { + $total = db_query('SELECT COUNT(*) FROM {node} WHERE status = 1')->fetchField(); + $remaining = db_query("SELECT COUNT(*) FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE n.status = 1 AND d.sid IS NULL OR d.reindex <> 0")->fetchField(); + return array('remaining' => $remaining, 'total' => $total); +} + +/** + * Add elements to the search settings form. + * + * @return + * Form array for the Search settings page at admin/config/search/settings. + * + * @ingroup search + */ +function _biblio_search_admin() { + // Output form for defining rank factor weights. + $form['content_ranking'] = array( + '#type' => 'fieldset', + '#title' => t('Content ranking'), + ); + $form['content_ranking']['#theme'] = 'node_search_admin'; + $form['content_ranking']['info'] = array( + '#value' => '' . t('The following numbers control which properties the content search should favor when ordering the results. Higher numbers mean more influence, zero means the property is ignored. Changing these numbers does not require the search index to be rebuilt. Changes take effect immediately.') . '' + ); + + // Note: reversed to reflect that higher number = higher ranking. + $options = drupal_map_assoc(range(0, 10)); + foreach (module_invoke_all('ranking') as $var => $values) { + $form['content_ranking']['factors']['node_rank_' . $var] = array( + '#title' => $values['title'], + '#type' => 'select', + '#options' => $options, + '#default_value' => variable_get('node_rank_' . $var, 0), + ); + } + return $form; +} + +/** + * Execute a search for a set of key words. + * + * Use database API with the 'PagerDefault' query extension to perform your + * search. + * + * If your module uses hook_update_index() and search_index() to index its + * items, use table 'search_index' aliased to 'i' as the main table in your + * query, with the 'SearchQuery' extension. You can join to your module's table + * using the 'i.sid' field, which will contain the $sid values you provided to + * search_index(). Add the main keywords to the query by using method + * searchExpression(). The functions search_expression_extract() and + * search_expression_insert() may also be helpful for adding custom search + * parameters to the search expression. + * + * See node_search_execute() for an example of a module that uses the search + * index, and user_search_execute() for an example that doesn't ues the search + * index. + * + * @param $keys + * The search keywords as entered by the user. + * @param $conditions + * An optional array of additional conditions, such as filters. + * + * @return + * An array of search results. To use the default search result + * display, each item should have the following keys': + * - 'link': Required. The URL of the found item. + * - 'type': The type of item (such as the content type). + * - 'title': Required. The name of the item. + * - 'user': The author of the item. + * - 'date': A timestamp when the item was last modified. + * - 'extra': An array of optional extra information items. + * - 'snippet': An excerpt or preview to show with the result (can be + * generated with search_excerpt()). + * - 'language': Language code for the item (usually two characters). + * + * @ingroup search + */ +function _biblio_search_execute($keys = NULL, $conditions = NULL) { + // Build matching conditions + $query = db_select('search_index', 'i', array('target' => 'slave'))->extend('SearchQuery')->extend('PagerDefault'); + $query->join('node', 'n', 'n.nid = i.sid'); + $query + ->condition('n.status', 1) + ->addTag('node_access') + ->searchExpression($keys, 'node'); + + // Insert special keywords. + $query->setOption('type', 'n.type'); + $query->setOption('language', 'n.language'); + if ($query->setOption('term', 'ti.tid')) { + $query->join('taxonomy_index', 'ti', 'n.nid = ti.nid'); + } + // Only continue if the first pass query matches. + if (!$query->executeFirstPass()) { + return array(); + } + + // Add the ranking expressions. + _node_rankings($query); + + // Load results. + $find = $query + ->limit(10) + ->execute(); + $results = array(); + foreach ($find as $item) { + // Build the node body. + $node = node_load($item->sid); + node_build_content($node, 'search_result'); + $node->body = drupal_render($node->content); + + // Fetch comments for snippet. + $node->rendered .= ' ' . module_invoke('comment', 'node_update_index', $node); + // Fetch terms for snippet. + $node->rendered .= ' ' . module_invoke('taxonomy', 'node_update_index', $node); + + $extra = module_invoke_all('node_search_result', $node); + + $results[] = array( + 'link' => url('node/' . $item->sid, array('absolute' => TRUE)), + 'type' => check_plain(node_type_get_name($node)), + 'title' => $node->title, + 'user' => theme('username', array('account' => $node)), + 'date' => $node->changed, + 'node' => $node, + 'extra' => $extra, + 'score' => $item->calculated_score, + 'snippet' => search_excerpt($keys, $node->body), + ); + } + return $results; +} + +/** + * Override the rendering of search results. + * + * A module that implements hook_search_info() to define a type of search + * may implement this hook in order to override the default theming of + * its search results, which is otherwise themed using theme('search_results'). + * + * Note that by default, theme('search_results') and theme('search_result') + * work together to create an ordered list (OL). So your hook_search_page() + * implementation should probably do this as well. + * + * @see search-result.tpl.php, search-results.tpl.php + * + * @param $results + * An array of search results. + * + * @return + * A renderable array, which will render the formatted search results with + * a pager included. + */ +function _biblio_search_page($results) { + $output['prefix']['#markup'] = '
      '; + + foreach ($results as $entry) { + $output[] = array( + '#theme' => 'search_result', + '#result' => $entry, + '#module' => 'my_module_name', + ); + } + $output['suffix']['#markup'] = '
    ' . theme('pager'); + + return $output; +} + +/** + * Preprocess text for search. + * + * This hook is called to preprocess both the text added to the search index and + * the keywords users have submitted for searching. + * + * Possible uses: + * - Adding spaces between words of Chinese or Japanese text. + * - Stemming words down to their root words to allow matches between, for + * instance, walk, walked, walking, and walks in searching. + * - Expanding abbreviations and acronymns that occur in text. + * + * @param $text + * The text to preprocess. This is a single piece of plain text extracted + * from between two HTML tags or from the search query. It will not contain + * any HTML entities or HTML tags. + * + * @return + * The text after preprocessing. Note that if your module decides not to alter + * the text, it should return the original text. Also, after preprocessing, + * words in the text should be separated by a space. + * + * @ingroup search + */ +function _biblio_search_preprocess($text) { + // Do processing on $text + return $text; +} + +/** + * Update the search index for this module. + * + * This hook is called every cron run if search.module is enabled, your + * module has implemented hook_search_info(), and your module has been set as + * an active search module on the Search settings page + * (admin/config/search/settings). It allows your module to add items to the + * built-in search index using search_index(), or to add them to your module's + * own indexing mechanism. + * + * When implementing this hook, your module should index content items that + * were modified or added since the last run. PHP has a time limit + * for cron, though, so it is advisable to limit how many items you index + * per run using variable_get('search_cron_limit') (see example below). Also, + * since the cron run could time out and abort in the middle of your run, you + * should update your module's internal bookkeeping on when items have last + * been indexed as you go rather than waiting to the end of indexing. + * + * @ingroup search + */ +function _biblio_update_index() { + $limit = (int)variable_get('search_cron_limit', 100); + + $result = db_query_range("SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS NULL OR d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, $limit); + + foreach ($result as $node) { + $node = node_load($node->nid); + + // Save the changed time of the most recent indexed node, for the search + // results half-life calculation. + variable_set('node_cron_last', $node->changed); + + // Render the node. + node_build_content($node, 'search_index'); + $node->rendered = drupal_render($node->content); + + $text = '

    ' . check_plain($node->title) . '

    ' . $node->rendered; + + // Fetch extra data normally not visible + $extra = module_invoke_all('node_update_index', $node); + foreach ($extra as $t) { + $text .= $t; + } + + // Update index + search_index($node->nid, 'node', $text); + } +} +/** + * @} End of "addtogroup hooks". + */ diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio.util.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio.util.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,387 @@ + and the file's + // original author. + // Original Author: Richard Karnesky // + // Adapted for biblio: Ron Jerome + // fmt_info (type) + $fmt = "info:ofi/fmt:kev:mtx:"; + // 'dissertation' is compatible with the 1.0 spec, but not the 0.1 spec + if ($node->biblio_type == 108) { + $fmt .= "dissertation"; + } + elseif ($node->biblio_type == 102) { + $fmt .= "journal"; + } + elseif ($node->biblio_type == 100 || $node->biblio_type == 101) { + $fmt .= "book"; + } + // 'dc' (dublin core) is compatible with the 1.0 spec, but not the 0.1 spec. + // We default to this, as it is the most generic type. + else { + $fmt .= "dc"; + } + + $co = biblio_contextObject($node); + $coins = "ctx_ver=Z39.88-2004&rft_val_fmt=" . urlencode($fmt); + foreach ($co as $coKey => $coValue) { + // 'urlencode()' differs from 'rawurlencode() (i.e., RFC1738 encoding) + // in that spaces are encoded as plus (+) signs + $coKey = preg_replace("/au[0-9]*/", "au", $coKey); + $coins .= "&" . $coKey . "=" . urlencode($coValue); + } + $coinsSpan = ""; + return $coinsSpan; +} + +function biblio_contextObject($node) { + // Copyright: Matthias Steffens and the file's + // original author. + // Original Author: Richard Karnesky // + // Adapted for biblio: Ron Jerome + global $base_url; + $i = 0; + // $openurl_base = variable_get('biblio_baseopenurl', ''); + $co = array(); + // rfr_id +// $co["rfr_id"] = "info:sid/". ereg_replace("http://", "", $base_url); +// // genre (type) +// if (isset($node->biblio_type)) { +// if ($node->biblio_type == 102) +// $co["rft.genre"] = "article"; +// elseif ($node->biblio_type == 101) $co["rft.genre"] = "bookitem"; +// elseif ($node->biblio_type == 100) $co["rft.genre"] = "book"; +// elseif ($node->biblio_type == "Journal") $co["rft.genre"] = "journal"; +// } + // atitle, btitle, title (title, publication) + if (($node->biblio_type == 102) || ($node->biblio_type == 101)) { + if (!empty($node->title)) $co["rft.atitle"] = check_plain($node->title); + if (!empty($node->biblio_secondary_title)) { + $co["rft.title"] = check_plain($node->biblio_secondary_title); + if ($node->biblio_type == 101) + $co["rft.btitle"] = check_plain($node->biblio_secondary_title); + } + } + elseif (!empty($node->title)) { + $co["rft.title"] = check_plain($node->title); + } + if (($node->biblio_type == 100) && (!empty($node->biblio_secondary_title))) $co["rft.btitle"] = check_plain($node->biblio_secondary_title); + // stitle (abbrev_journal) + if (!empty($node->biblio_short_title)) $co["rft.stitle"] = check_plain($node->biblio_short_title); + // series (series_title) + if (!empty($node->biblio_tertiary_title)) $co["rft.series"] = check_plain($node->biblio_tertiary_title); + // issn + if (!empty($node->biblio_issn)) $co["rft.issn"] = check_plain($node->biblio_issn); + // isbn + if (!empty($node->biblio_isbn)) $co["rft.isbn"] = check_plain($node->biblio_isbn); + // date (year) + if (!empty($node->biblio_year)) $co["rft.date"] = check_plain($node->biblio_year); + // volume + if (!empty($node->biblio_volume)) $co["rft.volume"] = check_plain($node->biblio_volume); + // issue + if (!empty($node->biblio_issue)) $co["rft.issue"] = check_plain($node->biblio_issue); + // spage, epage, tpages (pages) + // NOTE: lifted from modsxml.inc.php--should throw some into a new include file + if (!empty($node->biblio_pages)) { + if (preg_match("/[0-9] *- *[0-9]/", $node->biblio_pages)) { + list ($pagestart, $pageend) = preg_split('/\s*[-]\s*/', $node->biblio_pages); + if ($pagestart < $pageend) { + $co["rft.spage"] = check_plain($pagestart); + $co["rft.epage"] = check_plain($pageend); + } + } + elseif ($node->biblio_type == 100) { //"Book Whole") { + $pagetotal = preg_replace('/^(\d+)\s*pp?\.?$/', "\\1", $node->biblio_pages); + $co["rft.tpages"] = check_plain($pagetotal); + } + else { + $co["rft.spage"] = check_plain($node->biblio_pages); + } + } + // aulast, aufirst, author (author) + if (!empty($node->biblio_contributors)) { + if (!empty($node->biblio_contributors[0]['lastname'])) { + $co["rft.aulast"] = check_plain($node->biblio_contributors[0]['lastname']); + } + if (!empty($node->biblio_contributors[0]['firstname'])) { + $co["rft.aufirst"] = check_plain($node->biblio_contributors[0]['firstname']); + } + elseif (!empty($node->biblio_contributors[0]['initials'])) { + $co["rft.auinit"] = check_plain($node->biblio_contributors[0]['initials']); + } + for($i = 1; $i < count($node->biblio_contributors); $i++) { + $author = $node->biblio_contributors[$i]; + if ($author['auth_category'] == 1) { + if (!empty($author['lastname'])) { + $au = $author['lastname']; + if (!empty($author['firstname']) || !empty($author['initials'])) $au .= ", "; + } + if (!empty($author['firstname'])) { + $au .= $author['firstname']; + } + elseif (!empty($author['initials'])) { + $au .= $author['initials']; + } + if (!empty($au)) $co["rft.au" . $i] = $au; + } + } + } + // pub (publisher) + if (!empty($node->biblio_publisher)) $co["rft.pub"] = check_plain($node->biblio_publisher); + // place + if (!empty($node->biblio_place_published)) $co["rft.place"] = check_plain($node->biblio_place_published); + // id (doi, url) + if (!empty($node->biblio_doi)) { + $co["rft_id"] = "info:doi/" . check_plain($node->biblio_doi); + } +// elseif (!empty($node->biblio_url)) { +// $co["rft_id"] = $node->biblio_url; +// } + + return $co; +} + +function biblio_coins_generate(& $node) { + if (!isset($node->vid)) { + $node->biblio_coins = biblio_coins($node); + return; + } + if ($node) { + $node->biblio_coins = biblio_coins($node); + db_update('biblio') + ->fields(array('biblio_coins' => $node->biblio_coins)) + ->condition('vid', $node->vid) + ->execute(); + } + else { + $result = db_query("SELECT nr.*, b.* + FROM {node} AS n + LEFT JOIN {node_revision} AS nr ON n.vid = nr.vid LEFT JOIN {biblio} AS b ON n.vid = b.vid + WHERE n.type = 'biblio' "); + + foreach ($result as $node) { + $node->biblio_coins = biblio_coins($node); + db_update('biblio') + ->fields(array('biblio_coins' => $node->biblio_coins)) + ->condition('vid', $node->vid) + ->execute(); + } + drupal_goto(''); + } +} + +function _strip_punctuation($text) { + return preg_replace("/[[:punct:]]/", '', $text); +} +/** + * Copyright (c) 2008, David R. Nadeau, NadeauSoftware.com. + * All rights reserved. + * + * Strip punctuation characters from UTF-8 text. + * + * Characters stripped from the text include characters in the following + * Unicode categories: + * + * Separators + * Control characters + * Formatting characters + * Surrogates + * Open and close quotes + * Open and close brackets + * Dashes + * Connectors + * Numer separators + * Spaces + * Other punctuation + * + * Exceptions are made for punctuation characters that occur withn URLs + * (such as [ ] : ; @ & ? and others), within numbers (such as . , % # '), + * and within words (such as - and '). + * + * Parameters: + * text the UTF-8 text to strip + * + * Return values: + * the stripped UTF-8 text. + * + * See also: + * http://nadeausoftware.com/articles/2007/9/php_tip_how_strip_punctuation_characters_web_page + */ +function _strip_punctuation_utf8( $text ) +{ + $urlbrackets = '\[\]\(\)'; + $urlspacebefore = ':;\'_\*%@&?!' . $urlbrackets; + $urlspaceafter = '\.,:;\'\-_\*@&\/\\\\\?!#' . $urlbrackets; + $urlall = '\.,:;\'\-_\*%@&\/\\\\\?!#' . $urlbrackets; + + $specialquotes = '\'"\*<>'; + + $fullstop = '\x{002E}\x{FE52}\x{FF0E}'; + $comma = '\x{002C}\x{FE50}\x{FF0C}'; + $arabsep = '\x{066B}\x{066C}'; + $numseparators = $fullstop . $comma . $arabsep; + + $numbersign = '\x{0023}\x{FE5F}\x{FF03}'; + $percent = '\x{066A}\x{0025}\x{066A}\x{FE6A}\x{FF05}\x{2030}\x{2031}'; + $prime = '\x{2032}\x{2033}\x{2034}\x{2057}'; + $nummodifiers = $numbersign . $percent . $prime; + + return preg_replace( + array( + // Remove separator, control, formatting, surrogate, + // open/close quotes. + '/[\p{Z}\p{Cc}\p{Cf}\p{Cs}\p{Pi}\p{Pf}]/u', + // Remove other punctuation except special cases + '/\p{Po}(?]*?>.*?@siu', + '@]*?>.*?@siu', + '@]*?.*?@siu', + '@]*?.*?@siu', + '@]*?.*?@siu', + '@]*?.*?@siu', + '@]*?.*?@siu', + '@]*?.*?@siu', + '@]*?.*?@siu', + // Add line breaks before and after blocks + '@
    '; + } + else { + $output .= ''; + } + return $output; +} + +/** + * @param $node + * @return unknown_type + */ +function biblio_openurl($node) { + global $user; + $open_url = ''; + // Copyright: Matthias Steffens and the file's + // original author. + // Original Author: Richard Karnesky // + // Adapted for biblio: Ron Jerome + // global $open_url_resolver; // these variables are defined in 'ini.inc.php' + // global $hostInstitutionAbbrevName; + $open_url_resolver = !empty($user->data['biblio_baseopenurl']) ? $user->data['biblio_baseopenurl'] : ''; + if (empty($open_url_resolver)) $open_url_resolver = variable_get('biblio_baseopenurl', ''); + if (!empty($open_url_resolver)) { + $co = biblio_contextObject($node); + + $sid = "biblio:" . variable_get('site_name', 'Drupal'); + $user_sid = !empty($user->data['biblio_openurl_sid']) ? $user->data['biblio_openurl_sid'] : ''; + $co["sid"] = !empty($user_sid) ? $user_sid : variable_get('biblio_openurl_sid', $sid); + + $open_url = $open_url_resolver; + if (!preg_match("/\?/", $open_url_resolver)) { + $open_url .= "?"; + } + else { + $open_url .= "&"; + } + $open_url .= "ctx_ver=Z39.88-2004"; + foreach ($co as $coKey => $coValue) { + $coKey = preg_replace("/rft./", "", $coKey); + $coKey = preg_replace("/au[0-9]*/", "au", $coKey); + $open_url .= "&" . $coKey . "=" . rawurlencode($coValue); + } + } + return $open_url; +} + + +/** + * DEPRECIATED! this was the original output format which is not to flexable it will be removed + * TODO: remove this function + * @param $node + * @param $base + * @param $style + * @return unknown_type + */ +function theme_biblio_long($variables) { + $output = ''; + $node = $variables['node']; + if (module_exists('popups')) { + popups_add_popups(); + } + + $output .= filter_xss($node->biblio_coins, array('span')); + $layout = variable_get('biblio_node_layout', 'orig'); + + // Check body value. + $has_body = !empty($node->body); + + if (variable_get('biblio_node_layout', 'orig') == 'ft' && $has_body && user_access('view full text')) { + $output .= '
    ' . theme('biblio_style', $variables) . '
    '; + $annotation_field = variable_get('biblio_annotations', 'none'); + if ($annotation_field != 'none' && $node-> $annotation_field) { + $output .= '
    '; + $output .= filter_xss($node->$annotation_field, biblio_get_allowed_tags()); + $output .= '
    '; + } + $output .= drupal_render(field_view_field('node', $node, 'body', array('label' => 'hidden'))); + if (biblio_access('export', $node)) { + $output .= theme('biblio_export_links', array('node' => $node)); + } + return $output; + } + foreach ((array)$node->biblio_contributors as $auth) { + if ($auth['auth_category'] == 1) { + $authors[] = theme('biblio_author_link', array('author' => $auth)); + } + } + $authors = implode('; ', (array)$authors); + $openurl_base = variable_get('biblio_baseopenurl', ''); + if ($openurl_base) + $output .= theme('biblio_openurl', array('openURL' => biblio_openurl($node))); + $output .= '

    ' . t("Publication Type") . ":

    $node->biblio_type_name
    \n"; + $output .= '

    ' . t("Authors") . ':

    ' . $authors . "
    \n"; + $output .= '

    ' . t("Source") . ':

    '; + $source = NULL; + if ($node->biblio_secondary_title) + $source .= check_plain($node->biblio_secondary_title); + if ($node->biblio_publisher) { + $source .= $source ? ", " : ""; + $source .= check_plain($node->biblio_publisher); + } + if ($node->biblio_volume) { + $source .= $source ? ", " : ""; + $source .= t('Volume') . ' ' . check_plain($node->biblio_volume); + } + if ($node->biblio_issue) { + $source .= $source ? ", " : ""; + $source .= t('Issue') . ' ' . check_plain($node->biblio_issue); + } + if ($node->biblio_number) { + $source .= $source ? ", " : ""; + $source .= t('Number') . ' ' . check_plain($node->biblio_number); + } + if ($node->biblio_place_published) { + $source .= $source ? ", " : ""; + $source .= check_plain($node->biblio_place_published); + } + if ($node->biblio_pages) { + $source .= $source ? ", " : ""; + $source .= 'p.' . check_plain($node->biblio_pages); + } + if (isset ($node->biblio_year)) { + $node->biblio_year = _biblio_text_year($node->biblio_year); + $source .= ' (' . check_plain($node->biblio_year) . ')'; + } + $output .= "$source
    \n"; + if ($node->biblio_isbn) + $output .= '

    ' . t("ISBN") . ':

    ' . check_plain($node->biblio_isbn) . "\n"; + if ($node->biblio_call_number) + $output .= '

    ' . t("Call Number") . ':

    ' . check_plain($node->biblio_call_number) . "\n"; + if ($node->biblio_accession_number) + $output .= '

    ' . t("Accession Number") . ':

    ' . check_plain($node->biblio_accession_number) . "\n"; + if ($node->biblio_other_number) + $output .= '

    ' . t("Other Number") . ':

    ' . check_plain($node->biblio_other_number) . "\n"; + if ($node->biblio_url) { + $attrib = (variable_get('biblio_links_target_new_window', FALSE)) ? array('target' => '_blank') : array(); + $output .= '

    ' . t("URL") . ':

    ' . l($node->biblio_url, $node->biblio_url, $attrib) . "\n"; + } + if (!empty($node->biblio_keywords)) { + $output .= '

    ' . t("Keywords") . ':

    ' . _biblio_keyword_links( $node->biblio_keywords) . "\n"; + } + if ($node->biblio_abst_e) + $output .= '

    ' . t("Abstract") . ':

    ' . check_markup($node->biblio_abst_e) . "\n"; + if ($node->biblio_abst_f) + $output .= '

    ' . check_markup($node->biblio_abst_f) . "\n"; + if ($node->biblio_notes) + $output .= '

    ' . t("Notes") . ':

    ' . check_markup($node->biblio_notes) . "\n"; + if (!empty($node->body) && user_access('view full text') ) { + $output .= drupal_render(field_view_field('node', $node, 'body')); + } + + return $output; +} + +function _biblio_get_field_information($biblio_type, $only_visible = FALSE) { + $fields = array(); + $visible = $only_visible ? ' AND (bt.common = 1 OR bt.visible=1) ' : ''; + + $result = db_query("SELECT b.*, btd.*, btt.name AS type_name + FROM {biblio_fields} AS b + INNER JOIN {biblio_field_type} AS bt ON bt.fid = b.fid + INNER JOIN {biblio_field_type_data} AS btd ON btd.ftdid = bt.ftdid + INNER JOIN {biblio_types} as btt ON btt.tid = bt.tid + WHERE bt.tid = :tid $visible + ORDER BY bt.weight ASC", array(':tid' => $biblio_type), array('fetch' => PDO::FETCH_ASSOC)); + + foreach ($result as $row) { + $fields[$row['fid']] = $row; + } + + return $fields; +} + +/** + * @param $node + * @param $base + * @param $teaser + * @return unknown_type + */ +function theme_biblio_tabular($variables) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $node = $variables['node']; + $base = $variables['base']; + static $citeproc; + + if (module_exists('popups')) { + popups_add_popups(); + } + $tid = $node->biblio_type; + $fields = _biblio_get_field_information($node->biblio_type, TRUE); + $rows[] = array( + array('data' => t('Title'), 'class' => array('biblio-row-title')), + array('data' => filter_xss($node->title, biblio_get_allowed_tags())) + ); + + if (!isset($node->biblio_type_name) && isset($node->biblio_type)) { // needed for preview + if (($pub_type = db_query('SELECT t.tid, t.name FROM {biblio_types} as t WHERE t.tid=:tid', array(':tid' => $node->biblio_type))->fetchObject())) { + $node->biblio_type_name = drupal_ucfirst(_biblio_localize_type($pub_type->tid, $pub_type->name)); + } + } + + $rows[] = array( + array('data' => t('Publication Type'), 'class' => array('biblio-row-title')), + array('data' => $node->biblio_type_name) + ); + + $attrib = (variable_get('biblio_links_target_new_window', FALSE)) ? array('target' => '_blank') : array(); + + $doi = ''; + if (!empty($node->biblio_doi)) { + $doi_url = ''; + if ( ($doi_start = strpos($node->biblio_doi, '10.')) !== FALSE) { + $doi = substr($node->biblio_doi, $doi_start); + $doi_url .= 'http://dx.doi.org/' . $doi; + $doi = l($doi, $doi_url, $attrib); + } + + } + + foreach ($fields as $key => $row) { + // handling the contributor categories like any other field orders them correctly by weight + if ($row['type'] == 'contrib_widget' && ($authors = biblio_get_contributor_category($node->biblio_contributors, $row['fid']))) { + $data = biblio_format_authors($authors); + } + elseif (empty ($node->$row['name']) || $row['name'] == 'biblio_coins') { + continue; + } + else { + switch ($row['name']) { + case 'biblio_keywords' : + $data = _biblio_keyword_links($node->$row['name'], $base); + break; + case 'biblio_url' : + $data = l($node->$row['name'], $node->$row['name'], $attrib); + break; + case 'biblio_doi' : + $data = $doi; + break; + default: + if ($row['type'] == 'text_format') { + $format = filter_default_format(); + if (!empty($node->biblio_formats) ) { + $formats = $node->biblio_formats; + $format = isset($formats[$row['name']]) ? $formats[$row['name']] : $format; + } + $data = check_markup($node->$row['name'], $format); + } + else { + $data = check_plain($node->$row['name']); + } + } + } + $rows[] = array( + array( + 'data' => t($row['title']), + 'class' => array('biblio-row-title') + ), + array( + 'data' => $data + ) + ); + } + if (isset($node->body) && !empty($node->body) && user_access('view full text')) { + $rows[] = array( + array('data' => t('Full Text'), 'valign' => 'top'), + array('data' => drupal_render(field_view_field('node', $node, 'body', array('label' => 'hidden')))), + ); + + } + // let other modules add rows if desired... + drupal_alter('biblio_node_table_rows', $rows, $node); + + $output = '
    '; + $output .= filter_xss($node->biblio_coins, array('span')); + $header = array(); + $output .= theme('table', array('header' => $header, 'rows' => $rows)); + $output .= '
    '; + + return $output; +} + +function _biblio_get_latin1_regex() { + $alnum = "[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + + // Matches ISO-8859-1 letters: + $alpha = "[:alpha:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + + // Matches ISO-8859-1 control characters: + $cntrl = "[:cntrl:]"; + + // Matches ISO-8859-1 dashes & hyphens: + $dash = "-–"; + + // Matches ISO-8859-1 digits: + $digit = "[\d]"; + + // Matches ISO-8859-1 printing characters (excluding space): + $graph = "[:graph:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + + // Matches ISO-8859-1 lower case letters: + $lower = "[:lower:]äåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + + // Matches ISO-8859-1 printing characters (including space): + $print = "[:print:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + + // Matches ISO-8859-1 punctuation: + $punct = "[:punct:]"; + + // Matches ISO-8859-1 whitespace (separating characters with no visual representation): + $space = "[\s]"; + + // Matches ISO-8859-1 upper case letters: + $upper = "[:upper:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆ"; + + // Matches ISO-8859-1 "word" characters: + $word = "_[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + + // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables: + // More info: + $patternModifiers = ""; + + return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers); + +} +/* + * Helper function for theme_biblio_format_authors() and theme_biblio_page_number() + */ +function _biblio_get_utf8_regex() { + + // Matches Unicode letters & digits: + $alnum = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:alnum:]" + + // Matches Unicode letters: + $alpha = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}"; // Unicode-aware equivalent of "[:alpha:]" + + // Matches Unicode control codes & characters not in other categories: + $cntrl = "\p{C}"; // Unicode-aware equivalent of "[:cntrl:]" + + // Matches Unicode dashes & hyphens: + $dash = "\p{Pd}"; + + // Matches Unicode digits: + $digit = "\p{Nd}"; // Unicode-aware equivalent of "[:digit:]" + + // Matches Unicode printing characters (excluding space): + $graph = "^\p{C}\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:graph:]" + + // Matches Unicode lower case letters: + $lower = "\p{Ll}\p{M}"; // Unicode-aware equivalent of "[:lower:]" + + // Matches Unicode printing characters (including space): + $print = "\P{C}"; // same as "^\p{C}", Unicode-aware equivalent of "[:print:]" + + // Matches Unicode punctuation (printing characters excluding letters & digits): + $punct = "\p{P}"; // Unicode-aware equivalent of "[:punct:]" + + // Matches Unicode whitespace (separating characters with no visual representation): + $space = "\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:space:]" + + // Matches Unicode upper case letters: + $upper = "\p{Lu}\p{Lt}"; // Unicode-aware equivalent of "[:upper:]" + + // Matches Unicode "word" characters: + $word = "_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:word:]" (or "[:alnum:]" plus "_") + + // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables: + // More info: + $patternModifiers = "u"; // the "u" (PCRE_UTF8) pattern modifier causes PHP/PCRE to treat pattern strings as UTF-8 + + return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers); +} + +function _biblio_get_regex_patterns() { + // Checks if PCRE is compiled with UTF-8 and Unicode support + if (!@preg_match('/\pL/u', 'a')) { + // probably a broken PCRE library + return _biblio_get_latin1_regex(); + } + else { + // Unicode safe filter for the value + return _biblio_get_utf8_regex(); + } +} + +function biblio_format_authors($authors) { + if (module_exists('biblio_citeproc')) { + static $auth_proc; + if (!isset($auth_proc)) { + module_load_include('inc', 'biblio_citeproc', 'CSL'); + $csl = ''; + $csl_doc = new DOMDocument(); + $csl_doc->loadXML($csl); + $auth_proc = new csl_rendering_element($csl_doc); + } + $data = $auth_proc->render($authors); + } + else { + $style_name = biblio_get_style(); + $style_function = "biblio_style_$style_name" . "_author_options"; + if (!function_exists($style_function)) { + module_load_include('inc', 'biblio', "/styles/biblio_style_$style_name"); + } + $author_options = $style_function(); + $author_options['numberOfAuthorsTriggeringEtAl'] = 100; //set really high so we see all authors + $data = theme('biblio_format_authors', array('contributors' => $authors, 'options' => $author_options)); + } + + return $data; +} +function theme_biblio_format_authors($variables) { + $contributors = $variables['contributors']; + $options = $variables['options']; + + if (empty($contributors)) return; + list($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers) = _biblio_get_regex_patterns(); + $base = variable_get('biblio_base', 'biblio'); + $author_links = variable_get('biblio_author_links', 1); + + $authorCount = count($contributors); // check how many authors we have to deal with + $output = ""; // this variable will hold the final author string + $includeStringAfterFirstAuthor = FALSE; + + if (empty($options['numberOfAuthorsTriggeringEtAl'])) $options['numberOfAuthorsTriggeringEtAl'] = $authorCount; + + if (empty($options['includeNumberOfAuthors'])) $options['includeNumberOfAuthors'] = $authorCount; + + foreach ($contributors as $rank => $author) { + if (empty($author['name'])) continue; + if (!isset($author['lastname']) && empty($author['literal'])) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $author = biblio_parse_author($author, $author['auth_type']); // this is needed for form preview to fill in all fields + } + if (empty($author['literal'])) { + if (!empty($author['firstname'])) { + if ($options['shortenGivenNames']) {// if we're supposed to abbreviate given names + // within initials, reduce all full first names (-> defined by a starting uppercase character, followed by one ore more lowercase characters) + // to initials, i.e., only retain their first character + $author['firstname'] = preg_replace("/([$upper])[$lower]+/$patternModifiers", '\\1', $author['firstname']); + //$author['firstname'] = drupal_substr($author['firstname'], 0, 1); + // the next line caused extra betweenInitialsDelim to appear in some circumstances + //$author['firstname'] = preg_replace("/($space|$dash)?/$patternModifier", $options['betweenInitialsDelim'], $author['firstname']); + } + } + if (isset($author['initials'])) { + // within initials, remove any dots: + $author['initials'] = preg_replace("/([$upper])\.+/$patternModifiers", "\\1", $author['initials']); + //$author['initials'] = str_replace('.', '', $author['initials']); + + // within initials, remove any spaces *between* initials: + $author['initials'] = preg_replace("/(?<=[-$upper]) +(?=[-$upper])/$patternModifiers", "", $author['initials']); + //$author['initials'] = str_replace(' ', '', $author['initials']); + + // within initials, add a space after a hyphen, but only if ... + if (preg_match('/ $/', $options['betweenInitialsDelim'])) { // ... the delimiter that separates initials ends with a space + $author['initials'] = preg_replace("/-(?=[$upper])/$patternModifiers", "- ", $author['initials']); + } + // then, separate initials with the specified delimiter: + $delim = $options['betweenInitialsDelim']; + $author['initials'] = preg_replace("/([$upper])(?=[^$lower]+|$)/$patternModifiers", "\\1$delim", $author['initials']); + + $shortenInitials = (isset($options['numberOfInitialsToKeep'])) ? $options['numberOfInitialsToKeep'] : FALSE; + if ($shortenInitials) $author['initials'] = drupal_substr($author['initials'], 0, $shortenInitials); + + if ($options['shortenGivenNames'] && !empty($author['firstname'])) { + $author['firstname'] = $author['firstname'] . $options['betweenInitialsDelim'] . $author['initials']; + if ($shortenInitials) $author['firstname'] = drupal_substr($author['firstname'], 0, $shortenInitials); + } + elseif (!empty($author['firstname'])) { + $author['firstname'] = $author['firstname'] . ' ' . $author['initials']; + } + elseif (empty($author['firstname'])) { + $author['firstname'] = $author['initials']; + } + } + + // if there is a surname prefix like "van", "von" etc, stick it back before the last name. + if (!empty($author['prefix'])) $author['lastname'] = $author['prefix'] . ' ' . $author['lastname']; + if (!empty($author['suffix'])) $author['lastname'] = $author['lastname'] . ', ' . $author['suffix']; + + if (!empty($author['firstname'])) { + if ($rank == 0) { // -> first author + if ($options['initialsBeforeAuthorFirstAuthor']) { + $author['name'] = $author['firstname'] . $options['AuthorsInitialsDelimFirstAuthor'] . $author['lastname']; + } + else{ + $author['name'] = $author['lastname'] . $options['AuthorsInitialsDelimFirstAuthor'] . $author['firstname']; + } + } + else { // $rank > 0 // -> all authors except the first one + if ($options['initialsBeforeAuthorStandard']) { + $author['name'] = $author['firstname'] . $options['AuthorsInitialsDelimStandard'] . $author['lastname']; + } + else{ + $author['name'] = $author['lastname'] . $options['AuthorsInitialsDelimStandard'] . $author['firstname']; + } + } + } + else { + $author['name'] = $author['lastname']; + } + } + if ($author_links) { + $author['name'] = theme('biblio_author_link', array('author' => $author)); + } + else { + $author['name'] = check_plain($author['name']); + } + // append this author to the final author string: + if (($rank == 0) OR ($rank + 1) < $authorCount) {// -> first author, or (for multiple authors) all authors except the last one + if ($rank == 0) { // -> first author + $output .= $author['name']; + } + else {// -> for multiple authors, all authors except the first or the last one + $output .= $options['BetweenAuthorsDelimStandard'] . $author['name']; + } + // we'll append the string in '$customStringAfterFirstAuthors' to the number of authors given in '$includeNumberOfAuthors' if the total number of authors is greater than the number given in '$numberOfAuthorsTriggeringEtAl': + if ((($rank + 1) == $options['includeNumberOfAuthors']) AND ($authorCount > $options['numberOfAuthorsTriggeringEtAl'])) { + if (preg_match("/__NUMBER_OF_AUTHORS__/", $options['customStringAfterFirstAuthors'])) { + $customStringAfterFirstAuthors = preg_replace("/__NUMBER_OF_AUTHORS__/", ($authorCount - $options['includeNumberOfAuthors']), $options['customStringAfterFirstAuthors']); // resolve placeholder + } + $includeStringAfterFirstAuthor = TRUE; + break; + } + } + elseif (($authorCount > 1) AND (($rank + 1) == $authorCount)) {// -> last author (if multiple authors) + $output .= $options['BetweenAuthorsDelimLastAuthor'] . $author['name']; + } + } + + // do some final clean up: + //if ($options['encodeHTML']) + //$output = encodeHTML($output); // HTML encode higher ASCII characters within the newly arranged author contents + + if ($includeStringAfterFirstAuthor) { + $output .= $options['customStringAfterFirstAuthors']; // the custom string won't get HTML encoded so that it's possible to include HTML tags (such as '') within the string + } + $output = preg_replace("/ +/", " ", $output); // remove double spaces (which occur e.g., when both, $betweenInitialsDelim & $newAuthorsInitialsDelim..., end with a space) + $output = preg_replace("/ +([,.;:?!()]|$)/", "\\1", $output); // remove excess spaces before [,.;:?!()] and from the end of the author string + + return $output; +} + +function theme_biblio_author_link($variables) { + $base = variable_get('biblio_base', 'biblio'); + $author = $variables['author']; + $options = isset($variables['options']) ? $variables['options'] : array(); + + $link_to_profile = variable_get('biblio_author_link_profile', 0); + $link_to_profile_path = variable_get('biblio_author_link_profile_path', 'user/[user:uid]'); + + $uri = drupal_parse_url(request_uri()); + $uri = array_merge($uri, $options); + if (!isset($uri['attributes'])) { + $uri['attributes'] = array('rel' => 'nofollow'); + } + $path = $uri['path']; + + if (isset($author['drupal_uid']) && $author['drupal_uid'] > 0) { + $uri['attributes'] += array('class' => array('biblio-local-author')); + } + if (variable_get('biblio_links_target_new_window', null)){ + $uri['attributes'] += array('target'=>'_blank'); + $uri['html'] = TRUE; + } + + if ($link_to_profile && isset($author['drupal_uid']) && $author['drupal_uid'] > 0) { + $data['user'] = user_load($author['drupal_uid']); + $path = token_replace($link_to_profile_path, $data); + $alias = drupal_get_path_alias($path); + $path_profile = variable_get('biblio_show_profile', '0') ? "$path/$base" : $alias; + return l(trim($author['name']), $path_profile, $uri); + } + else { + $uri['path'] = variable_get('biblio_base', 'biblio'); + $uri['query']['f']['author'] = $author['cid']; + return l(trim($author['name']), $uri['path'], $uri ); + } + +} + +// Format page information: +// +// NOTES: - this function (and refbase in general) assumes following rules for the original formatting of page information in '$orig_page_info': +// - single-page items are given as a page range with identical start & end numbers (e.g. "127-127") +// - multi-page items are given as a page range where the end number is greater than the start number (e.g. "127-132") +// - for multi-page items where only the start page is known, a hyphen is appended to the start page (e.g. "127-") +// - total number of pages are given with a "pp" suffix (e.g. "498 pp"), see TODO +// - the given page info is left as is if it does not match any of the above rules (e.g. a single page number is ambiguous since it +// could mean a single page or the total number of pages) +// - the function attempts to deal with page locators that contain letters (e.g. "A1 - A3" or "4a-4c") but, ATM, locator parts (e.g. "A1") +// must contain at least one digit character & must not contain any whitespace +// +// TODO: - should we only use Unicode-aware regex expressions (i.e. always use '$space', '$digit' or '$word' instead of ' ', '\d' or '\w', etc)? +// - recognize & process total number of pages +// - for '$shortenPageRangeEnd=TRUE', add support for page locators that contain letters (e.g. "A1 - A3" or "4a-4c") +function theme_biblio_page_number($variables) { + $orig_page_info = $variables['orig_page_info']; + $page_range_delim = $variables['page_range_delim']; + $single_page_prefix = $variables['single_page_prefix']; + $page_range_prefix = $variables['page_range_prefix']; + $total_pages_prefix = $variables['total_pages_prefix']; + $single_page_suffix = $variables['single_page_suffix']; + $page_range_suffix = $variables['page_range_prefix']; + $total_pages_suffix = $variables['total_pages_prefix']; + $shorten_page_range_end = $variables['single_page_suffix']; + + list($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers) = _biblio_get_regex_patterns(); + + // Check original page info for any recognized page locators, and process them appropriately: + if (preg_match("/\w*\d+\w* *[$dash]+ *(?:\w*\d+\w*)?/$patternModifiers", $orig_page_info)) {// the original page info contains a page range (like: "127-127", "127-132", "A1 - A3", "4a-4c", or "127-" if only start page given) + // Remove any whitespace around dashes or hyphens that indicate a page range: + $orig_page_info = preg_replace("/(\w*\d+\w*) *([$dash]+) *(\w*\d+\w*)?(?=[^\w\d]|$)/$patternModifiers", "\\1\\2\\3", $orig_page_info); + + // Split original page info into its functional parts: + // NOTE: ATM, we simply split on any whitespace characters, then process all parts with page ranges + // (this will also reduce runs of whitespace to a single space) + $partsArray = preg_split("/ +/", $orig_page_info); + $partsCount = count($partsArray); + + for ($i=0; $i < $partsCount; $i++) { + // Format parts with page ranges: + // - single-page item: + if (preg_match("/(\w*\d+\w*)[$dash]+\\1(?=[^\w\d]|$)/$patternModifiers", $partsArray[$i])) // this part contains a page range with identical start & end numbers (like: "127-127") + $partsArray[$i] = preg_replace("/(\w*\d+\w*)[$dash]+\\1(?=[^\w\d]|$)/$patternModifiers", $single_page_prefix . "\\1" . $single_page_suffix, $partsArray[$i]); + + // - multi-page item: + elseif (preg_match("/\w*\d+\w*[$dash]+(?:\w*\d+\w*)?(?=[^\w\d]|$)/$patternModifiers", $partsArray[$i])) {// this part contains a page range (like: "127-132", or "127-" if only start page given) + // In case of '$shorten_page_range_end=TRUE', we abbreviate ending page numbers so that digits aren't repeated unnecessarily: + if ($shorten_page_range_end AND preg_match("/\d+[$dash]+\d+/$patternModifiers", $partsArray[$i])) {// ATM, only digit-only page locators (like: "127-132") are supported + // NOTE: the logic of this 'if' clause doesn't work if the original page info contains something like "173-190; 195-195" (where, for the first page range, '$endPage' would be "190;" and not "190") + list($startPage, $endPage) = preg_split("/[$dash]+/$patternModifiers", $partsArray[$i]); + + $countStartPage = strlen($startPage); + $countEndPage = strlen($endPage); + + if (($countStartPage == $countEndPage) AND ($startPage < $endPage)) { + for ($j=0; $j < $countStartPage; $j++) { + if (preg_match("/^" . substr($startPage, $j, 1) . "/", $endPage)) {// if the ending page number has a digit that's identical to the starting page number (at the same digit offset) + $endPage = substr($endPage, 1); // remove the first digit from the remaining ending page number + } + else { + break; + } + } + } + + $partsArray[$i] = $page_range_prefix . $startPage . $page_range_delim . $endPage . $page_range_suffix; + } + else {// don't abbreviate ending page numbers: + $partsArray[$i] = preg_replace("/(\w*\d+\w*)[$dash]+(\w*\d+\w*)?(?=[^\w\d]|$)/$patternModifiers", $page_range_prefix . "\\1" . $page_range_delim . "\\2" . $page_range_suffix, $partsArray[$i]); + } + } + } + + $newPageInfo = join(" ", $partsArray); // merge again all parts + } + else { + $newPageInfo = $orig_page_info; // page info is ambiguous, so we don't mess with it + } + + return $newPageInfo; +} + + +/** + * Applies a "style" function to a single node. + * + * @param $node A node + * @param $style_name The name of the style to apply + * @return A string containing the styled (HTML) node + */ +function theme_biblio_style($variables ) { + $node = $variables['node']; + $style_name = $variables['style_name']; + $styled_node = ''; + module_load_include('inc', 'biblio', "/styles/biblio_style_$style_name"); + + $style_function = "biblio_style_$style_name"; + if (function_exists($style_function)) { + $styled_node = $style_function ($node); + } + else { + drupal_set_message(t('The style function: @funct does not exist', array('@funct' => $style_function)), 'error'); + } + return ($styled_node . filter_xss($node->biblio_coins, array('span'))); +} + +/** + * @param $node + * @param $base + * @param $style + * @return unknown_type + */ +function theme_biblio_entry($variables) { + $node = $variables['node']; + $style_name = $variables['style_name']; + + $output = "\n" . '
    ' . "\n" ; + $output .= '
    ' . "\n" ; + if (!$node->status) { + $output .= '
    '; + } + + // first add the styled entry... + $output .= theme('biblio_style', array('node' => $node, 'style_name' => $style_name)); + + // now add the various links +// if ($node->biblio_abst_e) { +// $output .= ''; +// $output .= l(" Abstract", "node/$node->nid") . "\n"; +// $output .= ''; +// } + $annotation_field = variable_get('biblio_annotations', 'none'); + if ($annotation_field != 'none' && $node-> $annotation_field) { + $output .= '
    '; + $output .= filter_xss($node->$annotation_field, biblio_get_allowed_tags()); + $output .= '
    '; + } + $openurl_base = variable_get('biblio_baseopenurl', ''); + + if ($openurl_base) { + $output .= theme('biblio_openurl', array('openURL' => biblio_openurl($node))); + } + + if (biblio_access('export')) { + $base = variable_get('biblio_base', 'biblio'); + $output .= theme('biblio_export_links', array('node' => $node)); + } + + if (biblio_access('download', $node)) { + // add links to attached files (if any) + $output .= theme('biblio_download_links', array('node' => $node)); + } + if (!$node->status) { + $output .= '
    '; + } + + $output .= "\n
    "; + + return $output; +} + +/** + * @param $form + * @return unknown_type + */ +function theme_biblio_filters($variables) { + $form = $variables['form']; + $output = ''; + if (isset($form['current'])) { + $output .= '
      '; + foreach (element_children($form['current']) as $key) { + $output .= '
    • ' . drupal_render($form['current'][$key]) . '
    • '; + } + $output .= '
    '; + } + $output .= '
    ' . (isset($form['current']) ? '
    ' . t('and') . ' ' . t('where') . '
    ' : '') . '
    '; + foreach (element_children($form['filter']) as $key) { + $output .= drupal_render($form['filter'][$key]); + } + $output .= '
    '; + $output .= '
    ' . t('is') . '
    '; + foreach (element_children($form['status']) as $key) { + $output .= drupal_render($form['status'][$key]); + } + $output .= '
    '; + $output .= '
    '; + $output .= '
    ' . drupal_render($form['buttons']) . '
    '; + $output .= '
    '; + return $output; +} + +/** + * @param $form + * @return unknown_type + */ +function theme_biblio_form_filter($variables) { + $form = $variables['form']; + + $output .= '
    '; + $output .= drupal_render($form['filters']); + $output .= '
    '; + $output .= drupal_render($form); + return $output; +} + +function theme_biblio_field_tab($variables) { + $form = $variables['form']; + $rows = array(); + $headers = $form['#header']; + drupal_add_tabledrag($form['#id'], 'order', 'sibling', 'weight'); + + foreach (element_children($form['rows']) as $key) { + // No need to print the field title every time. +// unset($form[$key]['name']['#title'], $form[$key]['auth_type']['#title'], $form[$key]['auth_category']['#title']); + // Add class to group weight fields for drag and drop. + $form['rows'][$key]['weight']['#attributes']['class'] = array('weight'); + + // Build the table row. + $row = array(''); + $row[] = array('data' => drupal_render($form['rows'][$key]['name'])); + $row[] = array('data' => drupal_render($form['rows'][$key]['title'])); + $row[] = array('data' => drupal_render($form['rows'][$key]['hint'])); + foreach (element_children($form['rows'][$key]['checkboxes']) as $oid) { + if (is_array($form['rows'][$key]['checkboxes'])) { + $row[] = array( + 'data' => drupal_render($form['rows'][$key]['checkboxes'][$oid]), + 'title' => $oid); + } + } + + + $row[] = drupal_render($form['rows'][$key]['weight']); + $rows[] = array('data' => $row, 'class' => array('draggable')); + } + $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('id' => $form['#id']))); + $output .= drupal_render_children($form); + return $output; + +} +/** + * @param $form + * @return unknown_type + */ +function theme_biblio_admin_types_edit_form($variables) { + $form = $variables['form']; + $output = ''; + + $output .= drupal_render($form['help']); + $output .= drupal_render($form['pub_type']); + $output .= drupal_render($form['change_type']); + if (isset($form['type_name'])) { + $output .= drupal_render($form['type_name']); + } + $output .= drupal_render($form['biblio_tabs']); + $output .= drupal_render_children($form); + return $output; +} + +function theme_biblio_download_links($variables) { + static $langcode = NULL; + $file_links = array(); + $node = $variables['node']; + if (!isset($langcode)) { + $langcode = $GLOBALS['language_content']->language; + } + + $fields = field_attach_view('node', $node, 'full', $langcode); + foreach (element_children($fields) as $field) { + if($fields[$field]['#access'] && $fields[$field]['#field_type'] == 'file') { + foreach ($fields[$field]['#items'] as $delta => $item) { + if (module_exists('filefield_paths')) { + $alias = drupal_get_path_alias('filefield_paths/alias/' . $item['fid']); + } + $link_type = variable_get('biblio_file_link_type', 'text'); + if ($link_type == 'icon') { + $url = file_create_url($item['uri']); + $icon = theme('file_icon', array('file' => (object) $item)); + $options['html'] = TRUE; + $options['attributes']['title'] = check_plain($item['filename']); + $file_links[] = array( + l($icon, $url, $options), + format_size($item['filesize']), + ); + } + elseif ($link_type == 'text') { + $file_links[] = array( + theme('file_link', array('file' => (object) $item)), + format_size($item['filesize']), + ); + } + } + } + } + $files = ''; + if (count($file_links) > 0 && (user_access('show download links') || user_access('show own download links'))) { + $files .= ''; + // $files .= ' ' . t('Download') . ': '; + $file_count = 0; + foreach ($file_links as $file) { + $files .= $file[0] . ' (' . $file[1] . ')'; + + } + $files .= ''; + } + + return $files; +} + +/** + * Creates a group of links for the various export functions + * @param $nid the node id to export (if omitted, all nodes in the current view will be exported + * @return an un-ordered list of class "biblio-export-buttons" + */ +function theme_biblio_export_links($variables) { + global $pager_total_items; + $node = $variables['node']; + $filter = ($variables['node'] == NULL && isset($variables['filter'])) ? $variables['filter'] : array(); + $links = array(); + $output = ''; + + if (biblio_access('export')) { + $show_link = variable_get('biblio_lookup_links', array('google' => TRUE)); + $lookup_links = module_invoke_all('biblio_lookup_link', $node); + if ($show_link['google'] && !empty($node)) { + $lookup_links['biblio_google_scholar'] = theme('google_scholar_link', array('node' => $node)); + } + $nid = (isset($node->nid)) ? $node->nid : NULL; + $export_links = module_invoke_all('biblio_export_link', $nid, $filter); + $links = array_merge($lookup_links, $export_links); + } + if (empty($node) && !empty($links)) { + $output = t('Export @count results', array('@count' => $pager_total_items[0])) . ': '; + } + + return $output . theme('links', array('links' => $links, 'attributes' => array('class' => array('biblio-export-buttons')))); +} + + +function theme_google_scholar_link($variables) { + $node = $variables['node']; + $query = array(); + + $query['btnG'] = 'Search+Scholar'; + $query['as_q'] = '"' . str_replace(array(' ', '(', ')'), array('+'), $node->title) . '"'; // as_q = all the words + if (isset($node->biblio_contributors[0]['lastname'])) { + $query['as_sauthors'] = $node->biblio_contributors[0]['lastname']; + } + $query['as_occt'] = 'any'; + $query['as_epq'] = ''; // exact phrase + $query['as_oq'] = ''; // at least one of the words + $query['as_eq'] = ''; // without the words + $query['as_publication'] = ''; // published in + $query['as_ylo'] = ''; // lower date in date range + $query['as_yhi'] = ''; // upper date in date range + $query['as_sdtAAP'] = 1; //Search articles in all subject areas + $query['as_sdtp'] = 1; //include patents + + $attrs = array('title' => t("Click to search Google Scholar for this entry")); + if (variable_get('biblio_links_target_new_window', NULL)){ + $attrs = array_merge($attrs, array('target '=> '_blank')); + } + + $attrs = array_merge($attrs, array('rel' => 'nofollow')); + + return array( + 'title' => t('Google Scholar'), + 'href' => 'http://scholar.google.com/scholar', + 'attributes' => $attrs, + 'query' => $query, + ); +} + +/** + * @param $form + * @return unknown_type + */ +function theme_biblio_contributors($variables) { + $form = $variables['form']; + $rows = array(); + $headers = array('', t('Name'), t('Category'), t('Role'), t('Weight')); + drupal_add_tabledrag($form['#id'], 'order', 'sibling', 'rank'); + + foreach (element_children($form) as $key) { + // No need to print the field title every time. +// unset($form[$key]['name']['#title'], $form[$key]['auth_type']['#title'], $form[$key]['auth_category']['#title']); + // Add class to group weight fields for drag and drop. + $form[$key]['rank']['#attributes']['class'] = array('rank'); + + // Build the table row. + $row = array(''); + $row[] = array('data' => drupal_render($form[$key]['name']), + 'class' => array('biblio-contributor')); + $row[] = array('data' => drupal_render($form[$key]['auth_category']), + 'class' => array('biblio-contributor-category')); + $row[] = array('data' => drupal_render($form[$key]['auth_type']), + 'class' => array('biblio-contributor-type')); + $row[] = drupal_render($form[$key]['rank']); + $rows[] = array('data' => $row, 'class' => array('draggable')); + } + $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('id' => $form['#id']))); + //$output .= drupal_render_children($form); + return $output; +} + +/** + * This function creates a string of letters (A - Z), which + * depending on the sorting are either linked to author or title + * filters i.e. clicking on the A when in the listing is sorted by + * authors will bring up a list of all the entries where the first + * character of the primary authors last name is "A" + * + * @param $type either "author or title" + * @return a chunk of HTML code as described above + */ +function theme_biblio_alpha_line($variables) { + $type = $variables['type']; + $all = ''; + $uri = drupal_parse_url(request_path()); + + //replace path in case we are in a multi-language environment, otherwise we end up with double language prefixes. + $languages = language_list('enabled'); + if (isset($languages[1])) { + $path_parts = empty($uri['path']) ? array() : explode('/', $uri['path']); + foreach ($languages[1] as $code => $lang) { + if ($path_parts[0] == $code) { + array_shift($path_parts); + break; + } + } + $uri['path'] = implode('/', $path_parts); + } + + $uri['attributes'] = array('rel' => 'nofollow'); + $all_uri = $uri; + $current = ''; + + switch ($type) { + case 'authors': + $filter = 'author'; + $current = isset($uri['query']['f']['author']) ? $uri['query']['f']['author'] : ''; + unset($all_uri['query']['f']['author']); + break; + case 'keywords': + case 'keyword': + $filter = 'keyword'; + $current = isset($uri['query']['f']['keyword']) ? $uri['query']['f']['keyword'] : ''; + unset($all_uri['query']['f']['keyword']); + break; + case 'author': + $current = isset($uri['query']['f']['ag']) ? $uri['query']['f']['ag'] : ''; + $filter = 'ag'; + if (!isset($uri['query']['s'])) $uri['query']['s'] = 'author'; + unset($all_uri['query']['f']['ag']); + break; + case 'title': + $current = isset($uri['query']['f']['tg']) ? $uri['query']['f']['tg'] : ''; + $filter = 'tg'; + unset($all_uri['query']['f']['tg']); + break; + default: + } + $output = '
    '; +//TODO for Cyrillic use 1040 to 1071... e.g. Ь + + for ($i = 65; $i <= 90; $i++) { + if ($i == ord($current)) { + $output .= '[' . chr($i) . '] '; + } + else{ + $uri['query']['f'][$filter] = chr($i); + $output .= l(chr($i), $uri['path'], $uri) . ' '; + } + } + if ($current) { + if (empty($all_uri['query']['f'])) unset($all_uri['query']['f']); + $output .= '  ' . '[' . l(t('Show ALL'), $all_uri['path'], $all_uri) . ']'; + } + $output .= '
    '; + return $output; +} + +/** + * Themes the author editing form + * + * @param $form + * @return rendered form + */ +function theme_biblio_admin_author_edit_form($variables) { + $form = $variables['form']; + $rows = array(); + $rows[] = array( + array('data' => drupal_render($form['prefix'])), + array('data' => drupal_render($form['firstname'])), + array('data' => drupal_render($form['initials'])), + array('data' => drupal_render($form['lastname'])), + array('data' => drupal_render($form['suffix']))); + $rows[] = array(array('data' => drupal_render($form['name']) . drupal_render($form['literal']), 'colspan' => 5)); + $rows[] = array(array('data' => drupal_render($form['affiliation']), 'colspan' => 5)); + $rows[] = array(array('data' => drupal_render($form['drupal_uid']), 'colspan' => 5)); + + $output = theme('table', array('rows' => $rows)); + $output .= drupal_render($form['merge']); + $output .= drupal_render($form['link']); + $output .= drupal_render_children($form); + return $output; + +} + +function theme_biblio_admin_author_edit_merge_table($variables) { + $form = $variables['form']; + $headers = $form['#header']; + $rows = array(); + + foreach (element_children($form['candidates']) as $key) { + + // Build the table row. + $row = array(); + $row[] = array('data' => drupal_render($form['candidates'][$key]['name'])); + $row[] = drupal_render($form['candidates'][$key]['link']); + $row[] = drupal_render($form['candidates'][$key]['merge']); + $row[] = drupal_render($form['candidates'][$key]['retain']); + $rows[] = $row; + } + + $output = theme('table', array('header' => $headers, 'rows' => $rows, 'attributes' => array('id' => $form['#id']))); + + $row = $rows = array(); + $row[] = array('data' => drupal_render($form['more_authors_search']) . drupal_render($form['more_authors_add'])); + $rows[] = $row; + $output .= theme('table', array('header' => array(), 'rows' => $rows, 'attributes' => array('id' => $form['#id']))); + + $output .= drupal_render_children($form); + + return $output; +} + +function _biblio_keyword_links($keywords, $base='biblio') { + $uri = drupal_parse_url(request_uri()); + $uri['path'] = variable_get('biblio_base', 'biblio');; + $uri['attributes'] = array('rel' => 'nofollow'); + $html = ""; + if (!is_array($keywords)) { + require_once(drupal_get_path('module', 'biblio') . '/includes/biblio.keywords.inc'); + $keywords = biblio_explode_keywords($keywords); + } + + $sep = variable_get('biblio_keyword_sep', ','); + foreach ($keywords as $kid => $keyword ) { + $uri['query']['f']['keyword'] = $kid; + $linked_keywords [] = l(trim($keyword), $base , $uri); + } + return implode("$sep ", $linked_keywords); +} + + +function template_preprocess_biblio_sort_tabs(&$vars) { + $vars['tabs_style'] = variable_get('biblio_sort_tabs_style', 0); + + $uri = drupal_parse_url(request_uri()); + $uri['path'] = $_GET['q']; + $uri['path'] = trim($uri['path'], '/'); + + $defaults = array( + 'author' => 'asc', + 'title' => 'asc', + 'type' => 'asc', + 'year' => 'desc', + 'keyword' => 'asc', + ); + + if (isset($uri['query']['s'])) { + $sort = $uri['query']['s']; + } + else { + $sort = variable_get('biblio_sort', 'year'); + } + + if (isset($uri['query']['o'])) { + $order = strtolower($uri['query']['o']); + } + else { + $order = strtolower(variable_get('biblio_order', 'desc')); + } + // flip it + $order = ($order == 'desc') ? 'asc' : 'desc'; + $path = drupal_get_path('module', 'biblio'); + $order_arrow = ($order == 'asc') ? theme('image', array('path' => $path.'/misc/arrow-asc.png', 'alt' => '(Asc)')): + theme('image', array('path' => $path.'/misc/arrow-desc.png', 'alt' => '(Desc)')); + + $sort_links = array_filter(variable_get('biblio_sort_tabs', array('author' => 'author', 'title' => 'title', 'type' => 'type', 'year' => 'year', 'keyword' => 'keyword'))); + ksort($sort_links); + + $links = array(); + foreach ($sort_links as $key => $title) { + $uri_copy = $uri; + $uri_copy['attributes'] = array("title" => t("Click a second time to reverse the sort order")); + $uri_copy['html'] = TRUE; + $uri_copy['text'] = t(ucfirst($title)); + $uri_copy['query']['s'] = $title; + if ($key === $title && $title == $sort) { + $uri_copy['query']['o'] = $order; + $uri_copy['attributes']['class'][] = "active"; + $uri_copy['active'] = TRUE; + $uri_copy['pfx'] = ' [ '; + $uri_copy['sfx'] = '] '; + $uri_copy['arrow'] = $order_arrow; + + } + elseif ($key === $title ) { + $uri_copy['query']['o'] = $defaults[$title]; + $uri_copy['active'] = FALSE; + $uri_copy['pfx'] = ' '; + $uri_copy['sfx'] = ' '; + $uri_copy['arrow'] = ''; + } + $links[] = $uri_copy; + } + $vars['links'] = $links; +} + +function theme_biblio_sort_tabs($vars) { + $links = $vars['links']; + $style = $vars['tabs_style']; + $output = ''; + + if ($style) { + $output = '
      '; + } + foreach ($links as $l) { + $output .= _biblio_sort_tab($l, $style); + } + + if ($style) { + $output .= '
    '; + } + + return $output; +} + +function _biblio_sort_tab($tab, $tabs = FALSE) { + if ($tabs) { + $text = '' . $tab['text'] . $tab['arrow'] . ''; + $class = ($tab['active']) ? 'class="active"':''; + $link = l($text, $tab['path'], $tab); + return "
  • ". $link . '
  • '; + } + else { + return $tab['pfx'] . l($tab['text'], $tab['path'], $tab) . $tab['arrow'] . $tab['sfx']; + } + return; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/includes/biblio_xml.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/includes/biblio_xml.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,68 @@ +appendChild(new DOMElement('biblio_collection')); + $biblio_collection->setAttribute("Schema", "6010"); + $comment = $biblio_collection->appendChild(new DOMComment('Generated by the Biblio module from Drupal (http://drupal.org/project/biblio)')); + $db_result = db_query("SELECT nr.nid, nr.vid FROM {node_revision} nr join node n on nr.nid=n.nid where n.type='biblio' order by nr.nid, nr.vid"); + while($n=db_fetch_object($db_result)) { + $node = node_load($n->nid,$n->vid); + if ($n->nid == $nid) { + $revision = $domnode->appendChild(new DOMElement('revision')); + $node = (array)$node; + AtoX($node, $dom, $revision); + }else{ + $domnode = $biblio_collection->appendChild(new DOMElement('node')); + $node = (array)$node; + AtoX($node, $dom, $domnode); + } + $nid = $n->nid; + + } + return $dom->saveXML(); +} + +function AtoX($array, $DOM=null, $root=null) { + foreach ($array as $key => $value) { + if ($key == 'biblio_contributors') $name = 'contributor'; + if (is_numeric($key)) $key = 'c_' . $key; + if (is_array($value ) && count($value)) { + $subroot = $root->appendChild($DOM->createElement($key)); + AtoX($value, $DOM, $subroot); + } + else { + if (!empty($value)) { + $root->appendChild($DOM->createElement($key, htmlspecialchars($value, ENT_QUOTES))); + } + } + } + + return $DOM; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/arrow-asc.png Binary file sites/all/modules/biblio/misc/arrow-asc.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/arrow-desc.png Binary file sites/all/modules/biblio/misc/arrow-desc.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/biblio.field.link.data.csv --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/misc/biblio.field.link.data.csv Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,54 @@ +fid,title,hint, common, autocomplete, required, weight, visible,field name,type,width,maxlength,vtab +1,Authors,,1,1,1,1,1,biblio_authors,contrib_widget,60,255,1 +2,Secondary Authors,,1,1,1,2,1,biblio_secondary_authors,contrib_widget,60,255,1 +3,Tertiary Authors,,1,1,1,3,1,biblio_tertiary_authors,contrib_widget,60,255,1 +4,Subsidiary Authors,,1,1,1,4,1,biblio_subsidiary_authors,contrib_widget,60,255,1 +5,Corporate Authors,,1,1,1,5,1,biblio_corp_authors,contrib_widget,60,255,1 +6,Secondary Title,,0,0,0,12,0,biblio_secondary_title,textfield,60,255,2 +7,Tertiary Title,,0,0,0,13,0,biblio_tertiary_title,textfield,60,255,8 +8,Accession Number,,1,0,0,151,1,biblio_accession_number,textfield,24,128,4 +9,ISBN Number,,1,0,0,150,1,biblio_isbn,textfield,24,128,4 +10,Call Number,,1,0,0,152,1,biblio_call_number,textfield,24,128,4 +11,Other Numbers,,1,0,0,153,1,biblio_other_number,textfield,10,128,4 +12,Other Author Affiliations,,0,0,0,24,0,biblio_other_author_affiliations,textfield,60,255,9 +13,Publisher,,0,0,0,19,0,biblio_publisher,textfield,60,255,3 +14,Place Published,,0,0,0,20,0,biblio_place_published,textfield,60,255,3 +15,Year of Publication,"Enter YYYY, Submitted or In Press",1,0,1,-45,1,biblio_year,textfield,4,20,2 +16,Edition,,0,0,0,15,0,biblio_edition,textfield,60,255,2 +17,Volume,,0,0,0,14,0,biblio_volume,textfield,10,128,2 +18,Number,,0,0,0,16,0,biblio_number,textfield,10,128,2 +19,Pagination,,0,0,0,17,0,biblio_pages,textfield,24,128,2 +20,Date Published,(mm/yyyy),0,0,0,18,0,biblio_date,textfield,16,16,2 +21,Publication Language,,0,0,0,23,0,biblio_lang,textfield,24,24,2 +22,Abstract,,1,0,0,155,1,biblio_abst_e,text_format,60,65535,1 +23,French Abstract,,0,0,0,156,0,biblio_abst_f,text_format,60,65535,9 +24,Keywords,,1,1,0,154,0,biblio_keywords,textfield,60,1000,6 +25,Type of Work,Masters Thesis,0,0,0,22,0,biblio_type_of_work,textfield,60,255,2 +26,URL,,1,0,0,158,1,biblio_url,textfield,60,255,5 +27,Notes,,0,0,0,157,0,biblio_notes,text_format,60,65535,7 +28,Issue,,0,0,0,15,0,biblio_issue,textfield,10,128,2 +29,Reseach Notes,,0,0,0,160,0,biblio_research_notes,text_format,60,65535,7 +30,Custom 1,,0,0,0,161,0,biblio_custom1,text_format,60,65535,9 +31,Custom 2,,0,0,0,162,0,biblio_custom2,text_format,60,65535,9 +32,Custom 3,,0,0,0,163,0,biblio_custom3,text_format,60,65535,9 +33,Custom 4,,0,0,0,164,0,biblio_custom4,text_format,60,65535,9 +34,Custom 5,,0,0,0,165,0,biblio_custom5,text_format,60,65535,9 +35,Custom 6,,0,0,0,167,0,biblio_custom6,text_format,60,65535,9 +36,Custom 7,,0,0,0,168,0,biblio_custom7,text_format,60,65535,9 +37,Number of Volumes,,0,0,0,15,0,biblio_number_of_volumes,textfield,10,128,2 +38,Short Title,,0,0,0,169,0,biblio_short_title,textfield,60,255,8 +39,Alternate Title,,0,0,0,170,0,biblio_alternate_title,textfield,60,255,8 +40,Translated Title,,0,0,0,170,0,biblio_translated_title,textfield,60,255,8 +41,Original Publication,,0,0,0,171,0,biblio_original_publication,textfield,60,255,8 +42,Reprint Edition,,0,0,0,172,0,biblio_reprint_edition,textfield,120,255,2 +43,Section,,0,0,0,15,0,biblio_section,textfield,10,128,2 +44,Citation Key,,0,0,0,175,0,biblio_citekey,textfield,16,255,4 +45,COinS Data,This will be automatically generated,0,0,0,176,0,biblio_coins,text_format,60,65535,9 +46,ISSN Number,,0,0,0,150,0,biblio_issn,textfield,24,128,4 +47,DOI,,1,0,0,159,1,biblio_doi,textfield,60,255,5 +48,Author Address,,0,0,0,178,0,biblio_auth_address,text_format,60,65535,9 +49,Remote Database Name,,0,0,0,176,0,biblio_remote_db_name,textfield,60,255,9 +50,Remote Database Provider,,0,0,0,177,0,biblio_remote_db_provider,textfield,60,255,9 +51,Label,,0,0,0,178,0,biblio_label,textfield,60,255,9 +52,Access Date,,0,0,0,179,0,biblio_access_date,textfield,60,255,9 +53,Refereed Designation,,0,0,0,180,0,biblio_refereed,select,0,128,9 diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/biblio.field.type.data.csv --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/misc/biblio.field.type.data.csv Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,40 @@ +,tid,biblio_year,title,biblio_secondary_title,biblio_tertiary_title,biblio_short_title,biblio_secondary_authors,biblio_tertiary_authors,biblio_subsidiary_authors,biblio_corp_authors,biblio_alternate_title,biblio_place_published,biblio_publisher,biblio_volume,biblio_number_of_volumes,biblio_number,biblio_issue,biblio_pages,biblio_section,biblio_edition,biblio_date,biblio_type_of_work,biblio_issn,,biblio_original_publication,biblio_reprint_edition,,biblio_custom1,biblio_custom2,biblio_custom3,biblio_custom4,biblio_custom5,biblio_custom6,biblio_custom7,biblio_abst_e,biblio_refereed +Generic ,,Year ,Title ,Secondary Title ,Tertiary Title ,Short Title ,Editor ,Tertiary Author ,Subsidiary Author ,Corporate Author,Alternate Title ,Place Published ,Publisher ,Volume ,Number of Volumes ,Number ,Issue,Pages ,Section ,Edition ,Date ,Type of Work ,ISBN/ISSN ,Electronic Resource Number ,Original Publication ,Reprint Edition ,Reviewed Item ,Custom 1 ,Custom 2 ,Custom 3 ,Custom 4 ,Custom 5 ,Custom 6 ,Custom 7 ,Abstract ,Refereed Designation +Ancient Text ,,,,Publication Title ,Volume Title ,,Editor ,~,Translator ,,Abbreviated Publication ,City ,,,,,~,,~,,,,ISBN ,,,,Reviewed Item ,~,~,~,~,~,~,~,, +Artwork ,112,,,~,~,,~,~,~,,,City ,,~,~,~,~,Description ,~,~,,,~,,~,~,,~,~,~,~,~,~,~,, +Audiovisual Material ,114,,,Series Title ,~,,Series Editor ,~,Performers ,,,City ,,~,Extent of Work ,Number ,~,~,~,,,Type ,ISBN ,,Contents ,~,,Cast ,Credits ,~,~,Format ,~,~,, +Bill ,117,,,Code ,Legislative Body ,,~,~,Sponsor ,,~,~,~,Code Volume ,~,Bill Number ,~,Code Pages ,Code Section ,Session ,,~,~,,History ,~,,~,~,~,~,~,~,~,, +Book ,100,,,Series Title ,~,,Series Editor ,~,Translator ,,Abbreviation ,City ,,,,Series Volume ,~,Number of Pages ,~,,~,~,ISBN ,,,,~,~,~,~,~,~,~,~,, +Book Section ,101,,,Book Title ,Series Title ,,Editor ,Series Editor ,Translator ,,Abbreviation ,City ,,,,Series Volume ,~,,Chapter ,,~,~,ISBN ,,,,,~,~,~,~,~,~,~,, +Case ,116,,Case Name ,Reporter ,~,Abbreviated Case Name ,~,~,Counsel ,,~,~,Court ,Reporter Volume ,Reporter Abbreviation ,~,~,First Page ,Page Cited ,~,Date Decided ,~,~,,History ,~,~,~,~,~,~,~,~,~,, +Classical Work ,127,,,Series Title ,~,,Series Editor ,~,Translator ,,,City ,,,,Series Volume ,~,Number of Pages ,~,,~,Type ,ISSN/ISBN ,,,,~,~,~,~,~,~,~,~,, +Computer Program ,113,,,Series Title ,~,,Series Editor ,~,~,,,City ,,~,~,~,~,Description ,~,Version ,~,Type ,ISBN ,,Contents ,~,~,Computer ,~,~,~,~,~,~,, +Conference Paper ,103,,,Conference Name ,~,~,Editor ,~,~,,~,Conference Location ,,~,~,~,~,~,~,~,,~,~,,~,~,~,~,~,~,~,~,~,~,, +Conference Proceeding ,104,Year of Conference ,,Conference Name ,Series Title ,,Editor ,Series Editor ,Sponsor ,,~,Conference Location ,,,,~,~,,~,,,~,ISBN ,,~,~,~,~,~,~,~,~,~,~,, +Chart or Table ,123,,,Image Source Program ,Name of File ,~,~,~,~,,~,City ,,Image Size ,~,,~,Description ,~,Version ,,Type of Image ,~,,~,~,~,~,~,~,~,~,~,~,, +Dictionary ,,,,Dictionary Title ,~,,Editor ,~,Translator ,,Abbreviation ,City ,,,,,~,,~,,~,~,ISBN ,,,,,~,~,~,~,~,~,~,, +Edited Book ,,,,Series Title ,~,,Series Editor ,~,Translator ,,,City ,,,,Series Volume ,~,Number of Pages ,~,,~,~,ISBN ,,,,,~,~,~,~,~,~,~,, +Electronic Article ,,,,Periodical Title ,~,~,,~,~,,,~,~,,~,~,,,~,~,Date Accessed ,~,ISSN ,,~,~,~,~,~,~,~,~,~,~,, +Electronic Book ,,,,Secondary Title ,~,~,Editor ,~,~,,~,~,,,~,~,~,~,~,~,Date Accessed ,Type of Medium ,ISBN ,,~,~,~,~,~,~,~,~,~,~,, +Equation ,,,,Image Source Program ,Name of File ,~,~,~,~,,~,City ,,Image Size ,~,,~,Description ,~,Version ,,Type of Image ,~,,~,~,~,~,~,~,~,~,~,~,, +Encyclopedia ,,,,Encyclopedia Title ,~,,Editor ,~,Translator ,,Abbreviation ,City ,,,,~,~,,~,,,~,ISBN ,,,,,~,~,~,~,~,~,~,, +Figure ,,,,Image Source Program ,Name of File ,~,~,~,~,,~,City ,,Image Size ,~,,~,Description ,~,Version ,,Type of Image ,~,,~,~,~,~,~,~,~,~,~,~,, +Film or Broadcast ,110,Year Released ,,Series Title ,~,,Series Director ,Producer ,Performers ,,,Country ,,~,~,~,~,Running Time ,~,,Date Released ,Medium ,~,,~,~,~,Cast ,Credits ,~,Genre ,Format ,~,~,Synopsis , +Government Document ,126,,,~,Series Title ,~,Department ,~,~,,~,~,,,~,~,,,Section ,,~,~,Report Number ,,~,~,~,Government Body ,Congress Number ,Congress Session ,~,~,~,~,, +Grant ,,,Title of Grant ,~,~,,~,~,Translator ,,Abbreviation ,Activity Location ,Sponsoring Agency ,Amount Requested ,Amount Received ,Status ,~,,Duration of Grant ,Requirements ,Deadline ,Funding Type ,~,,Original Grant Number ,Review Date ,,Contact Name ,Contact Address ,Contact Phone ,Contact Fax ,Funding Number ,CFDA Number ,~,, +Hearing ,115,,,Committee ,Legislative Body ,,~,~,~,,~,City ,,~,,Document Number ,~,,~,Session ,,~,~,,History ,~,~,~,~,~,~,~,~,~,, +Journal Article ,102,,,Journal ,~,,~,~,~,,Alternate Journal ,~,~,,~,~,,,Start Page ,~,,Type of Article ,ISSN ,,,Reprint Edition ,,~,~,~,~,~,~,~,, +Legal Rule or Regulation ,128,,,~,~,~,~,~,~,Issuing Organization ,Abbreviation ,~,,Rule Number ,Session Number ,Start Page ,~,,Section Number ,,Date of Code Edition ,,Document Number ,,~,~,~,~,~,~,~,~,~,~,, +Magazine Article ,106,,,Magazine ,~,,~,~,~,,Alternate Magazine ,~,~,,Frequency ,Issue Number ,Issue Number ,,~,,,Type of Article ,ISSN ,,,,,~,~,~,~,~,~,~,, +Manuscript ,121,,,Collection Title ,~,,~,~,~,,Abbreviation ,City ,Library/Archive ,Volume/Storage Container ,Manuscript Number ,Folio Number ,~,,~,~,,,~,,~,~,~,~,~,~,~,~,~,~,, +Map ,122,,,Series Title ,~,,Series Editor ,~,~,,,City ,,~,~,~,~,Description ,~,,,Type ,ISBN ,,~,~,~,Scale ,~,~,~,~,~,~,, +Newspaper Article ,105,,,Newspaper ,~,,~,~,~,,~,City ,~,,Frequency ,Start Page ,~,,Section ,,Issue Date ,Type of Article ,ISSN ,,Original Publication ,,Reviewed Item ,~,~,~,~,~,~,~,~, +Online Database ,125,,,~,~,~,~,~,~,,~,~,Publisher ,~,~,~,~,~,~,~,Date Accessed ,~,~,,~,~,~,~,~,~,~,~,~,~,~, +Online Multimedia ,,,,Series Title ,~,~,Series Editor ,~,~,,~,~,Distributor ,~,~,~,~,~,~,~,Date Accessed ,Type of Work ,~,,~,~,~,~,~,~,~,Format/Length ,,~,~, +Patent ,119,,,Published Source ,International Title ,,~,International Author ,~,Issuing Organization ,~,Country ,Assignee ,Patent Version Number ,~,Application Number ,~,,International Patent Number ,International Patent Classification ,,Patent Type ,Patent Number ,,Priority Numbers ,~,~,~,Issue Date ,Designated States ,Attorney/Agent ,References ,Legal Status ,~,~, +Personal Communication ,120,,,~,~,,Recipient ,~,~,,Abbreviation ,City ,,~,Communication Number ,Folio Number ,~,,~,Description ,,Type ,~,,~,~,~,~,~,~,~,~,~,~,, +Report ,109,,,Series Title ,~,,Series Editor ,~,~,,,City ,Institution ,~,~,Document Number ,~,,~,~,,Type ,Report Number ,,Contents ,~,~,~,~,~,~,~,~,~,, +Statute ,118,,Name of Act ,Code ,~,,~,~,~,,Abbreviation ,Country ,~,Code Number ,Statute Number ,Public Law Number ,~,,Sections ,Session ,Date Enacted ,~,~,,History ,~,~,~,~,~,~,~,~,~,, +Thesis ,108,,,Academic Department ,~,,~,Advisor ,~,,~,City ,University ,Degree ,~,~,~,Number of Pages ,~,~,,Thesis Type ,~,,,~,~,,,,,,,,, +Unpublished Work ,124,,Title of Work ,Series Title ,~,,~,~,~,,Abbreviation ,City ,Institution ,~,~,Number ,~,,~,~,,Type of Work ,~,,~,~,~,~,~,~,~,~,~,~,~, +Web Page ,107,,,Series Title ,~,,Series Editor ,~,~,,,City ,Publisher ,Access Year ,~,Access Date ,~,Description ,~,,Last Update Date ,Type of Medium ,ISBN ,,Contents ,~,~,~,~,~,~,~,~,~,~, diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/biblio.highlight.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/misc/biblio.highlight.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,9 @@ +(function ($) { + Drupal.behaviors.BiblioHighlight = { + attach: function (context, settings) { + $('input#biblio-highlight', context).click(function(e) { + $("div.suspect").toggleClass('biblio-highlight'); + }); + } + }; +}(jQuery)); \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/biblio.nodeformbuttonhide.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/misc/biblio.nodeformbuttonhide.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,5 @@ +(function ($, Drupal, window, document, undefined) { + $(document).ready(function() { + $("#edit-biblio-next").addClass("element-invisible"); + }); +})(jQuery, Drupal, this, this.document); \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/minus.png Binary file sites/all/modules/biblio/misc/minus.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/plus-minus.png Binary file sites/all/modules/biblio/misc/plus-minus.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/misc/plus.png Binary file sites/all/modules/biblio/misc/plus.png has changed diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/CSL.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/CSL.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1744 @@ +init($csl, $lang); + } + } + + function init($csl, $lang) { + $this->mapper = new csl_mapper(); + $this->quash = array(); + + $csl_doc = new DOMDocument(); + + if ($csl_doc->loadXML($csl)) { + + $style_nodes = $csl_doc->getElementsByTagName('style'); + if ($style_nodes) { + foreach ($style_nodes as $style) { + $this->style = new csl_style($style); + } + } + + $info_nodes = $csl_doc->getElementsByTagName('info'); + if ($info_nodes) { + foreach ($info_nodes as $info) { + $this->info = new csl_info($info); + } + } + + $this->locale = new csl_locale($lang); + $this->locale->set_style_locale($csl_doc); + + + $macro_nodes = $csl_doc->getElementsByTagName('macro'); + if ($macro_nodes) { + $this->macros = new csl_macros($macro_nodes, $this); + } + + $citation_nodes = $csl_doc->getElementsByTagName('citation'); + foreach ($citation_nodes as $citation) { + $this->citation = new csl_citation($citation, $this); + } + + $bibliography_nodes = $csl_doc->getElementsByTagName('bibliography'); + foreach ($bibliography_nodes as $bibliography) { + $this->bibliography = new csl_bibliography($bibliography, $this); + } + } + } + function render($data, $mode = NULL) { + $text = ''; + switch ($mode) { + case 'citation': + $text .= (isset($this->citation))? $this->citation->render($data) : ''; + break; + case 'bibliography': + default: + $text .= (isset($this->bibliography))? $this->bibliography->render($data) : ''; + break; + } + return $text; + } + + function render_macro($name, $data, $mode) { + return $this->macros->render_macro($name, $data, $mode); + } + + function get_locale($type, $arg1, $arg2 = NULL, $arg3 = NULL) { + return $this->locale->get_locale($type, $arg1, $arg2, $arg3); + } + + function map_field($field) { + if ($this->mapper) { + return $this->mapper->map_field($field); + } + return ($field); + } + function map_type($field) { + if ($this->mapper) { + return $this->mapper->map_type($field); + } + return ($field); + } +} + +class csl_factory { + public static function create($dom_node, $citeproc = NULL) { + $class_name = 'csl_' . str_replace('-', '_', $dom_node->nodeName); + if (class_exists($class_name)) { + return new $class_name($dom_node, $citeproc); + } + else { + return NULL; + } + } +} + +class csl_collection { + protected $elements = array(); + + function add_element($elem) { + if (isset($elem)) $this->elements[] = $elem; + } + + function render($data, $mode = NULL) {} + + function format($text) {return $text;} + +} + +class csl_element extends csl_collection { + protected $attributes = array(); + protected $citeproc; + + function __construct($dom_node = NULL, $citeproc = NULL) { + + $this->citeproc = &$citeproc; + $this->set_attributes($dom_node); + $this->init($dom_node, $citeproc); + + } + + function init($dom_node, $citeproc) { + if (!$dom_node) return; + + foreach ($dom_node->childNodes as $node) { + if ($node->nodeType == 1) { + $this->add_element(csl_factory::create($node, $citeproc)); + } + } + } + + function __set($name, $value) { + $this->attributes[$name] = $value; + } + + function __isset($name) { + return isset($this->attributes[$name]); + } + + function __unset($name) { + unset($this->attributes[$name]); + } + + function &__get($name = NULL) { + $null = NULL; + if (array_key_exists($name, $this->attributes)) { + return $this->attributes[$name]; + } + if (isset($this->{$name})) { + return $this->{$name}; + } + return $null; + + } + + function set_attributes($dom_node) { + $att = array(); + $element_name = $dom_node->nodeName; + if (isset($dom_node->attributes->length)) { + for ($i=0; $i < $dom_node->attributes->length; $i++) { + $value = $dom_node->attributes->item($i)->value; + $name = str_replace(' ', '_', $dom_node->attributes->item($i)->name); + if ($name == 'type' ) { + $value = $this->citeproc->map_type($value); + } + + if (($name == 'variable' || $name == 'is-numeric') && $element_name != 'label') { + $value = $this->citeproc->map_field($value); + } + $this->{$name} = $value; + } + } + } + + function get_attributes() { + return $this->attributes; + } + + function get_hier_attributes() { + $hier_attr = array(); + $hier_names = array('and', 'delimiter-precedes-last', 'et-al-min', 'et-al-use-first', + 'et-al-subsequent-min', 'et-al-subsequent-use-first', 'initialize-with', + 'name-as-sort-order', 'sort-separator', 'name-form', 'name-delimiter', + 'names-delimiter'); + foreach ($hier_names as $name) { + if (isset($this->attributes[$name])) { + $hier_attr[$name] = $this->attributes[$name]; + } + } + return $hier_attr; + } + + function name($name = NULL) { + if ($name) { + $this->name = $name; + } + else { + return str_replace(' ', '_', $this->name); + } + } + +} + +class csl_rendering_element extends csl_element { + + function render($data, $mode = NULL) { + $text = ''; + $text_parts = array(); + + $delim = $this->delimiter; + foreach ($this->elements as $element) { + $text_parts[] = $element->render($data, $mode); + } + $text = implode($delim, $text_parts); // insert the delimiter if supplied. + + return $this->format($text); + } + +} + +class csl_format extends csl_rendering_element { + protected $no_op; + protected $format; + + function __construct($dom_node = NULL, $citeproc = NULL) { + parent::__construct($dom_node, $citeproc); + $this->init_formatting(); + } + + function init_formatting() { + $this->no_op = TRUE; + $this->format = ''; + if (isset($this->quotes) && strtolower($this->quotes) == "true") { + $this->quotes = array(); + $this->quotes['punctuation-in-quote'] = $this->citeproc->get_locale('style_option', 'punctuation-in-quote'); + $this->quotes['open-quote'] = $this->citeproc->get_locale('term', 'open-quote'); + $this->quotes['close-quote'] = $this->citeproc->get_locale('term', 'close-quote'); + $this->quotes['open-inner-quote'] = $this->citeproc->get_locale('term', 'open-inner-quote'); + $this->quotes['close-inner-quote'] = $this->citeproc->get_locale('term', 'close-inner-quote'); + $this->no_op = FALSE; + } + if (isset($this->{'prefix'})) $this->no_op = FALSE; + if (isset($this->{'suffix'})) $this->no_op = FALSE; + if (isset($this->{'display'})) $this->no_op = FALSE; + + $this->format .= (isset($this->{'font-style'})) ? 'font-style: ' . $this->{'font-style'} . ';' : ''; + $this->format .= (isset($this->{'font-family'})) ? 'font-family: ' . $this->{'font-family'} . ';' : ''; + $this->format .= (isset($this->{'font-weight'})) ? 'font-weight: ' . $this->{'font-weight'} . ';' : ''; + $this->format .= (isset($this->{'font-variant'})) ? 'font-variant: ' . $this->{'font-variant'} . ';' : ''; + $this->format .= (isset($this->{'text-decoration'})) ? 'text-decoration: ' . $this->{'text-decoration'} . ';' : ''; + $this->format .= (isset($this->{'vertical-align'})) ? 'vertical-align: ' . $this->{'vertical-align'} . ';' : ''; + // $this->format .= (isset($this->{'display'}) && $this->{'display'} == 'indent') ? 'padding-left: 25px;' : ''; + + if (isset($this->{'text-case'}) || + !empty($this->format) || + !empty($this->span_class) || + !empty($this->div_class)) { + $this->no_op = FALSE; + } + + } + + function format($text) { + + if (empty($text) || $this->no_op) return $text; + $quotes = $this->quotes; + $quotes = is_array($quotes) ? $quotes : array(); + + if (isset($this->{'text-case'})) { + switch ($this->{'text-case'}) { + case 'uppercase': + $text = drupal_strtoupper($text); + break; + case 'lowercase': + $text = drupal_strtolower($text); + break; + case 'capitalize-all': + case 'title': + $text = mb_convert_case($text, MB_CASE_TITLE); + break; + case 'capitalize-first': + $text = drupal_ucfirst($text); + break; + } + } + + $prefix = $this->prefix; + $prefix .= isset($quotes['open-quote']) ? $quotes['open-quote'] : ''; + $suffix = $this->suffix; + if (isset($quotes['close-quote']) && !empty($suffix) && isset($quotes['punctuation-in-quote'])) { + if (strpos($suffix, '.') !== FALSE || strpos($suffix, ',') !== FALSE) { + $suffix = $suffix . $quotes['close-quote']; + } + } + elseif (isset($quotes['close-quote'])) { + $suffix = $quotes['close-quote'] . $suffix; + } + if (!empty($suffix)) { // gaurd against repeaded suffixes... + $no_tags = strip_tags($text); + if (strlen($no_tags) && ($no_tags[(strlen($no_tags) - 1)] == $suffix[0]) ) { + $suffix = substr($suffix, 1); + } + } + + if (!empty($this->format) || !empty($this->span_class)) { + $style = (!empty($this->format)) ? 'style="' . $this->format . '"' : ''; + $class = (!empty($this->span_class)) ? 'class="' . $this->span_class . '"' : ''; + $text = '' . $text . ''; + } + $div_class = $div_style = ''; + if (!empty($this->div_class)) { + $div_class = (!empty($this->div_class)) ? 'class="' . $this->div_class . '"' : ''; + } + if ($this->display == 'indent') { + $div_style = 'style="text-indent: 0px; padding-left: 45px;"'; + } + if ($div_class || $div_style) { + return '
    ' . $prefix . $text . $suffix . '
    '; + } + + return $prefix . $text . $suffix; + } + +} + +class csl_info { + public $title; + public $id; + public $authors = array(); + public $links = array(); + + function __construct($dom_node) { + $name = array(); + foreach ($dom_node->childNodes as $node) { + if ($node->nodeType == 1) { + switch ($node->nodeName) { + case 'author': + case 'contributor': + foreach ($node->childNodes as $authnode) { + if ($node->nodeType == 1) { + $name[$authnode->nodeName] = $authnode->nodeValue; + } + } + $this->authors[] = $name; + break; + case 'link': + foreach ($node->attributes as $attribute) { + $this->links[] = $attribute->value; + } + break; + default: + $this->{$node->nodeName} = $node->nodeValue; + } + } + } + + } +} + +class csl_terms { + +} + +class csl_name extends csl_format { + private $name_parts = array(); + private $attr_init = FALSE; + + function __construct($dom_node, $citeproc = NULL) { + + $tags = $dom_node->getElementsByTagName('name-part'); + if ($tags) { + foreach ($tags as $tag) { + $name_part = $tag->getAttribute('name'); + $tag->removeAttribute('name'); + for ($i=0; $i < $tag->attributes->length; $i++) { + $value = $tag->attributes->item($i)->value; + $name = str_replace(' ', '_', $tag->attributes->item($i)->name); + $this->name_parts[$name_part][$name] = $value; + } + } + } + + parent::__construct($dom_node, $citeproc); + } + + function init_formatting() { + $this->no_op = array(); + $this->format = array(); + $this->base = $this->get_attributes(); + $this->format['base'] = ''; + $this->format['family'] = ''; + $this->format['given'] = ''; + $this->no_op['base'] = TRUE; + $this->no_op['family'] = TRUE; + $this->no_op['given'] = TRUE; + + if (isset($this->prefix)) { + $this->no_op['base'] = FALSE; + } + if (isset($this->suffix)) { + $this->no_op['base'] = FALSE; + } + $this->init_format($this->base); + + + if (!empty($this->name_parts)) { + foreach ($this->name_parts as $name => $formatting) { + $this->init_format($formatting, $name); + } + } + } + + function init_attrs($mode) { + // $and = $this->get_attributes('and'); + if (isset($this->citeproc)) { + $style_attrs = $this->citeproc->style->get_hier_attributes(); + $mode_attrs = $this->citeproc->{$mode}->get_hier_attributes(); + $this->attributes = array_merge($style_attrs, $mode_attrs, $this->attributes); + } + if (isset($this->and)) { + if ($this->and == 'text') { + $this->and = $this->citeproc->get_locale('term', 'and'); + } + elseif ($this->and == 'symbol') { + $this->and = '&'; + } + } + if (!isset($this->delimiter)) { + $this->delimiter = $this->{'name-delimiter'} ; + } + if (!isset($this->alnum)) { + list($this->alnum, $this->alpha, $this->cntrl, $this->dash, + $this->digit, $this->graph, $this->lower, $this->print, + $this->punct, $this->space, $this->upper, $this->word, + $this->patternModifiers) = $this->get_regex_patterns(); + } + $this->dpl = $this->{'delimiter-precedes-last'}; + $this->sort_separator = isset($this->{'sort-separator'}) ? $this->{'sort-separator'} : ', '; + + $this->delimiter = isset($this->{'name-delimiter'}) ? $this->{'name-delimiter'} : (isset($this->delimiter) ? $this->delimiter : ', '); + + $this->form = isset($this->{'name-form'}) ? $this->{'name-form'} : (isset($this->form) ? $this->form : 'long'); + $this->attr_init = $mode; + } + + function init_format($attribs, $part = 'base') { + if (!isset($this->{$part})) { + $this->{$part} = array(); + } + if (isset($attribs['quotes']) && strtolower($attribs['quotes']) == 'true') { + $this->{$part}['open-quote'] = $this->citeproc->get_locale('term', 'open-quote'); + $this->{$part}['close-quote'] = $this->citeproc->get_locale('term', 'close-quote'); + $this->{$part}['open-inner-quote'] = $this->citeproc->get_locale('term', 'open-inner-quote'); + $this->{$part}['close-inner-quote'] = $this->citeproc->get_locale('term', 'close-inner-quote'); + $this->no_op[$part] = FALSE; + } + + if (isset($attribs['prefix'])) $this->{$part}['prefix'] = $attribs['prefix']; + if (isset($attribs['suffix'])) $this->{$part}['suffix'] = $attribs['suffix']; + + $this->format[$part] .= (isset($attribs['font-style'])) ? 'font-style: ' . $attribs['font-style'] . ';' : ''; + $this->format[$part] .= (isset($attribs['font-family'])) ? 'font-family: ' . $attribs['font-family'] . ';' : ''; + $this->format[$part] .= (isset($attribs['font-weight'])) ? 'font-weight: ' . $attribs['font-weight'] . ';' : ''; + $this->format[$part] .= (isset($attribs['font-variant'])) ? 'font-variant: ' . $attribs['font-variant'] . ';' : ''; + $this->format[$part] .= (isset($attribs['text-decoration'])) ? 'text-decoration: ' . $attribs['text-decoration'] . ';' : ''; + $this->format[$part] .= (isset($attribs['vertical-align'])) ? 'vertical-align: ' . $attribs['vertical-align'] . ';' : ''; + + if (isset($attribs['text-case'])) { + $this->no_op[$part] = FALSE; + $this->{$part}['text-case'] = $attribs['text-case']; + } + if (!empty($this->format[$part])) $this->no_op[$part] = FALSE; + + } + + function format($text, $part = 'base') { + + if (empty($text) || $this->no_op[$part]) return $text; + if (isset($this->{$part}['text-case'])) { + switch ($this->{$part}['text-case']) { + case 'uppercase': + $text = drupal_strtoupper($text); + break; + case 'lowercase': + $text = drupal_strtolower($text); + break; + case 'capitalize-all': + $text = mb_convert_case($text, MB_CASE_TITLE); + break; + case 'capitalize-first': + $text = drupal_ucfirst($text); + break; + } + } + $open_quote = isset($this->{$part}['open-quote']) ? $this->{$part}['open-quote'] : ''; + $close_quote = isset($this->{$part}['close-quote']) ? $this->{$part}['close-quote'] : ''; + $prefix = isset($this->{$part}['prefix']) ? $this->{$part}['prefix'] : ''; + $suffix = isset($this->{$part}['suffix']) ? $this->{$part}['suffix'] : ''; + if ($text[(strlen($text) -1)] == $suffix) unset($suffix); + if (!empty($this->format[$part])) { + $text = '' . $text . ''; + } + return $prefix . $open_quote . $text . $close_quote . $suffix; + } + + function author_link($author) { + $base = variable_get('biblio_base', 'biblio'); + $options = array(); + + if (isset($_GET['sort'])) { + $options['query']['sort'] = $_GET['sort']; + } + if (isset($_GET['order'])) { + $options['query']['order'] = $_GET['order']; + } + + $html = l(trim($author['name']), "$base/author/" . $author['cid'], $options ); + + return $html; + } + + function render($names, $mode = NULL) { + $text = ''; + $authors = array(); + $count = 0; + $auth_count = 0; + $et_al_triggered = FALSE; + + if (!$this->attr_init || $this->attr_init != $mode) $this->init_attrs($mode); + + $initialize_with = $this->{'initialize-with'}; + $options = array('html' => FALSE); + + foreach ($names as $rank => $name) { + if (empty($name['literal'])) { + if (!isset($name['lastname'])) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $name = biblio_parse_author($name); // this is needed for form preview to fill in all fields + } + $count++; + if (!empty($name['firstname']) && isset($initialize_with)) { + $name['firstname'] = preg_replace("/([$this->upper])[$this->lower]+/$this->patternModifiers", '\\1', $name['firstname']); + $name['firstname'] = preg_replace("/(?<=[-$this->upper]) +(?=[-$this->upper])/$this->patternModifiers", "", $name['firstname']); + $name['initials'] = $name['firstname'] . $name['initials']; + } + if (isset($name['initials'])) { + // within initials, remove any dots: + $name['initials'] = preg_replace("/([$this->upper])\.+/$this->patternModifiers", "\\1", $name['initials']); + // within initials, remove any spaces *between* initials: + $name['initials'] = preg_replace("/(?<=[-$this->upper]) +(?=[-$this->upper])/$this->patternModifiers", "", $name['initials']); + if (isset($this->citeproc->style) && $this->citeproc->style->{'initialize-with-hyphen'} == 'false') { + $name['initials'] = preg_replace("/-/", '', $name['initials']); + } + // within initials, add a space after a hyphen, but only if ... + if (preg_match("/ $/", $initialize_with)) {// ... the delimiter that separates initials ends with a space + $name['initials'] = preg_replace("/-(?=[$this->upper])/$this->patternModifiers", "- ", $name['initials']); + } + // then, separate initials with the specified delimiter: + $name['initials'] = preg_replace("/([$this->upper])(?=[^$this->lower]+|$)/$this->patternModifiers", "\\1$initialize_with", $name['initials']); + // $shortenInitials = (isset($options['numberOfInitialsToKeep'])) ? $options['numberOfInitialsToKeep'] : FALSE; + // if ($shortenInitials) $given = drupal_substr($given, 0, $shortenInitials); + if (isset($initialize_with)) { + $name['firstname'] = $name['initials']; + // if ($shortenInitials) $name['firstname'] = drupal_substr($name['firstname'], 0, $shortenInitials); + } + elseif (!empty($name['firstname'])) { + $name['firstname'] = $name['firstname'] . ' ' . $name['initials']; + } + elseif (empty($name['firstname'])) { + $name['firstname'] = $name['initials']; + } + } + $given = $this->format($name['firstname'], 'given'); + if (isset($name['lastname'])) { + if (!empty($name['prefix'])) { + $name['lastname'] = $name['prefix'] . ' ' . $name['lastname']; + } + if (!empty($name['suffix'])) { + $name['lastname'] = $name['lastname'] . ', ' . $name['suffix']; + } + + $name['lastname'] = $this->format($name['lastname'], 'family'); + if ($this->form == 'short') { + $text = $name['lastname']; + } + else { + switch ($this->{'name-as-sort-order'}) { + case 'first' && $rank == 0: + case 'all': + $text = $name['lastname'] . $this->sort_separator . $given; + break; + default: + $text = $given . ' ' . $name['lastname'] ; + } + } + $text = $this->format($text); + $name['name'] = $text; + if (strstr($text, 'div') || strstr($text, 'span')) { + $options = array('html' => TRUE); + } + else { + $options = array('html' => FALSE); + } + } + } + if (variable_get('biblio_author_links', 1)) { + $text = theme('biblio_author_link', array('author' => $name, 'options' => $options)); + } + + $authors[] = $text; + + if (isset($this->{'et-al-min'}) && $count >= $this->{'et-al-min'}) break; + } + if (isset($this->{'et-al-min'}) && + $count >= $this->{'et-al-min'} && + isset($this->{'et-al-use-first'}) && + $count >= $this->{'et-al-use-first'} && + count($names) > $this->{'et-al-use-first'}) { + if ($this->{'et-al-use-first'} < $this->{'et-al-min'}) { + for ($i = $this->{'et-al-use-first'}; $i < $count; $i++) { + unset($authors[$i]); + } + } + if ($this->etal) { + $etal = $this->etal->render(); + } + else { + $etal = $this->citeproc->get_locale('term', 'et-al'); + } + $et_al_triggered = TRUE; + } + + if (!empty($authors) && !$et_al_triggered) { + $auth_count = count($authors); + if (isset($this->and) && $auth_count > 1) { + $authors[$auth_count-1] = $this->and . ' ' . $authors[$auth_count-1]; //stick an "and" in front of the last author if "and" is defined + } + } + + $text = implode($this->delimiter, $authors); + + if (!empty($authors) && $et_al_triggered) { + switch ($this->{'delimiter-precedes-et-al'}) { + case 'never': + $text = $text . " $etal"; + break; + case 'always': + $text = $text . "$this->delimiter$etal"; + break; + default: + $text = count($authors) == 1 ? $text . " $etal" : $text . "$this->delimiter$etal"; + } + } + + if ($this->form == 'count') { + if (!$et_al_triggered) { + return (int)count($authors); + } + else { + return (int)(count($authors) - 1); + } + } + // strip out the last delimiter if not required + if (isset($this->and) && $auth_count > 1) { + $last_delim = strrpos($text, $this->delimiter . $this->and); + switch ($this->dpl) { //dpl == delimiter proceeds last + case 'always': + return $text; + break; + case 'never': + return substr_replace($text, ' ', $last_delim, strlen($this->delimiter)); + break; + case 'contextual': + default: + if ($auth_count < 3) { + return substr_replace($text, ' ', $last_delim, strlen($this->delimiter)); + } + } + } + return $text ; + } + + function get_regex_patterns() { + // Checks if PCRE is compiled with UTF-8 and Unicode support + if (!@preg_match('/\pL/u', 'a')) { + // probably a broken PCRE library + return $this->get_latin1_regex(); + } + else { + // Unicode safe filter for the value + return $this->get_utf8_regex(); + } + } + + function get_latin1_regex() { + $alnum = "[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 letters: + $alpha = "[:alpha:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 control characters: + $cntrl = "[:cntrl:]"; + // Matches ISO-8859-1 dashes & hyphens: + $dash = "-–"; + // Matches ISO-8859-1 digits: + $digit = "[\d]"; + // Matches ISO-8859-1 printing characters (excluding space): + $graph = "[:graph:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 lower case letters: + $lower = "[:lower:]äåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 printing characters (including space): + $print = "[:print:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 punctuation: + $punct = "[:punct:]"; + // Matches ISO-8859-1 whitespace (separating characters with no visual representation): + $space = "[\s]"; + // Matches ISO-8859-1 upper case letters: + $upper = "[:upper:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆ"; + // Matches ISO-8859-1 "word" characters: + $word = "_[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables: + // More info: + $patternModifiers = ""; + + return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers); + + } + function get_utf8_regex() { + // Matches Unicode letters & digits: + $alnum = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:alnum:]" + // Matches Unicode letters: + $alpha = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}"; // Unicode-aware equivalent of "[:alpha:]" + // Matches Unicode control codes & characters not in other categories: + $cntrl = "\p{C}"; // Unicode-aware equivalent of "[:cntrl:]" + // Matches Unicode dashes & hyphens: + $dash = "\p{Pd}"; + // Matches Unicode digits: + $digit = "\p{Nd}"; // Unicode-aware equivalent of "[:digit:]" + // Matches Unicode printing characters (excluding space): + $graph = "^\p{C}\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:graph:]" + // Matches Unicode lower case letters: + $lower = "\p{Ll}\p{M}"; // Unicode-aware equivalent of "[:lower:]" + // Matches Unicode printing characters (including space): + $print = "\P{C}"; // same as "^\p{C}", Unicode-aware equivalent of "[:print:]" + // Matches Unicode punctuation (printing characters excluding letters & digits): + $punct = "\p{P}"; // Unicode-aware equivalent of "[:punct:]" + // Matches Unicode whitespace (separating characters with no visual representation): + $space = "\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:space:]" + // Matches Unicode upper case letters: + $upper = "\p{Lu}\p{Lt}"; // Unicode-aware equivalent of "[:upper:]" + // Matches Unicode "word" characters: + $word = "_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:word:]" (or "[:alnum:]" plus "_") + // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables: + // More info: + $patternModifiers = "u"; // the "u" (PCRE_UTF8) pattern modifier causes PHP/PCRE to treat pattern strings as UTF-8 + return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers); + } + +} + +class csl_names extends csl_format { + private $substitutes; + + function init_formatting() { + $this->span_class = 'biblio-authors'; + parent::init_formatting(); + + } + + function init($dom_node, $citeproc) { + $etal = ''; + $tag = $dom_node->getElementsByTagName('substitute')->item(0); + if ($tag) { + $this->substitutes = csl_factory::create($tag, $citeproc); + $dom_node->removeChild($tag); + } + + $tag = $dom_node->getElementsByTagName('et-al')->item(0); + if ($tag) { + $etal = csl_factory::create($tag, $citeproc); + $dom_node->removeChild($tag); + } + + $var = $dom_node->getAttribute('variable'); + foreach ($dom_node->childNodes as $node) { + if ($node->nodeType == 1) { + $element = csl_factory::create($node, $citeproc); + if (($element instanceof csl_label)) $element->variable = $var; + if (($element instanceof csl_name) && $etal) { + $element->etal = $etal; + } + $this->add_element($element); + } + } + } + + function render($data, $mode = NULL) { + $matches = array(); + $variable_parts = array(); + + if (!isset($this->delimiter)) { + $style_delimiter = $this->citeproc->style->{'names-delimiter'}; + $mode_delimiter = $this->citeproc->{$mode}->{'names-delimiter'}; + $this->delimiter = (isset($mode_delimiter)) ? $mode_delimiter : (isset($style_delimiter) ? $style_delimiter : ''); + } + + $variables = explode(' ', $this->variable); + + foreach ($variables as $var) { + if (in_array($var, $this->citeproc->quash)) continue; + list($contributor, $category) = explode(':', $var); + if ((isset($data->{$contributor}) && !empty($data->{$contributor})) && $this->_get_category($data->{$contributor}, $category) ) { + $matches[] = $var; + } + } + + if (empty($matches)) { // we don't have any primary suspects, so lets check the substitutes... + if (isset($this->substitutes)) { + foreach ($this->substitutes->elements as $element) { + if (($element instanceof csl_names)) { //test to see if any of the other names variables has content + $sub_variables = explode(' ', $element->variable); + foreach ($sub_variables as $var) { + list($contributor, $category) = explode(':', $var); + if ((isset($data->{$contributor}) && !empty($data->{$contributor})) && $this->_get_category($data->{$contributor}, $category)) { + $matches[] = $var; + $this->citeproc->quash[] = $var; + } + } + } + else { // if it's not a "names" element, just render it + $text = $element->render($data, $mode); + $this->citeproc->quash[] = isset($element->variable) ? $element->variable : $element->var; + if (!empty($text)) $variable_parts[] = $text; + } + if (!empty($matches)) break; + } + } + } + + foreach ($matches as $var) { + if (in_array($var, $this->citeproc->quash) && in_array($var, $variables)) continue; + $text = ''; + list($contributor, $category) = explode(':', $var); + if (!empty($contributor) && $authors = $this->_get_category($data->{$contributor}, $category)) { + foreach ($this->elements as $element) { + if (is_a($element, 'csl_label')) { + $element->variable = $this->_get_csl_name_variable($category); + $text .= $element->render($authors, $mode); + } + elseif (is_a($element, 'csl_name')) { + $text .= $element->render($authors, $mode); + } + } + } + if (!empty($text)) $variable_parts[] = $text; + } + + if (!empty($variable_parts)) { + $text = implode($this->delimiter, $variable_parts); + return $this->format($text); + } + + return ; + } + + private function _get_category($contributors, $category) { + $authors = array(); + foreach ($contributors as $author) { + if ($author['auth_category'] == $category) { + $authors[] = $author; + } + } + return count($authors) ? $authors : FALSE; + } + + private function _get_csl_name_variable($category) { + switch ($category) { + case 1: + return 'author'; + break; + case 2: + return 'editor'; + break; + case 3: + return 'translator'; + break; + default: + } + } +} + +class csl_date extends csl_format { + + function init($dom_node, $citeproc) { + $locale_elements = array(); + + if ($form = $this->form) { + $local_date = $this->citeproc->get_locale('date_options', $form); + $dom_elem = dom_import_simplexml($local_date[0]); + if ($dom_elem) { + foreach ($dom_elem->childNodes as $node) { + if ($node->nodeType == 1) { + $locale_elements[] = csl_factory::create($node, $citeproc); + } + } + } + foreach ($dom_node->childNodes as $node) { + if ($node->nodeType == 1) { + $element = csl_factory::create($node, $citeproc); + + foreach ($locale_elements as $key => $locale_element) { + if ($locale_element->name == $element->name) { + $locale_elements[$key]->attributes = array_merge($locale_element->attributes, $element->attributes); + $locale_elements[$key]->format = $element->format; + break; + } + + else { + $locale_elements[] = $element; + } + } + } + } + if ($date_parts = $this->{'date-parts'}) { + $parts = explode('-', $date_parts); + foreach ($locale_elements as $key => $element) { + if (array_search($element->name, $parts) === FALSE) { + unset($locale_elements[$key]); + } + } + if (count($locale_elements) != count($parts)) { + foreach ($parts as $part) { + $element = new csl_date_part(); + $element->name = $part; + $locale_elements[] = $element; + } + } + // now re-order the elements + foreach ($parts as $part) { + foreach ($locale_elements as $key => $element) + if ($element->name == $part) { + $this->elements[] = $element; + unset($locale_elements[$key]); + } + } + + } + else { + $this->elements = $locale_elements; + } + } + else { + parent::init($dom_node, $citeproc); + } + + + } + + function render($data, $mode = NULL) { + $date_parts = array(); + $text = ''; + + if (($var = $this->variable) && isset($data->{$var})) { + if (is_array($data->{$var})) { + $date = $data->{$var}; + } + else { + $date = array($data->{$var}); + } + foreach ($this->elements as $element) { + $date_parts[] = $element->render($date, $mode); + } + $text = implode($this->delimiter, $date_parts); + } +// else { +// $text = $this->citeproc->get_locale('term', 'no date'); +// } + + return $this->format($text); + } +} + +class csl_date_part extends csl_format { + + function render($date, $mode = NULL) { + $text = ''; + + switch ($this->name) { + case 'year': + $text = (isset($date[0])) ? $date[0] : ''; + if ($text > 0 && $text < 500) { + $text = $text . $this->citeproc->get_locale('term', 'ad'); + } + elseif ($text < 0) { + $text = $text * -1; + $text = $text . $this->citeproc->get_locale('term', 'bc'); + } + //return ((isset($this->prefix))? $this->prefix : '') . $date[0] . ((isset($this->suffix))? $this->suffix : ''); + break; + case 'month': + $text = (isset($date[1])) ? $date[1] : ''; + if (empty($text) || $text < 1 || $text > 12) return; + // $form = $this->form; + switch ($this->form) { + case 'numeric': break; + case 'numeric-leading-zeros': + if ($text < 10) { + $text = '0' . $text; + break; + } + break; + case 'short': + $month = 'month-' . sprintf('%02d', $text); + $text = $this->citeproc->get_locale('term', $month, 'short'); + break; + default: + $month = 'month-' . sprintf('%02d', $text); + $text = $this->citeproc->get_locale('term', $month); + break; + } + break; + case 'day': + $text = (isset($date[2])) ? $date[2] : ''; + break; + } + + return $this->format($text); + } +} + +class csl_number extends csl_format { + + function render($data, $mode = NULL) { + $var = $this->variable; + + if (!$var || empty($data->$var)) return; + + // $form = $this->form; + + switch ($this->form) { + case 'ordinal': + $text = $this->ordinal($data->$var); + break; + case 'long-ordinal': + $text = $this->long_ordinal($data->$var); + break; + case 'roman': + $text = $this->roman($data->$var); + break; + case 'numeric': + default: + $text = $data->$var; + break; + } + return $this->format($text); + } + + function ordinal($num) { + if ( ($num/10)%10 == 1) { + $num .= $this->citeproc->get_locale('term', 'ordinal-04'); + } + elseif ( $num%10 == 1) { + $num .= $this->citeproc->get_locale('term', 'ordinal-01'); + } + elseif ( $num%10 == 2) { + $num .= $this->citeproc->get_locale('term', 'ordinal-02'); + } + elseif ( $num%10 == 3) { + $num .= $this->citeproc->get_locale('term', 'ordinal-03'); + } + else { + $num .= $this->citeproc->get_locale('term', 'ordinal-04'); + } + return $num; + + } + + function long_ordinal($num) { + $num = sprintf("%02d", $num); + $ret = $this->citeproc->get_locale('term', 'long-ordinal-' . $num); + if (!$ret) { + return $this->ordinal($num); + } + return $ret; + } + + function roman($num) { + $ret = ""; + if ($num < 6000) { + $ROMAN_NUMERALS = array( + array( "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" ), + array( "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" ), + array( "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" ), + array( "", "m", "mm", "mmm", "mmmm", "mmmmm") + ); + $numstr = strrev($num); + $len = strlen($numstr); + for ($pos = 0; $pos < $len; $pos++) { + $n = $numstr[$pos]; + $ret = $ROMAN_NUMERALS[$pos][$n] . $ret; + } + } + + return $ret; + } + +} + +class csl_text extends csl_format { + public $source; + protected $var; + + function init($dom_node, $citeproc) { + foreach (array('variable', 'macro', 'term', 'value') as $attr) { + if ($dom_node->hasAttribute($attr)) { + $this->source = $attr; + if ($this->source == 'macro') { + $this->var = str_replace(' ', '_', $dom_node->getAttribute($attr)); + } + else { + $this->var = $dom_node->getAttribute($attr); + } + } + } + } + function init_formatting() { + if ($this->variable == 'title') { + $this->span_class = 'biblio-title'; + } + parent::init_formatting(); + + } + + function render($data = NULL, $mode = NULL) { + $text = ''; + if (in_array($this->var, $this->citeproc->quash)) return; + + switch ($this->source) { + case 'variable': + if (!isset($data->{$this->variable}) || empty($data->{$this->variable}) || trim($data->{$this->variable}) == FALSE) return; + if ($this->variable == 'biblio_url') { + $text = l($data->{$this->variable}, $data->{$this->variable}); + } + else { + $text = $data->{$this->variable}; //$this->data[$this->var]; // include the contents of a variable + } + break; + case 'macro': + $macro = $this->var; + $text = $this->citeproc->render_macro($macro, $data, $mode); //trigger the macro process + break; + case 'term': + $form = (($form = $this->form)) ? $form : ''; + $text = $this->citeproc->get_locale('term', $this->var, $form); + break; + case 'value': + $text = $this->var; //$this->var; // dump the text verbatim + break; + } + + if (empty($text)) return; + $text = $this->format($text); + if ($this->variable == 'title') { + $url = biblio_get_title_url_info($data); + $text = l($text, $url['link'], $url['options']) ; + } + return $text; + } +} + +class csl_et_al extends csl_text { + + function __construct($dom_node = NULL, $citeproc = NULL) { + $this->var = 'et-al'; + $this->source = 'term'; + parent::__construct($dom_node, $citeproc); + + } +} +class csl_label extends csl_format { + private $plural; + + function render($data, $mode = NULL) { + $text = ''; + + $variables = explode(' ', $this->variable); + $form = (($form = $this->form)) ? $form : 'long'; + switch ($this->plural) { + case 'never': + $plural = 'single'; + break; + case 'always': + $plural = 'multiple'; + break; + case 'contextual': + default: + } + foreach ($variables as $variable) { + $field = $this->citeproc->map_field($variable); + if (isset($data->{$field}) && !empty($data->{$field})) { + if (!isset($this->plural) && empty($plural) && is_array($data->{$field})) { + $count = count($data->{$field}); + if ($count == 1) { + $plural = 'single'; + } + elseif ($count > 1) { + $plural = 'multiple'; + } + } + else { + $plural = $this->evaluateStringPluralism($data, $variable); + } + if (($term = $this->citeproc->get_locale('term', $variable, $form, $plural))) { + $text = $term; + break; + } + } + } + + if (empty($text)) return; + if ($this->{'strip-periods'}) $text = str_replace('.', '', $text); + return $this->format($text); + } + + function evaluateStringPluralism($data, $variable) { + $field = $this->citeproc->map_field($variable); + $str = $data->{$field}; + $plural = 'single'; + + if (!empty($str)) { +// $regex = '/(?:[0-9],\s*[0-9]|\s+and\s+|&|([0-9]+)\s*[\-\x2013]\s*([0-9]+))/'; + switch ($variable) { + case 'page': + $page_regex = "/([a-zA-Z]*)([0-9]+)\s*(?:–|-)\s*([a-zA-Z]*)([0-9]+)/"; + $err = preg_match($page_regex, $str, $m); + if ($err !== FALSE && count($m) == 0) { + $plural = 'single'; + } + elseif ($err !== FALSE && count($m)) { + $plural = 'multiple'; + } + break; + default: + } + } + return $plural; + } +} + +class csl_macro extends csl_format{ + +} + +class csl_macros extends csl_collection{ + + function __construct($macro_nodes, $citeproc) { + foreach ($macro_nodes as $macro) { + $macro = csl_factory::create($macro, $citeproc); + $this->elements[$macro->name()] = $macro; + } + } + + function render_macro($name, $data, $mode) { + return $this->elements[$name]->render($data, $mode); + } +} + +class csl_group extends csl_format{ + + function render($data, $mode = NULL) { + $text = ''; + $text_parts = array(); + + $terms = $variables = $have_variables = $element_count = 0; + foreach ($this->elements as $element) { + $element_count++; + if (($element instanceof csl_text) && + ($element->source == 'term' || + $element->source == 'value' )) { + $terms++; + } + if (($element instanceof csl_label)) $terms++; + if ($element->source == 'variable' && + isset($element->variable) && + !empty($data->{$element->variable}) + ) { + $variables++; + } + + $text = $element->render($data, $mode); + + $delimiter = $this->delimiter; + if (!empty($text)) { + if ($delimiter && ($element_count < count($this->elements))) { + //check to see if the delimiter is already the last character of the text string + //if so, remove it so we don't have two of them when we paste together the group + $stext = strip_tags(trim($text)); + if ((strrpos($stext, $delimiter[0])+1) == drupal_strlen($stext) && drupal_strlen($stext) > 1) { + $text = str_replace($stext, '----REPLACE----', $text); + $stext = drupal_substr($stext, 0, -1); + $text = str_replace('----REPLACE----', $stext, $text); + } + } + $text_parts[] = $text; + if ($element->source == 'variable' || isset($element->variable)) $have_variables++; + if ($element->source == 'macro') $have_variables++; + } + } + + if (empty($text_parts)) return; + if ($variables && !$have_variables ) return; // there has to be at least one other none empty value before the term is output + if (count($text_parts) == $terms) return; // there has to be at least one other none empty value before the term is output + + $delimiter = $this->delimiter; + $text = implode($delimiter, $text_parts); // insert the delimiter if supplied. + + + return $this->format($text); + } +} + +class csl_layout extends csl_format { + + function init_formatting() { + // $this->div_class = 'csl-entry'; + parent::init_formatting(); + } + + function render($data, $mode = NULL) { + $text = ''; + $parts = array(); + // $delimiter = $this->delimiter; + + foreach ($this->elements as $element) { + $parts[] = $element->render($data, $mode); + } + + $text = implode($this->delimiter, $parts); + + if ($mode == 'bibliography') { + return $this->format($text); + } + else { + return $text; + } + + } + +} + +class csl_citation extends csl_format{ + private $layout = NULL; + + function init($dom_node, $citeproc) { + $options = $dom_node->getElementsByTagName('option'); + foreach ($options as $option) { + $value = $option->getAttribute('value'); + $name = $option->getAttribute('name'); + $this->attributes[$name] = $value; + } + + $layouts = $dom_node->getElementsByTagName('layout'); + foreach ($layouts as $layout) { + $this->layout = new csl_layout($layout, $citeproc); + } + } + + function render($data, $mode = NULL) { + $this->citeproc->quash = array(); + + $text = $this->layout->render($data, 'citation'); + + return $this->format($text); + } + +} +class csl_bibliography extends csl_format { + private $layout = NULL; + + function init($dom_node, $citeproc) { + $hier_name_attr = $this->get_hier_attributes(); + $options = $dom_node->getElementsByTagName('option'); + foreach ($options as $option) { + $value = $option->getAttribute('value'); + $name = $option->getAttribute('name'); + $this->attributes[$name] = $value; + } + + $layouts = $dom_node->getElementsByTagName('layout'); + foreach ($layouts as $layout) { + $this->layout = new csl_layout($layout, $citeproc); + } + + } + + function init_formatting() { + // $this->div_class = 'csl-bib-body'; + parent::init_formatting(); + } + + function render($data, $mode = NULL) { + $this->citeproc->quash = array(); + $text = $this->layout->render($data, 'bibliography'); + if ($this->{'hanging-indent'} == 'true') { + $text = '
    ' . $text . '
    '; + } + $text = str_replace('?.', '?', str_replace('..', '.', $text)); + return $this->format($text); + } +} + +class csl_option { + private $name; + private $value; + + function get() { + return array($this->name => $this->value); + } +} + +class csl_options extends csl_element{ + +} + +class csl_sort extends csl_element{ + +} +class csl_style extends csl_element{ + + function __construct($dom_node = NULL, $citeproc = NULL) { + if ($dom_node) { + $this->set_attributes($dom_node); + } + } +} + +class csl_choose extends csl_element{ + + function render($data, $mode = NULL) { + foreach ($this->elements as $choice) { + if ($choice->evaluate($data)) { + return $choice->render($data, $mode); + } + } + } +} + +class csl_if extends csl_rendering_element { + + function evaluate($data) { + $match = (($match = $this->match)) ? $match : 'all'; + if (($types = $this->type)) { + $types = explode(' ', $types); + $matches = 0; + foreach ($types as $type) { + if (isset($data->biblio_type)) { + if ($data->biblio_type == $type && $match == 'any') return TRUE; + if ($data->biblio_type != $type && $match == 'all') return FALSE; + if ($data->biblio_type == $type) $matches++; + } + } + if ($match == 'all' && $matches == count($types)) return TRUE; + if ($match == 'none' && $matches == 0) return TRUE; + return FALSE; + } + if (($variables = $this->variable)) { + $variables = explode(' ', $variables); + $matches = 0; + foreach ($variables as $var) { + if (isset($data->$var) && !empty($data->$var) && $match == 'any') return TRUE; + if ((!isset($data->$var) || empty($data->$var)) && $match == 'all') return FALSE; + if (isset($data->$var) && !empty($data->$var)) $matches++; + } + if ($match == 'all' && $matches == count($variables)) return TRUE; + if ($match == 'none' && $matches == 0) return TRUE; + return FALSE; + } + if (($is_numeric = $this->{'is-numeric'})) { + $variables = explode(' ', $is_numeric); + $matches = 0; + foreach ($variables as $var) { + if (isset($data->$var)) { + if (is_numeric($data->$var) && $match == 'any') return TRUE; + if (!is_numeric($data->$var)) { + if (preg_match('/(?:^\d+|\d+$)/', $data->$var)) { + $matches++; + } + elseif ($match == 'all') { + return FALSE; + } + } + if (is_numeric($data->$var)) $matches++; + } + } + if ($match == 'all' && $matches == count($variables)) return TRUE; + if ($match == 'none' && $matches == 0) return TRUE; + return FALSE; + } + if (isset($this->locator)) $test = explode(' ', $this->type); + + return FALSE; + } +} + +class csl_else_if extends csl_if { + +} + +class csl_else extends csl_if { + + function evaluate($data = NULL) { + return TRUE; // the last else always returns TRUE + } +} + +class csl_substitute extends csl_element{ + +} + +class csl_locale { + protected $locale_xmlstring = NULL; + protected $style_locale_xmlstring = NULL; + protected $locale = NULL; + protected $style_locale = NULL; + private $module_path; + + function __construct($lang = 'en') { + $this->module_path = drupal_get_path('module', 'biblio_citeproc'); + $this->locale = new SimpleXMLElement($this->get_locales_file_name($lang)); + if ($this->locale) { + $this->locale->registerXPathNamespace('cs', 'http://purl.org/net/xbiblio/csl'); + } + } + + // SimpleXML objects cannot be serialized, so we must convert to an XML string prior to serialization + function __sleep() { + $this->locale_xmlstring = ($this->locale) ? $this->locale->asXML() : ''; + $this->style_locale_xmlstring = ($this->style_locale) ? $this->style_locale->asXML() : ''; + return array('locale_xmlstring', 'style_locale_xmlstring'); + } + + // SimpleXML objects cannot be serialized, so when un-serializing them, they must rebuild from the serialized XML string. + function __wakeup() { + $this->style_locale = (!empty($this->style_locale_xmlstring)) ? new SimpleXMLElement($this->style_locale_xmlstring) : NULL; + $this->locale = (!empty($this->locale_xmlstring)) ? new SimpleXMLElement($this->locale_xmlstring) : NULL; + if ($this->locale) { + $this->locale->registerXPathNamespace('cs', 'http://purl.org/net/xbiblio/csl'); + } + } + + function get_locales_file_name($lang) { + $lang_bases = array( + "af" => "af-ZA", + "ar" => "ar-AR", + "bg" => "bg-BG", + "ca" => "ca-AD", + "cs" => "cs-CZ", + "da" => "da-DK", + "de" => "de-DE", + "el" => "el-GR", + "en" => "en-US", + "es" => "es-ES", + "et" => "et-EE", + "fa" => "fa-IR", + "fi" => "fi-FI", + "fr" => "fr-FR", + "he" => "he-IL", + "hu" => "hu-HU", + "is" => "is-IS", + "it" => "it-IT", + "ja" => "ja-JP", + "km" => "km-KH", + "ko" => "ko-KR", + "mn" => "mn-MN", + "nb" => "nb-NO", + "nl" => "nl-NL", + "nn" => "nn-NO", + "pl" => "pl-PL", + "pt" => "pt-PT", + "ro" => "ro-RO", + "ru" => "ru-RU", + "sk" => "sk-SK", + "sl" => "sl-SI", + "sr" => "sr-RS", + "sv" => "sv-SE", + "th" => "th-TH", + "tr" => "tr-TR", + "uk" => "uk-UA", + "vi" => "vi-VN", + "zh" => "zh-CN", + ); + return (isset($lang_bases[$lang])) ? file_get_contents($this->module_path . '/locale/locales-' . $lang_bases[$lang] . '.xml') : file_get_contents($this->module_path . '/locale/locales-en-US.xml'); + } + + function get_locale($type, $arg1, $arg2 = NULL, $arg3 = NULL) { + switch ($type) { + case 'term': + $term = ''; + $form = $arg2 ? " and @form='$arg2'" : ''; + $plural = $arg3 ? "/cs:$arg3" : ''; + if ($arg2 == 'verb' || $arg2 == 'verb-short') $plural = ''; + if ($this->style_locale) { + $term = @$this->style_locale->xpath("//locale[@xml:lang='en']/terms/term[@name='$arg1'$form]$plural"); + if (!$term) { + $term = @$this->style_locale->xpath("//locale/terms/term[@name='$arg1'$form]$plural"); + } + } + if (!$term) { + $term = $this->locale->xpath("//cs:term[@name='$arg1'$form]$plural"); + } + if (isset($term[0])) { + if (isset($arg3) && isset($term[0]->{$arg3})) return (string)$term[0]->{$arg3}; + if (!isset($arg3) && isset($term[0]->single)) return (string)$term[0]->single; + return (string)$term[0]; + } + break; + case 'date_option': + $attribs = array(); + if ($this->style_locale) { + $date_part = $this->style_locale->xpath("//date[@form='$arg1']/date-part[@name='$arg2']"); + } + if (!isset($date_part)) { + $date_part = $this->locale->xpath("//cs:date[@form='$arg1']/cs:date-part[@name='$arg2']"); + } + if (isset($date_part)) { + foreach ($$date_part->attributes() as $name => $value) { + $attribs[$name] = (string)$value; + } + } + return $attribs; + break; + case 'date_options': + $options = array(); + if ($this->style_locale) { + $options = $this->style_locale->xpath("//locale[@xml:lang='en']/date[@form='$arg1']"); + if (!$options) { + $options = $this->style_locale->xpath("//locale/date[@form='$arg1']"); + } + } + if (!$options) { + $options = $this->locale->xpath("//cs:date[@form='$arg1']"); + } + if (isset($options[0]))return $options[0]; + break; + case 'style_option': + $attribs = array(); + if ($this->style_locale) { + $option = $this->style_locale->xpath("//locale[@xml:lang='en']/style-options[@$arg1]"); + if (!$option) { + $option = $this->style_locale->xpath("//locale/style-options[@$arg1]"); + } + } + if (isset($option) && !empty($option)) { + $attribs = $option[0]->attributes(); + } + if (empty($attribs)) { + $option = $this->locale->xpath("//cs:style-options[@$arg1]"); + } + foreach ($option[0]->attributes() as $name => $value) { + if ($name == $arg1) return (string)$value; + } + break; + } + } + + public function set_style_locale($csl_doc) { + $xml = ''; + $locale_nodes = $csl_doc->getElementsByTagName('locale'); + if ($locale_nodes) { + $xml_open = ''; + $xml_close = ''; + foreach ($locale_nodes as $key => $locale_node) { + $xml .= $csl_doc->saveXML($locale_node); + } + if (!empty($xml)) { + $this->style_locale = new SimpleXMLElement($xml_open . $xml . $xml_close); + } + } + } + +} + +class csl_mapper { + + function map_field($field) { + if (!isset($this->field_map)) { + $this->field_map = biblio_get_map('field_map', 'csl'); + } + + $vars = explode(' ', $field); + foreach ($vars as $key => $value) { + $vars[$key] = (!empty($this->field_map[$value])) ? $this->field_map[$value] : ''; + } + + return implode(' ', $vars); + } + + function map_type($types) { + if (!isset($this->type_map)) { + $this->type_map = biblio_get_map('type_map', 'csl'); + } + $vars = explode(' ', $types); + foreach ($vars as $key => $value) { + $vars[$key] = (!empty($this->type_map[$value])) ? $this->type_map[$value] : ''; + } + + return implode(' ', $vars); + + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.admin.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,710 @@ +expire < time()) { + if (!($style_zip_file = variable_get('biblio_citeproc_styles_zip_file', FALSE))) { + $style_zip_file = _get_zip_from_github(); + } + + if ($style_zip_file) { + $file = drupal_realpath($style_zip_file->uri); + $options = _get_csl_list_from_zip($file); + } + + if (!empty($options)) { + //expire 30 days from now + $expire = time() + 2592000; + cache_set('biblio_citeproc_styles', $options, 'cache', $expire); + } + } + else { + $options = $cache->data; + } + + + $form['available_styles'] = array( + '#type' => 'select', + '#title' => t('Available styles'), + '#size' => 15, + '#multiple' => TRUE, + '#description' => t('Choose the styles you would like to download and install.'), + ); + + $form['install'] = array( + '#type' => 'submit', + '#value' => '<--', + '#description' => t('Install the selected styles from GitHub'), + ); + $form['remove'] = array( + '#type' => 'submit', + '#value' => '-->', + '#description' => t('Un-install the selected styles'), + ); + $form['default'] = array( + '#type' => 'submit', + '#value' => t('Set as site default'), + '#submit' => array('biblio_citeproc_set_site_default'), + ); + $form['update_installed'] = array( + '#type' => 'submit', + '#value' => t('Update installed styles'), + '#submit' => array('biblio_citeproc_update_installed'), + ); + $form['update_available'] = array( + '#type' => 'submit', + '#value' => t('Update available styles'), + '#submit' => array('biblio_citeproc_update_available'), + ); + $form['edit'] = array( + '#type' => 'submit', + '#value' => t('Edit selected'), + '#submit' => array('biblio_citeproc_edit_selected'), + ); +// $form['install_all'] = array( +// '#type' => 'submit', +// '#value' => t('Install all') +// ); + + $form['#attributes']['enctype'] = 'multipart/form-data'; + + $form['import_csl_file'] = array( + '#type' => 'file', + '#title' => t('Import Local CSL file'), + '#default_value' => '', + '#size' => 60 + ); + $form['import'] = array( + '#type' => 'submit', + '#value' => t('Import'), + '#submit' => array('biblio_citeproc_csl_file_import_submit'), + ); + + $result = db_select('biblio_citeproc_styles', 'csl') + ->fields('csl', array('filename', 'title', 'id', 'sha1', 'title', 'summary', 'changed', 'updated')) + ->orderBy('filename', 'ASC') + ->execute(); + + $details = array(); + $titles = array(); + foreach ($result as $style) { + $details[$style->filename] = $style; + $titles[] = $style->title; + } + + // now remove the installed titles from the available titles list + $options = array_diff($options, $titles); + $form['available_styles']['#options'] = $options; + + $form['installed_styles'] = array( + '#type' => 'select', + '#title' => t('Installed styles'), + '#size' => 15, + '#options' => biblio_get_styles(), + '#multiple' => TRUE, + '#description' => t('Currently installed styles.'), + ); + + $form['current_default'] = array( + '#markup' => empty($details) ? '' : $details[variable_get('biblio_citeproc_style', 'ieee.csl')]->title, + ); + + $form['current_summary'] = array( + '#markup' => empty($details) ? '' : $details[variable_get('biblio_citeproc_style', 'ieee.csl')]->summary, + ); + + $timestamp = $details[variable_get('biblio_citeproc_style', 'ieee.csl')]->updated; + $updated = $timestamp ? ' ('. t('Last updated:') . ' ' . format_date($timestamp, 'medium') . ')' : ''; + + $form['current_update'] = array( + '#markup' => $updated, + ); + + return $form; +} + +function theme_biblio_citeproc_style_manager_form($variables) { + $form = $variables['form']; + $rows = array(); + $updated = drupal_render($form['current_update']); + $updated = empty($updated) ? $updated : '
    ' . $updated; + $rows[] = array( + array('data' => t('Current default style:')), + array('data' => '' . drupal_render($form['current_default']) . '' . + '
    ' . drupal_render($form['current_summary']) . '' . $updated + ), + ); + $rows[] = array( + array('data' => t('Example citation:')), + array('data' => biblio_citeproc_example_citation())); + $output = theme('table', array('rows' => $rows)); + $rows = array(); + $rows[] = array( + array('data' => drupal_render($form['installed_styles']) ), + array('data' => drupal_render($form['install']) . '
    ' . drupal_render($form['remove'])), + array('data' => drupal_render($form['available_styles'])), + ); + $rows[] = array( + array('data' => drupal_render($form['default']) . drupal_render($form['edit']) .drupal_render($form['update_installed'])), + array('data' => ''), + array('data' => drupal_render($form['update_available'])), + ); + $rows[] = array(array('data' => drupal_render($form['import_csl_file']) . drupal_render($form['import']), 'colspan' => 3) ); + $output .= theme('table', array('rows' => $rows)); + + $output .= drupal_render_children($form); + return $output; + +} +function biblio_citeproc_style_manager_form_validate($form, &$form_state) { + if ($form_state['clicked_button']['#value'] == '<--' && count( $form_state['values']['available_styles'])) { + if (count($form_state['values']['available_styles']) > 60) { + form_error($form['available_styles'], t('You may not select more than 60 styles for installation at one time')); + } + } + if ($form_state['clicked_button']['#value'] == t('Set as site default') && !count( $form_state['values']['installed_styles'])) { + form_error($form['installed_styles'], t('You must select an installed style to set as the default.')); + } + +} +function biblio_citeproc_style_manager_form_submit($form, &$form_state) { + if ($form_state['clicked_button']['#value'] == '<--' && count( $form_state['values']['available_styles'])) { + if (!($style_zip_file = variable_get('biblio_citeproc_styles_zip_file', FALSE))) { + $style_zip_file = _get_zip_from_github(); + } + + if (!$style_zip_file) { + form_set_error('<--', t('Could not get the style files from GitHub')); + } + + $file = drupal_realpath($style_zip_file->uri); + $selected = $form_state['values']['available_styles']; + _install_selected_from_zip($file, $selected); + } + + if ($form_state['clicked_button']['#value'] == '-->' && count( $form_state['values']['installed_styles'])) { + $selected = $form_state['values']['installed_styles']; + _uninstall_selected($selected); + } +} + +function biblio_citeproc_edit_selected($form, &$form_state) { + if (count( $form_state['values']['installed_styles'])) { + $style = array_shift($form_state['values']['installed_styles']); + $dest = drupal_get_destination(); + drupal_goto('admin/config/content/biblio/citeproc/styles/' . $style . '/edit'); + } +} + +function biblio_citeproc_set_site_default($form, &$form_state) { + if (count( $form_state['values']['installed_styles']) == 1) { + $def = array_shift($form_state['values']['installed_styles']); + variable_set('biblio_citeproc_style', $def); + } + else { + form_set_error('installed_styles', t('You may only select one style when setting the default')); + } +} + +function biblio_citeproc_update_installed($form, &$form_state) { + $batch_op = array( + 'title' => t('Updating all installed styles from the main GitHub repository'), + 'operations' => array( + array('_get_zip_from_github', array()), + array('biblio_citeproc_update_installed_batch', array()), + ), + 'progressive' => TRUE, + 'finished' => 'biblio_citeproc_update_installed_finished', + 'init_message' => t('Downloading file...'), + 'progress_message' => t('Updating styles...'), + 'file' => './' . drupal_get_path('module', 'biblio_citeproc') . '/biblio_citeproc.admin.inc' + ); + batch_set($batch_op); +} + +function biblio_citeproc_update_available($form, &$form_state) { + _get_zip_from_github(); +} + +function biblio_citeproc_csl_file_import_submit($form, &$form_state) { + $validators = array( + 'file_validate_extensions' => array('csl xml'), + 'biblio_citeproc_validate_csl_file' => array() + ); + + if ($import_file = file_save_upload('import_csl_file', $validators)) { + $csl = file_get_contents($import_file->uri); +// if (biblio_citeproc_validate_csl($csl)) { + _install_csl($import_file->filename, $csl); +// } + } + +} +function _get_github_repo_tree($path = '') { + $options = array(); + $tree_url = 'https://api.github.com/repos/citation-style-language/styles/contents'; + if (!empty($path)) { + $tree_url .= '/' . $path; + } + + $result= drupal_http_request($tree_url); + if ($result->code == 200) { + $tree = json_decode($result->data); + } + else { + $message = t('Attempt to get list of styles from GitHub resulted in an HTTP error: !code.', array('!code' => $result->code)); + + $cache = cache_get('github_csl_repo'); + + if ($cache) { + $message .= ' ' . t('I will use cached data instead.'); + $mess_type = 'warning'; + $options = $cache->data; + } + else { + $message .= ' ' . t('I have no cached data, so you will not be able to install new styles at this time.'); + $mess_type = 'error'; + } + drupal_set_message(check_plain($message), $mess_type); + return $options; + } + + foreach ($tree as $file) { + if ($file->type == 'file' && strstr($file->name, '.csl')) { + $options[$file->path] = basename($file->name); + } + elseif ($file->type == 'dir') { + $options = array_merge($options, _get_github_repo_tree($file->name)); + } + } + return $options; +} + +function _install_csl($name = NULL, $csl = NULL, $sha = NULL, $all = FALSE, $update = FALSE) { + static $installed = array(); + + if (empty($installed)) { + $result = db_select('biblio_citeproc_styles', 'csl') + ->fields('csl', array('filename', 'id', 'sha1', 'title')) + ->orderBy('filename', 'ASC') + ->execute(); + + $installed = array(); + foreach ($result as $style) { + $installed[$style->id] = $style; + } + } + + $xml = simplexml_load_string($csl); + + if ($xml) { + $parent = ''; + foreach ($xml->info->link as $link) { + $attrs = $link->attributes(); + if (isset($attrs['rel']) && $attrs['rel'] == 'independent-parent') { + $parent = (string)$attrs['href']; + } + } + if (!$all && !$update && !empty($parent)) { + $csl_file_contents = db_query("SELECT csl FROM {biblio_citeproc_styles} WHERE id = :parent", array(':parent' => $parent))->fetchField(); + if (!$csl_file_contents) { + _install_csl_from_github(basename($parent) . '.csl'); + } + } + + $sha1 = (isset($sha)) ? $sha : sha1($csl); + + $record = array( + 'filename' => $name, + 'parent' => $parent, + 'title' => trim((string)$xml->info->title), + 'summary' => (string)$xml->info->summary, + 'csl' => $csl, + 'sha1' => $sha1, + 'id' => (string)$xml->info->id, + 'updated' => time(), + 'changed' => 0, + ); + + if (!array_key_exists($record['id'], $installed)) { + db_insert('biblio_citeproc_styles')->fields($record)->execute(); + $installed[$record['id']] = TRUE; + return 1; + } + elseif ($record['sha1'] != $installed[$record['id']]->sha1) { + db_update('biblio_citeproc_styles')->condition('id', $record['id'])->fields($record)->execute(); + return 2; + } + elseif (($record['sha1'] == $installed[$record['id']]->sha1 && $update == FALSE)) { + $message = t('The CSL file you supplied: !name, is already installed', array('!name' => $name)); + drupal_set_message(check_plain($message), 'warning'); + } + } + else { + drupal_set_message(t('I could not parse the CSL provided as valid XML', 'error')); + } +} + +function _get_zip_from_github() { + $zip_url = 'https://github.com/citation-style-language/styles/zipball/master'; + $destination = file_build_uri('Biblio-CiteProc-Styles.zip'); + $zip_file = system_retrieve_file($zip_url, $destination, TRUE, FILE_EXISTS_REPLACE); + $usage = file_usage_list($zip_file); + if (empty($usage)) { + file_usage_add($zip_file, 'biblio_citeproc', 'csl', 0); + } + variable_set('biblio_citeproc_styles_zip_file', $zip_file); + cache_clear_all('biblio_citeproc_styles', 'cache'); + return $zip_file; +} + +function _install_csl_from_github($path, $update = FALSE) { + $csl = ''; + $github_url = 'https://api.github.com/repos/citation-style-language/styles/contents/'; + $URL = $github_url . $path; + $result = drupal_http_request($URL); + if ($result->code == 200) { + $file = json_decode($result->data); + switch ($file->encoding) { + case 'base64': + $csl = base64_decode($file->content); + break; + } + _install_csl($file->name, $csl, $file->sha, FALSE, $update); + } + else { + $message = t('Attempt to get style: %name from GitHub resulted in an HTTP error: !code.', array('%name' => $path, '!code' => $result->code)); + $mess_type = 'error'; + drupal_set_message(check_plain($message), $mess_type); + } + return; +} + +function _get_csl_list_from_zip($filename) { + $options = array(); + $za = new ZipArchive(); + + if ($za->open($filename) !== TRUE) { + $message = t('Could not open zip file containing styles: @file', array('@file' => realpath($filename))); + $message = check_plain($message); + drupal_set_message($message, 'error'); + return $options; + } + + $num_files = $za->numFiles; + + for ($i = 0; $i < $num_files; $i++) { + $name = $za->getNameIndex($i); + $name = basename($name); + if (strstr($name, '.csl')) { + $csl = $za->getFromIndex($i); + $xml = simplexml_load_string($csl); + if ($xml) { + $options[$i] = trim((string)$xml->info->title); + } + } + } + + $za->close(); + asort($options); + return $options; +} + +function _install_selected_from_zip($filename = '', $ids = array()) { + $za = new ZipArchive(); + if ($za->open($filename) == TRUE) { + foreach ($ids as $id) { + $name = $za->getNameIndex($id); + $name = basename($name); + if (strstr($name, '.csl')) { + $csl = $za->getFromIndex($id); + _install_csl($name, $csl); + } + } + $za->close(); + } +} + +function _uninstall_selected($ids = array()) { + $result = db_select('biblio_citeproc_styles', 'csl') + ->fields('csl', array('id', 'filename', 'parent')) + ->orderBy('filename', 'ASC') + ->execute(); + + foreach ($result as $csl) { + $fp[$csl->filename] = $csl->parent; + $fi[$csl->filename] = $csl->id; + } + + foreach ($ids as $id) { + db_delete('biblio_citeproc_styles')->condition('filename', $id)->execute(); + //if this is a dependent style, delete the parent style if no others are using it + if (!empty($fp[$id])) { + $parent = array_keys($fp, $fp[$id]); + if ( count($parent) == 1) { + db_delete('biblio_citeproc_styles')->condition('id', $fp[$id])->execute(); + } + } + //delete all the dependents + $children = array_keys($fp, $fi[$id]); + if (!empty($children)) { + db_delete('biblio_citeproc_styles')->condition('filename', $children, 'IN')->execute(); + } + if (variable_get('biblio_citeproc_style', 'ieee.csl') == $id) { + variable_del('biblio_citeproc_style'); + } + } + +} + +function _install_all_from_zip(&$context = NULL) { + + $zipname = $context['results']['zipname']; + + if (!empty($zipname)) { + //variable_del('github_zip'); + $zip = zip_open($zipname); + $za = new ZipArchive(); + + if ($za->open($zipname) !== TRUE) { + $message = t('Could not open zip file containing styles: @file', array('@file' => realpath($zipname))); + $message = check_plain($message); + drupal_set_message($message, 'error'); + return; + } + if (empty($context['sandbox'])) { + $context['sandbox']['progress'] = 0; + $context['results']['install_count'] = 0; + } + + $num_files = $za->numFiles; + $start = $context['sandbox']['progress']; + $end = min(($start+50), $num_files); + + for ($i = $start; $i < $end; $i++) { + $name = $za->getNameIndex($i); + $name = basename($name); + if (strstr($name, '.csl')) { + $csl = $za->getFromIndex($i); + _install_csl($name, $csl, NULL, TRUE); + $context['results']['install_count']++; + } + $context['sandbox']['progress']++; + } + $za->close(); + + if ($context['sandbox']['progress'] != $num_files) { + $context['finished'] = $context['sandbox']['progress'] / $num_files; + } + } +} + +function _csl_import_batch_finished($success, $results, $operations) { + $zipname = variable_get('github_zip', ''); + file_unmanaged_delete($zipname); + variable_del('github_zip'); +} + +function biblio_citeproc_example_citation() { + global $language; + $contributors = array( + 0 => array( + 'lastname' => 'Oneauth', + 'firstname' => 'Joe', + 'initials' => 'A', + 'auth_category' => 1, + 'cid' => -1), + 1 => array( + 'lastname' => 'Twoauth', + 'firstname' => 'John', + 'initials' => 'B', + 'auth_category' => 1, + 'cid' => -2), + ); + $node = new stdClass(); + $node->nid = -1; + $node->title = 'This is a fantastic title.'; + $node->biblio_contributors = $contributors; + $node->biblio_type = 102; + $node->biblio_year = 2010; + $node->biblio_volume = 1; + $node->biblio_issue = 2; + $node->biblio_secondary_title = 'Journal of Fantastic Articles'; + $node->biblio_pages = '424-31'; + $node->biblio_coins = ''; + return theme_biblio_citeproc_style(array('node' => $node)); + +} + +function biblio_citeproc_csl_editor($form, &$form_state, $style) { + + $csl = db_query('SELECT id,parent,csl FROM {biblio_citeproc_styles} WHERE filename = :id', array(':id' => $style))->fetchObject(); + if (!isset($csl->csl)) { + drupal_set_message(t('Biblio-CiteProc could not fetch the style file: @csl_id from the database. Check your CiteProc settings.', array('@csl_id' => $style)), 'error'); + return; + } + if (!empty($csl->parent)) { + $csl = db_query("SELECT id,csl FROM {biblio_citeproc_styles} WHERE id = :id", array(':id' => $csl->parent))->fetchObject(); + + } + if (isset($csl->csl)) { + $csl_file_contents = $csl->csl; + } + + $form['editor'] = array( + '#title' => t('Editing %style', array('%style' => $style)), + '#type' => 'text_format', + '#rows' => 40, + '#format' => 'csl', + '#default_value' => $csl_file_contents, + ); + $form['save'] = array( + '#value' => t('Save'), + '#type' => 'submit', + ); + $form['cancel'] = array( + '#value' => t('Cancel'), + '#type' => 'submit', + ); + $form['style'] = array( + '#value' => $style, + '#type' => 'hidden', + ); + $form['id'] = array( + '#value' => $csl->id, + '#type' => 'hidden', + ); + + return $form; +} +function biblio_citeproc_csl_editor_validate($form, &$form_state) { + if ($form_state['triggering_element']['#value'] == t('Save')) { + $csl = $form_state['values']['editor']['value']; + $valid = biblio_citeproc_validate_csl($csl); + if (!empty($valid)) { + form_set_error('editor', $valid[0]); + } + else { + $form_state['values']['editor']['value'] = $csl; + } + } +} +function biblio_citeproc_csl_editor_submit($form, &$form_state) { + $form_state['redirect'] = 'admin/config/content/biblio/citeproc/styles'; + + if ($form_state['triggering_element']['#value'] == t('Save')) { + $csl = $form_state['values']['editor']['value']; + $id = $form_state['values']['id']; + + $record = array( + 'id' => $id, + 'csl' => $csl, + 'sha1' => sha1($csl), + 'changed' => time(), + 'updated' => time(), + ); + drupal_write_record('biblio_citeproc_styles', $record, 'id'); + } + +} + +function biblio_citeproc_validate_csl_file($file) { + if ($file->source == 'import_csl_file') { + $csl = file_get_contents($file->uri); + return biblio_citeproc_validate_csl($csl); + } +} + +function biblio_citeproc_validate_csl($csl) { + $rng_schema = drupal_get_path('module', 'biblio_citeproc') . '/schema/csl.rng'; + $doc = new DOMDocument(); + $doc->loadXML($csl); + $updated = $doc->getElementsByTagName('updated')->item(0); + $updated->nodeValue = date(DATE_ATOM, time()); + $valid = $doc->relaxNGValidate($rng_schema); + $csl = $doc->saveXML(); + return ($valid) ? array() : array(t('The supplied CSL file did not pass CSL 1.0 validation')); + +} + +function biblio_citeproc_update_installed_batch(&$context = NULL) { + $style_zip_file = variable_get('biblio_citeproc_styles_zip_file', FALSE); + $zipname = $style_zip_file ? drupal_realpath($style_zip_file->uri) : FALSE; + + if (empty($context['sandbox']['installed'])) { + $context['sandbox']['installed'] = array(); + + $result = db_select('biblio_citeproc_styles', 'csl') + ->fields('csl', array('filename', 'id', 'sha1', 'title', 'parent', 'changed', 'updated')) + ->orderBy('filename', 'ASC') + ->execute(); + + foreach ($result as $style) { + $context['sandbox']['installed'][] = $style; + } + + $context['sandbox']['progress'] = 0; + $context['results']['update_count'] = 0; + $context['results']['updated'] = array(); + } + + if (!empty($zipname)) { + //variable_del('github_zip'); + $zip = zip_open($zipname); + $za = new ZipArchive(); + + if ($za->open($zipname) !== TRUE) { + $message = t('Could not open zip file containing styles: @file', array('@file' => realpath($zipname))); + $message = check_plain($message); + drupal_set_message($message, 'error'); + $context['finished'] = 1; + return; + } + + $num_files = count($context['sandbox']['installed']); + $start = $context['sandbox']['progress']; + $end = min(($start+10), $num_files); + + for ($i = $start; $i < $end; $i++) { + $name = $context['sandbox']['installed'][$i]->filename; + $changed = $context['sandbox']['installed'][$i]->changed; + if (($index = $za->locateName($name, ZIPARCHIVE::FL_NOCASE|ZIPARCHIVE::FL_NODIR)) !== FALSE) { + if ($csl = $za->getFromIndex($index) && !$changed) { + $ret = _install_csl($name, $csl, NULL, NULL, TRUE); + if ($ret == 2) { + $context['results']['updated'][] = $name; + $context['message'] = t('Updated') . ': ' . $name; + } + } + } + $context['sandbox']['progress']++; + } + $za->close(); + + if ($context['sandbox']['progress'] != $num_files) { + $context['finished'] = $context['sandbox']['progress'] / $num_files; + } + } +} + +function biblio_citeproc_update_installed_finished($success, $results, $operations) { + if ($success) { + if (count($results['updated'])) { + $message = format_plural(count($results['updated']), 'The following style was updated.', 'The following @count styles were updated.'); + drupal_set_message($message); + foreach ($results['updated'] as $style) { + drupal_set_message($style); + } + } + else { + $message = t('No updates were found, all styles are current.'); + drupal_set_message($message); + } + } + else { + drupal_set_message(t('Finished with an error.')); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Biblio - CiteProc +description = Adds Citation Style Language (CSL) citation processing +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = CSL.inc + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,343 @@ + $t('PHP Multibyte String'), + 'severity' => $mbs_severity, + 'description' => $mbs_desc, + ); + } + return $requirements; +} + +function biblio_citeproc_uninstall() { + if (db_table_exists('biblio_type_maps')) { + db_delete('biblio_type_maps') + ->condition('format', 'csl') + ->execute(); + } + if ($file = variable_get('biblio_citeproc_styles_zip_file', NULL)) { + file_usage_delete($file, 'biblio_citeproc'); + file_delete($file); + } + cache_clear_all('biblio_citeproc_styles', 'cache'); + variable_del('biblio_citeproc_styles_zip_file'); + variable_del('biblio_citeproc_style'); +} + +function biblio_citeproc_schema() { + $schema['biblio_citeproc_styles'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'title' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'filename' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '' + ), + 'parent' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + 'default' => '' + ), + 'summary' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'csl' => array( + 'type' => 'blob', + 'not null' => TRUE + ), + 'sha1' => array( + 'type' => 'varchar', + 'length' => 40, + 'not null' => TRUE, + 'default' => '' + ), + 'changed' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'updated' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('id') + ); + + return $schema; +} + +function biblio_citeproc_install_default_styles() { + $record = array(); + $dir = drupal_get_path('module', 'biblio_citeproc') . '/style'; + $files = file_scan_directory($dir, '/..*.csl$/'); + + foreach ($files as $file) { + $csl = file_get_contents($file->uri); + $name = basename($file->filename); + biblio_citeproc_install_style($name, $csl); + } +} + +function biblio_citeproc_update_default_styles() { + biblio_citeproc_install_default_styles(); +} + +function _get_csl_type_map() { + $map['type_map'] = serialize( + array( + 'article' => '', + 'article-magazine' => 106, + 'article-newspaper' => 105, + 'article-journal' => 102, + 'bill' => 117, + 'book' => 100, + 'broadcast' => 111, + 'chapter' => 101, + 'entry' => '', + 'entry-dictionary' => '', + 'entry-encyclopedia' => '', + 'figure' => '', + 'graphic' => '', + 'interview' => '', + 'legislation' => 118, + 'legal_case' => 128, + 'manuscript' => 121, + 'map' => 122, + 'motion_picture' => 110, + 'musical_score' => '', + 'pamphlet' => '', + 'paper-conference' => 103, + 'patent' => 119, + 'post' => '', + 'post-weblog' => '', + 'personal\_communication' => 120, + 'report' => 109, + 'review' => '', + 'review-book' => '', + 'song' => '', + 'speech' => '', + 'thesis' => 108, + 'treaty' => '', + 'webpage' => 107, + ) + ); + $map['format'] = 'csl'; + return $map; +} +function _get_csl_type_names() { + $map['type_names'] = serialize( + array( + 'article' => '', + 'article-magazine' => "Magazine Article", + 'article-newspaper' => "Newspaper Article", + 'article-journal' => "Journal Article", + 'bill' => 'Bill', + 'book' => "Book", + 'broadcast' => 'Broadcast', + 'chapter' => "Book Section", + 'entry' => '', + 'entry-dictionary' => '', + 'entry-encyclopedia' => '', + 'figure' => '', + 'graphic' => '', + 'interview' => '', + 'legislation' => 'Legislation', + 'legal_case' => 'Legal Ruling', + 'manuscript' => 'Manuscript', + 'map' => 'Map', + 'motion_picture' => "Film or Broadcast", + 'musical_score' => '', + 'pamphlet' => '', + 'paper-conference' => "Conference Paper", + 'patent' => "Patent", + 'post' => '', + 'post-weblog' => '', + 'personal\_communication' => 'Personal Communication', + 'report' => "Report", + 'review' => '', + 'review-book' => '', + 'song' => '', + 'speech' => '', + 'thesis' => "Thesis", + 'treaty' => '', + 'webpage' => "Web Page", + ) + ); + + $map['format'] = 'csl'; + return $map; +} + +function _get_csl_field_map() { + $map['field_map'] = serialize( + array( + 'title' => 'title', + 'container-title' => 'biblio_secondary_title', + 'collection-title' => 'biblio_secondary_title', + 'original-title' => 'biblio_alternate_title', + 'publisher' => 'biblio_publisher', + 'publisher-place' => 'biblio_place_published', + 'original-publisher' => '', + 'original-publisher-place' => '', + 'archive' => '', + 'archive-place' => '', + 'authority' => '', + 'archive_location' => '', + 'event' => 'biblio_secondary_title', + 'event-place' => 'biblio_place_published', + 'page' => 'biblio_pages', + 'page-first' => '', + 'locator' => '', + 'version' => 'biblio_edition', + 'volume' => 'biblio_volume', + 'number-of-volumes' => 'biblio_number_of_volumes', + 'number-of-pages' => '', + 'issue' => 'biblio_issue', + 'chapter-number' => 'biblio_section', + 'medium' => '', + 'status' => '', + 'edition' => 'biblio_edition', + 'section' => 'biblio_section', + 'genre' => '', + 'note' => 'biblio_notes', + 'annote' => '', + 'abstract' => 'biblio_abst_e', + 'keyword' => 'biblio_keywords', + 'number' => 'biblio_number', + 'references' => '', + 'URL' => 'biblio_url', + 'DOI' => 'biblio_doi', + 'ISBN' => 'biblio_isbn', + 'call-number' => 'biblio_call_number', + 'citation-number' => '', + 'citation-label' => 'biblio_citekey', + 'first-reference-note-number' => '', + 'year-suffix' => '', + 'jurisdiction' => '', + + //Date Variables' + + 'issued' => 'biblio_year', + 'event' => 'biblio_date', + 'accessed' => 'biblio_access_date', + 'container' => 'biblio_date', + 'original-date' => 'biblio_date', + + //Name Variables' + + 'author' => 'biblio_contributors:1', + 'editor' => 'biblio_contributors:2', + 'translator' => 'biblio_contributors:3', + 'recipient' => '', + 'interviewer' => 'biblio_contributors:1', + 'publisher' => 'biblio_publisher', + 'composer' => 'biblio_contributors:1', + 'original-publisher' => '', + 'original-author' => '', + 'container-author' => '', + 'collection-editor' => '', + ) + ); + $map['format'] = 'csl'; + return $map; +} + +function _save_csl_maps() { + $typemap = _get_csl_type_map(); + $typenames = _get_csl_type_names(); + $fieldmap = _get_csl_field_map(); + $maps = array_merge($typemap, $typenames, $fieldmap); + biblio_save_map($maps); +} + +function _reset_csl_map($type = NULL) { + $count = db_query("SELECT COUNT(*) FROM {biblio_type_maps} WHERE format='csl'")->fetchField(); + if ($count && $type) { //update + $function = '_get_csl_' . $type; + if (!function_exists($function)) return; + $map = $function(); + db_update('biblio_type_maps') + ->fields($map) + ->condition('format', 'csl') + ->execute(); + } + else { // install + db_delete('biblio_type_maps') + ->condition('format', 'csl') + ->execute(); + _save_csl_maps(); + } +} +/** + * + * Adds CSL field and type maps to biblio_type_maps table + */ +function biblio_citeproc_update_7001() { + _reset_csl_map(); +} +/** + * + * Updates the default CSL styles + */ +function biblio_citeproc_update_7002() { + biblio_citeproc_update_default_styles(); +} +/** + * Adds "changed" and "updated" columns + * + */ +function biblio_citeproc_update_7003() { + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('biblio_citeproc_styles', 'changed', $spec); + db_add_field('biblio_citeproc_styles', 'updated', $spec); +} +/** + * Corrects an error in the field_map + * + */ +function biblio_citeproc_update_7004() { + $map = biblio_get_map('field_map', 'csl'); + if ($map['accessed'] == 'biblio_accessed') { + $map['accessed'] = 'biblio_access_date'; + biblio_set_map('field_map', 'csl', $map); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/biblio_citeproc.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,191 @@ + 'CiteProc', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_citeproc_style_manager_form'), + 'access arguments' => array('administer biblio'), + 'file' => 'biblio_citeproc.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 12 + ); + $items['admin/config/content/biblio/citeproc/styles'] = array( + 'title' => 'CiteProc Style Manager', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_citeproc_style_manager_form'), + 'access arguments' => array('administer biblio'), + 'file' => 'biblio_citeproc.admin.inc', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 12 + ); + $items['admin/config/content/biblio/citeproc/styles/%/edit'] = array( + 'title' => 'CiteProc Style Editor', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_citeproc_csl_editor', 6), + 'access arguments' => array('administer biblio'), + 'file' => 'biblio_citeproc.admin.inc', + 'type' => MENU_CALLBACK, + 'weight' => 12 + ); + $items['admin/config/content/biblio/citeproc/map'] = array( + 'title' => 'CSL Field Mapper', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_admin_io_mapper_form', 'csl', FALSE), + 'access arguments' => array('administer biblio'), + 'file' => '../../includes/biblio.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 12 + ); + return $items; +} +function biblio_citeproc_theme() { + return array( + 'biblio_citeproc_style' => array( + 'file' => 'biblio_citeproc.module', + 'variables' => array( + 'node' => '', + 'style_name' => 'cse', + ), + ), + 'biblio_citeproc_style_manager_form' => array( + 'render element' => 'form', + ), + ); +} + +/** + * Implements hook_biblio_map_alter(). + */ +function biblio_citeproc_biblio_map_alter(&$map, $type, $format) { + if ($type == 'field_map' && $format == 'csl') { + $map = array_merge($map, array( + 'author' => 'biblio_contributors:1', + 'editor' => 'biblio_contributors:2', + 'translator' => 'biblio_contributors:3', + 'recipient' => ':', + 'interviewer' => ':', + 'composer' => ':', + )); + } +} + +function biblio_citeproc_theme_registry_alter(&$theme_registry) { + $theme_registry['biblio_style'] = $theme_registry['biblio_citeproc_style']; +} + +function biblio_citeproc_load_csl($csl_id) { + $csl_file_contents = ''; + if (strpos($csl_id, '.csl') === FALSE) {// try to convert old style names to csl... + if (in_array($csl_id, array('ama', 'apa', 'cse', 'ieee', 'mla', 'vancouver'))) { + $csl_id .= '.csl'; + } + elseif ($csl_id == 'chicago') { + $csl_id = 'chicago-fullnote-bibliography.csl'; + } + else { + $csl_id = ''; + $message = t('An invalid style "@style" was selected, please check your "CiteProc" style settings.', array('@style' => $csl_id)); + drupal_set_message($message, 'error'); + } + } + if (!empty($csl_id)) { + $csl = db_query('SELECT parent,csl FROM {biblio_citeproc_styles} WHERE filename = :id', array(':id' => $csl_id))->fetchObject(); + if (!isset($csl->csl)) { + drupal_set_message(t('Biblio-CiteProc could not fetch the style file: @csl_id from the database. Check your CiteProc settings.', array('@csl_id' => $csl_id)), 'error'); + return; + } + if (!empty($csl->parent)) { + $csl_file_contents = db_query("SELECT csl FROM {biblio_citeproc_styles} WHERE id = :id", array(':id' => $csl->parent))->fetchField(); + + } + else { + $csl_file_contents = $csl->csl; + } + } + return $csl_file_contents; +} + +function theme_biblio_citeproc_style($variables) { + static $citeproc; + global $language; + $cached = NULL; + $node = $variables['node']; + $style = isset($variables['style_name']) ? $variables['style_name'] : NULL; + + module_load_include('inc', 'biblio_citeproc', 'CSL'); + + if (!$citeproc) { + $csl_id = ($style) ? $style : biblio_get_style(); + if ($csl_file_contents = biblio_citeproc_load_csl($csl_id)) { + // $cslid = $csl_file_name . '-' . $language->language; + // $cached = cache_get($cslid, 'cache_biblio_csl_object'); + if (!$cached) { + $citeproc = new citeproc($csl_file_contents, $language->language); + // cache_set($cslid, $citeproc, 'cache_biblio_csl_object'); + } + else { + $citeproc = $cached->data; + } + } + } + + $output = ''; + if ($citeproc) { + $styled_node = $citeproc->render($node); + $coins_data = isset($node->biblio_coins) ? filter_xss($node->biblio_coins, array('span')) : ''; + $output = $styled_node . $coins_data; + } + + return $output; +} + +function biblio_citeproc_csl_map_reset($type = NULL) { + module_load_include('install', 'biblio_citeproc', 'biblio_citeproc'); + _reset_csl_map($type); +} + +function biblio_citeproc_install_style($name, $csl) { + + $xml = simplexml_load_string($csl); + + $parent = ''; + foreach ($xml->info->link as $link) { + $attrs = $link->attributes(); + if (isset($attrs['rel']) && $attrs['rel'] == 'independent-parent') { + $parent = (string)$attrs['href']; + } + } + + $old_sha1 = NULL; + + $old_sha1 = db_query('SELECT sha1 FROM {biblio_citeproc_styles} WHERE id = :id', array(':id' => (string)$xml->info->id))->fetchField(); + + $record = array( + 'filename' => $name, + 'parent' => $parent, + 'title' => (string)$xml->info->title, + 'summary' => (string)$xml->info->summary, + 'csl' => $csl, + 'sha1' => sha1($csl), + 'id' => (string)$xml->info->id, + ); + + if ($old_sha1 && $old_sha1 == sha1($csl)) { //style exists and has not changed + return; + } + elseif ($old_sha1 && $old_sha1 != sha1($csl)) { // update an existing style + $query = db_update('biblio_citeproc_styles') + ->fields($record) + ->condition('id', $record['id']); + } + elseif (!$old_sha1) { //install new style + $query = db_insert('biblio_citeproc_styles') + ->fields(array('id', 'title', 'filename', 'summary', 'csl', 'sha1')); + + $query->values($record); + } + $query->execute(); + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-af-ZA.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-af-ZA.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + toegang verkry + en + and others + anonymous + anon + at + by + circa + c. + cited + + edition + editions + + ed + et al. + voorhande + van + ibid. + in + in press + internet + interview + letter + no date + n.d. + online + presented at the + + reference + references + + + ref. + refs. + + opgehaal + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + reël + reëls + + + note + notes + + + opus + opera + + + bladsy + bladsye + + + paragraaf + paragrawe + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk + chap + col + fig + f + no + op + + bl + bll + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + redakteur + redakteurs + + + editor + editors + + + vertaler + vertalers + + + editor & translator + editors & translators + + + + + + + + + red + reds + + + ed. + eds. + + + vert + verts + + + ed. & tran. + eds. & trans. + + + + onder redaksie van + edited by + vertaal deur + edited & translated by + to + interview by + + + by + red + ed. + verts + ed. & trans. by + + + Januarie + Februarie + Maart + April + Mei + Junie + Julie + Augustus + September + Oktober + November + Desember + + + Jan + Feb + Mrt + Apr + Mei + Jun + Jul + Aug + Sep + Okt + Nov + Des + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-ar-AR.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-ar-AR.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + تاريخ الوصول + و + وآخرون + مجهول + مجهول + عند + عن طريق + حوالي + حو. + وثق + + الطبعة + الطبعات + + ط. + وآخ. + التالي + من + المرجع السابق + في + قيد النشر + انترنت + مقابلة + خطاب + دون تاريخ + د.ت + على الخط المباشر + قُدَّم في + + مرجع + مراجع + + + مرجع + مراجع + + استرجع في + + + ب.م. + ق.م. + + + + + " + " + ' + ' + + + + + + + + + الاول + الثاني + الثالث + الرابع + الخامس + السادس + السابع + الثامن + التاسع + العاشر + + + الاناسة + الفلك + الأحياء + النبات + الكيمياء + الهندسة + العلوم العامة + الجغرافيا + الجيولوجيا + التاريخ + الإنسانيات + اللغويات + الأدب + الرياضيات + الطب + الفلسفة + الفيزياء + علم النفس + علم الإجتماع + العلوم البحتة + العلوم السياسية + العلوم الإجتماعية + الإلهيات + علم الحيوان + + + + كتاب + كتب + + + فصل + فصول + + + عمود + أعمدة + + + رسم توضيحي + رسوم توضيحية + + + ورقة + أوراق + + + عدد + أعداد + + + سطر + أسطر + + + ملاحظة + ملاحظات + + + نوته موسيقية + نوت موسيقية + + + صفحة + صفحات + + + فقرة + فقرات + + + جزء + أجزاء + + + قسم + أقسام + + + تفسير فرعي + تفسيرات فرعية + + + بيت شعر + أبيات شعر + + + مجلد + مجلدات + + + + كتاب + فصل + عمود + رسم توضيحي + مطوية + عدد + نوتة موسيقية + + ص + ص.ص. + + فقرة + ج. + قسم + + تفسير فرعي + تفسيرات فرعية + + + بيت شعر + أبيات شعر + + + مج. + مج. + + + + + + ¶¶ + + + § + §§ + + + + + مؤلف + مؤلفين + + + محرر + محررين + + + رئيس التحرير + رؤساء التحرير + + + مترجم + مترجمين + + + مترجم ومحرر + مترجمين ومحررين + + + + + مؤلف + مؤلفين + + + محرر + محررين + + + مشرف على الطبعة + مشرفين على الطبعة + + + مترجم + مترجمين + + + مترجم ومشرف على الطباعه + مترجمين ومشرفين على الطباعه + + + + تحرير + اعداد + ترجمة + اعداد وترجمة + مرسل الى + مقابلة بواسطة + + + + تحرير + اشرف على الطبعة + ترجمة + ترجمه واشرف على الطباعه + + + يناير + فبراير + مارس + ابريل + مايو + يونيو + يوليو + اغسطس + سبتمبر + اكتوبر + نوفمبر + ديسمبر + + + يناير + فبراير + مارس + ابريل + مايو + يونيو + يوليو + اغسطس + سبتمبر + اكتوبر + نوفمبر + ديسمبر + + + الربيع + الصيف + الخريف + الشتاء + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-bg-BG.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-bg-BG.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + отворен на + и + и други + анонимен + анон + в + by + circa + c. + цитиран + + издание + издания + + изд + и съавт. + предстоящ + от + пак там + в + под печат + интернет + интервю + писмо + no date + без дата + онлайн + представен на + + reference + references + + + ref. + refs. + + изтеглен на + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + антропология + астрономия + биология + ботаника + химия + инженерство + обща база + география + геология + история + хуманитарни науки + linguistics + литература + математика + медицина + философия + физика + физиология + социология + наука + политически науки + обществени науки + теология + зоология + + + + книга + книги + + + глава + глави + + + колона + колони + + + фигура + фигури + + + фолио + фолия + + + брой + броеве + + + ред + редове + + + бележка + бележки + + + опус + опуси + + + страница + страници + + + параграф + параграфи + + + част + части + + + раздел + раздели + + + sub verbo + sub verbis + + + стих + стихове + + + том + томове + + + + кн + гл + кол + фиг + фол + бр + оп + + с + с-ци + + п + ч + разд + + s.v. + s.vv. + + + ст + ст-ове + + + том + т-ове + + + + + + ¶¶ + + + § + §§ + + + + + автор + автори + + + редактор + редактори + + + editor + editors + + + преводач + преводачи + + + editor & translator + editors & translators + + + + + авт + авт-ри + + + ред + ред-ри + + + ed. + eds. + + + прев + прев-чи + + + ed. & tran. + eds. & trans. + + + + редактиран от + edited by + преведен от + edited & translated by + до + интервюиран от + + + by + ред + ed. + прев + ed. & trans. by + + + Януари + Февруари + Март + Април + Май + Юни + Юли + Август + Септември + Октомври + Ноември + Декември + + + Яну + Фев + Мар + Апр + Май + Юни + Юли + Авг + Сеп + Окт + Ное + Дек + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-ca-AD.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-ca-AD.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + accedit + i + i altres + anònim + anòn. + a + per + circa + c. + citat + + edició + edicions + + ed. + et al. + previst + de + ibíd. + en + en impremta + internet + entrevista + carta + sense data + s.d. + en línia + presentat a + + referència + referències + + + ref. + ref. + + recuperat + + + dC + aC + + + + « + » + + + + + a + a + a + a + + + primera + segona + tercera + quarta + cinquena + sisena + setena + vuitena + novena + desena + + + antropologia + astronomia + biologia + botànica + química + enginyeria + base genèrica + geografia + geologia + història + humanitats + lingüística + literatura + matemàtiques + medicina + filosofia + física + psicologia + sociologia + ciències + ciències polítiques + ciències socials + teologia + zoologia + + + + llibre + llibres + + + capítol + capítols + + + columna + columnes + + + figura + figures + + + foli + folis + + + número + números + + + línia + línies + + + nota + notes + + + opus + opera + + + pàgina + pàgines + + + paràgraf + paràgrafs + + + part + parts + + + secció + seccions + + + sub voce + sub vocibus + + + vers + versos + + + volum + volums + + + + llib. + cap. + col. + fig. + f. + núm. + op. + + p. + p. + + par. + pt. + sec. + + s.v. + s.v. + + + v. + v. + + + vol. + vol. + + + + + § + § + + + § + § + + + + + + + + + editor + editors + + + editor + editors + + + traductor + traductors + + + editor i traductor + editors i traductors + + + + + + + + + ed. + ed. + + + ed. + ed. + + + trad. + trad. + + + ed. i trad. + ed. i trad. + + + + editat per + editat per + traduït per + editat i traduït per + a + entrevistat per + + + per + ed. + ed. + trad. + ed. i trad. per + + + gener + febrer + març + abril + maig + juny + juliol + agost + setembre + octubre + novembre + desembre + + + gen. + febr. + març + abr. + maig + juny + jul. + ag. + set. + oct. + nov. + des. + + + primavera + estiu + tardor + hivern + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-cs-CZ.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-cs-CZ.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + přístup + a + a další + anonymous + anon. + v + by + circa + c. + citován + + vydání + vydání + + vyd. + et al. + nadcházející + z + ibid. + v + v tisku + internet + interview + dopis + bez data + nedatováno + online + prezentován v + + reference + references + + + ref. + refs. + + získáno + + + n. l. + př. n. l. + + + + + + + + + + . + . + . + . + + + první + druhé + třetí + čtvrté + páté + šesté + sedmé + osmé + deváté + desáté + + + antropologie + astronomie + biologie + botanika + chemie + technika + všeobecný základ + geografie + geologie + historie + humanitní + linguistics + literatura + matematika + medicína + filosofie + fyzika + psychologie + sociologie + věda + politologie + sociální věda + teologie + zoologie + + + + kniha + knihy + + + kapitola + kapitoly + + + sloupec + sloupce + + + obrázek + obrázky + + + list + listy + + + číslo + číslo + + + řádek + řádky + + + poznámka + poznámky + + + opus + opera + + + strana + strany + + + odstavec + odstavce + + + část + části + + + sekce + sekce + + + sub verbo + sub verbis + + + verš + verše + + + ročník + ročníky + + + + kn. + kap. + sl. + obr. + l. + čís. + op. + + s. + s. + + odst. + č. + sek. + + s.v. + s.vv. + + + v. + v. + + + roč. + roč. + + + + + + ¶¶ + + + § + §§ + + + + + autor + autoři + + + editor + editoři + + + editor + editors + + + překladatel + překladatelé + + + editor a překladatel + editoři a překladatelé + + + + + + + + + ed. + ed. + + + ed. + ed. + + + překl. + překl. + + + ed. a překl. + ed. a překl. + + + + editoval + editoval + přeložil + editoval a přeložil + pro + rozhovor vedl + + + by + ed. + ed. + překl. + ed. a přel. + + + leden + únor + březen + duben + květen + červen + červenec + srpen + září + říjen + listopad + prosinec + + + led. + úno. + bře. + dub. + kvě. + čer. + čvc. + srp. + zář. + říj. + lis. + pro. + + + jaro + léto + podzim + zima + + \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-da-DK.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-da-DK.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + åbnet + og + med flere + anonym + anon. + + af + cirka + ca. + citeret + + udgave + udgaver + + udg. + et al. + kommende + fra + ibid. + i + i tryk + internet + interview + brev + ingen dato + udateret + online + præsenteret ved + + reference + referencer + + + ref. + refr. + + hentet + + + e.Kr + f.Kr + + + + « + » + + + + + . + . + . + . + + + første + anden + tredje + fjerde + femte + sjette + syvende + ottende + niende + tiende + + + antropologi + astronomi + biologi + botanik + kemi + ingeniørvidenskab + generel + geografi + geologi + historie + humanistiske fag + lingvistik + litteratur + matematik + medicin + filosofi + fysik + fysiologi + sociologi + naturvidenskab + statskundskab + samfundsvidenskab + teologi + zoologi + + + + bog + bøger + + + kapitel + kapitler + + + kolonne + kolonner + + + figur + figurer + + + folio + folier + + + nummer + numre + + + linje + linjer + + + note + noter + + + opus + opuser + + + side + sider + + + afsnit + afsnit + + + del + dele + + + sektion + sektionerne + + + sub verbo + sub verbis + + + vers + vers + + + bind + bind + + + + b. + kap. + kol. + fig. + fol. + nr. + op. + + s. + s. + + afs. + d. + sekt. + + s.v. + s.vv. + + + v. + v. + + + bd. + bd. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + redaktør + redaktører + + + redaktør + redaktører + + + oversætter + oversættere + + + redaktør & oversætter + redaktører & oversættere + + + + + + + + + red. + red. + + + red. + red. + + + overs. + overs. + + + red. & overs. + red. & overs. + + + + redigeret af + redigeret af + oversat af + redigeret & oversat af + modtaget af + interviewet af + + + af + red. + red. + overs. + red. & overs. af + + + Januar + Februar + Marts + April + Maj + Juni + Juli + August + September + Oktober + November + December + + + Jan. + Feb. + Mar. + Apr. + Maj + Jun. + Jul. + Aug. + Sep. + Okt. + Nov. + Dec. + + + Forår + Sommer + Efterår + vinter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-de-AT.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-de-AT.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + zugegriffen + und + und andere + ohne Autor + o. A. + auf + von + circa + ca. + zitiert + + Auflage + Auflagen + + Aufl. + u. a. + i. E. + von + ebd. + in + im Druck + Internet + Interview + Brief + ohne Datum + o. J. + online + gehalten auf der + + Referenz + Referenzen + + + Ref. + Ref. + + abgerufen + + + v. Chr. + n. Chr. + + + + + + + + + . + . + . + . + + + erster + zweiter + dritter + vierter + fünfter + sechster + siebter + achter + neunter + zehnter + + + Anthropologie + Astronomie + Biologie + Botanik + Chemie + Ingenieurswissenschaften + generischer Stil + Geographie + Geologie + Geschichte + Geisteswissenschaften + Linguistik + Literatur + Mathematik + Medizin + Philosophie + Physik + Psychologie + Soziologie + Naturwissenschaften + Politikwissenschaft + Sozialwissenschaften + Theologie + Zoologie + + + + Buch + Bücher + + + Kapitel + Kapitel + + + Spalte + Spalten + + + Abbildung + Abbildungen + + + Blatt + Blätter + + + Nummer + Nummern + + + Zeile + Zeilen + + + Note + Noten + + + Opus + Opera + + + Seite + Seiten + + + Absatz + Absätze + + + Teil + Teile + + + Abschnitt + Abschnitte + + + sub verbo + sub verbis + + + Vers + Verse + + + Band + Bände + + + + B. + Kap. + Sp. + Abb. + Fol. + Nr. + op. + + S. + S. + + Abs. + Teil + Abschn. + + s.v. + s.vv. + + + V. + V. + + + Bd. + Bd. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + Herausgeber + Herausgeber + + + Herausgeber + Herausgeber + + + Übersetzer + Übersetzer + + + Herausgeber & Übersetzer + Herausgeber & Übersetzer + + + + + + + + + Hrsg. + Hrsg. + + + Hrsg. + Hrsg. + + + Übers. + Übers. + + + Hrsg. & Übers. + Hrsg. & Übers + + + + herausgegeben von + herausgegeben von + übersetzt von + herausgegeben und übersetzt von + an + interviewt von + + + von + hg. von + hg. von + übers. von + hg. & übers. von + + + Januar + Februar + März + April + Mai + Juni + Juli + August + September + Oktober + November + Dezember + + + Jan. + Feb. + März + Apr. + Mai + Juni + Juli + Aug. + Sep. + Okt. + Nov. + Dez. + + + Frühjahr + Sommer + Herbst + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-de-CH.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-de-CH.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + zugegriffen + und + und andere + ohne Autor + o. A. + auf + von + circa + ca. + zitiert + + Auflage + Auflagen + + Aufl. + u. a. + i. E. + von + ebd. + in + im Druck + Internet + Interview + Brief + ohne Datum + o. J. + online + gehalten auf der + + Referenz + Referenzen + + + Ref. + Ref. + + abgerufen + + + v. Chr. + n. Chr. + + + + + + + + + . + . + . + . + + + erster + zweiter + dritter + vierter + fünfter + sechster + siebter + achter + neunter + zehnter + + + Anthropologie + Astronomie + Biologie + Botanik + Chemie + Ingenieurswissenschaften + generischer Stil + Geographie + Geologie + Geschichte + Geisteswissenschaften + Linguistik + Literatur + Mathematik + Medizin + Philosophie + Physik + Psychologie + Soziologie + Naturwissenschaften + Politikwissenschaft + Sozialwissenschaften + Theologie + Zoologie + + + + Buch + Bücher + + + Kapitel + Kapitel + + + Spalte + Spalten + + + Abbildung + Abbildungen + + + Blatt + Blätter + + + Nummer + Nummern + + + Zeile + Zeilen + + + Note + Noten + + + Opus + Opera + + + Seite + Seiten + + + Absatz + Absätze + + + Teil + Teile + + + Abschnitt + Abschnitte + + + sub verbo + sub verbis + + + Vers + Verse + + + Band + Bände + + + + B. + Kap. + Sp. + Abb. + Fol. + Nr. + op. + + S. + S. + + Abs. + Teil + Abschn. + + s.v. + s.vv. + + + V. + V. + + + Bd. + Bd. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + Herausgeber + Herausgeber + + + Herausgeber + Herausgeber + + + Übersetzer + Übersetzer + + + Herausgeber & Übersetzer + Herausgeber & Übersetzer + + + + + + + + + Hrsg. + Hrsg. + + + Hrsg. + Hrsg. + + + Übers. + Übers. + + + Hrsg. & Übers. + Hrsg. & Übers + + + + herausgegeben von + herausgegeben von + übersetzt von + herausgegeben und übersetzt von + an + interviewt von + + + von + hg. von + hg. von + übers. von + hg. & übers. von + + + Januar + Februar + März + April + Mai + Juni + Juli + August + September + Oktober + November + Dezember + + + Jan. + Feb. + März + Apr. + Mai + Juni + Juli + Aug. + Sep. + Okt. + Nov. + Dez. + + + Frühjahr + Sommer + Herbst + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-de-DE.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-de-DE.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + zugegriffen + und + und andere + ohne Autor + o. A. + auf + von + circa + ca. + zitiert + + Auflage + Auflagen + + Aufl. + u. a. + i. E. + von + ebd. + in + im Druck + Internet + Interview + Brief + ohne Datum + o. J. + online + gehalten auf der + + Referenz + Referenzen + + + Ref. + Ref. + + abgerufen + + + v. Chr. + n. Chr. + + + + + + + + + . + . + . + . + + + erster + zweiter + dritter + vierter + fünfter + sechster + siebter + achter + neunter + zehnter + + + Anthropologie + Astronomie + Biologie + Botanik + Chemie + Ingenieurswissenschaften + generischer Stil + Geographie + Geologie + Geschichte + Geisteswissenschaften + Linguistik + Literatur + Mathematik + Medizin + Philosophie + Physik + Psychologie + Soziologie + Naturwissenschaften + Politikwissenschaft + Sozialwissenschaften + Theologie + Zoologie + + + + Buch + Bücher + + + Kapitel + Kapitel + + + Spalte + Spalten + + + Abbildung + Abbildungen + + + Blatt + Blätter + + + Nummer + Nummern + + + Zeile + Zeilen + + + Note + Noten + + + Opus + Opera + + + Seite + Seiten + + + Absatz + Absätze + + + Teil + Teile + + + Abschnitt + Abschnitte + + + sub verbo + sub verbis + + + Vers + Verse + + + Band + Bände + + + + B. + Kap. + Sp. + Abb. + Fol. + Nr. + op. + + S. + S. + + Abs. + Teil + Abschn. + + s.v. + s.vv. + + + V. + V. + + + Bd. + Bd. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + Herausgeber + Herausgeber + + + Herausgeber + Herausgeber + + + Übersetzer + Übersetzer + + + Herausgeber & Übersetzer + Herausgeber & Übersetzer + + + + + + + + + Hrsg. + Hrsg. + + + Hrsg. + Hrsg. + + + Übers. + Übers. + + + Hrsg. & Übers. + Hrsg. & Übers + + + + herausgegeben von + herausgegeben von + übersetzt von + herausgegeben und übersetzt von + an + interviewt von + + + von + hg. von + hg. von + übers. von + hg. & übers. von + + + Januar + Februar + März + April + Mai + Juni + Juli + August + September + Oktober + November + Dezember + + + Jan. + Feb. + März + Apr. + Mai + Juni + Juli + Aug. + Sep. + Okt. + Nov. + Dez. + + + Frühjahr + Sommer + Herbst + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-el-GR.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-el-GR.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + ημερομηνία πρόσβασης + και + και άλλοι + ανώνυμο + ανών. + εφ. + από + περίπου + περ. + παρατίθεται + + έκδοση + εκδόσεις + + έκδ. + κ.ά. + προσεχές + από + στο ίδιο + στο + υπό έκδοση + διαδίκτυο + συνέντευξη + επιστολή + χωρίς χρονολογία + χ.χ. + έκδοση σε ψηφιακή μορφή + παρουσιάστηκε στο + + παραπομπή + παραπομπές + + + παρ. + παρ. + + ανακτήθηκε + + + μ.Χ. + π.Χ. + + + + + + ' + ' + + + ος + ος + ος + ος + + + πρώτος + δεύτερος + τρίτος + τέταρτος + πέμπτος + έκτος + έβδομος + όγδοος + ένατος + δέκατος + + + ανθρωπολογία + αστρονομία + βιολογία + βοτανική + χημεία + μηχανική + γενική βιβλιογραφία + γεωγραφία + γεωλογία + ιστορία + ανθρωπιστικές σπουδές + λογοτεχνία + μαθηματικά + ιατρική + φιλοσοφία + φυσική + ψυχολογία + κοινωνιολογία + θετικές επιστήμες + πολιτικές επιστήμες + κοινωνικές επιστήμες + θεολογία + ζωολογία + + + + βιβλίο + βιβλίο + + + κεφάλαιο + κεφάλαια + + + στήλη + στήλες + + + εικόνα + εικόνες + + + φάκελος + φάκελοι + + + τεύχος + τεύχη + + + σειρά + σειρές + + + σημείωση + σημειώσεις + + + έργο + έργα + + + σελίδα + σελίδες + + + παράγραφος + παράγραφοι + + + μέρος + μέρη + + + τμήμα + τμήματα + + + λήμμα + λήμματα + + + στίχος + στίχοι + + + τόμος + τόμοι + + + + βιβ. + κεφ. + στ. + εικ. + φάκ + τχ. + έργ. + + σ + σσ + + παρ. + μέρ. + τμ. + + λήμ. + λήμ. + + + στ. + στ. + + + τ. + τ. + + + + + + ¶¶ + + + § + §§ + + + + + συγγραφέας + συγγραφείς + + + επιμελητής + επιμελητές + + + διευθυντής σειράς + διευθυντές σειράς + + + μεταφραστής + μεταφραστές + + + μεταφραστής και επιμελητής + μεταφραστές και επιμελητές + + + + + συγγρ. + συγγρ. + + + επιμ. + επιμ. + + + δ/ντής σειράς + δ/ντές σειρας + + + μτφ. + μτφ. + + + μτφ. και επιμ. + μτφ. και επιμ. + + + + επιμέλεια + διεύθυνση σειράς + μετάφραση + μετάφραση και επιμέλεια + παραλήπτης + συνέντευξη + + + στον συλλ. τόμο + επιμέλ. + δ/νση σειράς + μετάφρ. + μετάφρ. και επιμέλ. + + + Ιανουάριος + Φεβρουάριος + Μάρτιος + Απρίλιος + Μάιος + Ιούνιος + Ιούλιος + Αύγουστος + Σεπτέμβριος + Οκτώβριος + Νοέμβριος + Δεκέμβριος + + + Ιανουαρίου + Φεβρουαρίου + Μαρτίου + Απριλίου + Μαΐου + Ιουνίου + Ιουλίου + Αυγούστου + Σεπτεμβρίου + Οκτωβρίου + Νοεμβρίου + Δεκεμβρίου + + + Άνοιξη + Καλοκαίρι + Φθινόπωρο + Χειμώνας + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-en-GB.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-en-GB.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + accessed + and + and others + anonymous + anon. + at + by + circa + c. + cited + + edition + editions + + ed. + et al. + forthcoming + from + ibid. + in + in press + internet + interview + letter + no date + n.d. + online + presented at the + + reference + references + + + ref. + refs. + + retrieved + + + AD + BC + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + line + lines + + + note + notes + + + opus + opera + + + page + pages + + + paragraph + paragraph + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk. + chap. + col. + fig. + f. + no. + op. + + p. + pp. + + para. + pt. + sec. + + s.v. + s.vv. + + + v. + vv. + + + vol. + vols. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editor + editors + + + editor + editors + + + translator + translators + + + editor & translator + editors & translators + + + + + + + + + ed. + eds. + + + ed. + eds. + + + tran. + trans. + + + ed. & tran. + eds. & trans. + + + + edited by + edited by + translated by + edited & translated by + to + interview by + + + by + ed. + ed. + trans. + ed. & trans. by + + + January + February + March + April + May + June + July + August + September + October + November + December + + + Jan. + Feb. + Mar. + Apr. + May + Jun. + Jul. + Aug. + Sep. + Oct. + Nov. + Dec. + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-en-US.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-en-US.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + accessed + and + and others + anonymous + anon. + at + by + circa + c. + cited + + edition + editions + + ed. + et al. + forthcoming + from + ibid. + in + in press + internet + interview + letter + no date + n.d. + online + presented at the + + reference + references + + + ref. + refs. + + retrieved + + + AD + BC + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + line + lines + + + note + notes + + + opus + opera + + + page + pages + + + paragraph + paragraph + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk. + chap. + col. + fig. + f. + no. + op. + + p. + pp. + + para. + pt. + sec. + + s.v. + s.vv. + + + v. + vv. + + + vol. + vols. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editor + editors + + + editor + editors + + + translator + translators + + + editor & translator + editors & translators + + + + + + + + + ed. + eds. + + + ed. + eds. + + + tran. + trans. + + + ed. & tran. + eds. & trans. + + + + edited by + edited by + translated by + edited & translated by + to + interview by + + + by + ed. + ed. + trans. + ed. & trans. by + + + January + February + March + April + May + June + July + August + September + October + November + December + + + Jan. + Feb. + Mar. + Apr. + May + Jun. + Jul. + Aug. + Sep. + Oct. + Nov. + Dec. + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-es-ES.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-es-ES.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + accedido + y + y otros + anónimo + anón. + en + de + circa + c. + citado + + edición + ediciones + + ed. + et al. + previsto + a partir de + ibid. + en + en imprenta + internet + entrevista + carta + sin fecha + s. f. + en línea + presentado en + + referencia + referencias + + + ref. + refs. + + recuperado + + + d. C. + a. C. + + + + « + » + + + + + + + + + + + primera + segunda + tercera + cuarta + quinta + sexta + séptima + octava + novena + décima + + + antropología + astronomía + biología + botánica + química + ingeniería + base genérica + geografía + geología + historia + humanidades + lingüística + literatura + matemáticas + medicina + filosofía + física + psicología + sociología + ciencias + ciencias políticas + ciencias sociales + teología + zoología + + + + libro + libros + + + capítulo + capítulos + + + columna + columnas + + + figura + figuras + + + folio + folios + + + número + números + + + línea + líneas + + + nota + notas + + + opus + opera + + + página + páginas + + + párrafo + párrafos + + + parte + partes + + + sección + secciones + + + sub voce + sub vocibus + + + verso + versos + + + volumen + volúmenes + + + + lib. + cap. + col. + fig. + f. + n.º + op. + + p. + pp. + + párr. + pt. + sec. + + s. v. + s. vv. + + + v. + vv. + + + vol. + vols. + + + + + § + § + + + § + § + + + + + + + + + editor + editores + + + editor + editores + + + traductor + traductores + + + editor y traductor + editores y traductores + + + + + + + + + ed. + eds. + + + ed. + eds. + + + trad. + trads. + + + ed. y trad. + eds. y trads. + + + + editado por + editado por + traducido por + editado y traducido por + a + entrevistado por + + + de + ed. + ed. + trad. + ed. y trad. + + + enero + febrero + marzo + abril + mayo + junio + julio + agosto + septiembre + octubre + noviembre + diciembre + + + ene. + feb. + mar. + abr. + may + jun. + jul. + ago. + sep. + oct. + nov. + dic. + + + primavera + verano + otoño + invierno + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-et-EE.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-et-EE.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + vaadatud + ja + ja teised + anonüümne + anon + + + umbes + u + tsiteeritud + + väljaanne + väljaanded + + tr + et al. + ilmumisel + + ibid. + + trükis + internet + intervjuu + kiri + s.a. + s.a. + online + esitatud + + viide + viited + + + viide + viited + + salvestatud + + + pKr + eKr + + + + + + + + + + + + + + + esimene + teine + kolmas + neljas + viies + kuues + seitsmes + kaheksas + üheksas + kümnes + + + antropoloogia + astronoomia + bioloogia + botaanika + keemia + tehnikateadus + määratlemata + geograafia + geoloogia + ajalugu + humanitaarteadus + lingvistika + kirjandusteadus + matemaatika + meditsiin + filosoofia + füüsika + psühholoogia + sotsioloogia + reaalteadus + politoloogia + sotsiaalteadus + teoloogia + zooloogia + + + + raamat + raamatud + + + peatükk + peatükid + + + veerg + veerud + + + joonis + joonised + + + foolio + fooliod + + + number + numbrid + + + rida + read + + + viide + viited + + + opus + opera + + + lehekülg + leheküljed + + + lõik + lõigud + + + osa + osad + + + alajaotis + alajaotised + + + sub verbo + sub verbis + + + värss + värsid + + + köide + köited + + + + rmt + ptk + v + joon + f + nr + op + + lk + lk + + lõik + osa + alajaot. + + s.v. + s.vv. + + + v + vv + + + kd + kd + + + + + + ¶¶ + + + § + §§ + + + + + + + + + toimetaja + toimetajad + + + toimetaja + toimetajad + + + tõlkija + tõlkijad + + + toimetaja & tõlkija + toimetajad & tõlkijad + + + + + + + + + toim + toim + + + toim + toim + + + tõlk + tõlk + + + toim & tõlk + toim & tõlk + + + + toimetanud + toimetanud + tõlkinud + toimetanud & tõlkinud + + intervjueerinud + + + + toim + toim + tõlk + toim & tõlk + + + jaanuar + veebruar + märts + aprill + mai + juuni + juuli + august + september + oktoober + november + detsember + + + jaan + veebr + märts + apr + mai + juuni + juuli + aug + sept + okt + nov + dets + + + kevad + suvi + sügis + talv + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-eu.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-eu.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + eskuratua + eta + eta beste + ezezaguna + ezez. + -(e)n + -(e)k egina + inguru + ing. + aipatua + + argitalpena + argitalpenak + + arg. + et al. + bidean + -(e)tik + ibíd. + in + moldiztegian + internet + elkarrizketa + gutuna + datarik gabe + d. g. + sarean + -(e)n aurkeztua + + aipamena + aipamenak + + + aip. + aip. + + berreskuratua + + + K.a. + K.o. + + + + « + » + + + + + . + . + . + . + + + lehengo + bigarren + hirugarren + laugarren + bosgarren + seigarren + zazpigarren + zortzigarren + bederatzigarren + hamargarren + + + antropologia + astronomia + biologia + botanika + kimika + ingenieritza + oinarri orokorra + geografia + geologia + historia + giza-gaiak + hizkuntzalaritza + literatura + matematika + medikuntza + filosofia + fiska + psicologia + soziologia + zientziak + politika zientziak + gizarte zientziak + teologia + zoologia + + + + liburua + liburuak + + + kapitulua + kapituluak + + + zutabea + zutabeak + + + irudia + irudiak + + + orria + orriak + + + zenbakia + zenbakiak + + + lerroa + lerroak + + + oharra + oharrak + + + obra + obrak + + + orrialdea + orrialdeak + + + paragrafoa + paragrafoak + + + zatia + zatiak + + + atala + atalak + + + sub voce + sub vocem + + + bertsoa + bertsoak + + + luburikia + luburukiak + + + + lib. + kap. + zut. + iru. + or. + zenb. + op. + + or. + or. + + par. + zt. + atal. + + s.v. + s.v. + + + b. + bb. + + + libk. + libk. + + + + + + ¶¶ + + + § + § + + + + + + + + + argitaratzailea + argitaratzaileak + + + argitaratzailea + argitaratzaileak + + + itzultzailea + itzultzaileak + + + argitaratzaile eta itzultzailea + argitaratzaile eta itzultzaileak + + + + + + + + + arg. + arg. + + + arg. + arg. + + + itzul. + itzul. + + + arg. eta itzul. + arg. eta itzul. + + + + -(e)k argitaratua + -(e)k argitaratua + -(e)k itzulia + -(e)k argitaratu eta itzulia + -(r)entzat + -(e)k elkarrizketatua + + + + arg. + arg. + itzul. + -(e)k arg. eta itzul. + + + urtarrilak + otsailak + martxoak + apirilak + maiatzak + ekainak + uztailak + abuztuak + irailak + urriak + azaroak + abenduak + + + urt. + ots. + martx. + apr. + mai. + eka. + uzt. + abz. + ira. + urr. + aza. + abe. + + + udaberria + uda + udazkena + negua + + \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-fa-IR.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-fa-IR.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + دسترسی + و + و دیگران + ناشناس + ناشناس + در + توسط + circa + c. + یادکرد + + ویرایش + ویرایش‌های + + ویرایش + و دیگران + forthcoming + از + همان + در + زیر چاپ + اینترنت + مصاحبه + نامه + بدون تاریخ + بدون تاریخ + برخط + ارائه شده در + + مرجع + مراجع + + + مرجع + مراجع + + retrieved + + + AD + BC + + + + + + + + + st + nd + rd + th + + + اول + دوم + سوم + چهارم + پنجم + ششم + هفتم + هشتم + نهم + دهم + + + مردمشناسی + ستاره‌شناسی + زیستشناسی + گیاه‌شناسی + شیمی + مهندسی + کلیات + جغرافیا + زمینشناسی + تاریخ + علوم انسانی + زبان‌شناسی + ادبیات + ریاضیات + پزشکی + فلسفه + فیزیک + روانشناسی + جامعه‌شناسی + علوم + علوم سیاسی + علوم اجتماعی + الهیات + جانورشناسی + + + + کتاب + کتاب‌های + + + فصل + فصل‌های + + + ستون + ستون‌های + + + تصویر + تصاویر + + + برگ + برگ‌های + + + شماره + شماره‌های + + + خط + خطوط + + + یادداشت + یادداشت‌های + + + قطعه + قطعات + + + صفحه + صفحات + + + پاراگراف + پاراگراف‌های + + + بخش + بخش‌های + + + قسمت + قسمت‌های + + + sub verbo + sub verbis + + + بیت + بیت‌های + + + جلد + جلدهای + + + + کتاب + فصل + ستون + تصویر + برگ + ش + قطعه + + ص + صص + + پاراگراف + بخش + قسمت + + s.v + s.vv + + + بیت + ابیات + + + ج + جج + + + + + + ¶¶ + + + § + §§ + + + + + + + + + ویرایشگر + ویرایشگران + + + ویرایشگر + ویرایشگران + + + مترجم + مترجمین + + + ویرایشگر و مترجم + ویرایشگران و مترجمین + + + + + + + + + ویرایشگر + ویرایشگران + + + ویرایشگر + ویرایشگران + + + مترجم + مترجمین + + + ویرایشگر و مترجم + ویرایشگران و مترجمین + + + + edited by + ویراسته‌ی + ترجمه‌ی + ترجمه و ویراسته‌ی + به + مصاحبه توسط + + + توسط + ویراسته‌ی + ویراسته‌ی + ترجمه‌ی + ترجمه و ویراسته‌ی + + + ژانویه + فوریه + مارس + آوریل + می + ژوئن + جولای + آگوست + سپتامبر + اکتبر + نوامبر + دسامبر + + + ژانویه + فوریه + مارس + آوریل + می + ژوئن + جولای + آگوست + سپتامبر + اکتبر + نوامبر + دسامبر + + + بهار + تابستان + پاییز + زمستان + + \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-fi-FI.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-fi-FI.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + viitattu + ja + ym. + tuntematon + tuntematon + osoitteessa + tekijä + noin + n. + viitattu + + painos + painokset + + p. + ym. + tulossa + alkaen + mt. + teoksessa + painossa + internet + haastattelu + kirje + ei päivämäärää + n.d. + verkossa + esitetty tilaisuudessa + + viittaus + viittaukset + + + viit.. + viit. + + noudettu + + + eaa. + jaa. + + + + + + + + + . + . + . + . + + + ensimmäinen + toinen + kolmas + neljäs + viides + kuudes + seitsemäs + kahdeksas + yhdeksäs + kymmenes + + + antropologia + tähtitiede + biologia + kasvitiede + kemia + tekniikka + yleinen + maantiede + geologia + historia + humanistiset tieteet + kielitiede + kirjallisuus + matematiikka + lääketiede + filosofia + fysiikka + psykologia + sosiologia + luonnontieteet + politiikan tutkimus + yhteiskuntatieteet + teologia + eläintiede + + + + kirja + kirjat + + + luku + luvut + + + palsta + palstat + + + kuvio + kuviot + + + folio + foliot + + + numero + numerot + + + rivi + rivit + + + muistiinpano + muistiinpanot + + + opus + opukset + + + sivu + sivut + + + kappale + kappaleet + + + osa + osat + + + osa + osat + + + sub verbo + sub verbis + + + säkeistö + säkeistöt + + + vuosikerta + vuosikerrat + + + + kirja + luku + palsta + kuv. + fol. + nro + op. + + s. + ss. + + kappale + osa + osa + + s.v. + s.vv. + + + säk. + säk. + + + vol. + vol. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + toimittaja + toimittajat + + + toimittaja + toimittajat + + + suomentaja + suomentajat + + + toimittaja ja suomentaja + toimittajat ja suomentajat + + + + + + + + + toim. + toim. + + + toim. + toim. + + + suom. + suom. + + + toim. ja suom. + toim. ja suom. + + + + toimittanut + toimittanut + suomentanut + toimittanut ja suomentanut + vastaanottaja + haastatellut + + + + toim. + toim. + suom. + toim. ja suom. + + + tammikuu + helmikuu + maaliskuu + huhtikuu + toukokuu + kesäkuu + heinäkuu + elokuu + syyskuu + lokakuu + marraskuu + joulukuu + + + tammi + helmi + maalis + huhti + touko + kesä + heinä + elo + syys + loka + marras + joulu + + + kevät + kesä + syksy + talvi + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-fr-FR.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-fr-FR.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + consulté + et + et autres + anonyme + anon. + à + par + vers + v. + cité + + édition + éditions + + éd. + et al. + à paraître + de + ibid. + dans + sous presse + Internet + entretien + lettre + sans date + s. d. + en ligne + présenté à + + référence + références + + + réf. + réf. + + consulté + + + apr. J.-C. + av. J.-C. + + + «  +  » + + + + + ᵉʳ + + + + + + premier + deuxième + troisième + quatrième + cinquième + sixième + septième + huitième + neuvième + dixième + + + anthropologie + astronomie + biologie + botanique + chimie + ingénierie + base générique + géographie + géologie + histoire + lettres et sciences humaines + linguistique + littérature + mathématiques + médecine + philosophie + physique + psychologie + sociologie + sciences de la nature + science politique + sciences sociales + théologie + zoologie + + + + livre + livres + + + chapitre + chapitres + + + colonne + colonnes + + + figure + figures + + + folio + folios + + + numéro + numéros + + + ligne + lignes + + + note + notes + + + opus + opus + + + page + pages + + + paragraphe + paragraphes + + + partie + parties + + + section + sections + + + sub verbo + sub verbis + + + verset + versets + + + volume + volumes + + + + liv. + chap. + col. + fig. + fᵒ + nᵒ + l. + n. + op. + + p. + p. + + paragr. + part. + sect. + + s. v. + s. vv. + + + v. + v. + + + vol. + vol. + + + + + § + § + + + § + § + + + + + + + + + éditeur + éditeurs + + + directeur + directeurs + + + traducteur + traducteurs + + + éditeur et traducteur + éditeurs et traducteurs + + + + + + + + + éd. + éd. + + + dir. + dir. + + + trad. + trad. + + + éd. et trad. + éd. et trad. + + + + édité par + sous la direction de + traduit par + édité et traduit par + à + entretien réalisé par + + + par + éd. par + ss la dir. de + trad. par + éd. et trad. par + + + janvier + février + mars + avril + mai + juin + juillet + août + septembre + octobre + novembre + décembre + + + janv. + févr. + mars + avr. + mai + juin + juill. + août + sept. + oct. + nov. + déc. + + + printemps + été + automne + hiver + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-he-IL.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-he-IL.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + גישה + ו + and others + anonymous + anon + + by + circa + c. + cited + + edition + editions + + ed + ואחרים + forthcoming + מתוך + שם + בתוך + in press + internet + interview + letter + no date + nd + online + presented at the + + reference + references + + + ref. + refs. + + אוחזר + + + AD + BC + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + ספר + ספרים + + + פרק + פרקים + + + טור + טורים + + + figure + figures + + + folio + folios + + + מספר + מספרים + + + שורה + שורות + + + note + notes + + + אופוס + אופרה + + + עמוד + עמודים + + + paragraph + פיסקה + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + בית + בתים + + + כרך + כרכים + + + + bk + chap + col + fig + f + no + op + + 'עמ + 'עמ + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + עורך + עורכים + + + editor + editors + + + מתרגם + מתרגמים + + + editor & translator + editors & translators + + + + + + + + + ed + eds + + + ed. + eds. + + + tran + trans + + + ed. & tran. + eds. & trans. + + + + נערך ע"י + edited by + תורגם ע"י + edited & translated by + to + interview by + + + by + ed + ed. + trans + ed. & trans. by + + + ינואר + פברואר + מרץ + אפריל + מאי + יוני + יולי + אוגוסט + ספטמבר + אוקטובר + נובמבר + דצמבר + + + Jan + Feb + Mar + Apr + May + Jun + Jul + Aug + Sep + Oct + Nov + Dec + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-hu-HU.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-hu-HU.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + elérés + és + és mások + név nélkül + nn + + by + circa + c. + idézi + + edition + editions + + ed + et al. + megjelenés alatt + forrás + ibid. + in + nyomtatás alatt + internet + interjú + levél + no date + nd + online + előadás + + reference + references + + + ref. + refs. + + elérés + + + AD + BC + + + + + + » + « + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + antropológia + csillagászat + biológia + botanika + kémia + mérnöki tudományok + általános + földrajz + geológia + történelem + bölcsésztudományok + linguistics + irodalom + matematika + orvostudomány + filozófia + fizika + pszichológia + szociológia + tudomány + politikatudomány + társadalomtudomány + teológia + zoológia + + + + könyv + könyv + + + fejezet + fejezet + + + oszlop + oszlop + + + ábra + ábra + + + fóliáns + fóliáns + + + szám + szám + + + sor + sor + + + jegyzet + jegyzet + + + opus + opera + + + oldal + oldal + + + bekezdés + bekezdés + + + rész + rész + + + szakasz + szakasz + + + sub verbo + sub verbis + + + versszak + versszak + + + kötet + kötet + + + + könyv + fej + oszl + ábr + fol + sz + op + + o + o + + bek + rész + szak + + s.v. + s.vv. + + + vsz + vsz + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + szerkesztő + szerkesztő + + + editor + editors + + + fordító + fordító + + + editor & translator + editors & translators + + + + + + + + + szerk + szerk + + + ed. + eds. + + + ford + ford + + + ed. & tran. + eds. & trans. + + + + szerkesztette + edited by + fordította + edited & translated by + címzett + interjúkészítő + + + by + szerk + ed. + ford + ed. & trans. by + + + január + február + március + április + május + június + július + augusztus + szeptember + október + november + december + + + jan + febr + márc + ápr + máj + jún + júl + aug + szept + okt + nov + dec + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-is-IS.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-is-IS.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + sótt + og + og fleiri + nafnlaus + nafnl. + af + eftir + sirka + u.þ.b. + tilvitnun + + útgáfa + útgáfur + + útg. + o.fl. + óbirt + af + sama heimild + í + í prentun + rafrænt + viðtal + bréf + engin dagsetning + e.d. + rafrænt + flutt á + + tilvitnun + tilvitnanir + + + tilv. + tilv. + + sótt + + + e.Kr. + f.Kr. + + + + + + + + + . + . + . + . + + + fyrsti + annar + þriðji + fjórði + fimmti + sjötti + sjöundi + áttundi + níundi + tíundi + + + mannfræði + stjörnufræði + líffræði + grasafræði + efnafræði + verkfræði + almennt efni + landafræði + jarðfræði + saga + hugvísindi + málvísindi + bókmenntir + stærðfræði + læknisfræði + heimspeki + eðlisfræði + sálfræði + félagsfræði + vísindi + stjórnmálafræði + félagsvísindi + guðfræði + dýrafræði + + + + bók + bækur + + + kafli + kaflar + + + dálkur + dálkar + + + mynd + myndir + + + handrit + handrit + + + númer + númer + + + lína + línur + + + skilaboð + skilaboð + + + tónverk + tónverk + + + blaðsíða + blaðsíður + + + málsgrein + málsgreinar + + + hluti + hlutar + + + hluti + hlutar + + + sub verbo + sub verbis + + + vers + vers + + + bindi + bindi + + + + b. + k. + d. + mynd. + handr. + nr. + tónv. + + bls. + bls. + + málsgr. + hl. + hl. + + s.v. + s.vv. + + + v. + v. + + + bindi + bindi + + + + + + ¶¶ + + + § + §§ + + + + + höfundur + höfundar + + + ritstjóri + ritstjórar + + + ritstjóri + ritstjórar + + + þýðandi + þýðendur + + + ritstjóri og þýðandi + ritstjórar og þýðendur + + + + + höf. + höf. + + + ritstj. + ritstj. + + + ritstj. + ritstj. + + + þýð. + þýð. + + + ritstj. og þýð. + ritstj. og þýð. + + + + ritstjóri + ritstjóri + þýddi + ritstjóri og þýðandi + til + viðtal tók + + + eftir + ritst. + ritst. + þýð. + ritst. og þýð. + + + janúar + febrúar + mars + apríl + maí + júní + júlí + ágúst + september + október + nóvember + desember + + + jan. + feb. + mar. + apr. + maí + jún. + júl. + ágú. + sep. + okt. + nóv. + des. + + + vor + sumar + haust + vetur + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-it-IT.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-it-IT.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + consultato + e + e altri + anonimo + anon + a + by + circa + c. + citato + + edizione + edizioni + + ed + et al. + futuro + da + ibid. + in + in stampa + internet + intervista + lettera + no date + S.d. + in linea + presentato al + + reference + references + + + ref. + refs. + + recuperato + + + AD + BC + + + + « + » + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + antropologia + astronomia + biologia + botanica + chimica + ingegneria + generica + geografia + geologia + storia + discipline umanistiche + linguistics + letteratura + matematica + medicina + filosofia + fisica + psicologia + sociologia + scienze + scienze politiche + sociologia + teologia + zoologia + + + + libro + libri + + + capitolo + capitoli + + + colonna + colonne + + + figura + figure + + + foglio + fogli + + + numero + numeri + + + riga + righe + + + nota + note + + + opera + opere + + + pagina + pagine + + + capoverso + capoversi + + + parte + parti + + + paragrafo + paragrafi + + + sub verbo + sub verbis + + + verso + versi + + + volume + volumi + + + + lib + cap + col + fig + fgl + + op + + pag + pagg + + cpv + pt + par + + s.v. + s.vv. + + + v + vv + + + vol + vol + + + + + + ¶¶ + + + § + §§ + + + + + + + + + curatore + curatori + + + editor + editors + + + traduttore + traduttori + + + editor & translator + editors & translators + + + + + + + + + cur + cur + + + ed. + eds. + + + trad + trad + + + ed. & tran. + eds. & trans. + + + + a cura di + edited by + tradotto da + edited & translated by + a + intervista di + + + by + cur. da + ed. + trad. da + ed. & trans. by + + + Gennaio + Febbraio + Marzo + Aprile + Maggio + Giugno + Luglio + Agosto + Settembre + Ottobre + Novembre + Dicembre + + + Gen + Feb + Mar + Apr + Mag + Giu + Lug + Ago + Set + Ott + Nov + Dic + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-ja-JP.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-ja-JP.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + アクセス + + and others + anonymous + anon + at + by + circa + c. + cited + + edition + editions + + ed + + 近刊 + から + 前掲 + + in press + internet + interview + letter + no date + 日付なし + online + presented at the + + reference + references + + + ref. + refs. + + 読み込み + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + + + + + note + notes + + + opus + opera + + + ページ + ページ + + + 段落 + 段落 + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk + chap + col + fig + f + + op + + p + p + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + 編集者 + 編集者 + + + editor + editors + + + 翻訳者 + 翻訳者 + + + editor & translator + editors & translators + + + + + + + + + 編集者 + 編集者 + + + ed. + eds. + + + 翻訳者 + 翻訳者 + + + ed. & tran. + eds. & trans. + + + + 編集者: + edited by + 翻訳者: + edited & translated by + to + interview by + + + by + ed + ed. + trans + ed. & trans. by + + + 1月 + 2月 + 3月 + 4月 + 5月 + 6月 + 7月 + 8月 + 9月 + 10月 + 11月 + 12月 + + + 1月 + 2月 + 3月 + 4月 + 5月 + 6月 + 7月 + 8月 + 9月 + 10月 + 11月 + 12月 + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-km-KH.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-km-KH.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + accessed + and + and others + anonymous + anon. + at + by + circa + c. + cited + + edition + editions + + ed. + et al. + forthcoming + from + ibid + in + in press + internet + interview + letter + no date + n.d. + online + presented at the + + reference + references + + + ref. + refs. + + retrieved + + + AD + BC + + + + + + + + + st + nd + rd + th + + + ទីមួយ + ទីពីរ + ទីបី + ទីបួន + ទីប្រាំ + ទីប្រាំមួយ + ទីប្រាំពីរ + ទីប្រាំបី + ទីប្រាំបួន + ទីដប់មួយ + + + នរវិទ្យា + តារាវិទ្យា + ជីវវិទ្យា + រុក្ខវិទ្យា + គីមីវិទ្យា + វិស្វកម្ម + generic base + ភូមិវិទ្យា + ភូគព្ភវិទ្យា + ប្រវត្តិវិទ្យា + មនុស្សវិទ្យា + ភាសាវិទ្យា + អក្សរសាស្ត្រ + គណិតវិទ្យា + វេជ្ជសាស្ត្រ + ទស្សនវិទ្យា + រូបវិទ្យា + ចិត្តវិទ្យា + សង្គមវិទ្យា + វិទ្យាសាស្ត្រ + វិទ្យាសាស្ត្រនយោបាយ + សង្គមវិទ្យា + ទេវវិទ្យា + សត្តវិទ្យា + + + + សៀវភៅ + សៀវភៅ + + + ជំពូក + ជំពូក + + + កាឡោន + កាឡោន + + + តួលេខ + តួលេខ + + + folio + folios + + + ចំនួន + ចំនួន + + + បន្ទាត់ + បន្ទាត់ + + + កំណត់ចំណាំ + កំណត់ចំណាំ + + + opus + opera + + + ទំព័រ + ទំព័រ + + + កថាខណ្ឌ + កថាខណ្ឌ + + + ជំពូក + ជំពូក + + + ផ្នែក + ផ្នែក + + + sub verbo + sub verbis + + + verse + verses + + + វ៉ុល + វ៉ុល + + + + bk. + chap. + col. + fig. + f. + no. + op. + + p. + pp. + + para. + pt. + sec. + + s.v. + s.vv. + + + v. + vv. + + + vol. + vols. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editor + editors + + + + editors + + + translator + translator + + + editor & translator + editors & translators + + + + + + + + + ed. + eds. + + + ed. + eds. + + + tran. + trans. + + + ed. & tran. + eds. & trans. + + + + edited by + edited by + translated by + edited & translated by + to + interview by + + + by + ed. + ed. + trans. + ed. & trans. by + + + មករា + កុម្ភៈ + មីនា + មេសា + ឧសភា + មិថុនា + កក្កដា + សីហា + កញ្ញា + តុលា + វិច្ឆិកា + ធ្នូ + + + Jan. + Feb. + Mar. + Apr. + May + Jun. + Jul. + Aug. + Sep. + Oct. + Nov. + Dec. + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-ko-KR.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-ko-KR.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + 접근된 + 와/과 + and others + anonymous + anon + at + by + circa + c. + cited + + edition + editions + + ed + 기타 + 근간 + (으)로부터 + ibid. + in + in press + internet + interview + letter + no date + 일자 없음 + online + presented at the + + reference + references + + + ref. + refs. + + retrieved + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + + + + + note + notes + + + opus + opera + + + 페이지 + 페이지 + + + 단락 + 단락 + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk + chap + col + fig + f + + op + + p + pp + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + 편집자 + 편집자 + + + editor + editors + + + 번역자 + 번역자 + + + editor & translator + editors & translators + + + + + + + + + 편집자 + 편집자 + + + ed. + eds. + + + 번역자 + 번역자 + + + ed. & tran. + eds. & trans. + + + + 편집자: + edited by + 번역자: + edited & translated by + to + interview by + + + by + ed + ed. + trans + ed. & trans. by + + + 1월 + 2월 + 3월 + 4월 + 5월 + 6월 + 7월 + 8월 + 9월 + 10월 + 11월 + 12월 + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-mn-MN.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-mn-MN.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + accessed + and + and others + anonymous + anon + at + by + circa + c. + cited + + edition + editions + + ed + et al. + forthcoming + from + ibid. + in + in press + internet + interview + letter + no date + n.d. + online + presented at the + + reference + references + + + ref. + refs. + + retrieved + + + AD + BC + + + + + « + » + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + line + lines + + + note + notes + + + opus + opera + + + page + pages + + + paragraph + paragraph + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk + chap + col + fig + f + no + op + + p + pp + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editor + editors + + + editor + editors + + + translator + translators + + + editor & translator + editors & translators + + + + + + + + + ed + eds + + + ed. + eds. + + + tran + trans + + + ed. & tran. + eds. & trans. + + + + edited by + edited by + translated by + edited & translated by + to + interview by + + + by + ed + ed. + trans + ed. & trans. by + + + January + February + March + April + May + June + July + August + September + October + November + December + + + Jan + Feb + Mar + Apr + May + Jun + Jul + Aug + Sep + Oct + Nov + Dec + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-nb-NO.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-nb-NO.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + åpnet + og + med flere + anonym + anon. + + av + circa + ca. + sitert + + utgave + utgaver + + utg. + mfl. + kommende + fra + ibid. + i + i trykk + Internett + intervju + brev + ingen dato + udatert + online + presentert på + + referanse + referanser + + + ref. + refr. + + hentet + + + f.Kr + e.Kr + + + « + » + + + + + . + . + . + . + + + første + andre + tredje + fjerde + femte + sjette + sjuende + åttende + niende + tiende + + + antropologi + astronomi + biologi + botanikk + kjemi + ingeniørvitenskap + generell + geografi + geologi + historie + humanistiske fag + lingvistikk + litteratur + mattematikk + medisin + filosofi + fysikk + fysiologi + sosiologi + naturvitenskap + statsvitenskap + samfunnsvitenskap + teologi + zoologi + + + + bok + bøker + + + kapittel + kapitler + + + kolonne + kolonner + + + figur + figurer + + + folio + folioer + + + nummer + numre + + + linje + linjer + + + note + noter + + + opus + opus + + + side + sider + + + avsnitt + avsnitt + + + del + deler + + + paragraf + paragrafer + + + sub verbo + sub verbis + + + vers + vers + + + bind + bind + + + + b. + kap. + kol. + fig. + fol. + nr. + op. + + s. + s. + + avsn. + d. + pargr. + + s.v. + s.vv. + + + v. + v. + + + bd. + bd. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + redaktør + redaktører + + + redaktør + redaktører + + + oversetter + oversettere + + + redaktør & oversetter + redaktører & oversettere + + + + + + + + + red. + red. + + + red. + red. + + + overs. + overs. + + + red. & overs. + red. & overs. + + + + redigert av + redigert av + oversatt av + redigert & oversatt av + mottatt av + intervjuet av + + + av + red. + red. + overs. + red. & overs. av + + + januar + februar + mars + april + mai + juni + juli + august + september + oktober + november + desember + + + jan. + feb. + mar. + apr. + mai + jun. + jul. + aug. + sep. + okt. + nov. + des. + + + vår + sommer + høst + vinter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-nl-NL.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-nl-NL.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + bezocht + en + en anderen + anoniem + anon. + bij + door + circa + c. + geciteerd + + editie + edities + + ed. + e.a. + in voorbereiding + van + ibid. + in + in druk + internet + interview + brief + zonder datum + z.d. + online + gepresenteerd bij + + referentie + referenties + + + ref. + refs. + + verkregen + + + AD + BC + + + + + + + + + e + e + e + e + + + eerste + tweede + derde + vierde + vijfde + zesde + zevende + achtste + negende + tiende + + + anthropologie + astronomie + biologie + botanie + scheikunde + techniek + generiek + geografie + geologie + geschiedenis + geesteswetenschappen + taalkunde + literatuur + wiskunde + medicijnen + filosofie + natuurkunde + psychologie + sociologie + wetenschap + politieke wetenschappen + sociale wetenschappen + theologie + zoologie + + + + boek + boeken + + + hoofdstuk + hoofdstukken + + + column + columns + + + figuur + figuren + + + folio + folio's + + + nummer + nummers + + + regel + regels + + + aantekening + aantekeningen + + + opus + opera + + + pagina + pagina's + + + paragraaf + paragrafen + + + deel + delen + + + sectie + secties + + + sub verbo + sub verbis + + + vers + versen + + + volume + volumes + + + + bk. + hfdst. + col. + fig. + f. + nr. + op. + + p. + pp. + + par. + deel + sec. + + s.v. + s.vv. + + + v. + vv. + + + vol. + vols. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + redacteur + redacteuren + + + redacteur + redacteuren + + + vertaler + vertalers + + + redacteur & vertaler + redacteuren & vertalers + + + + + + + + + red. + red. + + + red. + red. + + + vert. + vert. + + + red. & vert. + red. & vert. + + + + bewerkt door + bewerkt door + vertaald door + bewerkt & vertaald door + ontvangen door + geïnterviewd door + + + door + bewerkt door + bewerkt door + vertaald door + bewerkt & vertaald door + + + januari + februari + maart + april + mei + juni + juli + augustus + september + oktober + november + december + + + jan. + feb. + mrt. + apr. + mei + jun. + jul. + aug. + sep. + okt. + nov. + dec. + + + lente + zomer + herst + winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-nn-NO.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-nn-NO.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + vitja + og + med fleire + anonym + anon. + + av + circa + ca. + sitert + + utgåve + utgåver + + utg. + mfl. + kommande + frå + ibid. + i + i trykk + Internett + intervju + brev + ingen dato + udatert + online + presentert på + + referanse + referansar + + + ref. + refr. + + henta + + + f.Kr + e.Kr + + + « + » + + + + + . + . + . + . + + + første + andre + tredje + fjerde + femte + sjette + sjuande + åttande + niande + tiande + + + antropologi + astronomi + biologi + botanikk + kjemi + ingeniørvitskap + generell + geografi + geologi + historie + humanistiske fag + lingvistikk + litteratur + mattematikk + medisin + filosofi + fysikk + fysiologi + sosiologi + naturvitskap + statsvitskap + samfunnsvitskap + teologi + zoologi + + + + bok + bøker + + + kapittel + kapittel + + + kolonne + kolonner + + + figur + figurar + + + folio + folioar + + + nummer + nummer + + + linje + linjer + + + note + notar + + + opus + opus + + + side + sider + + + avsnitt + avsnitt + + + del + deler + + + paragraf + paragrafar + + + sub verbo + sub verbis + + + vers + vers + + + bind + bind + + + + b. + kap. + kol. + fig. + fol. + nr. + op. + + s. + s. + + avsn. + d. + par. + + s.v. + s.vv. + + + v. + v. + + + bd. + bd. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + redaktør + redaktørar + + + redaktør + redaktørar + + + omsetjar + omsetjarar + + + redaktør & omsetjar + redaktørar & omsetjarar + + + + + + + + + red. + red. + + + red. + red. + + + oms. + oms. + + + red. & oms. + red. & oms. + + + + redigert av + redigert av + omsett av + redigert & omsett av + motteke av + intervjua av + + + av + red. + red. + oms. + red. & oms. av + + + januar + februar + mars + april + mai + juni + juli + august + september + oktober + november + desember + + + jan. + feb. + mar. + apr. + mai + jun. + jul. + aug. + sep. + okt. + nov. + des. + + + vår + sommar + haust + vinter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-pl-PL.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-pl-PL.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + udostępniono + i + i inni + anonim + anon. + na + przez + circa + ca + cytowane + + wydanie + wydania + + wyd. + et al. + w przygotowaniu + z + ibid. + w + w druku + internet + wywiad + list + brak daty + b.d. + online + zaprezentowano na + + referencja + referencje + + + ref. + ref. + + pobrano + + + n.e. + p.n.e. + + + + + + « + » + + + . + . + . + . + + + pierwszy + drugi + trzeci + czwarty + piąty + szósty + siódmy + ósmy + dziewiąty + dziesiąty + + + antropologia + astronomia + biologia + botanika + chemia + inżynieria + ogólny + geografia + geologia + historia + humanistyka + lingwistyka + literatura + matematyka + medycyna + filozofia + fizyka + psychologia + socjologia + nauki ścisłe + nauki polityczne + nauki społeczne + teologia + zoologia + + + + książka + książki + + + rozdział + rozdziały + + + kolumna + kolumny + + + rycina + ryciny + + + folio + folio + + + numer + numery + + + wers + wersy + + + notatka + notatki + + + opus + opera + + + strona + strony + + + akapit + akapity + + + część + części + + + sekcja + sekcje + + + sub verbo + sub verbis + + + wers + wersy + + + tom + tomy + + + + książka + rozdz. + kol. + ryc. + fol. + nr + op. + + s. + ss. + + akap. + cz. + sekc. + + s.v. + s.vv. + + + w. + w. + + + t. + t. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + redaktor + redaktorzy + + + edytor + edytorzy + + + tłumacz + tłumacze + + + redaktor & tłumacz + redaktorzy & tłumacze + + + + + + + + + red. + red. + + + red. + red. + + + tłum. + tłum. + + + red. & tłum. + red. & tłum. + + + + zredagowane przez + zredagowane przez + przetłumaczone przez + zredagowane & przetłumaczone przez + dla + przeprowadzony przez + + + przez + red. + red. + tłum. + red. & tłum. + + + styczeń + luty + marzec + kwiecień + maj + czerwiec + lipiec + sierpień + wrzesień + październik + listopad + grudzień + + + sty. + luty + mar. + kwi. + maj + cze. + lip. + sie. + wrz. + paź. + lis. + grudz. + + + wiosna + lato + jesień + zima + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-pt-BR.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-pt-BR.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + acessado + e + e outros + anônimo + anon + em + por + circa + c. + citado + + edição + edições + + ed + et al. + a ser publicado + de + ibidem + in + no prelo + internet + entrevista + carta + sem data + [s.d.] + online + apresentado em + + referência + referências + + + ref. + refs. + + recuperado + + + AD + BC + + + + + + + + + º + º + º + º + + + primeiro + segundo + terceiro + quarto + quinto + sexto + sétimo + oitavo + nono + décimo + + + antropologia + astronomia + biologia + botânica + química + engenharia + base genérica + geografia + geologia + história + humanidades + linguistics + literatura + matemática + medicina + philosofia + física + psicologia + sociologia + ciências + ciências políticas + ciências sociais + teologia + zoologia + + + + livro + livros + + + capítulo + capítulos + + + coluna + colunas + + + figura + figuras + + + folio + folios + + + número + números + + + linha + linhas + + + nota + notas + + + opus + opera + + + página + páginas + + + parágrafo + parágrafos + + + parte + partes + + + seção + seções + + + sub verbo + sub verbis + + + verso + versos + + + volume + volumes + + + + liv. + cap. + col. + fig. + f. + + op. + + p. + p. + + parag. + pt. + seç. + + s.v. + s.vv. + + + v. + vv. + + + vol. + vols. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + organizador + organizadores + + + editor + editors + + + tradutor + tradutores + + + editor e tradutor + editores e tradutores + + + + + + + + + org. + orgs. + + + ed. + eds. + + + trad. + trads. + + + ed. e trad. + eds. e trads. + + + + organizado por + editado por + traduzido por + editado & traduzido por + para + entrevista de + + + por + org. + ed. + trad. + ed. e trad. por + + + janeiro + fevereiro + março + abril + maio + junho + julho + agosto + setembro + outubro + novembro + dezembro + + + jan. + fev. + mar. + abr. + maio + jun. + jul. + ago. + set. + out. + nov. + dez. + + + Primavera + Verão + Outono + Inverno + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-pt-PT.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-pt-PT.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + acedido + e + e outros + anónimo + anón + em + by + circa + c. + citado + + edição + edições + + ed + et al. + a publicar + de + ibid. + em + no prelo + internet + entrevista + carta + no date + sem data + em linha + apresentado na + + reference + references + + + ref. + refs. + + obtido + + + AD + BC + + + + « + » + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + antropologia + astronomia + biologia + botânica + química + engenharia + base genérica + geografia + geologia + história + humanidades + linguistics + literatura + matemática + medicina + filosofia + física + psicologia + sociologia + ciência + ciência política + ciência social + teologia + zoologia + + + + livro + livros + + + capítulo + capítulos + + + coluna + colunas + + + figura + figuras + + + fólio + fólios + + + número + número + + + linha + linhas + + + nota + notas + + + opus + opera + + + página + páginas + + + parágrafo + parágrafos + + + parte + partes + + + secção + secções + + + sub verbo + sub verbis + + + versículo + versículos + + + volume + volumes + + + + liv + cap + col + fig + f + n + op + + p + pp + + par + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editor + editores + + + editor + editors + + + tradutor + tradutores + + + editor & translator + editors & translators + + + + + + + + + ed + eds + + + ed. + eds. + + + trad + trads + + + ed. & tran. + eds. & trans. + + + + editado por + edited by + traduzido por + edited & translated by + para + entrevistado por + + + by + ed + ed. + trad + ed. & trans. by + + + Janeiro + Fevereiro + Março + Abril + Maio + Junho + Julho + Agosto + Setembro + Outubro + Novembro + Dezembro + + + Jan + Fev + Mar + Abr + Mai + Jun + Jul + Ago + Set + Out + Nov + Dez + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-ro-RO.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-ro-RO.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + data accesării: + și + și alții + anonim + anon. + la + de + circa + cca. + citat + + ediția + edițiile + + ed + et al. + în curs de apariție + din + ibidem + în + sub tipar + internet + interviu + scrisoare + fără dată + f.a. + online + prezentat la + + referință + referințe + + + ref. + ref. + + preluat în + + + d.Hr. + î.Hr. + + + + + + « + » + + + + -lea + -lea + -lea + + + primul + al doilea + al treilea + al patrulea + al cincilea + al șaselea + al șaptelea + al optulea + al nouălea + al zecelea + + + antropologie + astronomie + biologie + botanică + chimie + inginerie + general (de bază) + geografie + geologie + istorie + științe umaniste + lingvistică + literatură + matematică + medicină + filosofie + fizică + psihologie + sociologie + știință + științe politice + științe sociale + teologie + zoologie + + + + cartea + cărțile + + + capitolul + capitolele + + + coloana + coloanele + + + figura + figurile + + + folio + folio + + + numărul + numerele + + + linia + liniile + + + nota + notele + + + opusul + opusurile + + + pagina + paginile + + + paragraful + paragrafele + + + partea + părțile + + + secțiunea + secțiunile + + + sub verbo + sub verbis + + + versetul + versetele + + + volumul + volumele + + + + cart. + cap. + col. + fig. + fol. + nr. + op. + + p. + pp. + + par. + part. + sec. + + s.v. + s.vv. + + + v. + vv. + + + vol. + vol. + + + + + + ¶¶ + + + § + §§ + + + + + autor + autori + + + editor + editori + + + editor + editori + + + traducător + traducători + + + editor & traducător + editori & traducători + + + + + aut. + aut. + + + ed. + ed. + + + ed. + ed. + + + trad. + trad. + + + ed. & trad. + ed. & trad. + + + + ediție de + ediție de + traducere de + ediție & traducere de + în + interviu de + + + de + ed. + ed. + trad. + ed. & trad. de + + + ianuarie + februarie + martie + aprilie + mai + iunie + iulie + august + septembrie + octombrie + noiembrie + decembrie + + + ian. + feb. + mar. + apr. + mai + iun. + iul. + aug. + sep. + oct. + nov. + dec. + + + primăvara + vara + toamna + iarna + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-ru-RU.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-ru-RU.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + просмотрено + и + и др. + аноним + анон. + на + + circa + ca. + цитируется по + цит. по + + издание + издания + + изд. + и др. + ожидается + от + там же + в + в печати + Интернет + интервью + письмо + без даты + б. д. + online + представлено на + + reference + references + + + ref. + refs. + + извлечено + + + н. э. + до н. э. + + + « + » + + + + + й + й + й + й + + + первый + второй + третий + четвертый + пятый + шестой + седьмой + восьмой + девятый + десятый + + + антропология + астрономия + биология + ботаника + химия + инженерное дело + общий + география + геология + история + гуманитарные науки + лингвистика + литературоведение + математика + медицина + философия + физика + психология + социология + естественные науки + политические науки + социальные науки + теология + зоология + + + + книга + книги + + + глава + главы + + + столбец + столбцы + + + рисунок + рисунки + + + лист + листы + + + выпуск + выпуски + + + строка + строки + + + примечание + примечания + + + сочинение + сочинения + + + страница + страницы + + + параграф + параграфы + + + часть + части + + + раздел + разделы + + + смотри + смотри + + + стих + стихи + + + том + тома + + + + кн. + гл. + стб. + рис. + л. + + соч. + + с. + с. + + пара. + ч. + разд. + + см. + см. + + + ст. + ст. + + + т. + тт. + + + + + + ¶¶ + + + § + §§ + + + + + + + + + редактор + редакторы + + + ответственный редактор + ответственные редакторы + + + переводчик + переводчики + + + редактор и переводчик + редакторы и переводчики + + + + + + + + + ред. + ред. + + + отв. ред. + отв. ред. + + + перев. + перев. + + + ред. и перев. + ред. и перев. + + + + отредактировано + отредактировано + переведено + отредактировано и переведено + к + интервью + + + + ред. + отв. ред. + перев. + ред. и перев. + + + январь + февраль + март + апрель + май + июнь + июль + август + сентябрь + октябрь + ноябрь + декабрь + + + янв. + фев. + мар. + апр. + май + июн. + июл. + авг. + сен. + окт. + ноя. + дек. + + + весна + лета + осень + зима + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-sk-SK.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-sk-SK.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + cit + a + and ďalší + anonym + anon + v + by + circa + c. + cit + + vydanie + vydania + + vyd + et al + nadchádzajúci + z + ibid. + v + v tlači + internet + osobná komunikácia + list + no date + n.d. + online + prezentované na + + reference + references + + + ref. + refs. + + cit + + + AD + BC + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + antropológia + astronómia + biológia + botanika + chémia + strojárstvo/technika + všeobecný základ + geografia + geológia + história + humanitné vedy + linguistics + literatúra + matematika + medicína + filozofia + fyzika + psychológia + sociológia + veda + politické vedy + sociálne vedy + teológia + zoológia + + + + kniha + knihy + + + kapitola + kapitoly + + + stĺpec + stĺpce + + + obrázok + obrázky + + + list + listy + + + číslo + čísla + + + riadok + riadky + + + poznámka + poznámky + + + opus + opera + + + strana + strany + + + odstavec + odstavce + + + časť + časti + + + sekcia + sekcie + + + sub verbo + sub verbis + + + verš + verše + + + ročník + ročníky + + + + k + kap + stĺp + obr + l + č + op + + s + s + + par + č + sek + + s.v. + s.vv. + + + v + v + + + roč + roč + + + + + + + + + § + § + + + + + + + + + editor + editori + + + editor + editors + + + prekladateľ + prekladatelia + + + editor & translator + editors & translators + + + + + + + + + ed + ed + + + ed. + eds. + + + prek + prek + + + ed. & tran. + eds. & trans. + + + + editoval + edited by + preložil + edited & translated by + adresát + rozhovor urobil + + + by + ed + ed. + prel + ed. & trans. by + + + január + február + marec + apríl + máj + jún + júl + august + september + október + november + december + + + jan + feb + mar + apr + máj + jún + júl + aug + sep + okt + nov + dec + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-sl-SI.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-sl-SI.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + dostopano + in + in drugi + anonimni + anon + pri + by + circa + c. + citirano + + izdaja + izdaje + + iz + idr. + pred izidom + od + isto + v + v tisku + internet + intervju + pismo + no date + b.d. + na spletu + predstavljeno na + + reference + references + + + ref. + refs. + + pridobljeno + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + antropologija + astronomija + biologija + botanika + kemija + strojništvo + splošno + geografija + geologija + zgodovina + humanistika + linguistics + literatura + matematika + medicina + filozofija + fizika + psihologija + sociologija + znanost + politologija + družbene vede + teologija + zoologija + + + + knjiga + knjige + + + poglavje + poglavja + + + stolpec + stolpci + + + slika + slike + + + folio + folii + + + številka + številke + + + vrstica + vrstice + + + opomba + opombe + + + opus + opera + + + stran + strani + + + odstavek + odstavki + + + del + deli + + + odsek + odseki + + + sub verbo + sub verbis + + + verz + verzi + + + letnik + letniki + + + + knj + pogl + sto + sl + f + št + op + + str + str + + odst + del + odsk + + s.v. + s.vv. + + + v + v + + + let + let + + + + + + ¶¶ + + + § + §§ + + + + + + + + + urednik + uredniki + + + editor + editors + + + prevajalec + prevajalci + + + editor & translator + editors & translators + + + + + + + + + ur + ur + + + ed. + eds. + + + prev + prev + + + ed. & tran. + eds. & trans. + + + + uredil + edited by + prevedel + edited & translated by + za + intervjuval + + + by + ur + ed. + prev + ed. & trans. by + + + januar + februar + marec + april + maj + junij + julij + avgust + september + oktober + november + december + + + jan + feb + mar + apr + maj + jun + jul + avg + sep + okt + nov + dec + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-sr-RS.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-sr-RS.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + приступљено + и + и остали + анонимна + анон. + на + by + circa + c. + цитирано + + издање + издања + + изд. + и остали + долазећи + од + ibid. + у + у штампи + Интернет + интервју + писмо + no date + без датума + на Интернету + представљено на + + reference + references + + + ref. + refs. + + преузето + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + антропологија + астрономија + биологија + ботаника + хемија + инжињерство + уопштена основа + географија + геологија + историја + култура и уметност + linguistics + литература + математика + медицина + филозофија + физика + психологија + социологија + наука + политичка наука + друштвена наука + теологија + зоологија + + + + књига + књиге + + + поглавље + поглавља + + + колона + колоне + + + цртеж + цртежи + + + фолио + фолији + + + број + бројеви + + + линија + линије + + + белешка + белешке + + + опус + опера + + + страница + странице + + + параграф + параграфи + + + део + делова + + + одељак + одељака + + + sub verbo + sub verbis + + + строфа + строфе + + + том + томова + + + + књига + Пог. + кол. + црт. + фолио + изд. + оп. + + стр. + стр. + + пар. + део + од. + + s.v. + s.vv. + + + стр. + стр. + + + том + томови + + + + + + ¶¶ + + + § + §§ + + + + + + + + + уредник + урединици + + + editor + editors + + + преводилац + преводиоци + + + editor & translator + editors & translators + + + + + + + + + ур. + ур. + + + ed. + eds. + + + прев. + прев. + + + ed. & tran. + eds. & trans. + + + + уредио + edited by + превео + edited & translated by + прима + интервјуисао + + + by + ур. + ed. + прев. + ed. & trans. by + + + Јануар + Фебруар + Март + Април + Мај + Јуни + Јули + Август + Септембар + Октобар + Новембар + Децембар + + + Јан. + Феб. + Март + Апр. + Мај + Јуни + Јули + Авг. + Сеп. + Окт. + Нов. + Дец. + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-sv-SE.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-sv-SE.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + åtkomstdatum + och + och andra + anonym + anon + vid + by + circa + c. + citerad + + upplaga + upplagor + + uppl + m.fl. + kommande + från + ibid. + i + i tryck + internet + intervju + brev + no date + nd + online + presenterad vid + + reference + references + + + ref. + refs. + + hämtad + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + antropologi + astronomi + biologi + botanik + kemi + teknik + generic base + geografi + geologi + historia + humaniora + linguistics + litteraturvetenskap + matematik + medicin + filosofi + fysik + psykologi + sociologi + vetenskap + statsvetenskap + samhällsvetenskap + teologi + zoologi + + + + bok + böcker + + + kapitel + kapitel + + + kolumn + kolumner + + + figur + figurer + + + folio + folios + + + nummer + nummer + + + rad + rader + + + not + noter + + + opus + opera + + + sida + sidor + + + stycke + stycken + + + dek + delar + + + avsnitt + avsnitt + + + sub verbo + sub verbis + + + vers + verser + + + volym + volumer + + + + bok + kap + kol + fig + f + num + op + + s + ss + + st + del + avs + + s.v. + s.vv. + + + vers + verser + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + redaktör + redaktörer + + + editor + editors + + + översättare + översättare + + + editor & translator + editors & translators + + + + + + + + + red + reds + + + ed. + eds. + + + övers + övers + + + ed. & tran. + eds. & trans. + + + + redigerad av + edited by + översatt av + edited & translated by + till + intervju av + + + by + red + ed. + övers + ed. & trans. by + + + Januari + Februari + Mars + April + Maj + Juni + Juli + Augusti + September + Oktober + November + December + + + Jan + Feb + Mar + Apr + Maj + Jun + Jul + Aug + Sep + Okt + Nov + Dec + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-th-TH.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-th-TH.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + สืบค้น + และ + และคณะ + นิรนาม + นิรนาม + ที่ + โดย + โดยประมาณ + ประมาณ + อ้างถึง + + พิมพ์ครั้งที่ + พิมพ์ครั้งที่ + + พิมพ์ครั้งที่ + และคณะ + เต็มใจให้ข้อมูล + จาก + ในที่เดียวกัน + ใน + กำลังรอตีพิมพ์ + อินเทอร์เน็ต + การสัมภาษณ์ + จดหมาย + ไม่ปรากฏปีที่พิมพ์ + ม.ป.ป. + ออนไลน์ + นำเสนอที่ + + เอกสารอ้างอิง + เอกสารอ้างอิง + + + อ้างอิง + อ้างอิง + + สืบค้น + + + ค.ศ. + พ.ศ. + + + + + + + + + หนึ่ง + สอง + สาม + สี่ + + + หนึ่ง + สอง + สาม + สี่ + ห้า + หก + เจ็ด + แปด + เก้า + สิบ + + + มานุษยวิทยา + ดาราศาสตร์ + ชีววิทยา + พฤกษศาสตร์ + เคมี + วิศวกรรมศาสตร์ + พื้นฐานทั่วไป + ภูมิศาสตร์ + ธรณีวิทยา + ประวัติศาสตร์ + มนุษยศาสตร์ + ภาษาศาสตร์ + วรรณคดี + คณิตศาสตร์ + เวชศาสตร์ + ปรัชญา + ฟิสิกส์ + จิตวิทยา + สังคมวิทยา + วิทยาศาสตร์ + รัฐศาสตร์ + สังคมศาสตร์ + เทววิทยา + สัตววิทยา + + + + หนังสือ + หนังสือ + + + บทที่ + บทที่ + + + สดมภ์ + สดมภ์ + + + รูปภาพ + รูปภาพ + + + หน้า + หน้า + + + ฉบับที่ + ฉบับที่ + + + บรรทัดที่ + บรรทัดที่ + + + บันทึก + บันทึก + + + บทประพันธ์ + บทประพันธ์ + + + หน้า + หน้า + + + ย่อหน้า + ย่อหน้า + + + ส่วนย่อย + ส่วนย่อย + + + หมวด + หมวด + + + ใต้คำ + ใต้คำ + + + ร้อยกรอง + ร้อยกรอง + + + ปีที่ + ปีที่ + + + + หนังสือ + บทที่ + สดมภ์ + รูปภาพ + หน้า + ฉบับที่ + บทประพันธ์ + + น. + น. + + ย่อหน้า + ส่วนย่อย + หมวด + + ใต้คำ + ใต้คำ + + + ร้อยกรอง + ร้อยกรอง + + + ปี + ปี + + + + + + ¶¶ + + + § + §§ + + + + + + + + + บรรณาธิการ + บรรณาธิการ + + + ผู้อำนวยการบทบรรณาธิการ + ผู้อำนวยการบทบรรณาธิการ + + + ผู้แปล + ผู้แปล + + + บรรณาธิการและผู้แปล + บรรณาธิการและผู้แปล + + + + + + + + + บ.ก. + บ.ก. + + + ผอ.บทบรรณาธิการ + ผอ.บทบรรณาธิการ + + + ผู้แปล + ผู้แปล + + + บ.ก. + บ.ก. + + + + เรียบเรียงโดย + เรียบเรียงโดย + แปลโดย + แปลและเรียบเรียงโดย + ถึง + สัมภาษณ์โดย + + + โดย + โดย + โดย + แปล + แปลและเรียบเรียงโดย + + + มกราคม + กุมภาพันธ์ + มีนาคม + เมษายน + พฤษภาคม + มิถุนายน + กรกฎาคม + สิงหาคม + กันยายน + ตุลาคาม + พฤศจิกายน + ธันวาคม + + + ม.ค. + ก.พ. + มี.ค. + เม.ย. + พ.ค. + มิ.ย. + ก.ค. + ส.ค. + ก.ย. + ต.ค. + พ.ย. + ธ.ค. + + + ฤดูใบไม้ผลิ + ฤดูร้อน + ฤดูใบไม้ร่วง + ฤดูหนาว + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-tr-TR.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-tr-TR.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + erişildi + ve + ve diğ. + isimsiz + isimsiz + de + by + circa + c. + kaynak + + edition + editions + + ed + ve diğ. + gelecek + den + ibid. + içinde + basımda + internet + interview + letter + no date + tarih yok + çevrimiçi + sunulan + + reference + references + + + ref. + refs. + + erişildi + + + AD + BC + + + + « + » + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + antropoloji + astronomi + biyoloji + botanik + kimya + mühendislik + generic-base + coğrafya + jeoloji + tarih + humanities + linguistics + edebiyat + matematik + tıp + felsefe + fizik + pisikoloji + sosyoloji + bilim + siyaset bilimi + sosyal bilimler + din bilimi + hayvanbilimi + + + + kitap + kitaplar + + + bölüm + bölümler + + + sütun + sütunlar + + + şekil + şekiller + + + folyo + folyo + + + sayı + sayılar + + + satır + satırlar + + + not + notlar + + + opus + opera + + + sayfa + sayfalar + + + paragraf + paragraflar + + + kısım + kısımlar + + + bölüm + bölümler + + + sub verbo + sub verbis + + + dize + dizeler + + + cilt + ciltler + + + + kit + böl + süt + şek + f + sayı + op + + s + ss + + para + kıs + böl + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editör + editörler + + + editor + editors + + + çevirmen + çevirmenler + + + editor & translator + editors & translators + + + + + + + + + ed + ed + + + ed. + eds. + + + çev + çev + + + ed. & tran. + eds. & trans. + + + + editör + edited by + çeviren + edited & translated by + to + Röportaj yapan + + + by + ed + ed. + çev + ed. & trans. by + + + Ocak + Şubat + Mart + Nisan + Mayıs + Haziran + Temmuz + Ağustos + Eylül + Ekim + Kasım + Aralık + + + Oca + Şub + Mar + Nis + May + Haz + Tem + Ağu + Eyl + Eki + Kas + Ara + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-uk-UA.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-uk-UA.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + accessed + і + та інші + анонімний + анон. + на + by + circa + c. + cited + + edition + editions + + ed + et al. + forthcoming + із + ibid. + в + у пресі + інтернет + інтервю + лист + no date + n.d. + online + presented at the + + reference + references + + + ref. + refs. + + retrieved + + + AD + BC + + + + + « + » + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + антропологія + астрономія + біологія + ботаніка + хімія + інженерія + generic base + географія + геологія + історія + гуманітарні + linguistics + література + математика + медицина + філософія + фізика + психологія + соціологія + наука + політичні науки + соціальні науки + теологія + зоологія + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + line + lines + + + note + notes + + + opus + opera + + + page + pages + + + paragraph + paragraph + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk + chap + col + fig + f + no + op + + p + pp + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editor + editors + + + editor + editors + + + translator + translators + + + editor & translator + editors & translators + + + + + + + + + ed + eds + + + ed. + eds. + + + tran + trans + + + ed. & tran. + eds. & trans. + + + + edited by + edited by + translated by + edited & translated by + to + interview by + + + by + ed + ed. + trans + ed. & trans. by + + + Січень + Лютий + Березень + Квітень + Травень + Червень + Липень + Серпень + Вересень + Жовтень + Листопад + Грудень + + + Січ + Лют + Бер + Квіт + Трав + Чер + Лип + Сер + Вер + Жов + Лис + Груд + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-vi-VN.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-vi-VN.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + truy cập + + and others + anonymous + anon + at + by + circa + c. + cited + + edition + editions + + ed + và c.s. + sắp tới + từ + n.t. + trong + in press + internet + interview + letter + no date + không ngày + online + presented at the + + reference + references + + + ref. + refs. + + truy vấn + + + AD + BC + + + + « + » + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + dòng + dòng + + + note + notes + + + opus + opera + + + trang + trang + + + đoạn văn + đoạn văn + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk + chap + col + fig + f + số p.h + op + + tr + tr + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + biên tập viên + biên tập viên + + + editor + editors + + + biên dịch viên + biên dịch viên + + + editor & translator + editors & translators + + + + + + + + + b.t.v + b.t.v + + + ed. + eds. + + + b.d.v + b.d.v + + + ed. & tran. + eds. & trans. + + + + biên tập bởi + edited by + biên dịch bởi + edited & translated by + to + interview by + + + by + b.t + ed. + b.d + ed. & trans. by + + + Tháng Giêng + Tháng Hai + Tháng Ba + Tháng Tư + Tháng Năm + Tháng Sáu + Tháng Bảy + Tháng Tám + Tháng Chín + Tháng Mười + Tháng Mười-Một + Tháng Chạp + + + tháng 1 + tháng 2 + tháng 3 + tháng 4 + tháng 5 + tháng 6 + tháng 7 + tháng 8 + tháng 9 + tháng 10 + tháng 11 + tháng 12 + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-zh-CN.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-zh-CN.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + accessed + and + and others + anonymous + anon + at + by + circa + c. + cited + + edition + editions + + ed + et al. + forthcoming + from + ibid. + in + in press + internet + interview + letter + no date + nd + online + presented at the + + reference + references + + + ref. + refs. + + retrieved + + + AD + BC + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + anthropology + astronomy + biology + botany + chemistry + engineering + generic base + geography + geology + history + humanities + linguistics + literature + math + medicine + philosophy + physics + psychology + sociology + science + political science + social science + theology + zoology + + + + book + books + + + chapter + chapters + + + column + columns + + + figure + figures + + + folio + folios + + + number + numbers + + + line + line + + + note + notes + + + opus + opera + + + page + pages + + + paragraph + paragraph + + + part + parts + + + section + sections + + + sub verbo + sub verbis + + + verse + verses + + + volume + volumes + + + + bk + chap + col + fig + f + no + op + + p + pp + + para + pt + sec + + s.v. + s.vv. + + + v + vv + + + vol + vols + + + + + + ¶¶ + + + § + §§ + + + + + + + + + editor + editors + + + editor + editors + + + translator + translators + + + editor & translator + editors & translators + + + + + + + + + ed + eds + + + ed. + eds. + + + tran + trans + + + ed. & tran. + eds. & trans. + + + + edited by + edited by + translated by + edited & translated by + to + interview by + + + by + ed + ed. + trans + ed. & trans. by + + + January + February + March + April + May + June + July + August + September + October + November + December + + + Jan + Feb + Mar + Apr + May + Jun + Jul + Aug + Sep + Oct + Nov + Dec + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/locale/locales-zh-TW.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/locale/locales-zh-TW.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + 被取用 + + 及其他 + 不具名的 + 無名 + + by + circa + c. + 被引用 + + 版本 + 版本 + + + 等人 + 將來的 + + 同上出處 + + 印行中 + 網際網路 + 訪問 + 信件 + no date + 無日期 + 在線上 + 簡報於 + + reference + references + + + ref. + refs. + + 被取回 + + + AD + BC + + + + + + + + + + st + nd + rd + th + + + first + second + third + fourth + fifth + sixth + seventh + eighth + ninth + tenth + + + 人類學 + 天文學 + 生物學 + 植物學 + 化學 + 工程學 + 一般基礎 + 地理學 + 地質學 + 歷史學 + 人文學科 + linguistics + 文學 + 數學 + 醫學 + 哲學 + 物理學 + 心理學 + 社會學 + 自然科學 + 政治科學 + 社會科學 + 神學 + 動物學 + + + + + + + + + + + + + + + + + + + + 對開紙 + 對開紙 + + + 期數 + 期數 + + + + + + + 筆記 + 筆記 + + + 作品 + 作品 + + + + + + + 段落 + 段落 + + + + + + + + + + + sub verbo + sub verbis + + + 詩句 + 詩句 + + + + + + + + + + + + + + + + + + + + + + + s.v. + s.vv. + + + + + + + + + + + + + + ¶¶ + + + § + §§ + + + + + 作者 + 作者 + + + 編輯 + 編輯 + + + editor + editors + + + 翻譯 + 翻譯 + + + editor & translator + editors & translators + + + + + + + + + + + + + ed. + eds. + + + + + + + ed. & tran. + eds. & trans. + + + + 編者是 + edited by + 譯者是 + edited & translated by + 授與 + 訪問者是 + + + by + + ed. + + ed. & trans. by + + + 一月 + 二月 + 三月 + 四月 + 五月 + 六月 + 七月 + 八月 + 九月 + 十月 + 十一月 + 十二月 + + + 1月 + 2月 + 3月 + 4月 + 5月 + 6月 + 7月 + 8月 + 9月 + 10月 + 11月 + 12月 + + + Spring + Summer + Autumn + Winter + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/schema/csl-categories.rng --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/schema/csl-categories.rng Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,51 @@ + + +
    + + + author-date + numeric + label + note + author + + + + "generic-base" is reserved for truly generic styles (APA, Harvard, etc.). + + anthropology + astronomy + biology + botany + chemistry + communications + engineering + generic-base + geography + geology + history + humanities + law + linguistics + literature + math + medicine + philosophy + physics + political_science + psychology + science + social_science + sociology + theology + zoology + + + + + Categories can be redefined in a customization schema, though please +report obvious gaps for inclusion in the schema. + + +
    +
    diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/schema/csl-terms.rng --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/schema/csl-terms.rng Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,148 @@ + + +
    + Terms + + + accessed + Miscellaneous Terms + ad + and + and others + anonymous + at + bc + by + circa + cited + et-al + forthcoming + from + ibid + in + in press + internet + interview + letter + no date + online + presented at + reference + retrieved + season-01 + Seasons + season-02 + season-03 + season-04 + open-quote + Punctuation + close-quote + open-inner-quote + close-inner-quote + page-range-delimiter + + Terms to which a gender may be assigned + + + Terms for which gender variants may be specified + + + Locators + + + Contributor Roles + + editortranslator + + Categories + + + + + + Terms to which a gender may be assigned + + month-01 + Months + month-02 + month-03 + month-04 + month-05 + month-06 + month-07 + month-08 + month-09 + month-10 + month-11 + month-12 + + Non-locator terms accompanying number variables + + + Locator terms with matching number variables + + + + + Terms for which gender variants may be specified + + ordinal-01 + Ordinals + ordinal-02 + ordinal-03 + ordinal-04 + long-ordinal-01 + Long Ordinals + long-ordinal-02 + long-ordinal-03 + long-ordinal-04 + long-ordinal-05 + long-ordinal-06 + long-ordinal-07 + long-ordinal-08 + long-ordinal-09 + long-ordinal-10 + + + + Locators + + book + chapter + column + figure + folio + line + note + opus + page + paragraph + part + section + sub verbo + verse + + Locator terms with matching number variables + + + + + Locator terms with matching number variables + + issue + volume + + + + Non-locator terms accompanying number variables + + chapter-number + collection-number + edition + number + number-of-pages + number-of-volumes + + +
    +
    diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/schema/csl-types.rng --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/schema/csl-types.rng Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,44 @@ + + +
    + CSL Types + + + article + article-journal + article-magazine + article-newspaper + bill + book + broadcast + chapter + entry + entry-dictionary + entry-encyclopedia + figure + graphic + interview + legal_case + legislation + manuscript + map + motion_picture + musical_score + pamphlet + paper-conference + patent + personal_communication + post + post-weblog + report + review + review-book + song + speech + thesis + treaty + webpage + + +
    +
    diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/schema/csl-variables.rng --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/schema/csl-variables.rng Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,102 @@ + + +
    + name variables + + + author + collection-editor + composer + container-author + editor + editorial-director + illustrator + interviewer + original-author + recipient + translator + + +
    +
    + date variables + + + accessed + container + event-date + issued + original-date + submitted + + +
    +
    + number variables + + + chapter-number + collection-number + edition + issue + number + number-of-pages + number-of-volumes + volume + + +
    +
    + standard variables + + + abstract + annote + archive + archive_location + archive-place + authority + call-number + citation-label + citation-number + collection-title + container-title + container-title-short + dimensions + DOI + event + event-place + first-reference-note-number + genre + ISBN + ISSN + jurisdiction + keyword + locator + medium + note + original-publisher + original-publisher-place + original-title + page + page-first + PMID + PMCID + publisher + publisher-place + references + section + source + status + title + title-short + URL + version + year-suffix + + number variables + + + +
    +
    diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/schema/csl.rng --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/schema/csl.rng Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1751 @@ + + + + Citation Style Language + Bruce D'Arcus + Simon Kornblith + Frank Bennett + Rintze Zelle + Bruce D'Arcus and Simon Kornblith, 2007-2011 + Permission to freely use, copy and distribute. + Citation Style Language (CSL) schema for describing bibliographic and citation formatting. + + + + + + + + + Subparts of the CSL schema + + + + + + + + + This macro call has no corresponding macro. + + + This macro call has no corresponding macro. + + +
    + Independent CSL style + + + + + Set a default style locale. + + + + + Select whether citations appear in-text or as notes. + + in-text + note + + + + + + + + + + + + + + + + + + + + + +
    +
    + Dependent CSL style + + + + + + + +
    +
    + + Style and Locale Metadata + + + + Set the CSL version of the style ("1.0" for CSL 1.0-compatible +styles). + 1.0 + + + + Metadata for independent styles. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Metadata for dependent styles. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Metadata for locale files. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Specify the citation format of the style (using the "citation-format" +attribute) or the fields and disciplines for which the style is +relevant (using the "field" attribute). + + + + + + + + + + + + + Specify the URI to establish the identity of the style. The URI +should be stable, unique and dereferenceable URI. + + + + + + Specify the journal's ISSN(s) for journal-specific styles. An ISSN +must consist of four digits, a hyphen, three digits, and a check +digit (a numeral digit or roman X), e.g. "1234-1231". + + \d{4}\-\d{3}(\d|x|X) + + + + + + Specify the journal's eISSN for journal-specific styles. + + \d{4}\-\d{3}(\d|x|X) + + + + + + Specify the journal's ISSN-L for journal-specific styles. + + \d{4}\-\d{3}(\d|x|X) + + + + + + + + + + Specify how the URL relates to the style. + + self + The URI of the CSL style itself. + template + URI of the style from which the current style is derived. + documentation + URI of style documentation. + independent-parent + Obsolete for independent styles. Will be disallowed with +CSL 1.1. + + + + + + + + + + + + Specify how the URL relates to the style. + + self + The URI of the CSL style itself. + independent-parent + URI of the CSL style whose content should be used for +processing. Required for dependent styles. + documentation + URI of style documentation. + template + Obsolete for dependent styles. Will be disallowed with CSL +1.1. + + + + + + + + Specify when the style was initially created or made available. + + + + + + + + + + + + + + + + + + + + + + + + + + Specify an abbreviated style title (e.g., "APA") + + + + + + Specify when the style was last updated (e.g., +"2007-10-26T21:32:52+02:00") + + + + + + + + + + + + + Obsolete for dependent styles. Will be disallowed with CSL 1.1. + + + + in-text + note + + + + + + + + + + +
    +
    + + Localization + + + CSL locale file (locales-xx-XX.xml) + + + Specify the locale of the locale file. + + + + Set the CSL version of the locale file ("1.0" for CSL +1.0-compatible locale files). + 1.0 + + + + + + + + + + + + + + + + Use to (re)define localized terms, dates and options. + + + Specify the affected locale(s). If "xml:lang" is not set, the +"cs:locale" element affects all locales. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + long + + + + + masculine + feminine + + + + + + + + + + long + + + + + masculine + feminine + + + + + + + "verb-short" reverts to "verb" if the "verb-short" form is not available +"symbol" reverts to "short" if the "symbol" form is not available +"verb" and "short" revert to "long" if the specified form is not available + + long + verb + short + verb-short + symbol + + + +
    + Extension structures. You may override these in a customization schema. +If you do, please contact the CSL project team to add the term or form to +the official schema. + + + + + + +
    + + Simple terms are basic strings, used to represent genres, media, etc. + + + + + + + Compound terms are those whose output can be either singular or plural. +Typically used for things like page number or editor labels. + + + + + + + + + Plural version of the term. + + + + + + Singular version of the term. + + + + + + + Select the localized date format ("text" or "numeric") that will +be defined. + + text + Text date form (e.g., "December 15, 2005" for "en-US"). + numeric + Numeric date form (e.g., "12-15-2005" for "en-US"). + + + + + + + + + + + + + + + + + + + + + Localized global options are specified as attributes in the +cs:style-options element. If future versions of CSL include localized +options that are citation or bibliography specific, the elements +cs:citation-options and cs:bibliography-options can be added. + + + + Specify whether punctuation (a period or comma) is placed within +or outside (default) the closing quotation mark. + + + + + +
    +
    + + Macros + + + + Use to create collections of (reusable) formatting instructions. + + + + + + +
    +
    + + Citation and Bibliography + + + + Use to describe the formatting of citations. + + + + + + + + + + + + Use to describe the formatting of the bibliography. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + Contributor Names + + + Options affecting cs:names, for cs:style, cs:citation and cs:bibliography. + + + Inheritable name option, companion for "delimiter" on cs:names. + + + + + + + + + Specify the delimiter for name lists of name variables rendered by +the same cs:names element. + + + + + + + + + + + Options affecting cs:name, for cs:style, cs:citation and cs:bibliography. + + + + Inheritable name option, companion for "form" on cs:name. + + long + short + count + + + + + + Inheritable name option, companion for "delimiter" on cs:name. + + + + + + + Use to separate the second-to-last and last name of a name list by +the "and" term or ampersand. + + text + Use the "and" term (e.g., "Doe, Johnson and Smith"). + symbol + Use the "ampersand" (e.g., "Doe, Johnson & Smith"). + + + + + + Specify when the name delimiter is used between a truncated name list +and the "et-al" (or "and others") term in case of et-al abbreviation +(e.g., "Smith, Doe et al." or "Smith, Doe, et al."). + + contextual + The name delimiter is only used when the truncated name list +consists of two or more names. + always + The name delimiter is always used. + never + The name delimiter is never used. + + + + + + Specify when the name delimiter is used between the second-to-last +and last name of a non-truncated name list. Only has an effect when +the "and" term or ampersand is used (e.g., "Doe and Smith" or "Doe, +and Smith"). + + contextual + The name delimiter is only used when the name list consists of +three or more names. + always + The name delimiter is always used. + never + The name delimiter is never used. + + + + + + Set the minimum number of names needed in a name variable to activate +et-al abbreviation. + + + + + + Set the number of names to render when et-al abbreviation is active. + + + + + + As "et-al-min", but only affecting subsequent citations to an item. + + + + + + As "et-al-use-first", but only affecting subsequent citations to an +item. + + + + + + If set to "true", the "et-al" (or "and others") term is replaced by +an ellipsis followed by the last name of the name variable. + + + + + + If set to "false", names are not initialized and "initialize-with" +only affects initials already present in the input data. + + + + + + Activate initializing of given names. The attribute value is appended +to each initial (e.g., with ". ", "Orson Welles" becomes "O. Welles"). + + + + + Specify whether (and which) names should be rendered in their sort +order (e.g., "Doe, John" instead of "John Doe"). + + first + Render the first name of each name variable in sort order. + all + Render all names in sort order. + + + + + + Sets the delimiter for name-parts that have switched positions as a +result of "name-as-sort-order" (e.g., ", " in "Doe, John"). + + + + + + + + + + + + + + + + + + + + + + + + + + Short version of "names" element, without children, allowed in <substitute/> + + + + + + + + + + + + Select the "long" (first name + last name, for Western names), +"short" (last name only, for Western names), or "count" name form +(returning the number of names in the name variable, which can be +useful for some sorting algorithms). + + long + short + count + + + + + Set the delimiter for names in a name variable (e.g., ", " in +"Doe, Smith") + + + + Use to format individual name parts (e.g., "Jane DOE"). + + + family + given + + + + + + + + + + + Inherits variable from the parent cs:names element. + + + + + + + + + + + + Specify the term used for et-al abbreviation and its formatting. + + + Select the term to use for et-al abbreviation. + + et-al + and others + + + + + + + + + + Specify substitution options when the name variables selected on the +parent cs:names element are empty. + + + + + + + + +
    +
    + + Dates + + + + + + + + + + Use to select a localized date format. + + text + Use the localized text form of the date (e.g., "December +15, 2005" for en-US). + numeric + Use the localized numeric form of the date (e.g., +"12-15-2005" for en-US) + + + + + Limit the date parts rendered. + + year-month-day + Year, month and day + year-month + Year and month + year + Year only + + + + + + Specify overriding formatting for localized dates (affixes +cannot be overridden, as these are considered locale-specific). +Example uses are forcing the use of leading-zeros, or of the +"short" month form. Has no effect on which, and in what order, +date parts are rendered. + + + + + + + + + + + + + + Specify, in the desired order, the date parts that should be +rendered and their formatting. + + + + + + + + + + + + + + + + + + + + + + + Specify a delimiter for date ranges (by default the en-dash). A custom +delimiter is retrieved from the largest date part ("day", "month" or +"year") that differs between the two dates. + + + + + + day + + + + Day forms: "numeric" ("5"), "numeric-leading-zeros" ("05"), "ordinal" +("5th"). + + numeric + numeric-leading-zeros + ordinal + + + + + + + + month + + + + Months forms: "long" (e.g., "January"), "short" ("Jan."), "numeric" +("1"), and "numeric-leading-zeros" ("01"). + + long + short + numeric + numeric-leading-zeros + + + + + + + + + year + + + + Year forms: "long" ("2005"), "short" ("05"). + + short + long + + + + + +
    +
    + + Formatting Text + + + + Use to call macros, render variables, terms, or verbatim text. + + + + + + + + + Select a macro. + + + + + Select a term. + + + + + + + + + + Specify term plurality: singular ("false") or plural ("true"). + + + + + + Specify verbatim text. + + + + Select a variable. + + + + + + short + long + + + + + + + + + + Use to render a number variable. + + + + + + + + + + Number forms: "numeric" ("4"), "ordinal" ("4th"), "long-ordinal" +("fourth"), "roman" ("iv"). + + numeric + ordinal + long-ordinal + roman + + + + + +
    +
    + + Label Text + + + + Use to render a term whose pluralization depends on the content of a +variable. E.g., if "page" variable holds a range, the plural label +"pp." is selected instead of the singular "p.". + + + + + locator + page + + + + + + long + short + symbol + + + + + + + + + + + + + Specify when the plural version of a term is selected. + + always + never + contextual + + + + +
    +
    + + Groups + + + + Use to group rendering elements. Groups are useful for setting a +delimiter for the group children, for organizing the layout of +bibliographic entries (using the "display" attribute), and for +suppressing the rendering of terms and verbatim text when variables +are empty. + + + + + + + + + +
    +
    + + Options + + + + + + + + + + + + + + + + + + + + + + + + + + + + Specify whether the non-dropping particle is demoted in inverted +names (e.g., "Koning, W. de"). + + never + sort-only + display-and-sort + + + + + + + + Specify whether compound given names (e.g., "Jean-Luc") are +initialized with ("J-L") or without a hyphen ("JL"). + + + + + + + + Reformat page ranges in the "page" variable. + + expanded + minimal + chicago + + + + + + + + Activate cite grouping and specify the delimiter for cites within a +cite group. + + + + + + + Activate cite grouping and specify the method of citation collapsing. + + citation-number + Collapse ranges of numeric cites, e.g. from "[1,2,3]" to "[1-3]". + year + Collapse cites by suppressing repeated names, e.g. from "(Doe +2000, Doe 2001)" to "(Doe 2000, 2001)". + year-suffix + Collapse cites as with "year", but also suppresses repeated +years, e.g. from "(Doe 2000a, Doe 2000b)" to "(Doe 2000a, b)". + year-suffix-ranged + Collapses cites as with "year-suffix", but also collapses +ranges of year-suffixes, e.g. from "(Doe 2000a, Doe 2000b, +Doe 2000c)" to "(Doe 2000a-c)". + + + + + + Specify the delimiter between year-suffixes. Defaults to the cite +delimiter. + + + + + Specify the delimiter following a group of collapsed cites. Defaults +to the cite delimiter. + + + + + + + Set to "true" to activate disambiguation by showing names that were +originally hidden as a result of et-al abbreviation. + + + + + + Set to "true" to activate disambiguation by expanding names, showing +initials or full given names. + + + + + + Set to "true" to activate disambiguation by adding year-suffixes +(e.g., "(Doe 2007a, Doe 2007b)") for items from the same author(s) +and year. + + + + + + Specify how name are expanded for disambiguation. + + all-names + Each ambiguous names is progressively transformed until +disambiguated (when disambiguation is not possible, the name +remains in its original form). + all-names-with-initials + As "all-names", but name expansion is limited to showing +initials. + primary-name + As "all-names", but disambiguation is limited to the first name +of each cite. + primary-name-with-initials + As "all-names-with-initials", but disambiguation is limited to +the first name of each cite. + by-cite + As "all-names", but only ambiguous names in ambiguous cites are +expanded. + + + + + + + + Set the number of preceding notes (footnotes or endnotes) within +which the current item needs to have been previously cited in order +for the "near-note" position to be "true". + + + + + + + + Set to "true" to render bibliographic entries with hanging indents. + + + + + + + + Set the spacing between bibliographic entries. + + + + + + Set the spacing between bibliographic lines. + + 0 + + + + + + + + Use to align any subsequent lines of bibliographic entries with the +beginning of the second field. + + flush + Align the first field with the margin. + margin + Put the first field in the margin and align all subsequent +lines of text with the margin. + + + + + + + + Substitute names that repeat in subsequent bibliographic entries by +the attribute value. + + + + + Specify the method of substitution of names repeated in subsequent +bibliographic entries. + + complete-all + Requires a match of all rendered names in the name variable, and +substitutes once for all names. + complete-each + Requires a match of all rendered names in the name variable, +and substitutes for each name. + partial-each + Substitutes for each name, until the first mismatch. + partial-first + Substitutes the first name if it matches. + + + + +
    +
    + + Sorting + + + + + + + + + + + Specify how cites and bibliographic entries should be sorted. By +default, items appear in the order in which they were cited. + + + + + + + + + + + + + + + + + + Select between an ascending and descending sort. + + ascending + descending + + + + + + The minimum number of names needed in a name variable to activate +name list truncation. Overrides the values set on any +"et-al-(subsequent-)min" attributes. + + + + + + The number of names to render when name list truncation is +activated. Overrides the values set on the +"et-al-(subsequent-)use-first" attributes. + + + + + + Use to override the value of the "et-at-use-last" attribute. + + + + + +
    +
    + + Conditional Statements + + + + Use to conditionally render rendering elements. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + If used, the element content is only rendered if it disambiguates two +otherwise identical citations. This attempt at disambiguation is only +made after all other disambiguation methods have failed. + true + + + Tests whether the given variables contain numeric text. + + + + + + + + Tests whether the given date variables contain approximate dates. + + + + + + + + Tests whether the locator matches the given locator types. + + + + + sub-verbo + + + + + + Tests whether the cite position matches the given positions. + + + + first + subsequent + ibid + ibid-with-locator + near-note + + + + + + Tests whether the item matches the given types. + + + + + + + + Tests whether the default ("long") forms of the given variables +contain non-empty values. + + + + + + + + + + + + Set the testing logic. + + all + Element only tests "true" when all conditions test "true" for all +given test values. + any + Element tests "true" when any condition tests "true" for any given +test value. + none + Element only tests "true" when none of the conditions test "true" +for any given test value. + + + + +
    +
    + Formatting attributes. + + + + + + + + + + + + + + + + + By default, bibliographic entries consist of continuous runs of text. +With the "display" attribute, portions of each entry can be +individually positioned. + + block + Places the content in a block stretching from margin to margin. + left-margin + Places the content in a block starting at the left margin. + right-inline + Places the content in a block to the right of a preceding +"left-margin" block. + indent + Places the content in a block indented to the right by a standard +amount. + + + + + + The font-formatting attributes are based on those of CSS and XSL-FO. + + + + italic + normal + oblique + + + + + + + normal + small-caps + + + + + + + normal + bold + light + + + + + + + none + underline + + + + + + + baseline + sup + sub + + + + + + + + When set to "true", quotes are placed around the rendered text. + + + + + + + + When set to "true", periods are removed from the rendered text. + + + + + + + + + lowercase + Renders text in lowercase. + uppercase + Renders text in uppercase. + capitalize-first + Capitalizes the first character (other characters remain in +their original case). + capitalize-all + Capitalizes the first character of every word (other characters +remain in their original case). + title + Renders text in title case. + sentence + Renders text in sentence case. + + + + +
    +
    diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/american-medical-association.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/american-medical-association.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,237 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/apa.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/apa.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,443 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/apsa.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/apsa.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,235 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/asa.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/asa.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,226 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/chicago-author-date.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/chicago-author-date.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,481 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/chicago-fullnote-bibliography.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/chicago-fullnote-bibliography.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1045 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/chicago-note-bibliography.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/chicago-note-bibliography.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1016 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/council-of-science-editors.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/council-of-science-editors.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,184 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/harvard1.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/harvard1.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,185 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/ieee.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/ieee.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,299 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/mhra.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/mhra.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,416 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/mla.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/mla.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,396 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/national-library-of-medicine-grant.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/national-library-of-medicine-grant.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,254 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/nature.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/nature.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,114 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/CiteProc/style/vancouver.csl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/CiteProc/style/vancouver.csl Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,267 @@ + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/RIS/biblio_ris.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/RIS/biblio_ris.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Biblio - RIS +description = Provides RIS file import and export to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = views/biblio_handler_field_export_link_ris.inc + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/RIS/biblio_ris.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/RIS/biblio_ris.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,218 @@ +condition('format', 'ris') + ->execute(); + } +} + +function biblio_ris_enable() { + biblio_ris_set_system_weight(); +} + +function biblio_ris_set_system_weight() { + db_update('system') + ->fields(array('weight' => 27)) + ->condition('name', 'biblio_ris') + ->execute(); +} + +function _save_ris_maps() { + + $typemap = _get_ris_type_map(); + $typenames = _get_ris_type_names(); + $fieldmap = _get_ris_field_map(); + $maps = array_merge($typemap, $typenames, $fieldmap); + biblio_save_map($maps); + +} +function _reset_ris_map($type) { + $count = db_query("SELECT COUNT(*) FROM {biblio_type_maps} WHERE format='ris'")->fetchField(); + if ($count && $type) { //update + $function = '_get_ris_' . $type; + if (!function_exists($function)) return; + $map = $function(); + + db_update('biblio_type_maps') + ->fields($map) + ->condition('format', 'ris') + ->execute(); + } + else { // install + db_delete('biblio_type_maps') + ->condition('format', 'ris') + ->execute(); + _save_ris_maps(); + } +} + function _get_ris_type_map() { + $map['type_map'] = serialize( + array( + 'ABST' => 129, + 'ADVS' => 114, + 'ART' => 112, + 'BILL' => 117, + 'BOOK' => 100, + 'CASE' => 116, + 'CHAP' => 101, + 'COMP' => 113, + 'CONF' => 103, + 'CTLG' => 129, + 'DATA' => 125, + 'ELEC' => 129, + 'GEN' => 129, + 'HEAR' => 115, + 'ICOMM' => 107, + 'INPR' => 129, + 'JFULL' => 129, + 'JOUR' => 102, + 'MAP' => 122, + 'MGZN' => 106, + 'MPCT' => 110, + 'MUSIC' => 129, + 'NEWS' => 105, + 'PAMP' => 129, + 'PAT' => 119, + 'PCOMM' => 120, + 'RPRT' => 109, + 'SER' => 100, + 'SLIDE' => 129, + 'SOUND' => 129, + 'STAT' => 125, + 'THES' => 108, + 'UNBILl' => 129, + 'UNPB' => 124, + 'VIDEO' => 129, + ) + ); + $map['format'] = 'ris'; + return $map; +} + +function _get_ris_type_names() { + $map['type_names'] = serialize( + array( + 'ABST' => 'Abstract', + 'ADVS' => 'Audiovisual material', + 'ART' => 'Art Work', + 'BILL' => 'Bill/Resolution', + 'BOOK' => 'Book, Whole', + 'CASE' => 'Case', + 'CHAP' => 'Book chapter', + 'COMP' => 'Computer program', + 'CONF' => 'Conference proceeding', + 'CTLG' => 'Catalog', + 'DATA' => 'Data file', + 'ELEC' => 'Electronic Citation', + 'GEN' => 'Generic', + 'HEAR' => 'Hearing', + 'ICOMM' => 'Internet Communication', + 'INPR' => 'In Press', + 'JFULL' => 'Journal (full)', + 'JOUR' => 'Journal', + 'MAP' => 'Map', + 'MGZN' => 'Magazine article', + 'MPCT' => 'Motion picture', + 'MUSIC' => 'Music score', + 'NEWS' => 'Newspaper', + 'PAMP' => 'Pamphlet', + 'PAT' => 'Patent', + 'PCOMM' => 'Personal communication', + 'RPRT' => 'Report', + 'SER' => 'Serial (Book, Monograph)', + 'SLIDE' => 'Slide', + 'SOUND' => 'Sound recording', + 'STAT' => 'Statute', + 'THES' => 'Thesis/Dissertation', + 'UNBILl' => 'Unenacted bill/resolution', + 'UNPB' => 'Unpublished work', + 'VIDEO' => 'Video recording', + ) + ); + $map['format'] = 'ris'; + return $map; +} +function _get_ris_field_map() { + $map['field_map'] = serialize( + array( + 'ID' => '', //- Reference ID (not imported to reference software) + 'T1' => 'title', //- Primary title + 'TI' => 'title', //- Book title + 'BT' => 'title', //- Book title + 'CT' => 'title', //- Title of unpublished reference + 'A1' => '', //- Primary author + 'A2' => '', //- Secondary author (each name on separate line) + 'AU' => '', //- Author (syntax. Last name, First name, Suffix) + 'Y1' => 'biblio_year', //- Primary date + 'PY' => '', //- Publication year (YYYY/MM/DD) + 'N1' => 'biblio_notes', //- Notes + 'KW' => '', //- Keywords (each keyword must be on separate line preceded KW -) + 'RP' => '', //- Reprint status (IN FILE, NOT IN FILE, ON REQUEST (MM/DD/YY)) + 'SP' => '', //- Start page number + 'EP' => '', //- Ending page number + 'JF' => 'biblio_secondary_title',//- Periodical full name + 'JO' => 'biblio_short_title', //- Periodical standard abbreviation + 'JA' => 'biblio_secondary_title',//- Periodical in which article was published + 'J1' => 'biblio_short_title', //- Periodical name //- User abbreviation 1 + 'J2' => 'biblio_short_title', //- Periodical name - User abbreviation 2 + 'VL' => 'biblio_volume', //- Volume number + 'IS' => 'biblio_issue', //- Issue number + 'CP' => 'biblio_issue', //- Issue number + 'T2' => 'biblio_secondary_title',//- Title secondary + 'CY' => 'biblio_place_published',//- City of Publication + 'PB' => 'biblio_publisher', //- Publisher + 'U1' => 'biblio_custom1', //- User definable 1 + 'U2' => 'biblio_custom2', //- User definable 2 + 'U3' => 'biblio_custom3', //- User definable 3 + 'U4' => 'biblio_custom4', //- User definable 4 + 'U5' => 'biblio_custom5', //- User definable 5 + 'T3' => 'biblio_tertiary_title', //- Title series + 'AB' => 'biblio_abst_e', //- Abstract + 'N2' => 'biblio_abst_e', //- Abstract + 'SN' => 'biblio_isbn', //- ISSN/ISBN (e.g. ISSN XXXX-XXXX) + 'AV' => '', //- Availability + 'M1' => '', //- Misc. 1 + 'M3' => '', //- Misc. 3 + 'AD' => '', //- Address + 'UR' => 'biblio_url', //- Web/URL + 'L1' => '', //- Link to PDF + 'L2' => '', //- Link to Full-text + 'L3' => '', //- Related records + 'L4' => '', //- Images + 'ER' => '', //- End of Reference (must be the last tag) + ) + ); + + $map['format'] = 'ris'; + return $map; +} +/** + * Implementation of hook_schema(). + * + * Note: Pro Drupal Development models use of t() to translate 'description' + * for field definitions, but Drupal core does not use them. We follow core. + */ +function biblio_ris_schema() { + $schema = array(); + $schema['biblio_ris'] = array( + 'fields' => array( + 'nid' => array('type' => 'int', 'not null' => TRUE), + 'biblio_ris_md5' => array('type' => 'char', 'length' => 32, 'not null' => TRUE), + ), + 'primary key' => array('nid'), + ); + return $schema; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/RIS/biblio_ris.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/RIS/biblio_ris.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,490 @@ + 2, + 'path' => drupal_get_path('module', 'biblio_ris') . '/views', + ); +} +/* + * add the ris option to the option list of the biblio_import_form + * the key is the module name use by module_invoke to call hook_biblio_import + * module_invoke('biblio_ris', 'biblio_import',...) + */ +function biblio_ris_biblio_import_options() { + return array('biblio_ris' => t('RIS')); +} +function biblio_ris_biblio_mapper_options() { + return array( + 'ris' => array( + 'title' => t('RIS'), + 'export' => TRUE, + ) + ); +} + +function biblio_ris_biblio_export_options() { + return array('ris' => t('RIS')); +} +function biblio_ris_form_biblio_node_form_alter(&$form, &$form_state) { + global $user; + if (!$form_state['submitted'] && !isset($form_state['values']) && !isset($form['#node']->nid)) { + if (!$form_state['submitted']) { + $form['biblio_ris_paste'] = array( + '#type' => 'fieldset', + '#title' => t('Paste RIS Record'), + '#weight' => -20, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['biblio_ris_paste']['paste_data_ris'] = array( + '#type' => 'textarea', + '#title' => t('RIS'), + '#required' => FALSE, + '#default_value' => isset($form_state['values']['paste_data_ris']) ? $form_state['values']['paste_data_ris'] : '', + '#description' => t('Paste a RIS entry here'), + '#size' => 60, + '#weight' => -4 + ); + $form['biblio_ris_paste']['paste_submit'] = array( + '#type' => 'submit', + '#value' => t('Populate using RIS'), + '#submit' => array('biblio_ris_form_biblio_node_form_submit') + ); + } + } + $biblio_ris_id = (isset($form_state['values']['biblio_ris_id'])) ? $form_state['values']['biblio_ris_id'] : ''; + $biblio_ris_md5 = (isset($form_state['values']['biblio_ris_md5'])) ? $form_state['values']['biblio_ris_md5'] : ''; + $form['biblio_ris_id'] = array('#type' => 'value', '#value' => $biblio_ris_id); + $form['biblio_ris_md5'] = array('#type' => 'value', '#value' => $biblio_ris_md5); +} + +function biblio_ris_form_biblio_node_form_submit($form, &$form_state) { + global $user; + $node_data = array(); + $dups = array(); + + if (strlen($form_state['values']['paste_data_ris'])) { + $node_data = _biblio_ris_import_string($form_state['values']['paste_data_ris']); + } + if (!empty($node_data) && is_object($node_data)) { + $form_state['values'] = array_merge($form_state['values'], (array)$node_data); + $form_state['input']['biblio_type'] = $form_state['biblio_type'] = $node_data->biblio_type; + } + $form_state['rebuild'] = TRUE; + return; +} + +/** + * Creates a link to export a node (or view) in ris format + * + * @param $base this is the base url (defaults to /biblio) + * @param $nid the node id, if NULL then the current view is exported + * @return a link (ris) + */ +function biblio_ris_biblio_export_link($nid = NULL, $filter = array()) { + $show_link = variable_get('biblio_export_links', array('ris' => TRUE)); + if (!isset($show_link['ris']) || empty($show_link['ris']) || !biblio_access('export') ) { + return array(); + } + $base = variable_get('biblio_base', 'biblio'); + + if (module_exists('popups') && !empty($nid)) { + $link = array( + 'attributes' => array( + 'class' => 'popups', + 'title' => t("Click to get the RIS output"))); + } + else { + $link = array( + 'attributes' => array( + 'title' => t("Click to download the RIS formatted file"))); + } + + $link['attributes'] += array('rel' => 'nofollow'); + + $link['href'] = "$base/export/ris/$nid"; + $link['title'] = t('RIS'); + + if (empty($nid) && !empty($filter)) { // add any filters which may be on the current page + $link['query'] = $filter; + } + + return array('biblio_ris' => $link); +} + +function biblio_ris_node_delete($node) { + if ($node->type != 'biblio') { + return; + } + db_delete('biblio_ris') + ->condition('nid', $node->nid) + ->execute(); +} + +function biblio_ris_node_insert($node) { + if (isset($node->biblio_ris_md5)) { + drupal_write_record('biblio_ris', $node); + } +} + +function biblio_ris_node_view($node, $view_mode) { + if ( $node->type == 'biblio') { + switch ($view_mode) { + case 'full': + case 'teaser': + $links = biblio_ris_biblio_export_link($node->nid); + $node->content['links']['biblio_ris'] = array( + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} + +function biblio_ris_biblio_import($file, $terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE, $string = FALSE) { + $nids = array(); + $dups = array(); + list($nids, $dups) = _biblio_ris_import($file, $terms, $batch, $session_id); + + return array($nids, $dups); +} + +function biblio_ris_biblio_export($nids) { + + if (module_exists('popups') && $nid) { + $popup = TRUE; + } + else { + $popup = FALSE; + drupal_add_http_header('Content-type', 'application/x-endnote-refer'); + drupal_add_http_header('Content-Disposition', 'attachment; filename="Drupal-Biblio.ris"'); + } + + $nodes = node_load_multiple($nids, array(), TRUE); + foreach ($nodes as $node) { + if (variable_get('biblio_hide_bibtex_braces', 0) ) { + $node->title = biblio_remove_brace($node->title); + } + + if (!$popup) { + print _biblio_ris_export($node); + } + else{ + $popup_data .= _biblio_ris_export($node); + } + } + if ($popup && !empty($popup_data)) return '
    ' . $popup_data . '
    '; + +} + +function _biblio_ris_import_string($string) { + $tag = ''; + $node = new stdClass(); + $unmapped = array(); + + $lines = preg_split('/[\r\n]/', $string, -1, PREG_SPLIT_NO_EMPTY); + foreach ($lines as $line) { + $line_len = strlen($line); + if ($line_len > 3) { + $start = strpos($line, ' -'); // There could be some unprintables at the beginning of the line so fine the location of the % + if ($start !== FALSE) { + $tag = drupal_substr($line, $start -2, 2); + $data = trim(drupal_substr($line, $start +3)); + } + else { + $data = $line; + } + } + if ($line_len > 3 && !empty($tag)) { // if this is not a blank line + if ($tag == 'ER') { + if (!empty($node)) { + $node->biblio_ris_md5 = md5(serialize($node)); + if (empty ($node->title )) $node->title = t("Untitled"); + if (! ($dup = biblio_ris_check_md5($node->biblio_ris_md5))) { + return $node; + } + else { + $message = t('The RIS node that you are trying to paste into the form already exists in the database, see !url', array('!url' => l('node/' . $dup, 'node/' . $dup))); + form_set_error('paste_data_ris', $message); + return; + } + } + } + else { + _biblio_ris_parse_line($tag, $data, $node, $unmapped); + } + } + } // end while + if (!empty($unmapped)) { + $ignored_tags = array_unique($unmapped); + $message = t("The following elements were ignored because they do not map to any biblio fields:") . ' '; + $message .= implode(', ', $ignored_tags); + if (user_access('administer biblio')) { + $message .= '. ' . t('Click !url if you wish to check the field mapping', array('!url' => l(t('here'), 'admin/config/content/biblio/iomap/edit/ris'))); + } + drupal_set_message($message, 'warning'); + } +} + +function _biblio_ris_import($file, $terms = array(), $batch = FALSE, $session_id = NULL) { + ini_set('auto_detect_line_endings', TRUE); + if (!($fp = fopen($file->uri, "r"))) { + drupal_set_message(t("Could not open RIS input file for reading."), 'error'); + return; + } + + $tag = ''; + $nids = array(); + $dups = array(); + $unmapped = array(); + $node = new stdClass(); + + while (!feof($fp)) { + $line = fgets($fp); + // Remove any character other than: carriage return, line feed, tab, or ANSI/ASCII character codes 32-255 + $line = preg_replace('/[^\r\n\t\x20-\xFF]/', '', $line); + $line_len = strlen($line); + if ($line_len > 3) { + $start = strpos($line, ' -'); // There could be some unprintables at the beginning of the line so fine the location of the % + if ($start !== FALSE) { + $tag = drupal_substr($line, $start -2, 2); + $data = trim(drupal_substr($line, $start +3)); + } + else { + $data = $line; + } + } + if ($line_len > 3 && !empty($tag)) { // if this is not a blank line + if ($tag == 'ER') { + if (!empty($node)) { + $node->biblio_ris_md5 = md5(serialize($node)); + if (empty ($node->title )) $node->title = t("Untitled"); + if (! ($dup = biblio_ris_check_md5($node->biblio_ris_md5))) { + biblio_save_node($node, $terms, $batch, $session_id); + if (!empty($node->nid)) $nids[] = $node->nid; + } + else { + $dups[] = $dup; + } + } + + $node = new stdClass(); + $node->biblio_contributors = array(); + } + else { + _biblio_ris_parse_line($tag, $data, $node, $unmapped); + } + + } + } // end while + + fclose($fp); + + if (!empty($unmapped)) { + $ignored_tags = array_unique($unmapped); + $message = t('The following elements were ignored because they do not map to any biblio fields:') . ' '; + $message .= implode(', ', $ignored_tags); + if (user_access('administer biblio')) { + $message .= '. ' . t('Click !url if you wish to check the field mapping', array('!url' => l(t('here'), 'admin/config/content/biblio/iomap/edit/ris'))); + } + drupal_set_message($message, 'warning'); + } + + return array($nids, $dups); +} + +function _biblio_ris_parse_line($tag, $data, $node, &$unmapped) { + switch ($tag) { + case 'TY' : + $node->biblio_type = _biblio_ris_type_map($data); + break; + case 'A1' : + case 'AU' : + $node->biblio_contributors[] = array( + 'name' => $data, + 'auth_category' => 1, + 'auth_type' => _biblio_get_auth_type(1, $node->biblio_type)); + break; + case 'DA' : + if (!isset($node->biblio_year) || empty($node->biblio_year)) { + $node->biblio_year = ($end = strpos($data, "/")) ? substr($data, 0, $end) : $data; + } + $node->biblio_date = $data; + break; + case 'Y1' : + case 'PY' : + if (!isset($node->biblio_year) || empty($node->biblio_year)) { + $node->biblio_year = ($end = strpos($data, "/")) ? substr($data, 0, $end) : $data; + } + if (!isset($node->biblio_date) || empty($node->biblio_date)) { + $node->biblio_date = $data; + } + break; + case 'A2' : + case 'ED' : + $node->biblio_contributors[] = array( + 'name' => $data, + 'auth_category' => 2, + 'auth_type' => _biblio_get_auth_type(2, $node->biblio_type)); + break; + case 'KW' : + $node->biblio_keywords[] = $data; + break; + case 'SP' : + case 'EP' : + $node->biblio_pages .= ($tag == "SP") ? $data : " - " . $data; + break; + case 'A3' : + $node->biblio_contributors[] = array( + 'name' => $data, + 'auth_category' => 5, + 'auth_type' => _biblio_get_auth_type(5, $node->biblio_type)); + break; + case 'BT' : + if ($node->biblio_type == 100) { + $node->title = $data; + } + else { + $node->biblio_secondary_title = $data; + } + break; + default : + if ($field = _biblio_ris_field_map($tag)) { + $node->$field .= $data; + } + else { + if (!in_array($tag, $unmapped)) { + $unmapped[] = $tag; + } + } + } +} + +function _biblio_ris_export($node) { + $reverse = TRUE; + $ris = ""; + $ris .= "TY - " . _biblio_ris_type_map($node->biblio_type, $reverse) . "\r\n"; + if (!empty($node->title)) $ris .= "T1 - " . trim($node->title) . "\r\n"; + switch ($node->biblio_type) { + case 100 : + case 101 : + case 103 : + case 104 : + case 105 : + case 108 : + case 119 : + if (!empty($node->biblio_secondary_title)) + $ris .= "T2 - " . trim($node->biblio_secondary_title) . "\r\n"; + break; + case 102 : + if (!empty($node->biblio_secondary_title)) + $ris .= "JF - " . trim($node->biblio_secondary_title) . "\r\n"; + unset($node->biblio_secondary_title); + break; // journal + } + if (isset($node->biblio_year) && $node->biblio_year < 9998) { + $ris .= "Y1 - " . trim($node->biblio_year) . "\r\n"; + } + + foreach (biblio_get_contributor_category($node->biblio_contributors, 1) as $auth) { + $ris .= "A1 - " . trim($auth['name']) . "\r\n"; + } + foreach (biblio_get_contributor_category($node->biblio_contributors, 2) as $auth) { + $ris .= "ED - " . trim($auth['name']) . "\r\n"; + } + + $kw_array = array(); + if (!empty($node->terms)) { + foreach ($node->terms as $term) { + $kw_array[] = $term->name; + } + } + if (!empty($node->biblio_keywords)) { + foreach ($node->biblio_keywords as $term) { + $kw_array[] = $term; + } + } + if (!empty($kw_array)) { + $kw_array = array_unique($kw_array); + foreach ($kw_array as $term) { + $ris .= "KW - " . trim($term) . "\r\n"; + } + } + $abst = ""; + if (!empty($node->biblio_abst_e)) $abst .= trim($node->biblio_abst_e); + if ($abst) { + $search = array("/\r/", "/\n/"); + $replace = " "; + $abst = preg_replace($search, $replace, $abst); + $ris .= "AB - " . $abst . "\r\n"; + } + $skip_fields = array('biblio_year', 'biblio_abst_e', 'biblio_abst_f', 'biblio_type' ); + $fields = drupal_schema_fields_sql('biblio'); + $fields = array_diff($fields, $skip_fields); + foreach ($fields as $field) { + if (!empty($node->$field)) { + $ris .= _biblio_ris_format_entry($field, $node->$field); + } + } + $ris .= "ER - \r\n\r\n"; + return $ris; +} + +function _biblio_ris_format_entry($key, $value) { + $reverse = TRUE; + $tag = _biblio_ris_field_map($key, $reverse); + if (!empty($tag)) { + return "$tag - " . trim($value) . "\r\n"; + } + +} + +function _biblio_ris_type_map($type, $reverse = FALSE) { + static $map = array(); + if (empty($map)) { + $map = biblio_get_map('type_map', 'ris'); + } + + if ($reverse) { + return ($tag = array_search($type, $map)) ? $tag : 'Generic'; //return the biblio type or 129 (Misc) if type not found + } + return (isset($map[$type]))?$map[$type]:129; //return the biblio type or 129 (Misc) if type not found +} + +function _biblio_ris_field_map($field, $reverse = FALSE) { + static $fmap = array(); + if (empty($fmap)) { + $fmap = biblio_get_map('field_map', 'ris'); + } + if ($reverse) { + return ($tag = array_search($field, $fmap)) ? $tag : ''; + + } + return (!empty($fmap[$field])) ? $fmap[$field] : ''; + +} +function biblio_ris_ris_map_reset($type = NULL) { + module_load_include('install', 'biblio_ris', 'biblio_ris'); + _reset_ris_map($type); +} + +function biblio_ris_check_md5($md5) { + static $ris_md5s = array(); + if (empty($ris_md5s)) { + $result = db_query("SELECT * FROM {biblio_ris} "); + foreach ($result as $row) { + $ris_md5s[$row->biblio_ris_md5] = $row->nid; + } + } + if (isset($ris_md5s[$md5])) { + return $ris_md5s[$md5]; + } + else { + $ris_md5s[$md5] = TRUE; // gaurd against duplicates in the same import + return; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/CHANGELOG --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/CHANGELOG Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,83 @@ +CHANGELOG + +Released through http://bibliophile.sourceforge.net under the GPL licence. +Do whatever you like with this -- some credit to the author(s) would be appreciated. + +A collection of PHP classes to manipulate bibtex files. + +If you make improvements, please consider contacting the administrators at bibliophile.sourceforge.net so that your improvements can be added to the release package. + +Mark Grimshaw 2004/2005/2006 +http://bibliophile.sourceforge.net + +################################################ +v2.2 +24/April/2006 - Esteban Zimanyi and Mark Grimshaw +1/ A 4th array, $this->undefinedStrings, is now returned that holds field values that are judged to be undefined strings. i.e. they are a non-numeric value that is not defined in a @string{...} entry and not enclosed by braces or double-quotes. This array will be empty unless the following condition is met: +($this->removeDelimit || $this->expandMacro && $this->fieldExtract) +2/ When an undefined string is found in function removeDelimiters return the empty string. Return $this->undefinedStrings in the last position to allow compatibility with previous versions. +3/ Fix management of preamble in function returnArrays. + +v2.1 +7/February/2006 - Esteban Zimanyi and Mark Grimshaw +Minor debugging to catch more unusually formatted entries. + + +v2.0 +3/February/2006 - Esteban Zimanyi and Mark Grimshaw +Substantial work on PARSEENTRES.php (mainly by Esteban) to: +1/ handle @strings concatenated from other @strings. +2/ handle all different types of comments possible. +3/ clean-up the code. +4/ handles more unusual formatting of white space between and inside entries. + +v1.5.4 +17/June/2005 - Mark Grimshaw +month fields that have multiple dates (e.g. dec # " 5--9," or nov # " 29" # "--" # dec # " 2") are correctly parsed. (list($startMonth, $startDay, $endMonth, $endDay) = $parseMonth->init($monthField);) + +v1.5.3 +10/June/2005 - Mark Grimshaw +Fixed excessive expansion of @strings in bibtex imports. + +v1.5.2 +5/May/2005 - Mark Grimshaw and Guillaume Gardey. +1/ Corrections to PARSEENTRIES when handling concatenations using '#' +2/ Corrections to the example commandline code for PARSECREATORS. + +v1.5.1 +30th April 2005 - Mark Grimshaw +1/ Ensure entries such as journal = {{Journal of } # JRNL23} are properly parsed and expanded with a hanging '}' removed. + +v1.5 +28th April 2005 - Mark Grimshaw +1/ Fixed a bug when parsing @preamble in PARSEENTRIES.php +2/ Made efficiency and accuracy improvements in PARSECREATORS.php +3/ Added class PARSEMONTH to split a bibtex month field into day and month components. + list($month, $day) = $parseMonth->init($monthField); +4/ Added class PARSEPAGE to split a bibtex pages field into page start and page end components. + list($start, $end) = $parsePage->init($pagesField); + +v1.4 +25th August 2004 +1/ Expand macros added by Guillaume Gardey. +2/ Discard comments on same line as @string. +3/ A few bug fixes. +4/ PARSEENTRIES can parse PHP strings. (loadBibTeXString) +5/ Supports user defined BibTeX macro. (loadStringMacro) + +v1.3 +20th August 2004 +1/ @string{...} now correctly parsed. +2/ Any final "," left on the end of the last field in an entry is now removed. + +v1.2 +15th August 2004 +Corrected bug in extraction of values from @string. +v1.1 +15th August 2004 +1/ Added another flag to PARSEENTRIES to decide whether to remove enclosing "..." or {...} from string and entry fields. +2/ Some debugging and simplification. Both flags now have a default value of TRUE. + +v1.0 +14th August 2004 +Initial release: PARSEENTRIES.php, PARSECREATORS.php diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/LICENSE --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/LICENSE Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,251 @@ +The GNU General Public License (GPL) + +Version 2, June 1991 + + + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. + +59 Temple Place, Suite 330, Boston, MA 02111-1307 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. + + + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + + +0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". + + + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. + + + +1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. + + + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + + + +2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + + + a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. + + + + b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. + + + + c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) + + + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + + + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. + + + +In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + + + +3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: + + + + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + + + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, + + + + c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) + + + +The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + + + +If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. + + + +4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + + + +5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. + + + +6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. + + + +7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. + + + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. + + + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + + + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + + + +8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + + + +9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + + + +Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. + + + +10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + + + +NO WARRANTY + + + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + + + +END OF TERMS AND CONDITIONS + + + +How to Apply These Terms to Your New Programs + + + +If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. + + + +To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + + + one line to give the program's name and a brief idea of what it does. + + Copyright (C) + + + + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. + + + + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + + + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + +Also add information on how to contact you by electronic and paper mail. + + + +If the program is interactive, make it output a short notice like this when it starts in an interactive mode: + + + + Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. + + + +The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. + + + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: + + + + Yoyodyne, Inc., hereby disclaims all copyright interest + + in the program `Gnomovision' (which makes passes at compilers) + + written by James Hacker. + + + + signature of Ty Coon, 1 April 1989 + + Ty Coon, President of Vice + + + +This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/PARSECREATORS.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/PARSECREATORS.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,319 @@ +buildTypeMap(); + if (is_array($init)) + { + $this->setCreators($init); + }elseif (is_numeric($init)) + { + $this->loadCreators($init); + } + + } + + function buildTypeMap() + { + $result = db_query("SELECT * FROM {biblio_contributor_type} ;"); + while ($type = db_fetch_object($result)) + { + $this->typeMap[$type->type] = $type->ctid; + } + } + function getCreatorByName($name) + { + $result = db_query('SELECT * + FROM {biblio_contributor_data} + WHERE lastname RLIKE "[[:<:]]%s[[:>:]]" ', $name); + } + + function getCreatorCount() + { + return count($this->authors); + } + + function getCreatorString() + { + foreach ($this->authors as $key => $author) + { + $author_array[$author['rank']] = $author['firstname'] .' '. $author['initials'].' '.$author['lastname']; + } + ksort($author_array); + + return implode(', ' , $author_array); + } + + + private + function loadMD5() + { + $result = db_query('SELECT md5,cid FROM {biblio_contributor_data} '); + while ($row = db_fetch_array($result)) + { + $this->md5[$row['cid']] = $row['md5']; + } + } + + public + function loadCreators($vid) + { + $query = 'SELECT bcd.lastname, bcd.firstname, bcd.initials, + bcd.affiliation, bct.type, bc.rank + FROM {biblio_contributor} bc, + {biblio_contributor_data} bcd, + {biblio_contributor_type} bct + WHERE bc.vid = %d + AND bc.cid = bcd.cid + AND bc.ctid = bct.ctid + ORDER BY bc.ctid ASC, bc.rank ASC;'; + + $result = db_query($query, array($vid)); + while($creator = db_fetch_array($result)) + { + $this->authors[] = $creator; + } + + } + + public + function saveCreators($nid, $vid) + { + if (!empty($this->authors)) + { + $this->loadMD5(); + db_query('DELETE FROM {biblio_contributor} WHERE nid = %d AND vid = %d', $nid, $vid); + foreach ($this->authors as $rank => $author) + { + if (empty($author['cid']) && !empty($this->md5)) $author['cid'] = array_search($author['md5'], $this->md5); + if (empty($author['cid']) ) + { + drupal_write_record('biblio_contributor_data', $author); + $cid = db_last_insert_id('biblio_contributor_data', 'cid'); + }else + { + $cid = $author['cid']; + } + + $link_array = array('nid' => $nid, 'vid' => $vid, + 'cid' => $cid, 'rank' => $rank, + 'ctid' => $author['type']); + + drupal_write_record('biblio_contributor', $link_array ); + + } + } + } + + + function getAuthorArray() + { + return $this->authors; + } + + function getAuthor($rank) + { + return $this->authors[$rank]; + } + +/** + * update object with an array of authors + * + * @param $authors + * an array containing two keys "name" and "type" + * the name is the full name of the contributor which will be parsed into + * component pieces, and type contains a string indicating the author type + */ + function setCreators($authors) + { + foreach ($authors as $author) { + if (strlen(trim($author['name']))) + { + $this->authors[] = $this->parseAuthor($author['name'], $author['type']); + } + } + } + + function setCreator($author, $type = 'author') + { + $this->authors[] = $this->parseAuthor($author, $type); + } + + + +} + +/* +Released through http://bibliophile.sourceforge.net under the GPL licence. +Do whatever you like with this -- some credit to the author(s) would be appreciated. + +A collection of PHP classes to manipulate bibtex files. + +If you make improvements, please consider contacting the administrators at bibliophile.sourceforge.net so that your improvements can be added to the release package. + +Mark Grimshaw 2004/2005 +http://bibliophile.sourceforge.net + +28/04/2005 - Mark Grimshaw. + Efficiency improvements. + +11/02/2006 - Daniel Reidsma. + Changes to preg_matching to account for Latex characters in names such as {\"{o}} +*/ +// For a quick command-line test (php -f PARSECREATORS.php) after installation, uncomment these lines: + +/*********************** + $authors = "Mark \~N. Grimshaw and Bush III, G.W. & M. C. H{\\'a}mmer Jr. and von Frankenstein, Ferdinand Cecil, P.H. & Charles Louis Xavier Joseph de la Vallee P{\\\"{o}}ussin"; + $creator = new PARSECREATORS(); + $creatorArray = $creator->parse($authors); + print_r($creatorArray); +***********************/ + +class PARSECREATORS +{ + function PARSECREATORS() + { + } + + function parse($input, $type = 'author') + { + $input = trim($input); + // split on ' and ' + $authorArray = preg_split("/\s(and|&)\s/i", $input); + return $this->parseArray($authorArray, $type); + } + + function parseArray($authorArray, $type = 'author') + { + foreach ($authorArray as $author) + { + $this->authors[] = $this->parseAuthor($author, $type); + } + } +/* Create writer arrays from bibtex input. +'author field can be (delimiters between authors are 'and' or '&'): +1. +2. , +3. , , +*/ + function parseAuthor($value, $type = 'author') + { + $appellation = $prefix = $surname = $firstname = $initials = ''; + $this->prefix = array(); + $author = explode(",", preg_replace("/\s{2,}/", ' ', trim($value))); + $size = sizeof($author); +// No commas therefore something like Mark Grimshaw, Mark Nicholas Grimshaw, M N Grimshaw, Mark N. Grimshaw + if ($size == 1) + { +// Is complete surname enclosed in {...}, unless the string starts with a backslash (\) because then it is +// probably a special latex-sign.. +// 2006.02.11 DR: in the last case, any NESTED curly braces should also be taken into account! so second +// clause rules out things such as author="a{\"{o}}" +// + if (preg_match("/(.*) {([^\\\].*)}/", $value, $matches) && + !(preg_match("/(.*) {\\\.{.*}.*}/", $value, $matches2))) + { + $author = split(" ", $matches[1]); + $surname = $matches[2]; + } + else + { + $author = split(" ", $value); +// last of array is surname (no prefix if entered correctly) + $surname = array_pop($author); + } + } +// Something like Grimshaw, Mark or Grimshaw, Mark Nicholas or Grimshaw, M N or Grimshaw, Mark N. + else if ($size == 2) + { +// first of array is surname (perhaps with prefix) + list($surname, $prefix) = $this->grabSurname(array_shift($author)); + } +// If $size is 3, we're looking at something like Bush, Jr. III, George W + else + { +// middle of array is 'Jr.', 'IV' etc. + $appellation = join(' ', array_splice($author, 1, 1)); +// first of array is surname (perhaps with prefix) + list($surname, $prefix) = $this->grabSurname(array_shift($author)); + } + $remainder = join(" ", $author); + list($firstname, $initials) = $this->grabFirstnameInitials($remainder); + if (!empty($this->prefix)) + $prefix = join(' ', $this->prefix); + $surname = $surname . ' ' . $appellation; + $creator = array('firstname' => utf8_encode(trim($firstname)), 'initials' => utf8_encode(trim($initials)), 'lastname' => utf8_encode(trim($surname)), 'prefix' => trim($prefix)); + if (isset($creator)) + { + $creator['type'] = $this->typeMap[$type]; + $creator['md5'] = $this->md5sum($creator); + return $creator; + } + return FALSE; + } + + function md5sum($creator) + { + $string = $creator['firstname'].$creator['initials'].$creator['lastname']; + $string = str_replace(' ', '', drupal_strtolower($string)); + + return md5($string); + } +// grab firstname and initials which may be of form "A.B.C." or "A. B. C. " or " A B C " etc. + function grabFirstnameInitials($remainder) + { + $firstname = $initials = ''; + $array = split(" ", $remainder); + foreach ($array as $value) + { + $firstChar = substr($value, 0, 1); + if ((ord($firstChar) >= 97) && (ord($firstChar) <= 122)) + $this->prefix[] = $value; + else if (preg_match("/[a-zA-Z]{2,}/", trim($value))) + $firstnameArray[] = trim($value); + else + $initialsArray[] = str_replace(".", " ", trim($value)); + } + if (isset($initialsArray)) + { + foreach ($initialsArray as $initial) + $initials .= ' ' . trim($initial); + } + if (isset($firstnameArray)) + $firstname = join(" ", $firstnameArray); + return array($firstname, $initials); + } +// surname may have title such as 'den', 'von', 'de la' etc. - characterised by first character lowercased. Any +// uppercased part means lowercased parts following are part of the surname (e.g. Van den Bussche) + function grabSurname($input) + { + $surnameArray = split(" ", $input); + $noPrefix = $surname = FALSE; + foreach ($surnameArray as $value) + { + $firstChar = substr($value, 0, 1); + if (!$noPrefix && (ord($firstChar) >= 97) && (ord($firstChar) <= 122)) + $prefix[] = $value; + else + { + $surname[] = $value; + $noPrefix = TRUE; + } + } + if ($surname) + $surname = join(" ", $surname); + if (isset($prefix)) + { + $prefix = join(" ", $prefix); + return array($surname, $prefix); + } + return array($surname, FALSE); + } +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/PARSEENTRIES.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/PARSEENTRIES.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,561 @@ +undefinedStrings, is now returned that holds field values that are judged to be undefined strings. + i.e. they are a non-numeric value that is not defined in a @string{...} entry and not enclosed by braces or double-quotes. + This array will be empty unless the following condition is met: + ($this->removeDelimit || $this->expandMacro && $this->fieldExtract) + + 24/04/2006 Esteban Zimanyi + - When an undefined string is found in function removeDelimiters return the empty string + - Return $this->undefinedStrings in the last position to allow compatibility with previous versions + - Fix management of preamble in function returnArrays + */ + +// For a quick command-line test (php -f PARSEENTRIES.php) after installation, uncomment these lines: +require_once(drupal_get_path('module', 'biblio') . '/includes/biblio.contributors.inc'); + +/************************* + // Parse a file + $parse = NEW PARSEENTRIES(); + $parse->expandMacro = TRUE; + // $array = array("RMP" =>"Rev., Mod. Phys."); + // $parse->loadStringMacro($array); + // $parse->removeDelimit = FALSE; + // $parse->fieldExtract = FALSE; + $parse->openBib("bib.bib"); + $parse->extractEntries(); + $parse->closeBib(); + list($preamble, $strings, $entries, $undefinedStrings) = $parse->returnArrays(); + print_r($preamble); + print "\n"; + print_r($strings); + print "\n"; + print_r($entries); + print "\n\n"; + *************************/ + +/************************ + // Parse a bibtex PHP string + $bibtex_data = <<< END + + @STRING{three = "THREE"} + @STRING{two = "TWO"} + @string{JRNL23 = {NatLA 23 } # " " # two # " " # three} + + @article{klitzing.1, + author = "v. Klitzing and Dorda and Pepper", + title = "New method for high mark@sirfragalot.com accuracy determination of fine structure constant based on quantized hall resistance", + volume = "45", + journal = {Journal of } # JRNL23, + pages = "494", + citeulike-article-id = {12222 + } + , + ignoreMe = {blah}, } + + @article + { + klitzing.2, + author = "Klaus von Klitzing", + title = "The Quantized Hall Effect", + volume = "58", + journal = two, + pages = "519", + } + + END; + + $parse = NEW PARSEENTRIES(); + $parse->expandMacro = TRUE; + // $parse->removeDelimit = FALSE; + // $parse->fieldExtract = FALSE; + $array = array("RMP" =>"Rev., Mod. Phys."); + $parse->loadStringMacro($array); + $parse->loadBibtexString($bibtex_data); + $parse->extractEntries(); + list($preamble, $strings, $entries, $undefinedStrings) = $parse->returnArrays(); + print_r($preamble); + print "\n"; + print_r($strings); + print "\n"; + print_r($entries); + print "\n\n"; + + **********************/ + +class PARSEENTRIES +{ + /** + * @return unknown_type + */ + function PARSEENTRIES() + { + require_once(drupal_get_path('module', 'biblio_bibtex') . '/transtab_latex_unicode.inc.php'); + $this->transtab_latex_unicode = get_transtab_latex_unicode(); + $this->preamble = $this->strings = $this->undefinedStrings = $this->entries = array(); + $this->count = 0; + $this->fieldExtract = TRUE; + $this->removeDelimit = TRUE; + $this->expandMacro = FALSE; + $this->parseFile = TRUE; + $this->outsideEntry = TRUE; + $this->translate_latex = TRUE; + } + // Open bib file + /** + * @param $file + * @return none + */ + function openBib($file) + { + if (!is_file($file)) + die; + ini_set('auto_detect_line_endings', true); + $this->fid = fopen ($file,'r'); + $this->parseFile = TRUE; + } + // Load a bibtex string to parse it + function loadBibtexString($bibtex_string) + { + if (is_string($bibtex_string)) { + //$bibtex_string = $this->searchReplaceText($this->transtab_latex_unicode, $bibtex_string, FALSE); + $this->bibtexString = explode("\n",$bibtex_string); + } else { + $this->bibtexString = $bibtex_string; + } + $this->parseFile = FALSE; + $this->currentLine = 0; + } + function searchReplaceText($searchReplaceActionsArray, $sourceString, $includesSearchPatternDelimiters=FALSE) + { + $searchStrings = array_keys($searchReplaceActionsArray); + if (!$includesSearchPatternDelimiters) { + foreach ($searchStrings as $key => $value) { + $searchStrings[$key] = "/" . $value . "/"; // add search pattern delimiters + } + } + + $replaceStrings= array_values($searchReplaceActionsArray); + + // apply the search & replace actions defined in '$searchReplaceActionsArray' to the text passed in '$sourceString': + return preg_replace($searchStrings, $replaceStrings, $sourceString); + } + + // Set strings macro + function loadStringMacro($macro_array) + { + $this->userStrings = $macro_array; + } + // Close bib file + function closeBib() + { + fclose($this->fid); + } + // Get a non-empty line from the bib file or from the bibtexString + function getLine() + { + if ($this->parseFile) { + if (!feof($this->fid)) { + do { + $line = trim(fgets($this->fid)); + } + while(!feof($this->fid) && !$line); + return $line; + } + return FALSE; + } + else { + do { + $line = array_shift($this->bibtexString); + $line = trim($line); + $this->currentLine++; + } + while($this->bibtexString && !$line); + return $line; + } + } // Extract value part of @string field enclosed by double-quotes or braces. + // The string may be expanded with previously-defined strings + function extractStringValue($string) + { + // $string contains a end delimiter, remove it + $string = trim(substr($string,0,strlen($string)-1)); + // remove delimiters and expand + $string = $this->removeDelimitersAndExpand($string); + return $string; + } + // Extract a field + function fieldSplit($seg) + { + // echo "**** ";print_r($seg);echo "
    "; + // handle fields like another-field = {} + $array = preg_split("/,\s*([-_.:,a-zA-Z0-9]+)\s*={1}\s*/U", $seg, PREG_SPLIT_DELIM_CAPTURE); + // echo "**** ";print_r($array);echo "
    "; + //$array = preg_split("/,\s*(\w+)\s*={1}\s*/U", $seg, PREG_SPLIT_DELIM_CAPTURE); + if (!array_key_exists(1, $array)) + return array($array[0], FALSE); + return array($array[0], $array[1]); + } + // Extract and format fields + function reduceFields($oldString) + { + // 03/05/2005 G. Gardey. Do not remove all occurences, juste one + // * correctly parse an entry ended by: somefield = {aValue}} + $lg = strlen($oldString); + if ($oldString[$lg-1] == "}" || $oldString[$lg-1] == ")" || $oldString[$lg-1] == ",") + $oldString = substr($oldString,0,$lg-1); + // $oldString = rtrim($oldString, "}),"); + $split = preg_split("/=/", $oldString, 2); + $string = $split[1]; + while($string) + { + list($entry, $string) = $this->fieldSplit($string); + $values[] = $entry; + } + foreach ($values as $value) + { + $pos = strpos($oldString, $value); + $oldString = substr_replace($oldString, '', $pos, strlen($value)); + } + $rev = strrev(trim($oldString)); + if ($rev{0} != ',') + $oldString .= ','; + $keys = preg_split("/=,/", $oldString); + // 22/08/2004 - Mark Grimshaw + // I have absolutely no idea why this array_pop is required but it is. Seems to always be + // an empty key at the end after the split which causes problems if not removed. + array_pop($keys); + foreach ($keys as $key) + { + $value = trim(array_shift($values)); + $rev = strrev($value); + // remove any dangling ',' left on final field of entry + if ($rev{0} == ',') + $value = rtrim($value, ","); + if (!$value) + continue; + // 21/08/2004 G.Gardey -> expand macro + // Don't remove delimiters now needs to know if the value is a string macro + // $this->entries[$this->count][strtolower(trim($key))] = trim($this->removeDelimiters(trim($value))); + $key = strtolower(trim($key)); + $value = trim($value); + $this->entries[$this->count][$key] = $value; + } + // echo "**** ";print_r($this->entries[$this->count]);echo "
    "; + } + // Start splitting a bibtex entry into component fields. + // Store the entry type and citation. + function fullSplit($entry) + { + $matches = preg_split("/@(.*)[{(](.*),/U", $entry, 2, PREG_SPLIT_DELIM_CAPTURE); + $this->entries[$this->count]['bibtexEntryType'] = strtolower(trim($matches[1])); + // sometimes a bibtex entry will have no citation key + if (preg_match("/=/", $matches[2])) // this is a field + $matches = preg_split("/@(.*)\s*[{(](.*)/U", $entry, 2, PREG_SPLIT_DELIM_CAPTURE); + // print_r($matches); print "

    "; + $this->entries[$this->count]['bibtexCitation'] = $matches[2]; + $this->reduceFields($matches[3]); + } + + // Grab a complete bibtex entry + function parseEntry($entry) + { + set_time_limit(30); // reset the script timer to avoid timeouts + $entry = $this->translate_latex ? $this->searchReplaceText($this->transtab_latex_unicode, $entry, FALSE) : $entry; + $count = 0; + $lastLine = FALSE; + if (preg_match("/@(.*)([{(])/U", preg_quote($entry), $matches)) + { + if (!array_key_exists(1, $matches)) + return $lastLine; + if (preg_match("/string/i", trim($matches[1]))) + $this->strings[] = $entry; + else if (preg_match("/preamble/i", trim($matches[1]))) + $this->preamble[] = $entry; + else if (preg_match("/comment/i", $matches[1])); // MG (31/Jan/2006) -- ignore @comment + else + { + if ($this->fieldExtract) + $this->fullSplit($entry); + else + $this->entries[$this->count] = $entry; + $this->count++; + } + return $lastLine; + } + } + + // Remove delimiters from a string + function removeDelimiters($string) + { + if ($string && ($string{0} == "\"")) + { + $string = substr($string, 1); + $string = substr($string, 0, -1); + } + else if ($string && ($string{0} == "{")) + { + if (strlen($string) > 0 && $string[strlen($string)-1] == "}") + { + $string = substr($string, 1); + $string = substr($string, 0, -1); + } + } + else if (!is_numeric($string) && !array_key_exists($string, $this->strings) + && (array_search($string, $this->undefinedStrings) === FALSE)) + { + $this->undefinedStrings[] = $string; // Undefined string that is not a year etc. + return ''; + } + return $string; + } + + // This function works like explode('#',$val) but has to take into account whether + // the character # is part of a string (i.e., is enclosed into "..." or {...} ) + // or defines a string concatenation as in @string{ "x # x" # ss # {xx{x}x} } + function explodeString($val) + { + $openquote = $bracelevel = $i = $j = 0; + while ($i < strlen($val)) + { + if ($val[$i] == '"') + $openquote = !$openquote; + elseif ($val[$i] == '{') + $bracelevel++; + elseif ($val[$i] == '}') + $bracelevel--; + elseif ( $val[$i] == '#' && !$openquote && !$bracelevel ) + { + $strings[] = substr($val,$j,$i-$j); + $j=$i+1; + } + $i++; + } + $strings[] = substr($val,$j); + return $strings; + } + + // This function receives a string and a closing delimiter '}' or ')' + // and looks for the position of the closing delimiter taking into + // account the following Bibtex rules: + // * Inside the braces, there can arbitrarily nested pairs of braces, + // but braces must also be balanced inside quotes! + // * Inside quotes, to place the " character it is not sufficient + // to simply escape with \": Quotes must be placed inside braces. + function closingDelimiter($val,$delimitEnd) + { + // echo "####>$delimitEnd $val
    "; + $openquote = $bracelevel = $i = $j = 0; + while ($i < strlen($val)) + { + // a '"' found at brace level 0 defines a value such as "ss{\"o}ss" + if ($val[$i] == '"' && !$bracelevel) + $openquote = !$openquote; + elseif ($val[$i] == '{') + $bracelevel++; + elseif ($val[$i] == '}') + $bracelevel--; + if ( $val[$i] == $delimitEnd && !$openquote && !$bracelevel ) + return $i; + $i++; + } + // echo "--> $bracelevel, $openquote"; + return 0; + } + + // Remove enclosures around entry field values. Additionally, expand macros if flag set. + function removeDelimitersAndExpand($string, $inpreamble = FALSE) + { + // only expand the macro if flag set, if strings defined and not in preamble + if (!$this->expandMacro || empty($this->strings) || $inpreamble) + $string = $this->removeDelimiters($string); + else + { + $stringlist = $this->explodeString($string); + $string = ""; + foreach ($stringlist as $str) + { + // trim the string since usually # is enclosed by spaces + $str = trim($str); + // replace the string if macro is already defined + // strtolower is used since macros are case insensitive + if (isset($this->strings[strtolower($str)])) + $string .= $this->strings[strtolower($str)]; + else + $string .= $this->removeDelimiters(trim($str)); + } + } + return $string; + } + + // This function extract entries taking into account how comments are defined in BibTeX. + // BibTeX splits the file in two areas: inside an entry and outside an entry, the delimitation + // being indicated by the presence of a @ sign. When this character is met, BibTex expects to + // find an entry. Before that sign, and after an entry, everything is considered a comment! + function extractEntries() + { + $inside = $possibleEntryStart = FALSE; + $entry=""; + while($line=$this->getLine()) + { + if ($possibleEntryStart) + $line = $possibleEntryStart . $line; + if (!$inside && strchr($line,"@")) + { + // throw all characters before the '@' + $line=strstr($line,'@'); + if (!strchr($line, "{") && !strchr($line, "(")) + $possibleEntryStart = $line; + elseif (preg_match("/@.*([{(])/U", preg_quote($line), $matches)) + { + $inside = TRUE; + if ($matches[1] == '{') + $delimitEnd = '}'; + else + $delimitEnd = ')'; + $possibleEntryStart = FALSE; + } + } + if ($inside) + { + $entry .= " ".$line; + if ($j=$this->closingDelimiter($entry,$delimitEnd)) + { + // all characters after the delimiter are thrown but the remaining + // characters must be kept since they may start the next entry !!! + $lastLine = substr($entry,$j+1); + $entry = substr($entry,0,$j+1); + // Strip excess whitespaces from the entry + $entry = preg_replace('/\s\s+/', ' ', $entry); + $this->parseEntry($entry); + $entry = strchr($lastLine,"@"); + if ($entry) + $inside = TRUE; + else + $inside = FALSE; + } + } + } + } + + // Return arrays of entries etc. to the calling process. + function returnArrays() + { + // global $transtab_latex_unicode; // defined in 'transtab_latex_unicode.inc.php' + foreach ($this->preamble as $value) + { + preg_match("/.*?[{(](.*)/", $value, $matches); + $preamble = substr($matches[1], 0, -1); + $preambles['bibtexPreamble'] = trim($this->removeDelimitersAndExpand(trim($preamble), TRUE)); + } + if (isset($preambles)) + $this->preamble = $preambles; + if ($this->fieldExtract) + { + // Next lines must take into account strings defined by previously-defined strings + $strings = $this->strings; + // $this->strings is initialized with strings provided by user if they exists + // it is supposed that there are no substitutions to be made in the user strings, i.e., no # + $this->strings = isset($this->userStrings) ? $this->userStrings : array() ; + foreach ($strings as $value) + { + // changed 21/08/2004 G. Gardey + // 23/08/2004 Mark G. account for comments on same line as @string - count delimiters in string value + $value = trim($value); + $matches = preg_split("/@\s*string\s*([{(])/i", $value, 2, PREG_SPLIT_DELIM_CAPTURE); + $delimit = $matches[1]; + $matches = preg_split("/=/", $matches[2], 2, PREG_SPLIT_DELIM_CAPTURE); + // macros are case insensitive + $this->strings[strtolower(trim($matches[0]))] = $this->extractStringValue($matches[1]); + } + } + // changed 21/08/2004 G. Gardey + // 22/08/2004 Mark Grimshaw - stopped useless looping. + // removeDelimit and expandMacro have NO effect if !$this->fieldExtract + if ($this->removeDelimit || $this->expandMacro && $this->fieldExtract) + { + for($i = 0; $i < count($this->entries); $i++) + { + foreach ($this->entries[$i] as $key => $value) + // 02/05/2005 G. Gardey don't expand macro for bibtexCitation + // and bibtexEntryType + if ($key != 'bibtexCitation' && $key != 'bibtexEntryType') + $this->entries[$i][$key] = trim($this->removeDelimitersAndExpand($this->entries[$i][$key])); + } + } + // EZ: Remove this to be able to use the same instance for parsing several files, + // e.g., parsing a entry file with its associated abbreviation file + // if (empty($this->preamble)) + // $this->preamble = FALSE; + // if (empty($this->strings)) + // $this->strings = FALSE; + // if (empty($this->entries)) + // $this->entries = FALSE; + return array($this->preamble, $this->strings, $this->entries, $this->undefinedStrings); + } + + function &getEntries() { + if ($this->removeDelimit || $this->expandMacro && $this->fieldExtract) + { + for($i = 0; $i < count($this->entries); $i++) + { + foreach ($this->entries[$i] as $key => $value) + // 02/05/2005 G. Gardey don't expand macro for bibtexCitation + // and bibtexEntryType + if ($key != 'bibtexCitation' && $key != 'bibtexEntryType') + $this->entries[$i][$key] = trim($this->removeDelimitersAndExpand($this->entries[$i][$key])); + } + } + return $this->entries; + } + +} + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/PARSEMONTH.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/PARSEMONTH.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,117 @@ +startDay = $endMonth = $this->endDay = FALSE; + $date = split("#", $monthField); + foreach ($date as $field) + { + $field = ucfirst(strtolower(trim($field))); + if ($month = array_search($field, $this->monthToLongName())) + { + if (!$startMonth) + $startMonth = $month; + else + $endMonth = $month; + continue; + } + else if ($month = array_search($field, $this->monthToShortName())) + { + if (!$startMonth) + $startMonth = $month; + else + $endMonth = $month; + continue; + } + $this->parseDay($field); + } + if ($this->endDay && !$endMonth) + $endMonth = $startMonth; + return array($startMonth, $this->startDay, $endMonth, $this->endDay); + } +// extract day of month from field + function parseDay($dayField) + { + preg_match("/([0-9]+).*([0-9]+)|([0-9]+)/", $dayField, $array); + if (array_key_exists(3, $array)) + { + if (!$this->startDay) + $this->startDay = $array[3]; + else if (!$this->endDay) + $this->endDay = $array[3]; + } + else + { + if (array_key_exists(1, $array)) + $this->startDay = $array[1]; + if (array_key_exists(2, $array)) + $this->endDay = $array[2]; + } + } +// Convert month to long name + function monthToLongName() + { + return array( + 1 => 'January', + 2 => 'February', + 3 => 'March', + 4 => 'April', + 5 => 'May', + 6 => 'June', + 7 => 'July', + 8 => 'August', + 9 => 'September', + 10 => 'October', + 11 => 'November', + 12 => 'December', + ); + } +// Convert month to short name + function monthToShortName() + { + return array( + 1 => 'Jan', + 2 => 'Feb', + 3 => 'Mar', + 4 => 'Apr', + 5 => 'May', + 6 => 'Jun', + 7 => 'Jul', + 8 => 'Aug', + 9 => 'Sep', + 10 => 'Oct', + 11 => 'Nov', + 12 => 'Dec', + ); + } +} +?> diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/PARSEPAGE.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/PARSEPAGE.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ +type1($item)) + return $this->return; +// else, return first number we can find + if (preg_match("/(\d+|[ivx]+)/i", $item, $array)) + return array($array[1], FALSE); +// No valid page numbers found + return array(FALSE, FALSE);; + } +// "77--99" or '-'type? + function type1($item) + { + $start = $end = FALSE; + $array = preg_split("/--|-/", $item); + if (sizeof($array) > 1) + { + if (is_numeric(trim($array[0]))) + $start = trim($array[0]); + else + $start = strtolower(trim($array[0])); + if (is_numeric(trim($array[1]))) + $end = trim($array[1]); + else + $end = strtolower(trim($array[1])); + if ($end && !$start) + $this->return = array($end, $start); + else + $this->return = array($start, $end); + return TRUE; + } + return FALSE; + } +} +?> diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/README --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/README Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,96 @@ +Released through http://bibliophile.sourceforge.net under the GPL licence. +Do whatever you like with this -- some credit to the author(s) would be appreciated. + +A collection of PHP classes to manipulate bibtex files. + +If you make improvements, please consider contacting the administrators at bibliophile.sourceforge.net so that your improvements can be added to the release package. + +Mark Grimshaw & Guillaume Gardey 2004 - 2006 +http://bibliophile.sourceforge.net + +################################################ +PARSEENTRIES +############ +This reads the contents of a BibTeX .bib file or a PHP string and returns arrays of information representing @preamble, @string and valid BibTeX entries. + +Entries may be enclosed by {...} or (...). Fields values may be enclosed by "...", {...} or without enclosure. + +FLAGS can be set: +$parse->fieldExtract; +$parse->removeDelimit; +$parse->expandMacro = FALSE/TRUE to expand macros within BibTeX entries ('#' and @string values). + +If $parse->fieldExtract == TRUE (default), the $entries array using the supplied example bib.bib file will be: +Array +( + [0] => Array + ( + [bibtexEntryType] => article + [bibtexCitation] => klitzing:qhe + [author] => K. v. Klitzing and G. Dorda = "and M. Pepper + [title] => New method for h{\i}gh mark@sirfragalot.com accuracy determination of fine structure constant based on quantized hall resistance + [journal] => PRL + [volume] => 45 + [pages] => 494 + [blah] => bl"ah + [year] => 1980 + ) + + [1] => Array + ( + [bibtexEntryType] => article + [bibtexCitation] => klitzing:nobel + [author] => Klaus von Klitzing + [title] => The Quantized Hall Effect + [journal] => RMP + [volume] => 58 + [pages] => 519 + [year] => 1986 + ) +) + +In other words, an array of separate BibTeX entries each one an array comprising the fields, entry type and given citation. @strings will be similarly formatted. + +If $parse->fieldExtract == FALSE, the $entries array using the supplied example bib.bib file will be: +Array +( + [0] => @ARTICLE{klitzing:qhe, AUTHOR="K. v. Klitzing and G. Dorda = "and M. Pepper", TITLE="New method for h{\i}gh mark@sirfragalot.com accuracy determination of fine structure constant based on quantized hall resistance", JOURNAL=PRL, VOLUME= 45, PAGES=494, blah=" bl"ah ", YEAR=1980 }, + [1] => @ARTICLE(klitzing:nobel, AUTHOR={Klaus von Klitzing}, TITLE="The Quantized Hall Effect",JOURNAL=RMP, VOLUME=58, PAGES=519, YEAR=1986 ) +) + +In other words, an array of separate BibTeX entries with no further processing. @strings will be similarly formatted. +NB - IF fieldExtract == FALSE, SETTINGS FOR expandMacro AND removeDelimit WILL HAVE NO EFFECT. + +If $parse->removeDelimit == TRUE (default), all double-quotes or braces that enclose field values of BibTeX entries/strings will be removed. Otherwise, they will be left in place. Setting this to TRUE only has an effect if $parse->fieldExtract is TRUE. + +In all cases, @preamble (from the given example bib.bib file) will be returned as: +Array +( + [bibtexPreamble] => Blah blah blah some preamble or other r +) + +Additional BibTeX macro can be supplied to the parser: +$more_macro = array("RMP" => "Rev., Mod. Phys.", "LNCS" => "Lecture Notes in Computer Science"); +$parse->loadStringMacro($more_macro); + +$parse->returnArrays() will then return $entries with all BibTeX macros (BibTeX file + $more_macro) expanded. + + +################################################ +PARSECREATORS +############# +This takes a BibTeX author or editor field and splits it into the component writers returning a multidimensional array consisting of writer arrays comprised of array(firstname(s), initials, surname). It attempts to recognise 'et. al' or 'et. al.' and returns either FALSE or TRUE if that exists. If the input is 'Anon', 'anon', 'Anonymous' or 'anonymous' FALSE is returned. +################################################ + + +################################################ +PARSEMONTH +############# +Split a bibtex month field into day and month components including date ranges. + list($startMonth, $startDay, $endMonth, $endDay) = $parseMonth->init($monthField); + +################################################ +PARSEPAGE +############# +Split a bibtex pages field into page start and page end components. + list($start, $end) = $parsePage->init($pagesField); \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/bib.bib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/bib.bib Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,44 @@ + + + +@STrinG{PRL="Phys. Rev. +Lett."} + +@preAmbLE {"Blah blah blah some preamble or other +r"} + +%%some comments + + + @ARTICLE{klitzing:qhe, + AUTHOR="K. v. Klitzing and G. Dorda = "and M. +Pepper", + + + TITLE="New method for h{\i}gh mark@sirfragalot.com accuracy determination of fine structure +constant based on quantized hall resistance", +JOURNAL=PRL, +%%some comments + + VOLUME= + +45, + +PAGES=494, +blah=" bl"ah ", +YEAR=1980 # Aug +} + +%% @stRING( +%%RMP="Rev., Mod. +%%Phys.") + @ARTICLE +(klitzing:nobel, +AUTHOR={Klaus von Klitzing}, +TITLE="The Quantized Hall Effect",JOURNAL=RMP, +VOLUME=58, +PAGES=519, +YEAR=1986 +) + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/biblio_bibtex.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/biblio_bibtex.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = Biblio - BibTex +description = Provides BibTex import and export to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = PARSEENTRIES.php +files[] = views/biblio_handler_field_export_link_bibtex.inc + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/biblio_bibtex.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/biblio_bibtex.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,165 @@ +condition('format', 'bibtex') + ->execute(); + } +} + +function biblio_bibtex_enable() { + biblio_bibtex_set_system_weight(); +} + +function biblio_bibtex_set_system_weight() { + db_update('system') + ->fields(array('weight' => 22)) + ->condition('name', 'biblio_bibtex') + ->execute(); +} + + +function _get_bibtex_type_map() { + $map['type_map'] = serialize( + array( + 'article' => 102, + 'book' => 100, + 'booklet' => 129, + 'conference' => 103, + 'inbook' => 101, + 'incollection' => 101, + 'inproceedings' => 103, + 'manual' => 129, + 'mastersthesis' => 108, + 'misc' => 129, + 'phdthesis' => 108, + 'proceedings' => 104, + 'techreport' => 129, + 'unpublished' => 124, + ) + ); + $map['format'] = 'bibtex'; + return $map; + +} + +function _get_bibtex_type_names() { + $map['type_names'] = serialize( + array( + 'article' => 'An article from a journal', + 'book' => 'A book with an explicit publisher', + 'booklet' => 'A work that is printed and bound, but without a named publisher or sponsoring institution', + 'conference' => 'An article in a conference proceedings', + 'inbook' => 'A part of a book, usually untitled. May be a chapter (or section or whatever) and/or a range of pages', + 'incollection' => 'A part of a book having its own title', + 'inproceedings' => 'An article in a conference proceedings', + 'manual' => 'Technical documentation', + 'mastersthesis' => 'A Master\'s thesis', + 'misc' => 'For use when nothing else fits', + 'phdthesis' => 'A Ph.D. thesis', + 'proceedings' => 'The proceedings of a conference', + 'techreport' => 'A report published by a school or other institution, usually numbered within a series', + 'unpublished' => 'A document having an author and title, but not formally published', + ) + ); + $map['format'] = 'bibtex'; + return $map; + +} +function _get_bibtex_field_map() { + + $map['field_map'] = serialize( + array( + 'journal' => 'biblio_secondary_title', + 'booktitle' => 'biblio_secondary_title', + 'series' => 'biblio_secondary_title', + 'volume' => 'biblio_volume', + 'number' => 'biblio_number', + 'year' => 'biblio_year', + 'note' => 'biblio_notes', + 'month' => 'biblio_date', + 'pages' => 'biblio_pages', + 'publisher' => 'biblio_publisher', + 'school' => 'biblio_publisher', + 'organization' => 'biblio_publisher', + 'institution' => 'biblio_publisher', + 'type' => 'biblio_type_of_work', + 'edition' => 'biblio_edition', + 'chapter' => 'biblio_section', + 'address' => 'biblio_place_published', + 'abstract' => 'biblio_abst_e', + 'keywords' => 'biblio_keywords', + 'isbn' => 'biblio_isbn', + 'issn' => 'biblio_issn', + 'doi' => 'biblio_doi', + 'url' => 'biblio_url', + + ) + ); + + $map['format'] = 'bibtex'; + return $map; + +} + +function _save_bibtex_maps() { + $typemap = _get_bibtex_type_map(); + $typenames = _get_bibtex_type_names(); + $fieldmap = _get_bibtex_field_map(); + $maps = array_merge($typemap, $typenames, $fieldmap); + biblio_save_map($maps); +} + +function _reset_bibtex_map($type) { + $count = db_query("SELECT COUNT(*) FROM {biblio_type_maps} WHERE format='bibtex'")->fetchField(); + if ($count && $type) { //update + $function = '_get_bibtex_' . $type; + if (!function_exists($function)) return; + $map = $function(); + db_update('biblio_type_maps') + ->fields($map) + ->condition('format', 'bibtex') + ->execute(); + } + else { // install + db_delete('biblio_type_maps') + ->condition('format', 'bibtex') + ->execute(); + _save_bibtex_maps(); + } +} + +function biblio_bibtex_schema() { + $schema = array(); + $schema['biblio_bibtex'] = array( + 'fields' => array( + 'nid' => array('type' => 'int', 'not null' => TRUE), + 'biblio_bibtex_md5' => array('type' => 'char', 'length' => 32, 'not null' => TRUE), + 'biblio_bibtex_id' => array('type' => 'varchar', 'length' => 255, 'not null' => FALSE), + ), + 'primary key' => array('nid'), + ); + return $schema; +} + +function biblio_bibtex_update_7001() { + if (!db_field_exists('biblio_bibtex', 'biblio_bibtex_id')) { + $spec = array('type' => 'varchar', 'length' => 255, 'not null' => FALSE); + db_add_field('biblio_bibtex', 'biblio_bibtex_id', $spec); + } +} + + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/biblio_bibtex.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/biblio_bibtex.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,455 @@ + 2, + 'path' => drupal_get_path('module', 'biblio_bibtex') . '/views', + ); +} +/* + * add the BibTex option to the option list of the biblio_import_form + * the key is the module name use by module_invoke to call hook_biblio_import + * module_invoke('biblio_bibtex', 'biblio_import',...) + */ +function biblio_bibtex_biblio_import_options() { + return array('biblio_bibtex' => t('BibTex')); +} +function biblio_bibtex_biblio_mapper_options() { + return array( + 'bibtex' => array( + 'title' => t('BibTex'), + 'export' => TRUE, + ) + ); +} + +function biblio_bibtex_form_biblio_node_form_alter(&$form, &$form_state) { + global $user; + if (!$form_state['submitted'] && !isset($form_state['values']) && !isset($form['#node']->nid)) { + if (!$form_state['submitted']) { + $form['biblio_cut_paste'] = array( + '#type' => 'fieldset', + '#title' => t('Paste BibTex Record'), + '#weight' => -20, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $form['biblio_cut_paste']['paste_data_bibtex'] = array( + '#type' => 'textarea', + '#title' => t('BibTex'), + '#required' => FALSE, + '#default_value' => isset($form_state['values']['paste_data_bibtex']) ? $form_state['values']['paste_data_bibtex'] : '', + '#description' => t('Paste a BibTex entry here'), + '#size' => 60, + '#weight' => -4 + ); + $form['biblio_cut_paste']['paste_submit'] = array( + '#type' => 'submit', + '#value' => t('Populate using BibTex'), + '#submit' => array('biblio_bibtex_form_biblio_node_form_submit') + ); + } + } + $biblio_bibtex_id = (isset($form_state['values']['biblio_bibtex_id'])) ? $form_state['values']['biblio_bibtex_id'] : ''; + $biblio_bibtex_md5 = (isset($form_state['values']['biblio_bibtex_md5'])) ? $form_state['values']['biblio_bibtex_md5'] : ''; + $form['biblio_bibtex_id'] = array('#type' => 'value', '#value' => $biblio_bibtex_id); + $form['biblio_bibtex_md5'] = array('#type' => 'value', '#value' => $biblio_bibtex_md5); +} + +function biblio_bibtex_form_biblio_node_form_submit($form, &$form_state) { + global $user; + $node_data = array(); + $dups = array(); + + if (strlen($form_state['values']['paste_data_bibtex'])) { + list($node_data, $dups) = biblio_bibtex_biblio_import($form_state['values']['paste_data_bibtex'], array(), FALSE, NULL, FALSE, TRUE); + } + if (!empty($node_data) && is_object($node_data[0])) { + $form_state['values'] = array_merge($form_state['values'], (array)$node_data[0]); + $form_state['input']['biblio_type'] = $form_state['biblio_type'] = $node_data[0]->biblio_type; + +// $form_state['storage']['biblio_type'] = $node_data[0]->biblio_type; + } + elseif (!empty($dups)) { + $message = t('The bibtex node that you are trying to paste into the form already exists in the database, see !url', array('!url' => l('node/' . $dups[0], 'node/' . $dups[0]))); + form_set_error('paste_data_bibtex', $message); + } + $form_state['rebuild'] = TRUE; + + return; +} + +function biblio_bibtex_biblio_export_options() { + return array('bibtex' => t('BibTex')); +} + +function biblio_bibtex_node_view($node, $view_mode) { + if ($node->type == 'biblio') { + switch ($view_mode) { + case 'full': + case 'teaser': + $links = biblio_bibtex_biblio_export_link($node->nid); + $node->content['links']['biblio_bibtex'] = array( + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} + +/** + * Creates a link to export a node (or view) in BibTEX format + * + * @param $base this is the base url (defaults to /biblio) + * @param $nid the node id, if NULL then the current view is exported + * @return a link (BibTEX) + */ +function biblio_bibtex_biblio_export_link($nid = NULL, $filter = array()) { + $show_link = variable_get('biblio_export_links', array('bibtex' => TRUE)); + if (!isset($show_link['bibtex']) || !biblio_access('export')) return array(); + $base = variable_get('biblio_base', 'biblio'); + + if (module_exists('popups') && !empty($nid)) { + $link = array( + 'attributes' => array( + 'class' => 'popups', + 'title' => t("Click to get the BibTEX output"))); + } + else { + $link = array( + 'attributes' => array( + 'title' => t("Click to download the BibTEX formatted file"))); + } + $link['attributes'] += array('rel' => 'nofollow'); + + $link['href'] = "$base/export/bibtex" ; + if (!empty($nid)) { + $link['href'] .= '/' . $nid; + } + $link['title'] = t('BibTex'); + + if (empty($nid) && !empty($filter)) { // add any filters which may be on the current page + $link['query'] = $filter; + } + + return array('biblio_bibtex' => $link); +} + +function biblio_bibtex_node_delete($node) { + if ($node->type != 'biblio') { + return; + } + db_delete('biblio_bibtex') + ->condition('nid', $node->nid) + ->execute(); +} + +function biblio_bibtex_node_insert($node) { + if ($node->type != 'biblio') { + return; + } + if (!isset($node->biblio_bibtex_md5)) { + return; + } + drupal_write_record('biblio_bibtex', $node); +} + +function biblio_bibtex_biblio_import($file, $terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE, $string = FALSE) { + $nids = array(); + $dups = array(); + + module_load_include('php', 'biblio_bibtex', 'PARSEENTRIES'); + $bibtex = new PARSEENTRIES(); + + if ($string) { + $bibtex->loadBibtexString($file); + } + else { + $bibtex->openBib($file->uri); + } + + $bibtex->extractEntries(); + + if ($bibtex->count) { + $entries =& $bibtex->getEntries(); + list($nids, $dups) = _biblio_bibtex_import($entries, $terms, $batch, $session_id, $save); + } + return array($nids, $dups); +} +function biblio_bibtex_biblio_export($nids) { + if (module_exists('popups') && $nid) { + $popup = TRUE; + } + else { + $popup = FALSE; + drupal_add_http_header('Content-type', 'application/text; charset=utf-8'); + drupal_add_http_header('Content-Disposition', 'attachment; filename="Biblio-Bibtex.bib"'); + } + + $nodes = node_load_multiple($nids, array(), TRUE); + foreach ($nodes as $node) { + if (!$popup) { + print _biblio_bibtex_export($node); + } + else{ + $popup_data .= _biblio_bibtex_export($node); + } + } + if ($popup && !empty($popup_data)) return '

    ' . $popup_data . '
    '; +} + +function _biblio_bibtex_import($entries, $terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE) { + $nids = array(); + $dups = array(); + + foreach ($entries as $entry) { + $node = new stdClass(); + $node->biblio_contributors = array(); + $node->biblio_type = _biblio_bibtex_type_map($entry['bibtexEntryType'], 'import'); + switch ($entry['bibtexEntryType']) { + case 'mastersthesis': + $node->biblio_type_of_work = 'masters'; + break; + case 'phdthesis': + $node->biblio_type_of_work = 'phd'; + break; + } + if (!empty($entry['author'])) { + // split on ' and ' + $author_array = preg_split("/\s(and|&)\s/i", trim($entry['author'])); + foreach ($author_array as $key => $author) { + // discard braces as biblio uses its own heuristic to split up human names, + // and the braces get in the way + $author = str_replace(array('{', '}'), array('', ''), $author); + $node->biblio_contributors[]= array('name' => $author, 'auth_category' => 1, 'auth_type' => _biblio_get_auth_type(1, $node->biblio_type)); + } + } + + $node->biblio_citekey = (!empty($entry['bibtexCitation'])) ? $entry['bibtexCitation'] : NULL; + if (!empty($entry['editor'])) { + $author_array = preg_split("/\s(and|&)\s/i", trim($entry['editor'])); + foreach ($author_array as $key => $author) { + // discard braces as biblio uses its own heuristic to split up human names, + // and the braces get in the way + $author = str_replace(array('{', '}'), array('', ''), $author); + $node->biblio_contributors[]= array('name' => $author, 'auth_category' => 2, 'auth_type' => _biblio_get_auth_type(2, $node->biblio_type)); + } + } + + $node->biblio_secondary_title = (!empty($entry['journal'])) ? $entry['journal'] : NULL; + if (!empty($entry['booktitle'])) $node->biblio_secondary_title = $entry['booktitle']; + if (!empty($entry['series'])) { + if (!empty($entry['booktitle'])) { + $node->biblio_tertiary_title = $entry['series']; + } + else { + $node->biblio_secondary_title = $entry['series']; + } + } + $node->biblio_volume = (!empty($entry['volume'])) ? $entry['volume'] : NULL; + $node->biblio_number = (!empty($entry['number'])) ? $entry['number'] : NULL; + $node->biblio_year = (!empty($entry['year'])) ? $entry['year'] : NULL; + $node->biblio_notes = (!empty($entry['note'])) ? $entry['note'] : NULL; + $node->biblio_date = (!empty($entry['month'])) ? $entry['month'] : NULL; + $node->biblio_pages = (!empty($entry['pages'])) ? $entry['pages'] : NULL; + $node->biblio_publisher = (!empty($entry['publisher'])) ? $entry['publisher'] : NULL; + if (!empty($entry['organization'])) $node->biblio_publisher = $entry['organization']; + if (!empty($entry['school'])) $node->biblio_publisher = $entry['school']; + if (!empty($entry['institution'])) $node->biblio_publisher = $entry['institution']; + $node->title = (!empty($entry['title'])) ? $entry['title'] : NULL; + $node->biblio_type_of_work .= (!empty($entry['type'])) ? $entry['type'] : NULL; + $node->biblio_edition = (!empty($entry['edition'])) ? $entry['edition'] : NULL; + $node->biblio_section = (!empty($entry['chapter'])) ? $entry['chapter'] : NULL; + $node->biblio_place_published = (!empty($entry['address'])) ? $entry['address'] : NULL; + $node->biblio_abst_e = (!empty($entry['abstract'])) ? $entry['abstract'] : NULL; + if (!empty($entry['keywords'])) { + if (strpos($entry['keywords'], ';')) { + $entry['keywords'] = str_replace(';', ',', $entry['keywords']); + } + $node->biblio_keywords = explode(',', $entry['keywords']); + } + $node->biblio_isbn = (!empty($entry['isbn'])) ? $entry['isbn'] : NULL; + $node->biblio_issn = (!empty($entry['issn'])) ? $entry['issn'] : NULL; + $node->biblio_url = (!empty($entry['url'])) ? $entry['url'] : NULL; + $node->biblio_doi = (!empty($entry['doi'])) ? $entry['doi'] : NULL; + if (module_exists('biblio_pm')) { + $node->biblio_pubmed_id = (!empty($entry['pmid'])) ? $entry['pmid'] : NULL; + $node->biblio_pubmed_md5 = ''; + } + + $node->biblio_bibtex_md5 = md5(serialize($node)); + $node->biblio_import_type = 'bibtex'; + + if (!($dup = biblio_bibtex_check_md5($node->biblio_bibtex_md5))) { + if ($save) { + biblio_save_node($node, $terms, $batch, $session_id, $save); + $nids[] = (!empty($node->nid))? $node->nid : NULL; + } + else { // return the whole node if we are not saveing to the DB (used for the paste function on the input form) + $nids[] = $node; + } + } + else { + $dups[] = $dup; + } + } + return array($nids, $dups); +} +/** + * Export data in bibtex format. + * + * @param $result + * a database result set pointer + * @return + * none + */ +function _biblio_bibtex_export($node) { + static $converter = NULL; + + $bibtex = ''; + $type = "article"; + $journal = $series = $booktitle = $school = $organization = $institution = NULL; + $type = _biblio_bibtex_type_map($node->biblio_type); + switch ($node->biblio_type) { + case 100 : + $series = $node->biblio_secondary_title; + $organization = $node->biblio_publisher; + break; + case 101 : + case 103 : + $booktitle = $node->biblio_secondary_title; + $organization = $node->biblio_publisher; + $series = $node->biblio_tertiary_title; + break; + case 108 : + $school = $node->biblio_publisher; + $node->biblio_publisher = NULL; + if (stripos($node->biblio_type_of_work, 'masters')) { + $type = "mastersthesis"; + } + break; + case 109 : + $institution = $node->biblio_publisher; + $node->biblio_publisher = NULL; + break; + case 102 : + default: + $journal = $node->biblio_secondary_title; + break; + } + + $bibtex .= '@' . $type . ' {'; + $bibtex .= ($node->biblio_citekey) ? $node->biblio_citekey : ""; + $bibtex .= _biblio_bibtex_format_entry('title', $node->title); + $bibtex .= _biblio_bibtex_format_entry('journal', $journal); + $bibtex .= _biblio_bibtex_format_entry('booktitle', $booktitle); + $bibtex .= _biblio_bibtex_format_entry('series', $series); + $bibtex .= _biblio_bibtex_format_entry('volume', $node->biblio_volume); + $bibtex .= _biblio_bibtex_format_entry('number', $node->biblio_number); + $bibtex .= _biblio_bibtex_format_entry('year', $node->biblio_year); + $bibtex .= _biblio_bibtex_format_entry('note', $node->biblio_notes); + $bibtex .= _biblio_bibtex_format_entry('month', $node->biblio_date); + $bibtex .= _biblio_bibtex_format_entry('pages', $node->biblio_pages); + $bibtex .= _biblio_bibtex_format_entry('publisher', $node->biblio_publisher); + $bibtex .= _biblio_bibtex_format_entry('school', $school); + $bibtex .= _biblio_bibtex_format_entry('organization', $organization); + $bibtex .= _biblio_bibtex_format_entry('institution', $institution); + $bibtex .= _biblio_bibtex_format_entry('type', $node->biblio_type_of_work); + $bibtex .= _biblio_bibtex_format_entry('edition', $node->biblio_edition); + $bibtex .= _biblio_bibtex_format_entry('chapter', $node->biblio_section); + $bibtex .= _biblio_bibtex_format_entry('address', $node->biblio_place_published); + $bibtex .= _biblio_bibtex_format_entry('abstract', $node->biblio_abst_e); + + $kw_array = array(); + if (!empty($node->terms)) { + foreach ($node->terms as $term) { + $kw_array[] = $term->name; + } + } + if (!empty($node->biblio_keywords)) { + foreach ($node->biblio_keywords as $term) { + $kw_array[] = $term; + } + } + if (!empty($kw_array)) { + $kw_array = array_unique($kw_array); + $bibtex .= _biblio_bibtex_format_entry('keywords', implode(', ', $kw_array)); + } + + $bibtex .= _biblio_bibtex_format_entry('isbn', $node->biblio_isbn); + $bibtex .= _biblio_bibtex_format_entry('issn', $node->biblio_issn); + $bibtex .= _biblio_bibtex_format_entry('doi', $node->biblio_doi); + $bibtex .= _biblio_bibtex_format_entry('url', $node->biblio_url); + + if (!empty ($node->upload) && count($node->upload['und']) && user_access('view uploaded files')) { + foreach ($node->upload['und'] as $file) { + $attachments[] = file_create_url($file['uri']); + } + $bibtex .= _biblio_bibtex_format_entry('attachments', implode(' , ', $attachments)); + } + + $a = $e = $authors = array(); + if ($authors = biblio_get_contributor_category($node->biblio_contributors, 1)) { + foreach ($authors as $auth) $a[] = trim($auth['name']); + } + if ($authors = biblio_get_contributor_category($node->biblio_contributors, 2)) { + foreach ($authors as $auth) $e[] = trim($auth['name']); + } + $a = implode(' and ', $a); + $e = implode(' and ', $e); + if (!empty ($a)) $bibtex .= _biblio_bibtex_format_entry('author', $a); + if (!empty ($e)) $bibtex .= _biblio_bibtex_format_entry('editor', $e); + $bibtex .= "\n}\n"; + + + //now convert any special characters to the latex equivelents... + if (!isset($converter)) { + module_load_include('php', 'biblio_bibtex', 'PARSEENTRIES'); + include_once(drupal_get_path('module', 'biblio_bibtex') . '/transtab_unicode_bibtex.inc.php'); + $converter = new PARSEENTRIES(); + } + $bibtex = $converter->searchReplaceText(_biblio_bibtex_get_transtab(), $bibtex, FALSE); + + return $bibtex; +} + +function _biblio_bibtex_format_entry($key, $value) { + return !empty($value) ? ",\n\t$key = {" . $value . "}" : ''; +} + +function _biblio_bibtex_type_map($type, $direction = 'export') { + static $map = array(); + if (empty($map)) { + $map = biblio_get_map('type_map', 'bibtex'); + } + if ($direction == 'export') { + return ($type = array_search($type, $map)) ? $type : 'article'; + } + else { + return (isset($map[$type])) ? $map[$type] : 129; //return the biblio type or 129 (Misc) if type not found + } +} +function biblio_bibtex_bibtex_map_reset($type = NULL) { + module_load_include('install', 'biblio_bibtex', 'biblio_bibtex'); + _reset_bibtex_map($type); +} + +function biblio_bibtex_check_md5($md5) { + static $bibtex_md5s = array(); + if (empty($bibtex_md5s)) { + $result = db_query("SELECT * FROM {biblio_bibtex} "); + foreach ($result as $row ) { + $bibtex_md5s[$row->biblio_bibtex_md5] = $row->nid; + } + } + if (isset($bibtex_md5s[$md5])) { + return $bibtex_md5s[$md5]; + } + else { + $bibtex_md5s[$md5] = TRUE; // gaurd against duplicates in the same import + return; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/transtab_latex_unicode.inc.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/transtab_latex_unicode.inc.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,256 @@ + for more info about Unicode and transtab. +function get_transtab_latex_unicode() { +return array( + '\\$\\\\#\\$' => "#", + "\\\\%" => "%", + "\\\\&" => "&", + "(? " ", + "\\{\\\\textexclamdown\\}" => "¡", + "\\{\\\\textcent\\}" => "¢", + "\\{\\\\textsterling\\}" => "£", + "\\{\\\\textyen\\}" => "¥", + "\\{\\\\textbrokenbar\\}" => "¦", + "\\{\\\\textsection\\}" => "§", + "\\{\\\\textasciidieresis\\}" => "¨", + "\\{\\\\textcopyright\\}" => "©", + "\\{\\\\textordfeminine\\}" => "ª", + "\\{\\\\guillemotleft\\}" => "«", + "\\{\\\\textlnot\\}" => "¬", + "\\{\\\\textregistered\\}" => "®", + "\\{\\\\textasciimacron\\}" => "¯", + "\\{\\\\textdegree\\}" => "°", + "\\{\\\\textpm\\}" => "±", + "\\{\\\\texttwosuperior\\}" => "²", + "\\{\\\\textthreesuperior\\}" => "³", + "\\{\\\\textasciiacute\\}" => "´", + "\\{\\\\textmu\\}" => "µ", + "\\{\\\\textparagraph\\}" => "¶", + "\\{\\\\textperiodcentered\\}" => "·", + "\\{\\\\c\\\\ \\}" => "¸", + "\\{\\\\textonesuperior\\}" => "¹", + "\\{\\\\textordmasculine\\}" => "º", + "\\{\\\\guillemotright\\}" => "»", + "\\{\\\\textonequarter\\}" => "¼", + "\\{\\\\textonehalf\\}" => "½", + "\\{\\\\textthreequarters\\}" => "¾", + "\\{\\\\textquestiondown\\}" => "¿", + "\\{?\\\\`\\{?A\\}?\\}?" => "À", + "\\{?\\\\'\\{?A\\}?\\}?" => "Á", + "\\{?\\\\\\^\\{?A\\}?\\}?" => "Â", + "\\{?\\\\~\\{?A\\}?\\}?" => "Ã", + "\\{?\\\\\"\\{?A\\}?\\}?" => "Ä", + "\\\\AA\\ +" => "Å", + "\\{?\\\\r\s\\{?A\\}?\\}?" => "Å", + "\\{?\\\\AE\\}?" => "Æ", + "\\{?\\\\c\sC\\}?" => "Ç", + "\\{?\\\\`\\{?E\\}?\\}?" => "È", + "\\{?\\\\'\\{?E\\}?\\}?" => "É", + "\\{?\\\\\\^\\{?E\\}?\\}?" => "Ê", + "\\{?\\\\\"\\{?E\\}?\\}?" => "Ë", + "\\{?\\\\`\\{?I\\}?\\}?" => "Ì", + "\\{?\\\\'\\{?I\\}?\\}?" => "Í", + "\\{?\\\\\\^\\{?I\\}?\\}?" => "Î", + "\\{?\\\\\"\\{?I\\}?\\}?" => "Ï", + "\\{?\\\\DH\\}?" => "Ð", + "\\{?\\\\~\\{?N\\}?\\}?" => "Ñ", + "\\{?\\\\`\\{?O\\}?\\}?" => "Ò", + "\\{?\\\\'\\{?O\\}?\\}?" => "Ó", + "\\{?\\\\\\^\\{?O\\}?\\}?" => "Ô", + "\\{?\\\\~\\{?O\\}?\\}?" => "Õ", + "\\{?\\\\\"\\{?O\\}?\\}?" => "Ö", + "\\{?\\\\texttimes\\}?" => "×", + "\\\\O\\ +" => "Ø", + "\\{?\\\\O\\}?" => "Ø", + "\\{?\\\\`\\{?U\\}?\\}?" => "Ù", + "\\{?\\\\'\\{?U\\}?\\}?" => "Ú", + "\\{?\\\\\\^\\{?U\\}?\\}?" => "Û", + "\\{?\\\\\"\\{?U\\}?\\}?" => "Ü", + "\\{?\\\\'\\{?Y\\}?\\}?" => "Ý", + "\\{?\\\\TH\\}?" => "Þ", + "\\{?\\\\ss\\{?\\}?\\}?" => "ß", + "\\{?\\\\`\\{?a\\}?\\}?" => "à", + "\\{?\\\\'\\{?a\\}?\\}?" => "á", + "\\{?\\\\\\^\\{?a\\}?\\}?" => "â", + "\\{?\\\\~\\{?a\\}?\\}?" => "ã", + "\\{?\\\\\"\\{?a\\}?\\}?" => "ä", + "\\\\aa\\ +" => "å", + "\\{?\\\\r\s\\{?a\\}?\\}?" => "å", + "\\{?\\\\ae\\}?" => "æ", + "\\{?\\\\c\\{?c\\}?\\}?" => "ç", + "\\{?\\\\`\\{?e\\}?\\}?" => "è", + "\\{?\\\\'\\{?e\\}?\\}?" => "é", + "\\{?\\\\\\^\\{?e\\}?\\}?" => "ê", + "\\{?\\\\\"\s?\\{?e\\}?\\}?" => "ë", + "\\{?\\\\`\\{?i\\}?\\}?" => "ì", + "\\{?\\\\'\\{?i\\}?\\}?" => "í", + "\\{?\\\\\\^\\{?i\\}?\\}?" => "î", + "\\{?\\\\\"\\{?i\\}?\\}?" => "ï", + "\\{?\\\\dh\\}?" => "ð", + "\\{?\\\\~\\{?n\\}?\\}?" => "ñ", + "\\{?\\\\`\\{?o\\}?\\}?" => "ò", + "\\{?\\\\'\\{?o\\}?\\}?" => "ó", + "\\{?\\\\\\^\\{?o\\}?\\}?" => "ô", + "\\{?\\\\~\\{?o\\}?\\}?" => "õ", + "\\{?\\\\\"\\{?o\\}?\\}?" => "ö", + "\\{?\\\\textdiv\\}?" => "÷", + "\\\\o\\ +" => "ø", + "\\{?\\\\o\\}?" => "ø", + "\\{?\\\\`\\{?u\\}?\\}?" => "ù", + "\\{?\\\\'\\{?u\\}?\\}?" => "ú", + "\\{?\\\\\\^\\{?u\\}?\\}?" => "û", + "\\{?\\\\\"\\{?u\\}?\\}?" => "ü", + "\\{?\\\\'\\{?y\\}?\\}?" => "ý", + "\\{?\\\\th\\}?" => "þ", + "\\{?\\\\\"\\{?y\\}?\\}?" => "ÿ", + "\\{?\\\\u\\{?A\\}?\\}?" => "Ă", + "\\{?\\\\u\\{?a\\}?\\}?" => "ă", +// "\\{?\\\\k\\{?A\\}?||}?" => "Ą", + "\\{?\\\\k\\{?a\\}?\\}?" => "ą", + "\\{?\\\\'\\{?C\\}?\\}?" => "Ć", + "\\{?\\\\'\\{?c\\}?\\}?" => "ć", + "\\{?\\\\v\\{?C\\}?\\}?" => "Č", + "\\{?\\\\v\\{?c\\}?\\}?" => "č", + "\\{?\\\\v\\{?c\\}?\\}?" => "č", + "\\{?\\\\v\\{?D\\}?\\}?" => "Ď", + "\\{?\\\\v\\{?d\\}?\\}?" => "ď", + "\\{?\\\\DJ\\}?" => "Đ", + "\\{?\\\\dj\\}?" => "đ", + "\\{?\\\\k\\{?E\\}?\\}?" => "Ę", + "\\{?\\\\k\\{?e\\}?\\}?" => "ę", + "\\{?\\\\v\\{?E\\}?\\}?" => "Ě", + "\\{?\\\\v\\{?e\\}?\\}?" => "ě", + "\\{?\\\\u\s?\\{?e\\}?\\}?" => "ĕ", + "\\{?\\\\u\\{?G\\}?\\}?" => "Ğ", + "\\{?\\\\u\\{?g\\}?\\}?" => "ğ", + "\\{?\\\\.\\{?g\\}?\\}?" => "ġ", + "\\{?\\\\.\\{?I\\}?\\}?" => "İ", + "\\\\'\\{?\\\\i\\}?" => "í", + "\\{?\\\\i\\}?" => "ı", + "\\{?\\\\'\\{?L\\}?\\}?" => "Ĺ", +// "\\{?\\\\'\\{?l\\}?||}?" => "ĺ", + "\\{?\\\\v\\{?L\\}?\\}?" => "Ľ", + "\\{?\\\\v\\{?l\\}?\\}?" => "ľ", + "\\{?\\\\L\\}?" => "Ł", + "\\{?\\\\l\\}?" => "ł", + "\\{?\\\\'\\{?N\\}?\\}?" => "Ń", + "\\{?\\\\'\\{?n\\}?\\}?" => "ń", + "\\{?\\\\v\\{?N\\}?\\}?" => "Ň", + "\\{?\\\\v\\{?n\\}?\\}?" => "ň", + "\\{?\\\\NG\\}?" => "Ŋ", + "\\{?\\\\ng\\}?" => "ŋ", + "\\{?\\\\H\\{?O\\}?\\}?" => "Ő", + "\\{?\\\\H\\{?o\\}?\\}?" => "ő", + "\\{?\\\\OE\\}?" => "Œ", + "\\{?\\\\oe\\}?" => "œ", + "\\{?\\\\'\\{?R\\}?\\}?" => "Ŕ", + "\\{?\\\\'\\{?r\\}?\\}?" => "ŕ", + "\\{?\\\\v\\{?R\\}?\\}?" => "Ř", + "\\{?\\\\v\\{?r\\}?\\}?" => "ř", + "\\{?\\\\'\\{?S\\}?\\}?" => "Ś", + "\\{?\\\\'\\{?s\\}?\\}?" => "ś", + "\\{?\\\\c\\{?S\\}?\\}?" => "Ş", + "\\{?\\\\c\\{?s\\}?\\}?" => "ş", + "\\{?\\\\v\\{?S\\}?\\}?" => "Š", + "\\{?\\\\v\\{?s\\}?\\}?" => "š", + "\\{?\\\\c\\{?T\\}?\\}?" => "Ţ", + "\\{?\\\\c\\{?t\\}?\\}?" => "ţ", + "\\{?\\\\v\\{?T\\}?\\}?" => "Ť", + "\\{?\\\\v\\{?t\\}?\\}?" => "ť", + "\\{?\\\\r\\{?U\\}?\\}?" => "Ů", + "\\{?\\\\r\\{?u\\}?\\}?" => "ů", + "\\{?\\\\H\\{?U\\}?\\}?" => "Ű", + "\\{?\\\\H\\{?u\\}?\\}?" => "ű", + "\\{?\\\\\"\\{?Y\\}?\\}?" => "Ÿ", + "\\{?\\\\'\\{?Z\\}?\\}?" => "Ź", + "\\{?\\\\'\\{?z\\}?\\}?" => "ź", + "\\{?\\\\.\\{?Z\\}?\\}?" => "Ż", + "\\{?\\\\.\\{?z\\}?\\}?" => "ż", + "\\{?\\\\v\\{?Z\\}?\\}?" => "Ž", + "\\{?\\\\v\\{?z\\}?\\}?" => "ž", + "\\{?\\\\textflorin\\}?" => "ƒ", + "\\{?\\\\textasciicircum\\}?" => "ˆ", + "\\{?\\\\textacutedbl\\}?" => "˝", + "\\{?\\\\textendash\\}?|--" => "–", + "\\{?\\\\textemdash\\}?|---" => "—", + "\\{?\\\\textbardbl\\}?" => "‖", + "\\{?\\\\textunderscore\\}?" => "‗", + "\\{?\\\\textquoteleft\\}?" => "‘", + "\\{?\\\\textquoteright\\}?" => "’", + "\\{?\\\\quotesinglbase\\}?" => "‚", + "\\{?\\\\textquotedblleft\\}?" => "“", + "\\{?\\\\textquotedblright\\}?" => "”", + "\\{?\\\\quotedblbase\\}?" => "„", + "\\{?\\\\textdagger\\}?" => "†", + "\\{?\\\\textdaggerdbl\\}?" => "‡", + "\\{?\\\\textbullet\\}?" => "•", + "\\{?\\\\textellipsis\\}?" => "…", + "\\{?\\\\textperthousand\\}?" => "‰", + "\\{?\\\\guilsinglleft\\}?" => "‹", + "\\{?\\\\guilsinglright\\}?" => "›", + "\\{?\\\\textfractionsolidus\\}?" => "⁄", + '\\$\\^\\{0\\}\\$' => "⁰", + '\\$\\^\\{4\\}\\$' => "⁴", + '\\$\\^\\{5\\}\\$' => "⁵", + '\\$\\^\\{6\\}\\$' => "⁶", + '\\$\\^\\{7\\}\\$' => "⁷", + '\\$\\^\\{8\\}\\$' => "⁸", + '\\$\\^\\{9\\}\\$' => "⁹", + '\\$\\^\\{+\\}\\$' => "⁺", + '\\$\\^\\{-\\}\\$' => "⁻", + '\\$\\^\\{=\\}\\$' => "⁼", + '\\$\\^\\{n\\}\\$' => "ⁿ", + '\\$_\\{0\\}\\$' => "₀", + '\\$_\\{1\\}\\$' => "₁", + '\\$_\\{2\\}\\$' => "₂", + '\\$_\\{3\\}\\$' => "₃", + '\\$_\\{4\\}\\$' => "₄", + '\\$_\\{5\\}\\$' => "₅", + '\\$_\\{6\\}\\$' => "₆", + '\\$_\\{7\\}\\$' => "₇", + '\\$_\\{8\\}\\$' => "₈", + '\\$_\\{9\\}\\$' => "₉", + '\\$_\\{+\\}\\$' => "₊", + '\\$_\\{-\\}\\$' => "₋", + '\\$_\\{=\\}\\$' => "₌", + "\\{?\\\\texteuro\\}?" => "€", + "\\{?\\\\textcelsius\\}?" => "℃", + "\\{?\\\\textnumero\\}?" => "№", + "\\{?\\\\textcircledP\\}?" => "℗", + "\\{?\\\\textservicemark\\}?" => "℠", + "\\{?\\\\texttrademark\\}?" => "™", + "\\{?\\\\textohm\\}?" => "Ω", + "\\{?\\\\textestimated\\}?" => "℮", + "\\{?\\\\textleftarrow\\}?" => "←", + "\\{?\\\\textuparrow\\}?" => "↑", + "\\{?\\\\textrightarrow\\}?" => "→", + "\\{?\\\\textdownarrow\\}?" => "↓", + '\\$\\\\infty\\$' => "∞", + "\\{?\\\\textlangle\\}?" => "〈", + "\\{?\\\\textrangle\\}?" => "〉", + "\\{?\\\\textvisiblespace\\}?" => "␣", + "\\{?\\\\textopenbullet\\}?" => "◦", + "\\{?\\\\Delta\\}?" => "Δ", + "\\{?\\\\iota\\}?" => "ι", + "\\{?\\\\omicron\\}?" =>"ο", + "\\{?\\\\mu\\}?" => "μ", + "\\{?\\\\eta\\}?" => "η", + "\\{?\\\\delta\\}?" => "δ", + "\\{?\\\\varsigma\\}?" => "ς", + "\\{?\\\\Sigma\\}?" => "Σ", + "\\{?\\\\pi\\}?" => "π", + "\\{?\\\\nu\\}?" => "ν", + "\\{?\\\\epsilon\\}?" => "ε", + "\\{?\\\\lambda\\}?" => "λ", + "\\{?\\\\=\\{?o\\}?\\}?" => "ō", +); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/bibtexParse/transtab_unicode_bibtex.inc.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/bibtexParse/transtab_unicode_bibtex.inc.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,849 @@ + for more info about Unicode and transtab. + +function _biblio_bibtex_get_transtab() { + + return array( + "(? '$\\#$', + "(? "\\%", + "(? "\\&", + "(? "{\\textquoteright}", + "(? "{\\textquoteleft}", + " " => "~", + "¡" => "{\\textexclamdown}", + "¢" => "{\\textcent}", + "£" => "{\\textsterling}", + "¥" => "{\\textyen}", + "¦" => "{\\textbrokenbar}", + "§" => "{\\textsection}", + "¨" => "{\\textasciidieresis}", + "©" => "{\\textcopyright}", + "ª" => "{\\textordfeminine}", + "«" => "{\\guillemotleft}", + "¬" => "{\\textlnot}", + "­" => "-", + "®" => "{\\textregistered}", + "¯" => "{\\textasciimacron}", + "°" => "{\\textdegree}", + "±" => "{\\textpm}", + "²" => "{\\texttwosuperior}", + "³" => "{\\textthreesuperior}", + "´" => "{\\textasciiacute}", + "µ" => "{\\textmu}", + "¶" => "{\\textparagraph}", + "·" => "{\\textperiodcentered}", + "¸" => "{\\c\\ }", + "¹" => "{\\textonesuperior}", + "º" => "{\\textordmasculine}", + "»" => "{\\guillemotright}", + "¼" => "{\\textonequarter}", + "½" => "{\\textonehalf}", + "¾" => "{\\textthreequarters}", + "¿" => "{\\textquestiondown}", + "À" => "{\\`A}", + "Á" => "{\\'A}", + "Â" => "{\\^A}", + "Ã" => "{\\~A}", + "Ä" => "{\\\"A}", + "Å" => "{\\r A}", + "Æ" => "{\\AE}", + "Ç" => "{\\c C}", + "È" => "{\\`E}", + "É" => "{\\'E}", + "Ê" => "{\\^E}", + "Ë" => "{\\\"E}", + "Ì" => "{\\`I}", + "Í" => "{\\'I}", + "Î" => "{\\^I}", + "Ï" => "{\\\"I}", + "Ð" => "{\\DH}", + "Ñ" => "{\\~N}", + "Ò" => "{\\`O}", + "Ó" => "{\\'O}", + "Ô" => "{\\^O}", + "Õ" => "{\\~O}", + "Ö" => "{\\\"O}", + "×" => "{\\texttimes}", + "Ø" => "{\\O}", + "Ù" => "{\\`U}", + "Ú" => "{\\'U}", + "Û" => "{\\^U}", + "Ü" => "{\\\"U}", + "Ý" => "{\\'Y}", + "Þ" => "{\\TH}", + "ß" => "{\\ss}", + "à" => "{\\`a}", + "á" => "{\\'a}", + "â" => "{\\^a}", + "ã" => "{\\~a}", + "ä" => "{\\\"a}", + "å" => "{\\r a}", + "æ" => "{\\ae}", + "ç" => "{\\c c}", + "è" => "{\\`e}", + "é" => "{\\'e}", + "ê" => "{\\^e}", + "ë" => "{\\\"e}", + "ì" => "{\\`\\i}", + "í" => "{\\'\\i}", + "î" => "{\\^\\i}", + "ï" => "{\\\"\\i}", + "ð" => "{\\dh}", + "ñ" => "{\\~n}", + "ò" => "{\\`o}", + "ó" => "{\\'o}", + "ô" => "{\\^o}", + "õ" => "{\\~o}", + "ö" => "{\\\"o}", + "÷" => "{\\textdiv}", + "ø" => "{\\o}", + "ù" => "{\\`u}", + "ú" => "{\\'u}", + "û" => "{\\^u}", + "ü" => "{\\\"u}", + "ý" => "{\\'y}", + "þ" => "{\\th}", + "ÿ" => "{\\\"y}", + "Ā" => "A", + "ā" => "{\\={a}}", + "Ă" => "{\\u A}", + "ă" => "{\\u a}", + "Ą" => "{\\k A}", + "ą" => "{\\k a}", + "Ć" => "{\\'C}", + "ć" => "{\\'c}", + "Ĉ" => "Ch", + "ĉ" => "ch", + "Ċ" => "C", + "ċ" => "c", + "Č" => "{\\v C}", + "č" => "{\\v c}", + "Ď" => "{\\v D}", + "ď" => "{\\v d}", + "Đ" => "{\\DJ}", + "đ" => "{\\dj}", + "Ē" => "E", + "ē" => "e", + "Ĕ" => "E", + "ĕ" => "e", + "Ė" => "E", + "ė" => "e", + "Ę" => "{\\k E}", + "ę" => "{\\k e}", + "Ě" => "{\\v E}", + "ě" => "{\\v e}", + "Ĝ" => "Gh", + "ĝ" => "gh", + "Ğ" => "{\\u G}", + "ğ" => "{\\u g}", + "Ġ" => "G", + "ġ" => "g", + "Ģ" => "G", + "ģ" => "g", + "Ĥ" => "Hh", + "ĥ" => "hh", + "Ħ" => "H", + "ħ" => "h", + "Ĩ" => "I", + "ĩ" => "i", + "Ī" => "I", + "ī" => "i", + "Ĭ" => "I", + "ĭ" => "i", + "Į" => "I", + "į" => "i", + "İ" => "{\\.I}", + "ı" => "{\\i}", + "IJ" => "IJ", + "ij" => "ij", + "Ĵ" => "Jh", + "ĵ" => "jh", + "Ķ" => "K", + "ķ" => "k", + "ĸ" => "k", + "Ĺ" => "{\\'L}", + "ĺ" => "{\\'l}", + "Ļ" => "L", + "ļ" => "l", + "Ľ" => "{\\v L}", + "ľ" => "{\\v l}", + "Ŀ" => "L·", + "ŀ" => "l·", + "Ł" => "{\\L}", + "ł" => "{\\l}", + "Ń" => "{\\'N}", + "ń" => "{\\'n}", + "Ņ" => "N", + "ņ" => "n", + "Ň" => "{\\v N}", + "ň" => "{\\v n}", + "ʼn" => "'n", + "Ŋ" => "{\\NG}", + "ŋ" => "{\\ng}", + "Ō" => "O", + "ō" => "o", + "Ŏ" => "O", + "ŏ" => "o", + "Ő" => "{\\H O}", + "ő" => "{\\H o}", + "Œ" => "{\\OE}", + "œ" => "{\\oe}", + "Ŕ" => "{\\'R}", + "ŕ" => "{\\'r}", + "Ŗ" => "R", + "ŗ" => "r", + "Ř" => "{\\v R}", + "ř" => "{\\v r}", + "Ś" => "{\\'S}", + "ś" => "{\\'s}", + "Ŝ" => "Sh", + "ŝ" => "sh", + "Ş" => "{\\c S}", + "ş" => "{\\c s}", + "Š" => "{\\v S}", + "š" => "{\\v s}", + "Ţ" => "{\\c T}", + "ţ" => "{\\c t}", + "Ť" => "{\\v T}", + "ť" => "{\\v t}", + "Ŧ" => "T", + "ŧ" => "t", + "Ũ" => "U", + "ũ" => "u", + "Ū" => "U", + "ū" => "u", + "Ŭ" => "U", + "ŭ" => "u", + "Ů" => "{\\r U}", + "ů" => "{\\r u}", + "Ű" => "{\\H U}", + "ű" => "{\\H u}", + "Ų" => "U", + "ų" => "u", + "Ŵ" => "W", + "ŵ" => "w", + "Ŷ" => "Y", + "ŷ" => "y", + "Ÿ" => "{\\\"Y}", + "Ź" => "{\\'Z}", + "ź" => "{\\'z}", + "Ż" => "{\\.Z}", + "ż" => "{\\.z}", + "Ž" => "{\\v Z}", + "ž" => "{\\v z}", + "ſ" => "s", + "ƒ" => "{\\textflorin}", + "Ș" => "S", + "ș" => "s", + "Ț" => "T", + "ț" => "t", + "ʹ" => "'", + "ʻ" => "'", + "ʼ" => "'", + "ʽ" => "'", + "ˆ" => "{\\textasciicircum}", + "ˈ" => "'", + "ˉ" => "-", + "ˌ" => ",", + "ː" => ":", + "˚" => "o", + "˜" => "\\~{}", + "˝" => "{\\textacutedbl}", + "ʹ" => "'", + "͵" => ",", + ";" => ";", + "Ḃ" => "B", + "ḃ" => "b", + "Ḋ" => "D", + "ḋ" => "d", + "Ḟ" => "F", + "ḟ" => "f", + "Ṁ" => "M", + "ṁ" => "m", + "Ṗ" => "P", + "ṗ" => "p", + "Ṡ" => "S", + "ṡ" => "s", + "Ṫ" => "T", + "ṫ" => "t", + "Ẁ" => "W", + "ẁ" => "w", + "Ẃ" => "W", + "ẃ" => "w", + "Ẅ" => "W", + "ẅ" => "w", + "Ỳ" => "Y", + "ỳ" => "y", + " " => " ", + " " => " ", + " " => " ", + " " => " ", + " " => " ", + " " => " ", + " " => " ", + " " => " ", + " " => " ", + " " => " ", + "‐" => "-", + "‑" => "-", + "‒" => "-", + "–" => "{\\textendash}", + "—" => "{\\textemdash}", + "―" => "--", + "‖" => "{\\textbardbl}", + "‗" => "{\\textunderscore}", + "‘" => "{\\textquoteleft}", + "’" => "{\\textquoteright}", + "‚" => "{\\quotesinglbase}", + "‛" => "'", + "“" => "{\\textquotedblleft}", + "”" => "{\\textquotedblright}", + "„" => "{\\quotedblbase}", + "‟" => "\"", + "†" => "{\\textdagger}", + "‡" => "{\\textdaggerdbl}", + "•" => "{\\textbullet}", + "‣" => ">", + "․" => ".", + "‥" => "..", + "…" => "{\\textellipsis}", + "‧" => "-", + " " => " ", + "‰" => "{\\textperthousand}", + "′" => "'", + "″" => "\"", + "‴" => "'''", + "‵" => "`", + "‶" => "``", + "‷" => "```", + "‹" => "{\\guilsinglleft}", + "›" => "{\\guilsinglright}", + "‼" => "!!", + "‾" => "-", + "⁃" => "-", + "⁄" => "{\\textfractionsolidus}", + "⁈" => "?!", + "⁉" => "!?", + "⁊" => "7", + "⁰" => '$^{0}$', + "⁴" => '$^{4}$', + "⁵" => '$^{5}$', + "⁶" => '$^{6}$', + "⁷" => '$^{7}$', + "⁸" => '$^{8}$', + "⁹" => '$^{9}$', + "⁺" => '$^{+}$', + "⁻" => '$^{-}$', + "⁼" => '$^{=}$', + "⁽" => '$^{(}$', + "⁾" => '$^{)}$', + "ⁿ" => '$^{n}$', + "₀" => '$_{0}$', + "₁" => '$_{1}$', + "₂" => '$_{2}$', + "₃" => '$_{3}$', + "₄" => '$_{4}$', + "₅" => '$_{5}$', + "₆" => '$_{6}$', + "₇" => '$_{7}$', + "₈" => '$_{8}$', + "₉" => '$_{9}$', + "₊" => '$_{+}$', + "₋" => '$_{-}$', + "₌" => '$_{=}$', + "₍" => '$_{(}$', + "₎" => '$_{)}$', + "€" => "{\\texteuro}", + "℀" => "a/c", + "℁" => "a/s", + "℃" => "{\\textcelsius}", + "℅" => "c/o", + "℆" => "c/u", + "℉" => "F", + "ℓ" => "l", + "№" => "{\\textnumero}", + "℗" => "{\\textcircledP}", + "℠" => "{\\textservicemark}", + "℡" => "TEL", + "™" => "{\\texttrademark}", + "Ω" => "{\\textohm}", + "K" => "K", + "Å" => "A", + "℮" => "{\\textestimated}", + "⅓" => " 1/3", + "⅔" => " 2/3", + "⅕" => " 1/5", + "⅖" => " 2/5", + "⅗" => " 3/5", + "⅘" => " 4/5", + "⅙" => " 1/6", + "⅚" => " 5/6", + "⅛" => " 1/8", + "⅜" => " 3/8", + "⅝" => " 5/8", + "⅞" => " 7/8", + "⅟" => " 1/", + "Ⅰ" => "I", + "Ⅱ" => "II", + "Ⅲ" => "III", + "Ⅳ" => "IV", + "Ⅴ" => "V", + "Ⅵ" => "VI", + "Ⅶ" => "VII", + "Ⅷ" => "VIII", + "Ⅸ" => "IX", + "Ⅹ" => "X", + "Ⅺ" => "XI", + "Ⅻ" => "XII", + "Ⅼ" => "L", + "Ⅽ" => "C", + "Ⅾ" => "D", + "Ⅿ" => "M", + "ⅰ" => "i", + "ⅱ" => "ii", + "ⅲ" => "iii", + "ⅳ" => "iv", + "ⅴ" => "v", + "ⅵ" => "vi", + "ⅶ" => "vii", + "ⅷ" => "viii", + "ⅸ" => "ix", + "ⅹ" => "x", + "ⅺ" => "xi", + "ⅻ" => "xii", + "ⅼ" => "l", + "ⅽ" => "c", + "ⅾ" => "d", + "ⅿ" => "m", + "←" => "{\\textleftarrow}", + "↑" => "{\\textuparrow}", + "→" => "{\\textrightarrow}", + "↓" => "{\\textdownarrow}", + "↔" => "<->", + "⇐" => "<=", + "⇒" => "=>", + "⇔" => "<=>", + "−" => "-", + "∕" => "/", + "∖" => "\\", + "∗" => "*", + "∘" => "o", + "∙" => ".", + "∞" => '$\\infty$', + "∣" => "|", + "∥" => "||", + "∶" => ":", + "∼" => "\\~{}", + "≠" => "/=", + "≡" => "=", + "≤" => "<=", + "≥" => ">=", + "≪" => "<<", + "≫" => ">>", + "⊕" => "(+)", + "⊖" => "(-)", + "⊗" => "(x)", + "⊘" => "(/)", + "⊢" => "|-", + "⊣" => "-|", + "⊦" => "|-", + "⊧" => "|=", + "⊨" => "|=", + "⊩" => "||-", + "⋅" => ".", + "⋆" => "*", + "⋕" => '$\\#$', + "⋘" => "<<<", + "⋙" => ">>>", + "⋯" => "...", + "〈" => "{\\textlangle}", + "〉" => "{\\textrangle}", + "␀" => "NUL", + "␁" => "SOH", + "␂" => "STX", + "␃" => "ETX", + "␄" => "EOT", + "␅" => "ENQ", + "␆" => "ACK", + "␇" => "BEL", + "␈" => "BS", + "␉" => "HT", + "␊" => "LF", + "␋" => "VT", + "␌" => "FF", + "␍" => "CR", + "␎" => "SO", + "␏" => "SI", + "␐" => "DLE", + "␑" => "DC1", + "␒" => "DC2", + "␓" => "DC3", + "␔" => "DC4", + "␕" => "NAK", + "␖" => "SYN", + "␗" => "ETB", + "␘" => "CAN", + "␙" => "EM", + "␚" => "SUB", + "␛" => "ESC", + "␜" => "FS", + "␝" => "GS", + "␞" => "RS", + "␟" => "US", + "␠" => "SP", + "␡" => "DEL", + "␣" => "{\\textvisiblespace}", + "␤" => "NL", + "␥" => "///", + "␦" => "?", + "①" => "(1)", + "②" => "(2)", + "③" => "(3)", + "④" => "(4)", + "⑤" => "(5)", + "⑥" => "(6)", + "⑦" => "(7)", + "⑧" => "(8)", + "⑨" => "(9)", + "⑩" => "(10)", + "⑪" => "(11)", + "⑫" => "(12)", + "⑬" => "(13)", + "⑭" => "(14)", + "⑮" => "(15)", + "⑯" => "(16)", + "⑰" => "(17)", + "⑱" => "(18)", + "⑲" => "(19)", + "⑳" => "(20)", + "⑴" => "(1)", + "⑵" => "(2)", + "⑶" => "(3)", + "⑷" => "(4)", + "⑸" => "(5)", + "⑹" => "(6)", + "⑺" => "(7)", + "⑻" => "(8)", + "⑼" => "(9)", + "⑽" => "(10)", + "⑾" => "(11)", + "⑿" => "(12)", + "⒀" => "(13)", + "⒁" => "(14)", + "⒂" => "(15)", + "⒃" => "(16)", + "⒄" => "(17)", + "⒅" => "(18)", + "⒆" => "(19)", + "⒇" => "(20)", + "⒈" => "1.", + "⒉" => "2.", + "⒊" => "3.", + "⒋" => "4.", + "⒌" => "5.", + "⒍" => "6.", + "⒎" => "7.", + "⒏" => "8.", + "⒐" => "9.", + "⒑" => "10.", + "⒒" => "11.", + "⒓" => "12.", + "⒔" => "13.", + "⒕" => "14.", + "⒖" => "15.", + "⒗" => "16.", + "⒘" => "17.", + "⒙" => "18.", + "⒚" => "19.", + "⒛" => "20.", + "⒜" => "(a)", + "⒝" => "(b)", + "⒞" => "(c)", + "⒟" => "(d)", + "⒠" => "(e)", + "⒡" => "(f)", + "⒢" => "(g)", + "⒣" => "(h)", + "⒤" => "(i)", + "⒥" => "(j)", + "⒦" => "(k)", + "⒧" => "(l)", + "⒨" => "(m)", + "⒩" => "(n)", + "⒪" => "(o)", + "⒫" => "(p)", + "⒬" => "(q)", + "⒭" => "(r)", + "⒮" => "(s)", + "⒯" => "(t)", + "⒰" => "(u)", + "⒱" => "(v)", + "⒲" => "(w)", + "⒳" => "(x)", + "⒴" => "(y)", + "⒵" => "(z)", + "Ⓐ" => "(A)", + "Ⓑ" => "(B)", + "Ⓒ" => "(C)", + "Ⓓ" => "(D)", + "Ⓔ" => "(E)", + "Ⓕ" => "(F)", + "Ⓖ" => "(G)", + "Ⓗ" => "(H)", + "Ⓘ" => "(I)", + "Ⓙ" => "(J)", + "Ⓚ" => "(K)", + "Ⓛ" => "(L)", + "Ⓜ" => "(M)", + "Ⓝ" => "(N)", + "Ⓞ" => "(O)", + "Ⓟ" => "(P)", + "Ⓠ" => "(Q)", + "Ⓡ" => "(R)", + "Ⓢ" => "(S)", + "Ⓣ" => "(T)", + "Ⓤ" => "(U)", + "Ⓥ" => "(V)", + "Ⓦ" => "(W)", + "Ⓧ" => "(X)", + "Ⓨ" => "(Y)", + "Ⓩ" => "(Z)", + "ⓐ" => "(a)", + "ⓑ" => "(b)", + "ⓒ" => "(c)", + "ⓓ" => "(d)", + "ⓔ" => "(e)", + "ⓕ" => "(f)", + "ⓖ" => "(g)", + "ⓗ" => "(h)", + "ⓘ" => "(i)", + "ⓙ" => "(j)", + "ⓚ" => "(k)", + "ⓛ" => "(l)", + "ⓜ" => "(m)", + "ⓝ" => "(n)", + "ⓞ" => "(o)", + "ⓟ" => "(p)", + "ⓠ" => "(q)", + "ⓡ" => "(r)", + "ⓢ" => "(s)", + "ⓣ" => "(t)", + "ⓤ" => "(u)", + "ⓥ" => "(v)", + "ⓦ" => "(w)", + "ⓧ" => "(x)", + "ⓨ" => "(y)", + "ⓩ" => "(z)", + "⓪" => "(0)", + "─" => "-", + "━" => "=", + "│" => "|", + "┃" => "|", + "┄" => "-", + "┅" => "=", + "┆" => "|", + "┇" => "|", + "┈" => "-", + "┉" => "=", + "┊" => "|", + "┋" => "|", + "┌" => "+", + "┍" => "+", + "┎" => "+", + "┏" => "+", + "┐" => "+", + "┑" => "+", + "┒" => "+", + "┓" => "+", + "└" => "+", + "┕" => "+", + "┖" => "+", + "┗" => "+", + "┘" => "+", + "┙" => "+", + "┚" => "+", + "┛" => "+", + "├" => "+", + "┝" => "+", + "┞" => "+", + "┟" => "+", + "┠" => "+", + "┡" => "+", + "┢" => "+", + "┣" => "+", + "┤" => "+", + "┥" => "+", + "┦" => "+", + "┧" => "+", + "┨" => "+", + "┩" => "+", + "┪" => "+", + "┫" => "+", + "┬" => "+", + "┭" => "+", + "┮" => "+", + "┯" => "+", + "┰" => "+", + "┱" => "+", + "┲" => "+", + "┳" => "+", + "┴" => "+", + "┵" => "+", + "┶" => "+", + "┷" => "+", + "┸" => "+", + "┹" => "+", + "┺" => "+", + "┻" => "+", + "┼" => "+", + "┽" => "+", + "┾" => "+", + "┿" => "+", + "╀" => "+", + "╁" => "+", + "╂" => "+", + "╃" => "+", + "╄" => "+", + "╅" => "+", + "╆" => "+", + "╇" => "+", + "╈" => "+", + "╉" => "+", + "╊" => "+", + "╋" => "+", + "╌" => "-", + "╍" => "=", + "╎" => "|", + "╏" => "|", + "═" => "=", + "║" => "|", + "╒" => "+", + "╓" => "+", + "╔" => "+", + "╕" => "+", + "╖" => "+", + "╗" => "+", + "╘" => "+", + "╙" => "+", + "╚" => "+", + "╛" => "+", + "╜" => "+", + "╝" => "+", + "╞" => "+", + "╟" => "+", + "╠" => "+", + "╡" => "+", + "╢" => "+", + "╣" => "+", + "╤" => "+", + "╥" => "+", + "╦" => "+", + "╧" => "+", + "╨" => "+", + "╩" => "+", + "╪" => "+", + "╫" => "+", + "╬" => "+", + "╭" => "+", + "╮" => "+", + "╯" => "+", + "╰" => "+", + "╱" => "/", + "╲" => "\\", + "╳" => "X", + "╼" => "-", + "╽" => "|", + "╾" => "-", + "╿" => "|", + "○" => "o", + "◦" => "{\\textopenbullet}", + "★" => "*", + "☆" => "*", + "☒" => "X", + "☓" => "X", + "☹" => ":-(", + "☺" => ":-)", + "☻" => "(-:", + "♭" => "b", + "♯" => '$\\#$', + "✁" => '$\\%<$', + "✂" => '$\\%<$', + "✃" => '$\\%<$', + "✄" => '$\\%<$', + "✌" => "V", + "✓" => "v", + "✔" => "V", + "✕" => "x", + "✖" => "x", + "✗" => "X", + "✘" => "X", + "✙" => "+", + "✚" => "+", + "✛" => "+", + "✜" => "+", + "✝" => "+", + "✞" => "+", + "✟" => "+", + "✠" => "+", + "✡" => "*", + "✢" => "+", + "✣" => "+", + "✤" => "+", + "✥" => "+", + "✦" => "+", + "✧" => "+", + "✩" => "*", + "✪" => "*", + "✫" => "*", + "✬" => "*", + "✭" => "*", + "✮" => "*", + "✯" => "*", + "✰" => "*", + "✱" => "*", + "✲" => "*", + "✳" => "*", + "✴" => "*", + "✵" => "*", + "✶" => "*", + "✷" => "*", + "✸" => "*", + "✹" => "*", + "✺" => "*", + "✻" => "*", + "✼" => "*", + "✽" => "*", + "✾" => "*", + "✿" => "*", + "❀" => "*", + "❁" => "*", + "❂" => "*", + "❃" => "*", + "❄" => "*", + "❅" => "*", + "❆" => "*", + "❇" => "*", + "❈" => "*", + "❉" => "*", + "❊" => "*", + "❋" => "*", + "ff" => "ff", + "fi" => "fi", + "fl" => "fl", + "ffi" => "ffi", + "ffl" => "ffl", + "ſt" => "st", + "st" => "st" + ); + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/crossref/biblio.crossref.client.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/crossref/biblio.crossref.client.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,326 @@ +setDOI($doi); + $this->setUserID($id); + $this->setURL(self::BASE_URL); + $this->field_map = array(); + $this->type_map = array(); + } + + public function setURL($url) { + $this->url = $url; + } + + public function setDOI($doi) { + $this->doi = $doi; + } + + public function getDOI() { + return $this->doi; + } + + public function setUserID($id) { + $this->pid = $id; + } + + public function getUserID() { + return $this->pid; + } + + public function getQuery() { + return $this->query; + } + + public function fetch() { + $this->query = $this->url . '?pid=' . $this->pid . '&noredirect=true&format=unixref&id=doi%3A' . $this->doi; + + $request_options = array('method' => 'POST'); + $result = drupal_http_request($this->query, $request_options); + + if ($result->code != 200) { + drupal_set_message(t('HTTP error: !error when trying to contact crossref.org for XML input', array('!error' => $result->code)),'error'); + return; + } + if (empty($result->data)) { + drupal_set_message(t('Did not get any data from crossref.org'),'error'); + return; + } + $sxml = @simplexml_load_string($result->data); + if (!isset($sxml->doi_record)) { + drupal_set_message(t('Failed to retrieve data for doi ') . $this->doi, 'error'); + return; + } + + if ($error = (string)$sxml->doi_record->crossref->error) { + drupal_set_message($error,'error'); + return; + } + $this->nodes = array(); + $this->parser = drupal_xml_parser_create($result->data); + // use case-folding so we are sure to find the tag in + xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, FALSE); + xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, TRUE); + + xml_set_object($this->parser, $this); + xml_set_element_handler($this->parser, 'unixref_startElement', 'unixref_endElement'); + xml_set_character_data_handler($this->parser, 'unixref_characterData'); + + if(!xml_parse($this->parser, $result->data)){ + drupal_set_message(sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($this->parser)), + xml_get_current_line_number($this->parser)),'error'); + } + + xml_parser_free($this->parser); + + return $this->node; + } + + + function unixref_startElement($parser, $name, $attrs) { + switch ($name) { + case 'doi_record' : + $this->node = array(); + $this->node['biblio_contributors'] = array(); + $this->contributors = array(); + $this->element = $name; + break; + case 'book': + case 'journal': + case 'standard': + case 'conference': + case 'report-paper': + case 'dissertation': + case 'database': + case 'sa_component': + $this->node['biblio_type'] = $this->_unixref_type_map($name); + $this->element = $name; + break; + case 'journal_article': + case 'conference_paper': + case 'content_item': + case 'report-paper_metadata': + case 'standard_metadata': + case 'database_date': + case 'component': + $this->node['year'] = ''; + $this->node['doi'] = ''; + $this->element = $name; + break; + case 'person_name' : + $this->auth_category = $this->_unixref_get_contributor_category($attrs['contributor_role']); + if (!isset($this->contrib_count)) $this->contrib_count = 0; + $this->element = $name; + break; + case 'organization': + if (!isset($this->org_count)) $this->org_count = 0; + $this->element = $name; + break; + case 'issn': + if (isset($attrs['media_type']) ) $this->attribute = $attrs['media_type']; + $this->element = $name; + break; + case 'isbn': + if (isset($attrs['media_type']) ) $this->attribute = $attrs['media_type']; + $this->element = $name; + break; + case 'i': // HTML font style tags + case 'b': + case 'u': + case 'sub': + case 'sup': + $this->unixref_characterData(NULL, ' <' . $name . '>'); + break; + case 'doi_data': + $this->doi_data = TRUE; + break; + default : + $this->element = $name; + } + + } + + function unixref_decode(&$item, $key) { + $item = html_entity_decode($item, NULL, 'UTF-8'); + } + + function unixref_endElement($parser, $name) { + switch ($name) { + case 'doi_record' : + $this->node['biblio_contributors'] += $this->contributors; + array_walk_recursive($this->node, array($this,'unixref_decode') ); + $this->node['biblio_crossref_id'] = $this->getDOI(); + $this->node['biblio_crossref_md5'] = md5(serialize($this->node)); + $this->nodes[] = $this->node; //biblio_save_node($node, $batch, $session_id, $save_node); + break; + case 'person_name' : + $this->contributors[$this->contrib_count]['auth_type'] = _biblio_get_auth_type($this->auth_category, $this->node['biblio_type']); + $this->contributors[$this->contrib_count]['auth_category'] = $this->auth_category; + $this->contributors[$this->contrib_count]['name'] = + $this->contributors[$this->contrib_count]['lastname']; + if (isset($this->contributors[$this->contrib_count]['firstname'])) { + $this->contributors[$this->contrib_count]['name'] .= + ', ' . $this->contributors[$this->contrib_count]['firstname']; + } + + $this->auth_category = ''; + $this->contrib_count++; + break; + case 'organization' : + $this->contributors[$this->contrib_count]['auth_type'] = _biblio_get_auth_type(5, $this->node['biblio_type']); + $this->contributors[$this->contrib_count]['auth_category'] = 5; + $this->contrib_count++; + break; + case 'pages': + if (isset($this->node['biblio_first_page'])) $this->node['biblio_pages'] = $this->node['biblio_first_page']; + if (isset($this->node{'biblio_last_page'})) $this->node['biblio_pages'] .= ' - ' . $this->node['biblio_last_page']; + break; + case 'publication_date': + + break; + case 'journal_issue': + case 'journal_article': + if (!isset($this->node['biblio_date']) || empty($this->node['biblio_date'])) { + $day = !empty($this->node['day']) ? $this->node['day'] : 1; + $month = !empty($this->node['month']) ? $this->node['month'] : 1; + $year = !empty($this->node['year']) ? $this->node['year'] : 0; + if ($year) { + $this->node['biblio_date'] = date("M-d-Y", mktime(0, 0, 0, $day, $month, $year)); + } + } + if ((!isset($this->node['biblio_year']) || empty($this->node['biblio_year'])) && isset($this->node['year'])) { + $this->node['biblio_year'] = $this->node['year']; + } + break; + case 'conference_paper': + case 'content_item': + case 'report-paper_metadata': + case 'standard_metadata': + case 'database_date': + case 'component': + if ((!isset($this->node['biblio_year']) || empty($this->node['biblio_year'])) && isset($this->node['year'])) { + $this->node['biblio_year'] = $this->node['year']; + unset($this->node['year']); + } +// $this->node['biblio_doi'] = $this->node['doi']; + break; + case 'issn': + case 'isbn': + $this->attribute = ''; + break; + case 'i': // HTML font style tags + case 'b': + case 'u': + case 'sub': + case 'sup': + $this->unixref_characterData(NULL, ' '); + break; + case 'doi_data': + $this->doi_data = FALSE; + break; + default : + } + } + + function unixref_characterData($parser, $data) { + $data = htmlspecialchars_decode($data); + if (trim($data)) { + switch ($this->element) { + case 'surname' : + $this->contributors[$this->contrib_count]['lastname'] = $data; + break; + case 'given_name' : + $this->contributors[$this->contrib_count]['firstname'] = $data; + break; + case 'suffix': + $this->contributors[$this->contrib_count]['suffix'] = $data; + break; + case 'affiliation' : + $this->contributors[$this->contrib_count]['affiliation'] = $data; + break; + case 'organization': + $this->contributors[$this->contrib_count]['name'] = $data; + break; + case 'year': + case 'month': + case 'day': + $this->node[$this->element] = $data; + break; + case 'issn': + case 'isbn': + if ($this->attribute == 'print') { + if ($field = $this->_unixref_field_map(trim($this->element))) { + $this->_set_data($field, $data); + } + } + break; + case 'doi': + if ($this->doi_data) { + if ($field = $this->_unixref_field_map(trim($this->element))) { + $this->_set_data($field, $data); + } + } + break; + case 'resource': + if ($this->doi_data) { + $this->_set_data('biblio_url', $data); + } + break; + + default: + if ($field = $this->_unixref_field_map(trim($this->element))) { + $this->_set_data($field, $data); + } + + } + } + } + function _set_data($field, $data) { + $this->node[$field] = (isset($this->node[$field]) ? $this->node[$field] . $data : $data); + } + /* + * map a unixref XML field to a biblio field + */ + function _unixref_field_map($field) { + if (empty($this->field_map)) { + $this->field_map = unserialize(db_query("SELECT field_map FROM {biblio_type_maps} WHERE format='crossref'")->fetchField()); + } + return (isset($this->field_map[$field])) ? $this->field_map[$field]: FALSE; + } + + function _unixref_type_map($type) { + if (empty($this->type_map)) { + $this->type_map = unserialize(db_query("SELECT type_map FROM {biblio_type_maps} WHERE format='crossref'")->fetchField()); + } + return (isset($this->type_map[$type]))?$this->type_map[$type]:129; //return the biblio type or 129 (Misc) if type not found + } + + function _unixref_get_contributor_category($role) { + if ($role == 'author') return 1; + if ($role == 'editor') return 2; + if ($role == 'chair') return 3; + if ($role == 'translator') return 4; + return NULL; + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/crossref/biblio_crossref.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/crossref/biblio_crossref.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Biblio - Crossref +description = Provides DOI lookup and import to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = biblio.crossref.client.php + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/crossref/biblio_crossref.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/crossref/biblio_crossref.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,151 @@ +condition('format', 'crossref') + ->execute(); + } +} + +function biblio_crossref_enable() { + biblio_crossref_set_system_weight(); +} + +function biblio_crossref_set_system_weight() { + db_update('system') + ->fields(array('weight' => 20)) + ->condition('name', 'biblio_crossref') + ->execute(); +} +function _save_crossref_maps() { + $typemap = _get_crossref_type_map(); + $typenames = _get_crossref_type_names(); + $fieldmap = _get_crossref_field_map(); + $maps = array_merge($typemap, $typenames, $fieldmap); + biblio_save_map($maps); +} +function _reset_crossref_map($type) { + $count = db_query("SELECT COUNT(*) FROM {biblio_type_maps} WHERE format='crossref'")->fetchField(); + if ($count && $type) { //update + $function = '_get_crossref_' . $type; + if (!function_exists($function)) return; + $map = $function(); + db_update('biblio_type_maps') + ->fields($map) + ->condition('format', 'crossref') + ->execute(); + } + else { // install +// db_query("DELETE FROM {biblio_type_maps} WHERE format='crossref'"); + db_delete('biblio_type_maps') + ->condition('format', 'crossref') + ->execute(); + _save_crossref_maps(); + } +} + +function _get_crossref_type_map() { + $map['type_map'] = serialize( + array( + 'error' => 0, + 'book' => 100, // Book + 'journal' => 102, // Journal Article + 'standard' => 129, // Generic + 'conference' => 103, // conference_paper + 'report-paper' => 109, // Report + 'dissertation' => 108, // Thesis + 'database' => 125, // online database + 'sa_component' => 129 + ) + ); + $map['format'] = 'crossref'; + return $map; +} + +function _get_crossref_type_names() { + $map['type_names'] = serialize( + array( + 'error' => 'Error', + 'book' => 'Book', + 'journal' => 'Journal Article', + 'standard' => 'Generic', + 'conference' => 'Conference Paper', + 'report-paper' => 'Report', + 'dissertation' => 'Thesis', + 'database' => 'Online database', + 'sa_component' => 'SA Component', + ) + ); + $map['format'] = 'crossref'; + return $map; +} + +function _get_crossref_field_map() { + $map['field_map'] = serialize( + array( + 'publisher_place' => 'biblio_place_published', + 'publisher_name' => 'biblio_publisher', + 'volume' => 'biblio_volume', + 'number' => 'biblio_number', + 'issue' => 'biblio_issue', + 'edition_number' => 'biblio_edition', + 'section' => 'biblio_section', + 'doi' => 'biblio_doi', + 'title' => 'title', + 'isbn' => 'biblio_isbn', + 'issn' => 'biblio_issn', + 'first_page' => 'biblio_first_page', + 'last_page' => 'biblio_last_page', + // Journal metadata + 'full_title' => 'biblio_secondary_title', + 'abbrev_title' => 'biblio_short_title', + // Conference metadata + 'conference_location' => 'biblio_place_published', + 'conference_name' => 'biblio_secondary_title', + 'conference_acronym' => 'biblio_short_title', + // Proceedings metadata + 'proceedings_title' => 'biblio_secondary_title', + 'year' => 'year', + 'month' => 'month', + 'day' => 'day', + 'degree' => 'biblio_type_of_work', + 'error' => 'error', + 'language' => 'biblio_lang', + ) + ); + + $map['format'] = 'crossref'; + return $map; + +} + +/** + * Implementation of hook_schema(). + * + * Note: Pro Drupal Development models use of t() to translate 'description' + * for field definitions, but Drupal core does not use them. We follow core. + */ +function biblio_crossref_schema() { + $schema = array(); + $schema['biblio_crossref'] = array( + 'fields' => array( + 'nid' => array('type' => 'int', 'not null' => TRUE), + 'biblio_crossref_md5' => array('type' => 'char', 'length' => 32, 'not null' => TRUE), + 'biblio_crossref_id' => array('type' => 'char', 'length' => 255, 'not null' => TRUE), + ), + 'primary key' => array('nid'), + ); + return $schema; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/crossref/biblio_crossref.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/crossref/biblio_crossref.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,168 @@ + 5 && !$form_state['submitted'] && !isset($form['#node']->nid)) { + $form['biblio_doi_lookup'] = array( + '#type' => 'fieldset', + '#title' => t('DOI Lookup'), + '#weight' => -20, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + $have_pid = variable_get('biblio_crossref_pid', ''); + $user_pid = (isset($user->data['biblio_crossref_pid']) && !empty($user->data['biblio_crossref_pid'])) ? $user->data['biblio_crossref_pid'] : ''; + if (variable_get('biblio_show_crossref_profile_form', '1') && !empty($user_pid)) { + $have_pid = $user_pid; + } + if (empty($have_pid)) { + $form['biblio_doi_lookup']['doi_register'] = array( + '#prefix' => '

    ', + '#suffix' => '

    ', + '#markup' => t('!url1 and then enter your CrossRef UserID in the "CrossRef Login Information" section of your account profile !url2', array('!url1' => l(t('You need to register with CrossRef'), 'http://www.crossref.org/requestaccount/', array('attributes' => array('target' => '_blank'), 'absolue' => TRUE)), '!url2' => l(t('here...'), "user/$user->uid/edit"))), + ); + } + + $form['biblio_doi_lookup']['doi_data'] = array( + '#type' => 'textfield', + '#title' => t('DOI'), + '#required' => FALSE, + '#default_value' => (isset($form_state['values']['doi_data']) ? $form_state['values']['doi_data'] : ''), + '#description' => t('Enter a DOI name in the form: 10.1000/123456'), + '#disabled' => empty($have_pid), + '#size' => 60, + '#maxlength' => 255, + '#weight' => -4 + ); + $form['biblio_doi_lookup']['doi_submit'] = array( + '#type' => 'submit', + '#disabled' => empty($have_pid), + '#value' => t('Populate using DOI'), + '#submit' => array('biblio_crossref_form_biblio_node_form_submit') + ); + } + $biblio_crossref_id = (isset($form_state['values']['biblio_crossref_id'])) ? $form_state['values']['biblio_crossref_id'] : ''; + $biblio_crossref_md5 = (isset($form_state['values']['biblio_crossref_md5'])) ? $form_state['values']['biblio_crossref_md5'] : ''; + $form['biblio_crossref_id'] = array('#type' => 'value', '#value' => $biblio_crossref_id); + $form['biblio_crossref_md5'] = array('#type' => 'value', '#value' => $biblio_crossref_md5); +} + +function biblio_crossref_form_biblio_node_form_submit($form, &$form_state) { + global $user; + $node_data = array(); + if (strlen($doi = $form_state['values']['doi_data'])) { + if (($doi_start = strpos($form_state['values']['doi_data'], '10.')) !== FALSE) { + if (!($dup = biblio_crossref_check_doi($doi))) { + $crossref_pid = variable_get('biblio_crossref_pid', ''); + $user_pid = (isset($user->data['biblio_crossref_pid']) && !empty($user->data['biblio_crossref_pid'])) ? $user->data['biblio_crossref_pid'] : ''; + if (variable_get('biblio_show_crossref_profile_form', '1') && !empty($user_pid)) { + $crossref_pid = $user_pid; + } + + if (empty($crossref_pid)) { + form_set_error('doi_data', l(t('You need to register with CrossRef'), 'http://www.crossref.org/requestaccount/', array('attributes' => array('target' => '_blank'), 'absolue' => TRUE)) . ' ' . t('and then enter your CrossRef UserID in the "CrossRef Login Information" section of your account profile !url', array('!url' => l(t('here...'), "user/$user->uid/edit")))); + return; + } + + module_load_include('php', 'biblio_crossref', 'biblio.crossref.client'); + $client = new BiblioCrossRefClient($doi, $crossref_pid); + $node_data = $client->fetch(); + + if (!empty($node_data)) { + $form_state['values'] = array_merge($form_state['values'], $node_data); + $form_state['input']['biblio_type'] = $form_state['biblio_type'] = $node_data['biblio_type']; + } + else { + form_set_error('doi_data', ''); + } + } + else { + $message = t('The DOI that you are trying to import already exists in the database, see !url', array('!url' => l('node/' . $dup, 'node/' . $dup))); + form_set_error('doi_data', $message); + } + } + else { + form_set_error('doi_data', t('This does not appear to be a valid DOI name, it should start with "10."')); + } + } + $form_state['rebuild'] = TRUE; + return; +} + +function biblio_crossref_check_doi($doi) { + return db_query("SELECT nid FROM {biblio_crossref} WHERE biblio_crossref_id = :doi", array(':doi' => $doi))->fetchField(); +} + +function biblio_crossref_biblio_lookup_link_settings() { + return array('crossref' => t('DOI')); +} +function biblio_crossref_biblio_mapper_options() { + return array( + 'crossref' => array( + 'title' => t('CrossRef XML'), + 'export' => FALSE, + ), + ); +} + +function biblio_crossref_biblio_lookup_link($node) { + $show_link = variable_get('biblio_lookup_links', array('crossref' => TRUE)); + if (empty($show_link['crossref']) || !isset($node) || (!isset($node->biblio_crossref_id) && empty($node->biblio_doi))) { + return; + } + if ($node->type == 'biblio') { + $doi = isset($node->biblio_crossref_id) ? $node->biblio_crossref_id : $node->biblio_doi; + if ( ($doi_start = strpos($doi, '10.')) !== FALSE) { + $doi = substr($doi, $doi_start); + } + $link = 'http://dx.doi.org/' . $doi; + return array('biblio_crossref' => array( + 'title' => t('DOI'), + 'href' => $link, + 'attributes' => array('title' => t("Click to view the CrossRef listing for this node")), + )); + } + return ; + } + + +function biblio_crossref_node_view($node, $view_mode, $langcode) { + if ($node->type == 'biblio' && (isset($node->biblio_crossref_id) || !empty($node->biblio_doi))) { + switch ($view_mode) { + case 'full': + case 'teaser': + $node->content['links']['biblio_crossref'] = array( + '#links' => biblio_crossref_biblio_lookup_link($node), + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} + +function biblio_crossref_node_delete($node) { + if ($node->type != 'biblio') return; + db_delete('biblio_crossref') + ->condition('nid', $node->nid) + ->execute(); +} + +function biblio_crossref_node_insert($node) { + if ($node->type != 'biblio') return; + if (empty($node->biblio_crossref_id)) return; + drupal_write_record('biblio_crossref', $node); +} + +function biblio_crossref_node_load($nodes, $types) { + $result = db_query('SELECT nid, biblio_crossref_id FROM {biblio_crossref} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes))); + foreach ($result as $record) { + $nodes[$record->nid]->biblio_crossref_id = $record->biblio_crossref_id; + } +} +function biblio_crossref_crossref_map_reset($type = NULL) { + module_load_include('install', 'biblio_crossref', 'biblio_crossref'); + _reset_crossref_map($type); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/biblio_tagged.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/biblio_tagged.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Biblio - EndNote Tagged +description = Provides EndNote tagged file import and export to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = views/biblio_handler_field_export_link_tagged.inc + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/biblio_tagged.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/biblio_tagged.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,196 @@ +condition('format', 'tagged') + ->execute(); + } +} + +function biblio_tagged_enable() { + biblio_tagged_set_system_weight(); +} + +function biblio_tagged_set_system_weight() { + db_update('system') + ->fields(array('weight' => 22)) + ->condition('name', 'biblio_tagged') + ->execute(); +} + +function _get_tagged_type_map() { + $map['type_map'] = serialize( + array( + "Journal Article" => 102, + "Conference Paper" => 103, + "Conference Proceedings" => 104, + "Report" => 109, + "Book" => 100, + "Edited Book" => 100, + "Book Section" => 101, + "Thesis" => 108, + "Patent" => 119, + "Generic" => 129, + "Newspaper Article" => 105, + "Magazine Article" => 106, + "Web Page" => 107, + "Film or Broadcast" => 110, + "Artwork" => 112, + "Audiovisual Material" => 114, + "Hearing" => 115, + "Case" => 116, + "Bill" => 117, + "Statute" => 118, + "Personal Communication" => 120, + "Manuscript" => 121, + "Map" => 122, + "Chart or Table" => 123, + "Unpublished Work" => 124, + "Online Database" => 125, + "Government Document" => 126, + "Classical Work" => 127, + "Legal Rule or Regulation" => 128, + ) + ); + $map['format'] = 'tagged'; + return $map; +} +function _get_tagged_type_names() { + $map['type_names'] = serialize( + array( + "Journal Article" => "Journal Article", + "Conference Paper" => "Conference Paper", + "Conference Proceedings" => "Conference Proceedings", + "Report" => "Report", + "Book" => "Book", + "Edited Book" => "Edited Book", + "Book Section" => "Book Section", + "Thesis" => "Thesis", + "Patent" => "Patent", + "Generic" => "Generic", + "Newspaper Article" => "Newspaper Article", + "Magazine Article" => "Magazine Article", + "Web Page" => "Web Page", + "Film or Broadcast" => "Film or Broadcast", + "Artwork" => "Artwork", + "Audiovisual Material" => "Audiovisual Material", + "Hearing" => "Hearing", + "Case" => "Case", + "Bill" => "Bill", + "Statute" => "Statute", + "Personal Communication" => "Personal Communication", + "Manuscript" => "Manuscript", + "Map" => "Map", + "Chart or Table" => "Chart or Table", + "Unpublished Work" => "Unpublished Work", + "Online Database" => "Online Database", + "Government Document" => "Government Document", + "Classical Work" => "Classical Work", + "Legal Rule or Regulation" => "Legal Rule or Regulation", + ) + ); + + $map['format'] = 'tagged'; + return $map; +} + +function _get_tagged_field_map() { + $map['field_map'] = serialize( + array( + '%B' => 'biblio_secondary_title', + '%C' => 'biblio_place_published', + '%D' => 'biblio_year', + '%F' => 'biblio_label', + '%G' => 'biblio_lang', + '%I' => 'biblio_publisher', + '%J' => 'biblio_secondary_title', + '%K' => 'biblio_keywords', + '%L' => 'biblio_call_number', + '%M' => 'biblio_accession_number', + '%N' => 'biblio_issue', + '%P' => 'biblio_pages', + '%R' => 'biblio_doi', + '%S' => 'biblio_tertiary_title', + '%U' => 'biblio_url', + '%V' => 'biblio_volume', + '%1' => 'biblio_custom1', + '%2' => 'biblio_custom2', + '%3' => 'biblio_custom3', + '%4' => 'biblio_custom4', + '%#' => 'biblio_custom5', + '%$' => 'biblio_custom6', + '%]' => 'biblio_custom7', + '%6' => 'biblio_number_of_volumes', + '%7' => 'biblio_edition', + '%8' => 'biblio_date', + '%9' => 'biblio_type_of_work', + '%?' => '', + '%@' => 'biblio_isbn', + '%<' => 'biblio_research_notes', + '%!' => 'biblio_short_title', + '%&' => 'biblio_section', + '%(' => 'biblio_original_publication', + '%)' => 'biblio_reprint_edition', + '%*' => '', + '%+' => '', + ) + ); + $map['format'] = 'tagged'; + return $map; +} + +function _save_tagged_maps() { + $typemap = _get_tagged_type_map(); + $typenames = _get_tagged_type_names(); + $fieldmap = _get_tagged_field_map(); + $maps = array_merge($typemap, $typenames, $fieldmap); + biblio_save_map($maps); +} + +function _reset_tagged_map($type) { + $count = db_query("SELECT COUNT(*) FROM {biblio_type_maps} WHERE format='tagged'")->fetchField(); + if ($count && $type) { //update + $function = '_get_tagged_' . $type; + if (!function_exists($function)) return; + $map = $function(); + db_update('biblio_type_maps') + ->fields($map) + ->condition('format', 'tagged') + ->execute(); + } + else { // install + db_delete('biblio_type_maps') + ->condition('format', 'tagged') + ->execute(); + _save_tagged_maps(); + } +} +/** + * Implementation of hook_schema(). + * + * Note: Pro Drupal Development models use of t() to translate 'description' + * for field definitions, but Drupal core does not use them. We follow core. + */ +function biblio_tagged_schema() { + $schema = array(); + $schema['biblio_tagged'] = array( + 'fields' => array( + 'nid' => array('type' => 'int', 'not null' => TRUE), + 'biblio_tagged_md5' => array('type' => 'char', 'length' => 32, 'not null' => TRUE), + ), + 'primary key' => array('nid'), + ); + return $schema; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/biblio_tagged.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/biblio_tagged.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,407 @@ + 2, + 'path' => drupal_get_path('module', 'biblio_tagged') . '/views', + ); +} +/* + * add the tagged option to the option list of the biblio_import_form + * the key is the module name use by module_invoke to call hook_biblio_import + * module_invoke('biblio_tagged', 'biblio_import',...) + */ +function biblio_tagged_biblio_import_options() { + return array('biblio_tagged' => t('EndNote Tagged')); +} +function biblio_tagged_biblio_mapper_options() { + return array( + 'tagged' => array( + 'title' => t('EndNote Tagged'), + 'export' => TRUE, + ) + ); +} + +function biblio_tagged_biblio_export_options() { + return array('tagged' => t('EndNote Tagged')); +} + +function biblio_tagged_node_view($node, $view_mode) { + if ($node->type == 'biblio') { + switch ($view_mode) { + case 'full': + case 'teaser': + $links = biblio_tagged_biblio_export_link($node->nid); + $node->content['links']['biblio_tagged'] = array( + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} + +/** + * Creates a link to export a node (or view) in tagged format + * + * @param $base this is the base url (defaults to /biblio) + * @param $nid the node id, if NULL then the current view is exported + * @return a link (tagged) + */ +function biblio_tagged_biblio_export_link($nid = NULL, $filter = array()) { + $show_link = variable_get('biblio_export_links', array('tagged' => TRUE)); + if (!isset($show_link['tagged']) || empty($show_link['tagged']) || !biblio_access('export')) { + return array(); + } + $base = variable_get('biblio_base', 'biblio'); + + if (module_exists('popups') && !empty($nid)) { + $link = array( + 'attributes' => array( + 'class' => 'popups', + 'title' => t("Click to get the EndNote Tagged output"))); + } + else { + $link = array( + 'attributes' => array( + 'title' => t("Click to download the EndNote Tagged formatted file"))); + } + + $link['attributes'] += array('rel' => 'nofollow'); + + $link['href'] = "$base/export/tagged"; + if (!empty($nid)) { + $link['href'] .= '/' . $nid; + } + $link['title'] = t('Tagged'); + + if (empty($nid) && !empty($filter)) { // add any filters which may be on the current page + $link['query'] = $filter; + } + + return array('biblio_tagged' => $link); +} + + +function biblio_tagged_node_delete($node) { + if ($node->type != 'biblio') { + return; + } + db_delete('biblio_tagged') + ->condition('nid', $node->nid) + ->execute(); +} + +function biblio_tagged_node_insert($node) { + if ($node->type != 'biblio' || !isset($node->biblio_tagged_md5)) { + return; + } + drupal_write_record('biblio_tagged', $node); +} + +function biblio_tagged_biblio_import($file, $terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE, $string = FALSE) { + $nids = array(); + $dups = array(); + list($nids, $dups) = _biblio_tagged_import($file, $terms, $batch, $session_id); + + return array($nids, $dups); +} + +function biblio_tagged_biblio_export($nids) { + if (module_exists('popups') && count($nids)) { + $popup = TRUE; + } + else { + $popup = FALSE; + drupal_add_http_header('Content-type', 'application/x-endnote-refer'); + drupal_add_http_header('Content-Disposition', 'attachment; filename="Drupal-Biblio.enw"'); + } + + $nodes = node_load_multiple($nids, array(), TRUE); + foreach ($nodes as $node) { + if (variable_get('biblio_hide_bibtex_braces', 0) ) { + $node->title = biblio_remove_brace($node->title); + } + + if (!$popup) { + print _biblio_tagged_export($node); + } + else{ + $popup_data .= _biblio_tagged_export($node); + } + } + if ($popup && !empty($popup_data)) return '
    ' . $popup_data . '
    '; + +} + +/** + * Export data in tagged format. + * + * @param $result + * a database result set pointer + * @return + * none + */ + +function _biblio_tagged_import($file, $terms = array(), $batch = FALSE, $session_id = NULL) { + ini_set('auto_detect_line_endings', TRUE); + if (!($fp = fopen($file->uri, "r"))) { + drupal_set_message(t("Could not open EndNote Tagged input"), 'error'); + return; + } + $nids = array(); + $dups = array(); + $ignored_tags = array(); + $node = NULL; + $incite = FALSE; + $node_id = NULL; + $contributors = NULL; + while (!feof($fp)) { + $line = trim(fgets($fp)); + $line_len = strlen($line); + if ($line_len) { + $start = strpos($line, "%"); // There could be some unprintables at the beginning of the line so fine the location of the % + if ($start !== FALSE) { + $tag = drupal_substr($line, $start, 2); + $value = trim(drupal_substr($line, $start +3)); + } + else { + $value = $line; + } + } + if ($line_len) { // if this is not a blank line + if (!$incite) { + $incite = TRUE; + $node = new stdClass(); + //$node->biblio_contributors = array(); + + } + switch ($tag) { + case '%0' : + $node->biblio_type = _biblio_tagged_type_map($value); + break; + case '%A' : + $node->biblio_contributors[] = array( + 'name' => $value, + 'auth_category' => 1, + 'auth_type' => _biblio_get_auth_type(1, $node->biblio_type)); + break; + case '%E' : + $node->biblio_contributors[] = array( + 'name' => $value, + 'auth_category' => 2, + 'auth_type' => _biblio_get_auth_type(2, $node->biblio_type)); + break; + case '%T' : + $node->title = $value; + break; + case '%Y' : + $node->biblio_contributors[] = array( + 'name' => $value, + 'auth_category' => 3, + 'auth_type' => _biblio_get_auth_type(3, $node->biblio_type)); + break; + case '%?' : + $node->biblio_contributors[] = array( + 'name' => $value, + 'auth_category' => 4, + 'auth_type' => _biblio_get_auth_type(4, $node->biblio_type)); + break; + case '%X' : + $node->biblio_abst_e .= $value; + break; + case '%Z' : + $node->biblio_notes .= $value; + break; + + default : + $field = _biblio_tagged_field_map($tag); + if (!empty($field)) { + $node->$field = $value; + } + else { + if (!in_array($tag, $ignored_tags)) { + $ignored_tags[] = $tag; + } + } + break; + } //end switch + } + else { + $incite = FALSE; + if (!empty($node)) { + _biblio_tagged_save($node, $terms, $batch, $session_id, $nids, $dups); + $node = NULL; + } + + } // end if ($start !== FALSE) + } // end while + + fclose($fp); + + if ($incite && !empty($node)) { // this catches the case where the file ends without a blank line at the end + _biblio_tagged_save($node, $terms, $batch, $session_id, $nids, $dups); + } + + if (!empty($ignored_tags)) { + $ignored_tags = array_unique($ignored_tags); + $message = t("The following elements were ignored because they do not map to any biblio fields:") . ' '; + $message .= implode(', ', $ignored_tags); + if (user_access('administer biblio')) { + $message .= '. ' . t('Click !url if you wish to check the field mapping', array('!url' => l(t('here'), 'admin/config/content/biblio/iomap/edit/tagged'))); + } + drupal_set_message($message, 'warning'); + } + + return array($nids, $dups); +} + +function _biblio_tagged_save($node, $terms, $batch, $session_id, &$nids, &$dups) { + $node->biblio_tagged_md5 = md5(serialize($node)); + if (! ($dup = biblio_tagged_check_md5($node->biblio_tagged_md5))) { + biblio_save_node($node, $terms, $batch, $session_id); + if (!empty($node->nid)) $nids[] = $node->nid; + } + else { + $dups[] = $dup; + } +} + +function _biblio_tagged_export($node) { + $export = TRUE; + $tagged = ""; + $tagged .= "%0 " . _biblio_tagged_type_map($node->biblio_type, $export) . "\r\n"; + switch ($node->biblio_type) { + case 100 : + case 101 : + case 103 : + case 104 : + case 105 : + case 108 : + case 119 : + if (!empty($node->biblio_secondary_title)) + $tagged .= "%B " . trim($node->biblio_secondary_title) . "\r\n"; + break; + case 102 : + if (!empty($node->biblio_secondary_title)) + $tagged .= "%J " . trim($node->biblio_secondary_title) . "\r\n"; + break; // journal + } + if (isset($node->biblio_year) && $node->biblio_year < 9998) $tagged .= "%D " . trim($node->biblio_year) . "\r\n"; + if (!empty($node->title)) $tagged .= "%T " . trim($node->title) . "\r\n"; + + foreach (biblio_get_contributor_category($node->biblio_contributors, 1) as $auth) { + $tagged .= "%A " . trim($auth['name']) . "\r\n"; + } + foreach (biblio_get_contributor_category($node->biblio_contributors, 2) as $auth) { + $tagged .= "%E " . trim($auth['name']) . "\r\n"; + } + foreach (biblio_get_contributor_category($node->biblio_contributors, 3) as $auth) { + $tagged .= "%Y " . trim($auth['name']) . "\r\n"; + } + foreach (biblio_get_contributor_category($node->biblio_contributors, 4) as $auth) { + $tagged .= "%? " . trim($auth['name']) . "\r\n"; + } + + $kw_array = array(); + if (!empty($node->terms)) { + foreach ($node->terms as $term) { + $kw_array[] = $term->name; + } + } + if (!empty($node->biblio_keywords)) { + foreach ($node->biblio_keywords as $term) { + $kw_array[] = $term; + } + } + if (!empty($kw_array)) { + $kw_array = array_unique($kw_array); + foreach ($kw_array as $term) { + $tagged .= "%K " . trim($term) . "\r\n"; + } + } + $abst = ""; + if (!empty($node->biblio_abst_e)) $abst .= trim($node->biblio_abst_e); + if ($abst) { + $search = array("/\r/", "/\n/"); + $replace = " "; + $abst = preg_replace($search, $replace, $abst); + $tagged .= "%X " . $abst . "\r\n"; + } + $skip_fields = array('biblio_year', 'biblio_abst_e', 'biblio_abst_f', 'biblio_type' ); + $fields = drupal_schema_fields_sql('biblio'); + $fields = array_diff($fields, $skip_fields); + foreach ($fields as $field) { + if (!empty($node->$field)) { + $tagged .= _biblio_tagged_format_entry($field, $node->$field); + } + } + if (!empty ($node->upload) && count($node->upload['und']) && user_access('view uploaded files')) { + foreach ($node->upload['und'] as $file) { + $tagged .= "%> " . file_create_url($file['uri']) . "\r\n"; // insert file here. + } + } + $tagged .= "\r\n"; + return $tagged; +} + +function _biblio_tagged_format_entry($key, $value) { + $reverse = TRUE; + $tag = _biblio_tagged_field_map($key, $reverse); + if (!empty($tag)) { + return "$tag " . trim($value) . "\r\n"; + } + +} + +function _biblio_tagged_type_map($type, $reverse = FALSE) { + static $map = array(); + + if (empty($map)) { + $map = biblio_get_map('type_map', 'tagged'); + } + + if ($reverse) { + return ($tag = array_search($type, $map)) ? $tag : 'Generic'; //return the biblio type or 129 (Misc) if type not found + } + return (isset($map[$type]))?$map[$type]:129; //return the biblio type or 129 (Misc) if type not found +} + +function _biblio_tagged_field_map($field, $reverse = FALSE) { + static $fmap = array(); + + if (empty($fmap)) { + $fmap = biblio_get_map('field_map', 'tagged' ); + } + + if ($reverse) { + return ($tag = array_search($field, $fmap)) ? $tag : ''; + } + return (!empty($fmap[$field])) ? $fmap[$field] : ''; + +} + +function biblio_tagged_tagged_map_reset($type = NULL) { + module_load_include('install', 'biblio_tagged', 'biblio_tagged'); + _reset_tagged_map($type); +} + +function biblio_tagged_check_md5($md5) { + static $tagged_md5s = array(); + if (empty($tagged_md5s)) { + $result = db_query("SELECT * FROM {biblio_tagged} "); + foreach ($result as $row ) { + $tagged_md5s[$row->biblio_tagged_md5] = $row->nid; + } + } + if (isset($tagged_md5s[$md5])) { + return $tagged_md5s[$md5]; + } + else { + $tagged_md5s[$md5] = TRUE; // gaurd against duplicates in the same import + return; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/biblio_xml.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/biblio_xml.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = Biblio - EndNote XML +description = Provides EndNote XML file import and export to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = endnote_xml_parser.inc +files[] = views/biblio_handler_field_export_link_xml.inc + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/biblio_xml.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/biblio_xml.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,390 @@ +condition('format', 'endnote8') + ->execute(); + + db_delete('biblio_type_maps') + ->condition('format', 'endnote7') + ->execute(); + } +} + +function biblio_xml_enable() { + biblio_xml_set_system_weight(); +} + +function biblio_xml_set_system_weight() { + db_update('system') + ->fields(array('weight' => 26)) + ->condition('name', 'biblio_xml') + ->execute(); +} + +function _save_xml_maps() { + _save_endnote7_maps(); + _save_endnote8_maps(); +} +function _save_endnote7_maps() { + $format = 'endnote7'; + $typemap = _get_endnote7_type_map(); + $typenames = _get_endnote7_type_names(); + $fieldmap = _get_endnote7_field_map(); + $maps = array_merge($typemap, $typenames, $fieldmap); + biblio_save_map($maps); +} + +function _save_endnote8_maps() { + $typemap = _get_endnote8_type_map(); + $typenames = _get_endnote8_type_names(); + $fieldmap = _get_endnote8_field_map(); + $maps = array_merge($typemap, $typenames, $fieldmap); + biblio_save_map($maps); +} + +function _reset_endnote_xml_map($version, $type) { + $count = db_query("SELECT COUNT(*) FROM {biblio_type_maps} WHERE format=:format", array(':format' => $version))->fetchField(); + if ($count && $type) { //update + $function = '_get_' . $version . '_' . $type; + if (!function_exists($function)) return; + $map = $function(); + drupal_write_record('biblio_type_maps', $map, 'format'); + db_update('biblio_type_maps') + ->fields($map) + ->condition('format', $version) + ->execute(); + } + else { // install + db_delete('biblio_type_maps') + ->condition('format', $version) + ->execute(); + $save_maps = '_save_' . $version . '_maps'; + $save_maps(); + } +} + +function _get_endnote8_type_map() { + $map['type_map'] = serialize( + array( + 2 => 112, // artwork + 3 => 114, // Audio Visual + 4 => 117, // bill + 5 => 101, // Book Section + 6 => 100, // Book + 7 => 116, // case + 9 => 113, // software + 17 => 102, // Journal Article + 10 => 104, // Conference Proceeding + 12 => 107, // web page + 13 => 129, // Generic + 14 => 115, // hearing + 19 => 106, // magazine_article + 20 => 122, // map + 21 => 110, // film + 21 => 111, // broadcast + 23 => 105, // newspaper_article + 25 => 119, // patent + 26 => 120, // personal communication + 27 => 109, // Report + 28 => 129, // Edited Book + 31 => 118, // statute + 32 => 108, // Thesis + 34 => 124, // unpublished + 36 => 121, // manuscript + 37 => 129, // figure + 38 => 123, // chart + 39 => 129, // equation + 43 => 129, // electronic article + 44 => 129, // electronic book + 45 => 125, // online database + 46 => 126, // government_document + 47 => 103, // conference_paper + 48 => 129, // online multimedia + 49 => 127, // Classical Work + 50 => 128, // legal_ruling + 52 => 129, // Dictionary + 53 => 129, // Encyclopedia + 54 => 129, // Grant + ) + ); + $map['format'] = 'endnote8'; + return $map; +} + +function _get_endnote8_type_names() { + $map['type_names'] = serialize( + array( + 2 => 'Artwork', + 3 => 'Audio Visual', + 4 => 'Bill', + 5 => 'Book Section', + 6 => 'Book', + 7 => 'Case', + 9 => 'Software', + 17 => 'Journal Article', + 10 => 'Conference Proceeding', + 12 => 'Web page', + 13 => 'Generic', + 14 => 'Hearing', + 19 => 'Magazine Article', + 20 => 'Map', + 21 => 'Film', + 21 => 'Broadcast', + 23 => 'Newspaper Article', + 25 => 'Patent', + 26 => 'Personal Communication', + 27 => 'Report', + 28 => 'Edited Book', + 31 => 'Statute', + 32 => 'Thesis', + 34 => 'Unpublished', + 36 => 'Manuscript', + 37 => 'Figure', + 38 => 'Chart', + 39 => 'Equation', + 43 => 'Electronic Article', + 44 => 'Electronic Book', + 45 => 'Online Database', + 46 => 'Government Document', + 47 => 'Conference Paper', + 48 => 'Online Multimedia', + 49 => 'Classical Work', + 50 => 'Legal Ruling', + 52 => 'Dictionary', + 53 => 'Encyclopedia', + 54 => 'Grant', + ) + ); + $map['format'] = 'endnote8'; + return $map; +} + +function _get_endnote8_field_map() { + + $map['field_map'] = serialize( + array( + 'source-app' => '', + 'rec-number' => '', + 'ref-type' => 'biblio_type', + 'auth-address' => 'biblio_auth_address', + 'auth-affiliaton' => '', + 'secondary-title' => 'biblio_secondary_title', + 'tertiary-title' => 'biblio_tertiary_title', + 'alt-title' => 'biblio_alternate_title', + 'short-title' => 'biblio_short_title', + 'translated-title' => 'biblio_translated_title', + 'full-title' => 'biblio_secondary_title', + 'abbr-1' => 'biblio_short_title', + 'abbr-2' => '', + 'abbr-3' => '', + 'pages' => 'biblio_pages', + 'volume' => 'biblio_volume', + 'number' => 'biblio_number', + 'issue' => 'biblio_issue', + 'secondary-volume' => '', + 'secondary-issue' => '', + 'num-vols' => 'biblio_number_of_volumes', + 'edition' => 'biblio_edition', + 'section' => 'biblio_section', + 'reprint-edition' => 'biblio_reprint_edition', + 'reprint-status' => '', + 'year' => 'biblio_year', + 'pub-dates' => 'biblio_date', + 'copyright-dates' => '', + 'pub-location' => 'biblio_place_published', + 'publisher' => 'biblio_publisher', + 'orig-pub' => 'biblio_original_publication', + 'isbn' => 'biblio_isbn', + 'accession-num' => 'biblio_accession_number', + 'call-num' => 'biblio_call_number', + 'report-id' => '', + 'coden' => '', + 'electronic-resource-num' => '', + 'abstract' => 'biblio_abst_e', + 'label' => 'biblio_label', + 'image' => '', + 'caption' => '', + 'notes' => 'biblio_notes', + 'research-notes' => 'biblio_research_notes', + 'work-type' => 'biblio_type_of_work', + 'reviewed-item' => '', + 'availability' => '', + 'remote-source' => '', + 'meeting-place' => '', + 'work-location' => '', + 'work-extent' => '', + 'pack-method' => '', + 'size' => '', + 'repro-ratio' => '', + 'remote-database-name' => 'biblio_remote_db_name', + 'remote-database-provider' => 'biblio_remote_db_provider', + 'language' => 'biblio_lang', + 'web-urls' => '', + 'pdf-urls' => '', + 'text-urls' => '', + 'image-urls' => '', + 'related-urls' => 'biblio_url', + 'access-date' => 'biblio_access_date', + 'modified-date' => '', + 'custom1' => 'biblio_custom1', + 'custom2' => 'biblio_custom2', + 'custom3' => 'biblio_custom3', + 'custom4' => 'biblio_custom4', + 'custom5' => 'biblio_custom5', + 'custom6' => 'biblio_custom6', + 'custom7' => 'biblio_custom7', + 'misc1' => '', + 'misc2' => '', + 'misc3' => '', + ) + ); + + $map['format'] = 'endnote8'; + return $map; +} +function _get_endnote7_type_map() { + + $map['type_map'] = serialize( + array( + 0 => 102, // Journal Article + 1 => 100, // Book + 2 => 108, // Thesis + 3 => 103, // Conference Proceedings + 4 => 120, // Personal Communication + 5 => 105, // NewsPaper Article + 6 => 113, // Computer Program + 7 => 101, // Book Section + 8 => 106, // Magazine Article + 9 => 100, // Edited Book + 10 => 109, // Report + 11 => 122, // Map + 12 => 114, // Audiovisual Material + 13 => 112, // Artwork + 15 => 119, // Patent + 16 => 107, // Electronic Source + 17 => 117, // Bill + 18 => 116, // Case + 19 => 115, // Hearing + 20 => 121, // Manuscript + 21 => 110, // Film or Broadcast + 22 => 118, // Statute + 26 => 123, // Chart or Table + 31 => 129 // Generic + ) + ); + $map['format'] = 'endnote7'; + return $map; +} +function _get_endnote7_type_names() { + + $map['type_names'] = serialize( + array( + 0 => 'Journal Article', + 1 => 'Book', + 2 => 'Thesis', + 3 => 'Conference Proceedings', + 4 => 'Personal Communication', + 5 => 'NewsPaper Article', + 6 => 'Computer Program', + 7 => 'Book Section', + 8 => 'Magazine Article', + 9 => 'Edited Book', + 10 => 'Report', + 11 => 'Map', + 12 => 'Audiovisual Material', + 13 => 'Artwork', + 15 => 'Patent', + 16 => 'Electronic Source', + 17 => 'Bill', + 18 => 'Case', + 19 => 'Hearing', + 20 => 'Manuscript', + 21 => 'Film or Broadcast', + 22 => 'Statute', + 26 => 'Chart or Table', + 31 => 'Generic', + ) + ); + $map['format'] = 'endnote7'; + return $map; +} + +function _get_endnote7_field_map() { + + $map['field_map'] = serialize( + array( + 'REFERENCE_TYPE' => 'biblio_type', + 'REFNUM' => '', + 'YEAR' => 'biblio_year', + 'SECONDARY_TITLE' => 'biblio_secondary_title', + 'PLACE_PUBLISHED' => 'biblio_place_published', + 'PUBLISHER' => 'biblio_publisher', + 'VOLUME' => 'biblio_volume', + 'ISSUE' => 'biblio_issue', + 'NUMBER_OF_VOLUMES' => 'biblio_number_of_volumes', + 'NUMBER' => 'biblio_number', + 'PAGES' => 'biblio_pages', + 'SECTION' => 'biblio_section', + 'TERTIARY_TITLE' => 'biblio_tertiary_title', + 'EDITION' => 'biblio_edition', + 'DATE' => 'biblio_date', + 'TYPE_OF_WORK' => 'biblio_type_of_work', + 'SHORT_TITLE' => 'biblio_short_title', + 'ALTERNATE_TITLE' => 'biblio_alternate_title', + 'ISBN' => 'biblio_isbn', + 'ORIGINAL_PUB' => 'biblio_original_publication', + 'REPRINT_EDITION' => 'biblio_reprint_edition', + 'REVIEWED_ITEM' => '', + 'CUSTOM1' => 'biblio_custom1', + 'CUSTOM2' => 'biblio_custom2', + 'CUSTOM3' => 'biblio_custom3', + 'CUSTOM4' => 'biblio_custom4', + 'CUSTOM5' => 'biblio_custom5', + 'CUSTOM6' => 'biblio_custom6', + 'ACCESSION_NUMBER' => 'biblio_accession_number', + 'CALL_NUMBER' => 'biblio_call_number', + 'LABEL' => 'biblio_label', + 'KEYWORD' => 'biblio_keywords', + 'ABSTRACT' => 'biblio_abst_e', + 'NOTES' => 'biblio_notes', + 'URL' => 'biblio_url', + 'AUTHOR_ADDRESS' => '', + 'IMAGE' => '', + 'CAPTION' => '', + ) + ); + + $map['format'] = 'endnote7'; + return $map; +} +/** + * Implementation of hook_schema(). + * + * Note: Pro Drupal Development models use of t() to translate 'description' + * for field definitions, but Drupal core does not use them. We follow core. + */ +function biblio_xml_schema() { + $schema = array(); + $schema['biblio_xml'] = array( + 'fields' => array( + 'nid' => array('type' => 'int', 'not null' => TRUE), + 'biblio_xml_md5' => array('type' => 'char', 'length' => 32, 'not null' => TRUE), + ), + 'primary key' => array('nid'), + ); + return $schema; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/biblio_xml.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/biblio_xml.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,139 @@ + 2, + 'path' => drupal_get_path('module', 'biblio_xml') . '/views', + ); +} + + /* + * add the xml option to the option list of the biblio_import_form + * the key is the module name use by module_invoke to call hook_biblio_import + * module_invoke('biblio_xml', 'biblio_import',...) + */ +function biblio_xml_biblio_import_options() { + return array('biblio_xml' => t('EndNote XML')); +} +function biblio_xml_biblio_mapper_options() { + return array( + 'endnote7' => array( + 'title' => t('EndNote 7 XML'), + 'export' => TRUE, + ), + 'endnote8' => array( + 'title' => t('EndNote X3 XML'), + 'export' => TRUE, + ), + ); +} + +function biblio_xml_biblio_export_options() { + return array('xml' => t('EndNote XML')); +} + +function biblio_xml_node_view($node, $view_mode) { + if ($node->type == 'biblio') { + switch ($view_mode) { + case 'full': + case 'teaser': + $links = biblio_xml_biblio_export_link($node->nid); + $node->content['links']['biblio_xml'] = array( + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} + +/** + * Creates a link to export a node (or view) in xml format + * + * @param $base this is the base url (defaults to /biblio) + * @param $nid the node id, if NULL then the current view is exported + * @return a link (xml) + */ +function biblio_xml_biblio_export_link($nid = NULL, $filter = array()) { + $show_link = variable_get('biblio_export_links', array('xml' => TRUE)); + if (!isset($show_link['xml']) || empty($show_link['xml']) || !biblio_access('export')) { + return array(); + } + $base = variable_get('biblio_base', 'biblio'); + + $link = array(); + $link['attributes']['title'] = t("Click to download the EndNote XML formatted file"); + $link['attributes'] += array('rel' => 'nofollow'); + + $link['href'] = "$base/export/xml"; + if (!empty($nid)) { + $link['href'] .= '/' . $nid; + } + $link['title'] = t('XML'); + + if (empty($nid) && !empty($filter)) { // add any filters which may be on the current page + $link['query'] = $filter; + } + return array('biblio_xml' => $link); +} + +function biblio_xml_node_delete($node) { + if ($node->type != 'biblio') { + return; + } + db_delete('biblio_xml') + ->condition('nid', $node->nid) + ->execute(); +} + +function biblio_xml_node_insert($node) { + if ($node->type != 'biblio' || !isset($node->biblio_xml_md5)) { + return; + } + drupal_write_record('biblio_xml', $node); +} + +function biblio_xml_biblio_import($file, $terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE, $string = FALSE) { + module_load_include('inc', 'biblio_xml', 'endnote_xml_parser'); + + $nids = array(); + $dups = array(); + + $parser = new EndNoteXMLParser; + + list($nids, $dups) = $parser->parse($file, $terms, $batch, $session_id); + + return array($nids, $dups); +} + +function biblio_xml_biblio_export($nids) { + module_load_include('inc', 'biblio_xml', 'endnote8_export'); + drupal_add_http_header('Content-type', 'application/xml; charset=utf-8'); + drupal_add_http_header('Content-Disposition', 'attachment; filename="Biblio-EndNote.xml"'); + + print _endnote8_XML_export('', 'begin'); + $nodes = node_load_multiple($nids, array(), TRUE); + foreach ($nodes as $node) { + // $node = node_load($nid, FALSE, TRUE); + if (variable_get('biblio_hide_bibtex_braces', 0) ) { + $node->title = biblio_remove_brace($node->title); + } + print _endnote8_XML_export($node); + } + + print _endnote8_XML_export('', 'end'); +} + +function biblio_xml_endnote8_map_reset($type = NULL) { + module_load_include('install', 'biblio_xml', 'biblio_xml'); + _reset_endnote_xml_map('endnote8', $type); +} + +function biblio_xml_endnote7_map_reset($type = NULL) { + module_load_include('install', 'biblio_xml', 'biblio_xml'); + _reset_endnote_xml_map('endnote7', $type); +} + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/endnote8_export.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/endnote8_export.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,201 @@ +'; + $xml .= ""; + break; + case 'record': + $xml = ""; + $xml .= 'Drupal-Biblio'; + $xml .= "" . _endnote8_type_map($node->biblio_type) . ""; + unset($node->biblio_type); + // + $xml .= en8_add_contributors($node); + $xml .= en8_add_titles($node); + $xml .= en8_add_keywords($node); + $xml .= en8_add_dates($node); + $xml .= en8_add_urls($node); + + foreach ($node as $key => $value) { + $entag = en8_field_map($key); + if (!empty($entag) && !empty($value)) { + $xml .= "<" . $entag . '>"; + } + } + $xml .= ""; + break; + case 'end': + $xml = ''; + break; + } + return $xml; +} + +function en8_encode_font_faces(&$node) { + $search = array('', '', '', '', '', '', '', '', '', ''); + $replace = array( + '" :''; + $xml .= (!empty ($node->biblio_secondary_title)) ? '" :''; + $xml .= (!empty ($node->biblio_tertiary_title)) ? '" :''; + $xml .= (!empty ($node->biblio_alternate_title)) ? '" :''; + $xml .= (!empty ($node->biblio_short_title)) ? '" :''; + $xml .= (!empty ($node->biblio_translated_title)) ? '" :''; + $xml .= ''; + unset($node->title); + unset($node->biblio_secondary_title); + unset($node->biblio_tertiary_title); + unset($node->biblio_alternate_title); + unset($node->biblio_short_title); + unset($node->biblio_translated_title); + + return $xml; +} +function en8_add_urls(&$node) { + global $base_path; + $xml = ''; + // TODO: fix URLS + if (!empty($node->biblio_url)) { + $xml .= ""; + $xml .= '"; + $xml .= ""; + } + unset($node->biblio_url); + if (!empty ($node->upload) && count($node->upload['und']) && user_access('view uploaded files')) { + $xml .= ""; + foreach ($node->upload['und'] as $file) { + $xml .= '"; + } + $xml .= ""; + } + unset($node->upload['und']); + if (!empty($xml)) return "$xml"; + return ; +} + +function en8_add_dates(&$node) { + $xml = ''; + if (!empty($node->biblio_year) || !empty($node->biblio_date) ) { + $xml .= ''; + $xml .= (!empty($node->biblio_year)) ? '":''; + $xml .= (!empty($node->biblio_date)) ? '":''; + $xml .= ""; + } + unset($node->biblio_year); + unset($node->biblio_date); + return $xml; +} + +function en8_add_keywords(&$node) { + $kw_array = array(); + $xml = ''; + if (!empty($node->biblio_keywords)) { + foreach ($node->biblio_keywords as $term) { + $kw_array[] = trim($term); + } + } + if (!empty($kw_array)) { + $kw_array = array_unique($kw_array); + $xml .= ''; + foreach ($kw_array as $word) { + $xml .= '"; + } + $xml .= ""; + } + unset($node->biblio_keywords); + return $xml; +} + +function en8_add_contributors(&$node) { + $xml = ''; + $authors = biblio_get_contributor_category($node->biblio_contributors, 1); + if (!empty($authors)) { + $xml .= ""; + foreach ($authors as $auth) { + $xml .= '"; + } + $xml .= ""; + } + $authors = biblio_get_contributor_category($node->biblio_contributors, 2); + if (!empty($authors)) { + $xml .= ""; + foreach ($authors as $auth) { + $xml .= '"; + } + $xml .= ""; + } + $authors = biblio_get_contributor_category($node->biblio_contributors, 3); + if (!empty($authors)) { + $xml .= ""; + foreach ($authors as $auth) { + $xml .= '"; + } + $xml .= ""; + } + $authors = biblio_get_contributor_category($node->biblio_contributors, 4); + if (!empty($authors)) { + $xml .= ""; + foreach ($authors as $auth) { + $xml .= '"; + } + $xml .= ""; + } + $authors = biblio_get_contributor_category($node->biblio_contributors, 5); + if (!empty($authors)) { + $xml .= ""; + foreach ($authors as $auth) { + $xml .= '"; + } + $xml .= ""; + } + $xml .= ''; + unset($node->biblio_contributors); + return $xml; +} + +function en8_field_map($biblio_field) { + static $fmap = array(); + if (empty($fmap)) { + $fmap = biblio_get_map('field_map', 'endnote8'); + } + return ($en8_field = array_search($biblio_field, $fmap)) ? $en8_field : ''; +} + +function _endnote8_type_map($bibliotype) { + static $map = array(); + if (empty($map)) { + $map = biblio_get_map('type_map', 'endnote8'); + } + return ($en8_type = array_search($bibliotype, $map)) ? $en8_type : 13; //return the biblio type or 129 (Misc) if type not found +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/endnote/endnote_xml_parser.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/endnote/endnote_xml_parser.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,456 @@ +terms = $terms; + $this->batch_proc = $batch; + $this->session_id = $session_id; + $this->nids = array(); + $this->dups = array(); + $this->unmapped = array(); + + if (!($fp = fopen($file->uri, "r"))) { + drupal_set_message(t("could not open XML input"), 'error'); + return; + } + $data = fread($fp, 2048); + if ((strpos($data, 'record') !== FALSE) && (strpos($data, 'ref-type') !== FALSE)) { + $this->format = 'endnote8'; + } + elseif (strpos($data, 'RECORD') !== FALSE && strpos($data, 'REFERENCE_TYPE') !== FALSE) { + $this->format = 'endnote7'; + } + if ($this->format) { + $this->parser = drupal_xml_parser_create($data); + xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, FALSE); + xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, TRUE); + xml_set_object($this->parser, $this); + xml_set_element_handler($this->parser, $this->format . '_startElement', $this->format . '_endElement'); + xml_set_character_data_handler($this->parser, $this->format . '_characterData'); + + xml_parse($this->parser, $data, feof($fp)); + + while ($data = fread($fp, 2048)) { + // $data = fread($fp, 2048); + set_time_limit(300); + if (!xml_parse($this->parser, $data, feof($fp))) { + drupal_set_message(sprintf("XML error: %s at line %d", + xml_error_string(xml_get_error_code($this->parser)), + xml_get_current_line_number($this->parser)), 'error'); + } + } + xml_parser_free($this->parser); + } + + fclose($fp); + + if (!empty($this->unmapped)) { + $ignored_tags = array_unique($this->unmapped); + $message = t("The following elements were ignored because they do not map to any biblio fields:") . ' '; + $message .= implode(', ', $ignored_tags); + if (user_access('administer biblio')) { + $message .= '. ' . t('Click !url if you wish to check the field mapping', array('!url' => l(t('here'), 'admin/config/content/biblio/iomap/edit/' . $this->format))); + } + drupal_set_message($message, 'warning'); + } + return array($this->nids, $this->dups); + + } + + function endnote8_startElement($parser, $name, $attrs) { + switch ($name) { + case 'record' : + $this->node = new stdClass(); + $this->node->biblio_contributors = array(); + break; + case 'style' : + $this->font_attr = explode(' ', $attrs['face']); + foreach ($this->font_attr as $fatt) { + switch ($fatt) { + case 'normal': + break; + case 'bold': + $this->endnote8_characterData(NULL, ''); + break; + case 'italic': + $this->endnote8_characterData(NULL, ''); + break; + case 'underline': + $this->endnote8_characterData(NULL, ''); + break; + case 'superscript': + $this->endnote8_characterData(NULL, ''); + break; + case 'subscript': + $this->endnote8_characterData(NULL, ''); + break; + } + } + break; + case 'keywords' : + $this->keyword_count = 0; + break; + case 'authors' : + case 'secondary-authors' : + case 'tertiary-authors' : + case 'subsidiary-authors' : + case 'translated-authors' : + $this->contributors_type = $name; + $this->contributors = array(); + $this->contrib_count = 0; + break; + case 'author' : + $this->contributors[$this->contrib_count]['name'] = ''; + $this->element = $name; + break; + case 'year' : + case 'pub-dates' : + case 'copyright-dates' : + $this->dates = $name; + break; + case 'web-urls' : + case 'pdf-urls' : + case 'text-urls' : + case 'related-urls' : + case 'image-urls' : + $this->urls = $name; + break; + case 'keyword': + $this->node->biblio_keywords[$this->keyword_count] = ''; + $this->element = $name; + break; + + default : + $this->element = $name; + } + } + + function endnote8_endElement($parser, $name) { + // global $this->node, $nids, $this->element, $terms, $batch_proc, $session_id, $this->contributors_type, $this->contrib_count, $this->dates, $this->urls, $this->keyword_count, $this->font_attr; + switch ($name) { + case 'record' : + $this->element = $this->contributors_type = $this->contrib_count = $this->dates = $this->urls = ''; + $this->node->biblio_xml_md5 = md5(serialize($this->node)); + if ( !($dup = $this->biblio_xml_check_md5($this->node->biblio_xml_md5)) ) { + biblio_save_node($this->node, $this->terms, $this->batch_proc, $this->session_id); + if (!empty($this->node->nid)) $this->nids[] = $this->node->nid; + } + else { + $this->dups[] = $dup; + } + break; + case 'authors' : + case 'secondary-authors' : + case 'tertiary-authors' : + case 'subsidiary-authors' : + case 'translated-authors' : + $this->contributors_type = ''; + foreach ($this->contributors as $contributor) { + $this->node->biblio_contributors[] = $contributor; + } + break; + case 'author' : + switch ($this->contributors_type) { + case 'authors' : + $this->contributors[$this->contrib_count]['auth_category'] = 1; + $this->contributors[$this->contrib_count]['auth_type'] = 1; + break; + case 'secondary-authors' : + $this->contributors[$this->contrib_count]['auth_category'] = 2; + $this->contributors[$this->contrib_count]['auth_type'] = 2; + break; + case 'tertiary-authors' : + $this->contributors[$this->contrib_count]['auth_category'] = 3; + $this->contributors[$this->contrib_count]['auth_type'] = 3; + break; + case 'subsidiary-authors' : + $this->contributors[$this->contrib_count]['auth_category'] = 4; + $this->contributors[$this->contrib_count]['auth_type'] = 4; + break; + case 'translated-authors' : + $this->contributors[$this->contrib_count]['auth_category'] = 5; + $this->contributors[$this->contrib_count]['auth_type'] = 5; + break; + } + $this->contrib_count++; + break; + case 'keyword' : + $this->keyword_count++; + break; + case 'year' : + case 'pub-dates' : + case 'copyright-dates' : + $this->dates = ''; + break; + case 'web-urls' : + case 'pdf-urls' : + case 'text-urls' : + case 'related-urls' : + case 'image-urls' : + $this->urls = ''; + break; + case 'ref-type': + $this->node->biblio_type = $this->type_map($this->node->biblio_type); + $this->element = ''; + break; + case 'style' : + foreach ($this->font_attr as $fatt) { + switch ($fatt) { + case 'normal': + break; + case 'bold': + $this->endnote8_characterData(NULL, ''); + break; + case 'italic': + $this->endnote8_characterData(NULL, ''); + break; + case 'underline': + $this->endnote8_characterData(NULL, ''); + break; + case 'superscript': + $this->endnote8_characterData(NULL, ''); + break; + case 'subscript': + $this->endnote8_characterData(NULL, ''); + break; + } + } + $this->font_attr = array(); + break; + default : + $this->element = ''; + } + + + } + + function endnote8_characterData($parser, $data) { + // first replace any carriage returns with html line breaks + $data = str_ireplace("\r", "
    ", $data); + if (trim(htmlspecialchars_decode($data))) { + switch ($this->element) { + //Author information + case 'author' : + $this->contributors[$this->contrib_count]['name'] .= $data; + break; + case 'keyword' : + $this->node->biblio_keywords[$this->keyword_count] .= $data; + break; + case 'dates' : + switch ($this->dates) { + case 'year' : + $this->node->biblio_year .= $data; + break; + } + break; + case 'date' : + switch ($this->dates) { + case 'pub-dates' : + $this->node->biblio_date .= $data; + break; + case 'copyright-dates' : + break; + } + break; + case 'urls' : + case 'url' : + switch ($this->urls) { + case 'web-urls' : + $this->node->biblio_url .= $data; + break; + case 'pdf-urls' : + case 'text-urls' : + case 'image-urls' : + break; + case 'related-urls' : + } + break; + case 'title': + $this->node->title .= $data; + break; + default: + if ($field = $this->field_map(trim($this->element))) { + $this->node->$field .= $data; + } + else { + if (!in_array($this->element, $this->unmapped)) { + $this->unmapped[] = $this->element; + } + } + } + } + } + + function endnote7_startElement($parser, $name, $attrs) { + switch ($name) { + case 'RECORD' : + $this->node = new stdClass(); + $this->node->biblio_contributors = array(); + $this->node->biblio_type = 102; // we set 102 here because the xml parser won't + // process a value of 0 (ZERO) which is the + // ref-type 102. if there is a non-zero value it will be overwritten + $this->element = ''; + break; + case 'AUTHORS': + case 'SECONDARY_AUTHORS': + case 'TERTIARY_AUTHORS': + case 'SUBSIDIARY_AUTHORS': + $this->contrib_count = 0; + $this->contributors = array(); + break; + case 'AUTHOR': + case 'SECONDARY_AUTHOR': + case 'TERTIARY_AUTHOR': + case 'SUBSIDIARY_AUTHOR': + $this->contributors[$this->contrib_count]['name'] = ''; + $this->element = $name; + break; + case 'KEYWORDS': + $this->keyword_count = 0; + break; + case 'KEYWORD': + $this->node->biblio_keywords[$this->keyword_count] = ''; + $this->element = $name; + break; + default: + $this->element = $name; + } + } + + function endnote7_endElement($parser, $name) { + switch ($name) { + case 'RECORD' : + $this->node->biblio_xml_md5 = md5(serialize($this->node)); + if ( !($dup = $this->biblio_xml_check_md5($this->node->biblio_xml_md5)) ) { + biblio_save_node($this->node, $this->terms, $this->batch_proc, $this->session_id); + if (!empty($this->node->nid)) $this->nids[] = $this->node->nid; + } + else { + $this->dups[] = $dup; + } + break; + case 'AUTHORS': + case 'SECONDARY_AUTHORS': + case 'TERTIARY_AUTHORS': + case 'SUBSIDIARY_AUTHORS': + $this->contributors_type = ''; + foreach ($this->contributors as $contributor) { + $this->node->biblio_contributors[] = $contributor; + } + break; + case 'AUTHOR': + $this->contributors[$this->contrib_count]['auth_category'] = 1; + $this->contributors[$this->contrib_count]['auth_type'] = 1; + $this->contrib_count++; + break; + case 'SECONDARY_AUTHOR': + $this->contributors[$this->contrib_count]['auth_category'] = 2; + $this->contributors[$this->contrib_count]['auth_type'] = 2; + $this->contrib_count++; + break; + case 'TERTIARY_AUTHOR': + $this->contributors[$this->contrib_count]['auth_category'] = 3; + $this->contributors[$this->contrib_count]['auth_type'] = 3; + $this->contrib_count++; + break; + case 'SUBSIDIARY_AUTHOR': + $this->contributors[$this->contrib_count]['auth_category'] = 4; + $this->contributors[$this->contrib_count]['auth_type'] = 4; + $this->contrib_count++; + break; + case 'KEYWORD': + $this->keyword_count++; + break; + default: + + } + $this->element = ''; + } + + function endnote7_characterData($parser, $data) { + if (trim($data)) { + switch ($this->element) { + case 'REFERENCE_TYPE': + $this->node->biblio_type = $this->type_map($data); + break; + case 'AUTHOR': + case 'SECONDARY_AUTHOR': + case 'TERTIARY_AUTHOR': + case 'SUBSIDIARY_AUTHOR': + $this->contributors[$this->contrib_count]['name'] .= $data; + break; + case 'KEYWORD': + $this->node->biblio_keywords[$this->keyword_count] .= $data; + break; + case 'TITLE': + $this->node->title .= $data; + break; + default: + if ($field = $this->field_map(trim($this->element))) { + $this->node->$field .= $data; + } + else { + if (!in_array($this->element, $this->unmapped)) { + $this->unmapped[] = $this->element; + } + } + } + } + } + + function field_map($enfield) { + static $fmap = array(); + if (empty($fmap)) { + $fmap = biblio_get_map('field_map', $this->format); + } + return (!empty($fmap[$enfield])) ? $fmap[$enfield] : ''; + } + + function type_map($entype) { + static $map = array(); + if (empty($map)) { + $map = biblio_get_map('type_map', $this->format); + } + return (isset($map[$entype]))?$map[$entype]:129; //return the biblio type or 129 (Misc) if type not found + } + + function biblio_xml_check_md5($md5) { + static $xml_md5s = array(); + if (empty($xml_md5s)) { + $result = db_query("SELECT * FROM {biblio_xml} "); + foreach ($result as $row) { + $xml_md5s[$row->biblio_xml_md5] = $row->nid; + } + } + if (isset($xml_md5s[$md5])) { + return $xml_md5s[$md5]; + } + else { + $xml_md5s[$md5] = TRUE; // gaurd against duplicates in the same import + return; + } + } + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/marcParse/biblio_marc.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/marcParse/biblio_marc.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Biblio - MARC +description = Provides MARC file import to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = php-marc.php + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/marcParse/biblio_marc.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/marcParse/biblio_marc.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,96 @@ +condition('format', 'marc') + ->execute(); + } +} + +function biblio_marc_enable() { + biblio_marc_set_system_weight(); +} + +function biblio_marc_set_system_weight() { + db_update('system') + ->fields(array('weight' => 24)) + ->condition('name', 'biblio_marc') + ->execute(); +} + +function _save_marc_maps() { + $maps['type_map'] = serialize( + array( + 'ab' => 102, // Journal Article + 'as' => 102, // Journal Article + 'am' => 100, // Book + 2 => 108, // Thesis + 3 => 103, // Conference Proceedings + 4 => 120, // Personal Communication + 5 => 105, // NewsPaper Article + 6 => 113, // Computer Program + 'aa' => 101, // Book Section + 8 => 106, // Magazine Article + 9 => 100, // Edited Book + 10 => 109, // Report + 'em' => 122, // Map + 12 => 114, // Audiovisual Material + 13 => 112, // Artwork + 15 => 119, // Patent + 16 => 107, // Electronic Source + 17 => 117, // Bill + 18 => 116, // Case + 19 => 115, // Hearing + 20 => 121, // Manuscript + 21 => 110, // Film or Broadcast + 22 => 118, // Statute + 26 => 123, // Chart or Table + 31 => 129 // Generic + + ) + ); + + $maps['type_names'] = serialize( + array( + ) + ); + + $maps['field_map'] = serialize( + array( + ) + ); + + $maps['format'] = 'marc'; + biblio_save_map($maps); + + +} +/** + * Implementation of hook_schema(). + * + * Note: Pro Drupal Development models use of t() to translate 'description' + * for field definitions, but Drupal core does not use them. We follow core. + */ +function biblio_marc_schema() { + $schema = array(); + $schema['biblio_marc'] = array( + 'fields' => array( + 'nid' => array('type' => 'int', 'not null' => TRUE), + 'biblio_marc_md5' => array('type' => 'char', 'length' => 32, 'not null' => TRUE), + ), + 'primary key' => array('nid'), + ); + return $schema; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/marcParse/biblio_marc.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/marcParse/biblio_marc.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,376 @@ + t('MARC')); +} + +function biblio_marc_node_delete($node) { + if ($node->type != 'biblio') { + return; + } + db_delete('biblio_marc') + ->condition('nid', $node->nid) + ->execute(); +} + +function biblio_marc_node_insert($node) { + if ($node->type != 'biblio') { + return; + } + if (!isset($node->biblio_marc_md5)) { + return; + } + drupal_write_record('biblio_marc', $node); +} + +function biblio_marc_biblio_import($file, $terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE, $string = FALSE) { + $nids = array(); + $dups = array(); + module_load_include('php', 'biblio_marc', 'php-marc'); + $marcfile = new File($file->uri); + while ($record = $marcfile->next() ) { + $node = new stdClass(); + $node->biblio_contributors = array(); + $fields = $record->fields(); + $leader = $record->leader(); + $pubtype = $leader[6]; + $pubtype .= $leader[7]; + $node->biblio_type = _biblio_marc_type_map($pubtype); + foreach ($record->fields() as $fields) { + foreach ($fields as $field) { + $tagnum = $field->tagno; + switch ($tagnum) { + case '008': + $data = $field->data(); + $node->biblio_year = substr($data, 7, 4); + $node->biblio_lang = substr($data, 35, 3); + break; + case '020': + $node->biblio_isbn = $field->subfield('a'); + break; + case '022': + $node->biblio_issn = $field->subfield('a'); + break; + case '024': + $node->biblio_other_number = $field->subfield('a'); + break; + case '050': //LIBRARY OF CONGRESS CALL NUMBER + case '055': //CLASSIFICATION NUMBERS ASSIGNED IN CANADA + case '060': //NATIONAL LIBRARY OF MEDICINE CALL NUMBER + $node->biblio_call_number = $field->subfield('a'); + break; + case '130': + $node->title = str_replace(' /', '', $field->subfield('a')); + break; + case '210': + $node->biblio_short_title = str_replace(' /', '', $field->subfield('a')); + break; + case '245': + $node->title = str_replace(' /', '', $field->subfield('a')) . ' ' . $field->subfield('b'); + break; + case '250': + $node->biblio_edition = $field->subfield('a'); + break; + case '260': + $node->biblio_place_published = str_replace(' :', '', $field->subfield('a')); + $node->biblio_publisher = $field->subfield('b'); + $node->biblio_date = $field->subfield('c'); + break; + case '300': + $node->biblio_pages = $field->subfield('a'); + break; + case '490': + $node->biblio_volume = $field->subfield('v'); + break; + case ($tagnum >= 500 && $tagnum <= 599): + $value = $field->subfield('a'); + if (!empty($value)) { + $node->biblio_notes .= $value; + } + break; + case '650': + foreach ($field->subfields() as $subject) { + $node->biblio_keywords[] = $subject[0]; + } + break; + case '100': + case '700': + $value = $field->subfield('a'); + if (!empty($value)) { + $node->biblio_contributors[] = array( + 'name' => $value, + 'auth_category' => 1, + 'auth_type' => 1 + ); + } + break; + case '110': + case '710': + $node->biblio_contributors[] = array( + 'name' => $field->subfield('a'), + 'auth_category' => 5, + 'auth_type' => 5 + ); + break; + case '856': + $value = $field->subfield('u'); + if (!empty($value)) { + $node->biblio_url = $value; + } + break; + } + } + } + if (!empty($node)) { + $node->biblio_marc_md5 = md5(serialize($node)); + + if (! ($dup = biblio_marc_check_md5($node->biblio_marc_md5))) { + biblio_save_node($node, $terms, $batch, $session_id); + if (!empty($node->nid)) $nids[] = $node->nid; + } + else { + $dups[] = $dup; + } + } + + } + return array($nids, $dups); +} + +function biblio_marc_check_md5($md5) { + static $marc_md5s = array(); + if (empty($marc_md5s)) { + $result = db_query("SELECT * FROM {biblio_marc} "); + foreach ($result as $row) { + $marc_md5s[$row->biblio_marc_md5] = $row->nid; + } + } + if (isset($marc_md5s[$md5])) { + return $marc_md5s[$md5]; + } + else { + $marc_md5s[$md5] = TRUE; // gaurd against duplicates in the same import + return; + } +} + + +function _biblio_marc_type_map($type, $reverse = FALSE) { + static $map = array(); + + if (empty($map)) { + $map = biblio_get_map('type_map', 'marc'); + } + + if ($reverse) { + return ($tag = array_search($type, $map)) ? $tag : 'Generic'; //return the biblio type or 129 (Misc) if type not found + } + return (isset($map[$type]))?$map[$type]:129; //return the biblio type or 129 (Misc) if type not found +} + +function biblio_marc_biblio_export_options() { + return array('marc' => t('MARC')); +} + +function biblio_marc_node_view($node, $view_mode) { + if ($node->type == 'biblio') { + switch ($view_mode) { + case 'full': + case 'teaser': + $links = biblio_marc_biblio_export_link($node->nid); + $node->content['links']['biblio_marc'] = array( + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} + +/** + * Creates a link to export a node (or view) in MARC format + * + * @param $nid the node id, if NULL then the current view is exported + * @return a link (marc) + */ +function biblio_marc_biblio_export_link($nid = NULL, $filter = array()) { + $show_link = variable_get('biblio_export_links', array('marc' => TRUE)); + if (!isset($show_link['marc']) || empty($show_link['marc']) || !biblio_access('export')) { + return array(); + } + $base = variable_get('biblio_base', 'biblio'); + + if (module_exists('popups') && !empty($nid)) { + $link = array( + 'attributes' => array( + 'class' => 'popups', + 'title' => t("Click to get the MARC formatted output"))); + } + else { + $link = array( + 'attributes' => array( + 'title' => t("Click to download the MARC formatted file"))); + } + + $link['attributes'] += array('rel' => 'nofollow'); + + $link['href'] = "$base/export/marc"; + if (!empty($nid)) { + $link['href'] .= '/' . $nid; + } + $link['title'] = t('MARC'); + + if (empty($nid) && !empty($filter)) { // add any filters which may be on the current page + $link['query'] = $filter; + } + + return array('biblio_marc' => $link); +} + +function biblio_marc_biblio_export($nids) { + if (module_exists('popups') && count($nids)) { + $popup = TRUE; + } + else { + $popup = FALSE; + drupal_add_http_header('Content-type', 'application/text; charset=utf-8'); + drupal_add_http_header('Content-Disposition', 'attachment; filename="Biblio.mrc"'); + } + + $nodes = node_load_multiple($nids, array(), TRUE); + foreach ($nodes as $node) { + if (!$popup) { + print _biblio_marc_export($node); + } + else{ + $popup_data .= _biblio_marc_export($node); + } + } + if ($popup && !empty($popup_data)) return '
    ' . $popup_data . '
    '; +} + +function _biblio_marc_export($node) { + module_load_include('php', 'biblio_marc', 'php-marc'); + $record = new Record(); + // case '008': + // $data = $field->data(); + // $node->biblio_year = substr($data, 7, 4); + // $node->biblio_lang = substr($data, 35, 3); + // break; + $leader = $record->leader(); + if ($node->biblio_type == 100) { + $type = 'nam a'; + } + else { + $type = 'nas a'; + } + $record->leader(substr_replace($leader, $type, 5, 5)); + + $rec_eight = str_repeat(' ', 40); + $rec_eight = substr_replace($rec_eight, $node->biblio_year, 7, 4); + $rec_eight = substr_replace($rec_eight, $node->biblio_lang, 35, 3); + $rec_eight = substr_replace($rec_eight, 'd', 39, 1); + $field = new Field("008", $rec_eight); + $record->append_fields($field); + + if (!empty($node->biblio_isbn)) { + $field = new Field("020", "", "", array("a" => $node->biblio_isbn)); + $record->append_fields($field); + } + if (!empty($node->biblio_issn)) { + $field = new Field("022", "", "", array("a" => $node->biblio_issn)); + $record->append_fields($field); + } + if (!empty($node->biblio_other_number)) { + $field = new Field("024", "", "", array("a" => $node->biblio_other_number)); + $record->append_fields($field); + } + if (!empty($node->biblio_call_number)) { + $field = new Field("050", "", "", array("a" => $node->biblio_call_number)); + $record->append_fields($field); + } + if (!empty($node->title)) { + $field = new Field("245", "0", "0", array("a" => $node->title)); + $record->append_fields($field); + } + if (!empty($node->biblio_sort_title)) { + $field = new Field("210", "0", "#", array("a" => $node->biblio_sort_title)); + $record->append_fields($field); + } + // case '245': + // $node->title = str_replace(' /', '', $field->subfield('a')) . ' ' . $field->subfield('b'); + // break; + if (!empty($node->biblio_edition)) { + $field = new Field("250", "", "", array("a" => $node->biblio_edition)); + $record->append_fields($field); + } + if (!empty($node->biblio_place_published)) { + $subfields['a'] = $node->biblio_place_published; + } + if (!empty($node->biblio_publisher)) { + $subfields['b'] = $node->biblio_publisher; + } + if (!empty($node->biblio_date)) { + $subfields['c'] = $node->biblio_date; + } + if (!empty($subfields)) { + $field = new Field("260", "", "", $subfields); + $record->append_fields($field); + } + if (!empty($node->biblio_pages)) { + $field = new Field("300", "", "", array("a" => $node->biblio_pages)); + $record->append_fields($field); + } + if (!empty($node->biblio_volume)) { + $field = new Field("490", "0", "", array("v" => $node->biblio_volume)); + $record->append_fields($field); + } + if (!empty($node->biblio_abst_e)) { + $field = new Field("520", "3", "#", array("a" => $node->biblio_abst_e)); + $record->append_fields($field); + } + // case ($tagnum >= 500 && $tagnum <= 599): + // $value = $field->subfield('a'); + // if (!empty($value)) { + // $node->biblio_notes .= $value; + // } + if (!empty($node->biblio_keywords)) { + foreach($node->biblio_keywords as $keyword) { + $field = new Field("653", "1", "0", array("a" => $keyword)); + $record->append_fields($field); + } + } + if (!empty($node->biblio_contributors)) { + foreach ($node->biblio_contributors as $i => $author) { + $first = $author['firstname']; + $last = $author['lastname']; + $init = $author['initials']; + $cat = $author['auth_category']; + $name = $last . ($first ? ', ' . $first : '') . ($init ? ', ' . $init : ''); + $tag = ($i == 0 ? ($cat == 5 ? 110 :100) : ($cat == 5 ? 710 :700)); + $field = new Field($tag, "1", "#", array("a" => $name)); + $record->append_fields($field); + } + } + if (!empty($node->biblio_url)) { + $url = $node->biblio_url; + } + else { + $options['absolute'] = TRUE; + $url = url("node/$node->nid", $options); + } + if (!empty($url)) { + $field = new Field("856", "", "", array("u" => $url)); + $record->append_fields($field); + } + return $record->raw(); + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/marcParse/example.mrc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/marcParse/example.mrc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1 @@ +01850 2200517 45000010011000000030007000110080039000180200026000570350015000830400007000980420012001050840018001170840018001350840021001530840022001741000030001962450062002262500013002882600058003013000033003594400037003925000023004295990010004527400024004627750034004868410048005208410049005688410047006178410048006648410047007128410047007598520038008068520021008448520013008658520016008788520028008948520021009229000056009439000060009999000057010599000056011169000057011729000060012299760026012890050017013150000000044EMILDA980120s1998 fi j 000 0 swe a9515008808cFIM 72:00 99515008808 aNB 9NB9SEE aHcd,u2kssb/6 5NBauHc2kssb 5SEEaHcf2kssb/6 5QaHcd,uf2kssb/61 aJansson, Tove,d1914-200104aDet osynliga barnet och andra berttelser /cTove Jansson a7. uppl. aHelsingfors :bSchildt,c1998 ;e(Falun :fScandbook) a166, [4] s. :bill. ;c21 cm 0aMumin-biblioteket,x99-0698931-9 aOriginaluppl. 1962 aLi: S4 aDet osynliga barnet1 z951-50-0385-7w9515003857907 5Liaxab0201080u 0 4000uu |000000e1 5SEEaxab0201080u 0 4000uu |000000e1 5Laxab0201080u 0 4000uu |000000e1 5NBaxab0201080u 0 4000uu |000000e1 5Qaxab0201080u 0 4000uu |000000e1 5Saxab0201080u 0 4000uu |000000e1 5NBbNBcNB98:12hpliktjR, 980520 5LibLicCNBhh,u 5SEEbSEE 5QbQj98947 5LbLc0100h98/j3043 H 5SbShSv97j72351saYanson, Tobe,d1914-2001uJansson, Tove,d1914-20011saJanssonov, Tove,d1914-2001uJansson, Tove,d1914-20011saJansone, Tuve,d1914-2001uJansson, Tove,d1914-20011saJanson, Tuve,d1914-2001uJansson, Tove,d1914-20011saJansson, Tuve,d1914-2001uJansson, Tove,d1914-20011saJanssonova, Tove,d1914-2001uJansson, Tove,d1914-2001 2aHcd,ubSknlitteratur20050204111518.0 \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/marcParse/example.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/marcParse/example.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,85 @@ + +// +//----------------------------------------------------------------------------- +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// $Revision$ +// +//----------------------------------------------------------------------------- + +require_once "../php-marc/php-marc.php"; + +// Other way to access file +/*$string = file("example.mrc"); +$file = new USMARC($string[0]);*/ + +// Open file +$file = new File("example.mrc"); + +// Read next record +$record = $file->next(); + +// Create new field +$field = new Field("245", "", "", array("a" => "Mumin")); +// Add subfield +$field->add_subfields(array("b" => "Det Osynliga Barnet")); +// Other ways to update field +$field->update(array("ind2" => "1", "b" => "Vinter i Mumindalen", "c" => "Tove Jansson")); + +// Replace existing field +$existing =& $record->field("245"); +$existing->replace_with($field); + +$clone = $field->make_clone(); +// Change some more +$clone->update(array("a" => "Muminsagor", "b" => "Muminpappans memoarer")); + +// And append to record +$record->append_fields($clone); + +// Some output +print "
    ";
    +print $record->formatted();
    +print "\n\n";
    +print $file->raw[0];
    +print "\n";
    +print $record->raw();
    +print "\n\n";
    +print $record->ffield("245", "Formatted output: Title: %a, Remainder of title: %b, Responsibility: %c\n");
    +print "
    "; + +?> \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/marcParse/php-marc.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/marcParse/php-marc.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1016 @@ + +// +//----------------------------------------------------------------------------- +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +//----------------------------------------------------------------------------- +// +// $Revision$ +// +//----------------------------------------------------------------------------- + +/** + * Hexadecimal value for Subfield indicator + * @global hex SUBFIELD_INDICATOR + */ +define("SUBFIELD_INDICATOR", "\x1F"); +/** + * Hexadecimal value for End of Field + * @global hex END_OF_FIELD + */ +define("END_OF_FIELD", "\x1E"); +/** + * Hexadecimal value for End of Record + * @global hex END_OF_RECORD + */ +define("END_OF_RECORD", "\x1D"); +/** + * Length of the Directory + * @global integer DIRECTORY_ENTRY_LEN + */ +define("DIRECTORY_ENTRY_LEN", 12); +/** + * Length of the Leader + * @global integer LEADER_LEN + */ +define("LEADER_LEN", 24); + +/** + * Class File + * Class to read MARC records from file(s) + */ +Class File { + + /** + * ========== VARIABLE DECLARATIONS ========== + */ + + /** + * Array containing raw records + * @var array + */ + var $raw; + /** + * Array of warnings + * @var array + */ + var $warn; + /** + * Current position in the array of records + * @var integer + */ + var $pointer; + + /** + * ========== ERROR FUNCTIONS ========== + */ + + /** + * Croaking function + * + * Similar to Perl's croak function, which ends parsing and raises an + * user error with a descriptive message. + * @param string The message to display + */ + function _croak($msg) { + trigger_error($msg, E_USER_ERROR); + } + + /** + * Fuction to issue warnings + * + * Warnings will not be displayed unless explicitly accessed, but all + * warnings issued during parse will be stored + * @param string Warning + * @return string Last added warning + */ + function _warn($msg) { + $this->warn[] = $msg; + return $msg; + } + + /** + * Get warning(s) + * + * Get either all warnings or a specific warning ID + * @param integer ID of the warning + * @return array|string Return either Array of all warnings or specific warning + */ + function warnings($id = "") { + if (!$id) { + return $this->warn; + } else { + if (array_key_exists($id, $this->warn)) { + return $this->warn[$id]; + } else { + return "Invalid warning ID: $id"; + } + } + } + + /** + * ========== PROCESSING FUNCTIONS ========== + */ + + /** + * Return the next raw MARC record + * + * Returns th nexts raw MARC record from the read file, unless all + * records already have been read. + * @return string|FALSE Either a raw record or False + */ + function _next() { + /** + * Exit if we are at the end of the file + */ + if ($this->pointer >= count($this->raw)) { + return FALSE; + } + + /** + * Read next line + */ + $usmarc = $this->raw[$this->pointer++]; + + // remove illegal stuff that sometimes occurs between records + // preg_replace does not know what to do with \x00, thus omitted. + $usmarc = preg_replace("/^[\x0a\x0d]+/", "", $usmarc); + + /** + * Record validation + */ + if ( strlen($usmarc) < 5 ) { + $this->_warn( "Couldn't find record length" ); + } + $reclen = substr($usmarc,0,5); + if ( preg_match("/^\d{5}$/", $reclen) || $reclen != strlen($usmarc) ) { + $this->_warn( "Invalid record length \"$reclen\"" ); + } + + return $usmarc; + } + + /** + * Read in MARC record file + * + * This function will read in MARC record files that either + * contain a single MARC record, or numerous records. + * @param string Name of the file + * @return string Returns warning if issued during read + */ + function file($in) { + if (file_exists($in)) { + $input = file($in); + $recs = explode(END_OF_RECORD, join("", $input)); + // Append END_OF_RECORD as we lost it when splitting + // Last is not record, as it is empty because every record ends + // with END_OF_RECORD. + for ($i = 0; $i < (count($recs)-1); $i++) { + $this->raw[] = $recs[$i].END_OF_RECORD; + } + $this->pointer = 0; + } else { + return $this->_warn("Invalid input file: $i"); + } + } + + /** + * Return next Record-object + * + * Decode the next raw MARC record and return + * @return Record A Record object + */ + function next() { + if ($raw = $this->_next()) { + return $this->decode($raw); + } else { + return FALSE; + } + } + + /** + * Decode a given raw MARC record + * + * "Port" of Andy Lesters MARC::File::USMARC->decode() function into PHP. Ideas and + * "rules" have been used from USMARC::decode(). + * + * @param string Raw MARC record + * @return Record Decoded MARC Record object + */ + function decode($text) { + if (!preg_match("/^\d{5}/", $text, $matches)) { + $this->_croak('Record length "'.substr( $text, 0, 5 ).'" is not numeric'); + } + + $marc = new Record; + + // Store record length + $reclen = $matches[0]; + + if ($reclen != strlen($text)) { + $this->_croak( "Invalid record length: Leader says $reclen bytes, but it's actually ".strlen($text)); + } + + if (substr($text, -1, 1) != END_OF_RECORD) + $this->_croak("Invalid record terminator"); + + // Store leader + $marc->leader(substr( $text, 0, LEADER_LEN )); + + // bytes 12 - 16 of leader give offset to the body of the record + $data_start = 0 + substr( $text, 12, 5 ); + + // immediately after the leader comes the directory (no separator) + $dir = substr( $text, LEADER_LEN, $data_start - LEADER_LEN - 1 ); // -1 to allow for \x1e at end of directory + + // character after the directory must be \x1e + if (substr($text, $data_start-1, 1) != END_OF_FIELD) { + $this->_croak("No directory found"); + } + + // All directory entries 12 bytes long, so length % 12 must be 0 + if (strlen($dir) % DIRECTORY_ENTRY_LEN != 0) { + $this->_croak("Invalid directory length"); + } + + // go through all the fields + $nfields = strlen($dir) / DIRECTORY_ENTRY_LEN; + for ($n=0; $n<$nfields; $n++) { + // As pack returns to key 1, leave place 0 in list empty + list(, $tagno) = unpack("A3", substr($dir, $n*DIRECTORY_ENTRY_LEN, DIRECTORY_ENTRY_LEN)); + list(, $len) = unpack("A3/A4", substr($dir, $n*DIRECTORY_ENTRY_LEN, DIRECTORY_ENTRY_LEN)); + list(, $offset) = unpack("A3/A4/A5", substr($dir, $n*DIRECTORY_ENTRY_LEN, DIRECTORY_ENTRY_LEN)); + + // Check directory validity + if (!preg_match("/^[0-9A-Za-z]{3}$/", $tagno)) { + $this->_croak("Invalid tag in directory: \"$tagno\""); + } + if (!preg_match("/^\d{4}$/", $len)) { + $this->_croak("Invalid length in directory, tag $tagno: \"$len\""); + } + if (!preg_match("/^\d{5}$/", $offset)) { + $this->_croak("Invalid offset in directory, tag $tagno: \"$offset\""); + } + if ($offset + $len > $reclen) { + $this->_croak("Directory entry runs off the end of the record tag $tagno"); + } + + $tagdata = substr( $text, $data_start + $offset, $len ); + + if ( substr($tagdata, -1, 1) == END_OF_FIELD ) { + # get rid of the end-of-tag character + $tagdata = substr($tagdata, 0, -1); + --$len; + } else { + $this->_croak("field does not end in end of field character in tag $tagno"); + } + + if ( preg_match("/^\d+$/", $tagno) && ($tagno < 10) ) { + $marc->append_fields(new Field($tagno, $tagdata)); + } else { + $subfields = explode(SUBFIELD_INDICATOR, $tagdata); + $indicators = array_shift($subfields); + + if ( strlen($indicators) > 2 || strlen( $indicators ) == 0 ) { + $this->_warn("Invalid indicators \"$indicators\" forced to blanks for tag $tagno\n"); + list($ind1,$ind2) = array(" ", " "); + } else { + $ind1 = substr( $indicators, 0, 1 ); + $ind2 = substr( $indicators, 1, 1 ); + } + + // Split the subfield data into subfield name and data pairs + $subfield_data = array(); + foreach ($subfields as $subfield) { + if ( strlen($subfield) > 0 ) { + $subfield_data[substr($subfield, 0, 1)][] = substr($subfield, 1); + } else { + $this->_warn( "Entirely empty subfield found in tag $tagno" ); + } + } + + if (!isset($subfield_data)) { + $this->_warn( "No subfield data found $location for tag $tagno" ); + } + + $marc->append_fields(new Field($tagno, $ind1, $ind2, $subfield_data )); + } + } + return $marc; + } + + /** + * Get the number of records available in this Record + * @return int The number of records + */ + function num_records() { + return count($this->raw); + } +} + +/** + * USMARC Class + * Extension class to File class, which allows passing of raw MARC string + * instead of filename + */ +Class USMARC Extends File { + /** + * Read raw MARC string for decoding + * @param string Raw MARC + */ + function usmarc($string) { + $this->raw[] = $string; + $this->pointer = 0; + } +} + +/** + * Record Class + * Create a MARC Record class + */ +Class Record { + + /** + * ========== VARIABLE DECLARATIONS ========== + */ + + /** + * Contain all @link Field objects of the Record + * @var array + */ + var $fields; + /** + * Leader of the Record + * @var string + */ + var $ldr; + /** + * Array of warnings + * @var array + */ + var $warn; + + /** + * ========== ERROR FUNCTIONS ========== + */ + + /** + * Croaking function + * + * Similar to Perl's croak function, which ends parsing and raises an + * user error with a descriptive message. + * @param string The message to display + */ + function _croak($msg) { + trigger_error($msg, E_USER_ERROR); + } + + /** + * Fuction to issue warnings + * + * Warnings will not be displayed unless explicitly accessed, but all + * warnings issued during parse will be stored + * @param string Warning + * @return string Last added warning + */ + function _warn($msg) { + $this->warn[] = $msg; + return $msg; + } + + /** + * Return an array of warnings + */ + function warnings() { + return $this->warn; + } + + /** + * ========== PROCESSING FUNCTIONS ========== + */ + + /** + * Start function + * + * Set all variables to defaults to create new Record object + */ + function record() { + $this->fields = array(); + $this->ldr = str_repeat(' ', 24); + } + + /** + * Get/Set Leader + * + * If argument specified, sets leader, otherwise gets leader. No validation + * on the specified leader is performed + * @param string Leader + * @return string|null Return leader in case requested. + */ + function leader($ldr = "") { + if ($ldr) { + $this->ldr = $ldr; + } else { + return $this->ldr; + } + } + + /** + * Append field to existing + * + * Given Field object will be appended to the existing list of fields. Field will be + * appended last and not in its "correct" location. + * @param Field The field to append + */ + function append_fields($field) { + if (strtolower(get_class($field)) == "field") { + $this->fields[$field->tagno][] = $field; + } else { + $this->_croak(sprintf("Given argument must be Field object, but was '%s'", get_class($field))); + } + } + + /** + * Build Record Directory + * + * Generate the directory of the Record according to existing data. + * @return array Array ( $fields, $directory, $total, $baseaddress ) + */ + function _build_dir() { + // Vars + $fields = array(); + $directory = array(); + + $dataend = 0; + foreach ($this->fields as $field_group ) { + foreach ($field_group as $field) { + // Get data in raw format + $str = $field->raw(); + $fields[] = $str; + + // Create directory entry + $len = strlen($str); + $direntry = sprintf( "%03s%04d%05d", $field->tagno(), $len, $dataend ); + $directory[] = $direntry; + $dataend += $len; + } + } + + /** + * Rules from MARC::Record::USMARC + */ + $baseaddress = + LEADER_LEN + // better be 24 + ( count($directory) * DIRECTORY_ENTRY_LEN ) + + // all the directory entries + 1; // end-of-field marker + + + $total = + $baseaddress + // stuff before first field + $dataend + // Length of the fields + 1; // End-of-record marker + + return array($fields, $directory, $total, $baseaddress); + } + + /** + * Set Leader lengths + * + * Set the Leader lengths of the record according to defaults specified in + * http://www.loc.gov/marc/bibliographic/ecbdldrd.html + */ + function leader_lengths($reclen, $baseaddr) { + $this->ldr = substr_replace($this->ldr, sprintf("%05d", $reclen), 0, 5); + $this->ldr = substr_replace($this->ldr, sprintf("%05d", $baseaddr), 12, 5); + $this->ldr = substr_replace($this->ldr, '22', 10, 2); + $this->ldr = substr_replace($this->ldr, '4500', 20, 4); + } + + /** + * Return all Field objects + * @return array Array of Field objects + */ + function fields() { + return $this->fields; + } + + /** + * Get specific field + * + * Search for field in Record fields based on field name, e.g. 020 + * @param string Field name + * @return Field|FALSE Return Field if found, otherwise FALSE + */ + function field($spec) { + if (array_key_exists($spec, $this->fields)) { + return $this->fields[$spec][0]; + } else { + return FALSE; + } + } + + /** + * Get subfield of Field object + * + * Returns the value of a specific subfield of a given Field object + * @param string Name of field + * @param string Name of subfield + * @return string|FALSE Return value of subfield if Field exists, otherwise FALSE + */ + function subfield($field, $subfield) { + if (!$field = $this->field($field)) { + return FALSE; + } else { + return $field->subfield($subfield); + } + } + + /** + * Delete Field + * + * Delete a given field from within a Record + * @param Field The field to be deleted + */ + function delete_field($obj) { + unset($this->fields[$obj->field]); + } + + /** + * Clone record + * + * Clone a record with all its Fields and subfields + * @return Record Clone record + */ + function make_clone() { + $clone = new Record; + $clone->leader($this->ldr); + + foreach ($this->fields() as $data) { + foreach ($data as $field) { + $clone->append_fields($field); + } + } + + return $clone; + } + + /** + * ========== OUTPUT FUNCTIONS ========== + */ + + /** + * Formatted representation of Field + * + * Format a Field with a sprintf()-like formatting syntax. The formatting + * codes are the names of the subfields of the Field. + * @param string Field name + * @param string Format string + * @return string|FALSE Return formatted string if Field exists, otherwise False + */ + function ffield($tag, $format) { + $result = ""; + if ($field = $this->field($tag)) { + for ($i=0; $isubfield($curr); + } + } + } + return implode("", $result); + } else { + return FALSE; + } + } + + /** + * Return Raw + * + * Return the Record in raw MARC format. + * @return string Raw MARC data + */ + function raw() { + list ($fields, $directory, $reclen, $baseaddress) = $this->_build_dir(); + $this->leader_lengths($reclen, $baseaddress); + + /** + * Glue together all parts + */ + return $this->ldr.implode("", $directory).END_OF_FIELD.implode("", $fields).END_OF_RECORD; + } + + /** + * Return formatted + * + * Return the Record in a formatted fashion. Similar to the output + * of the formatted() function in MARC::Record in Perl + * @return string Formatted representation of MARC record + */ + function formatted() { + $formatted = ""; + foreach ($this->fields as $field_group) { + foreach ($field_group as $field) { + $formatted .= $field->formatted(). "\n"; + } + } + return $formatted; + } +} + +/** + * Field Class + * Create a MARC Field object + */ +Class Field { + + /** + * ========== VARIABLE DECLARATIONS ========== + */ + + /** + * The tag name of the Field + * @var string + */ + var $tagno; + /** + * Value of the first indicator + * @var string + */ + var $ind1; + /** + * Value of the second indicator + * @var string + */ + var $ind2; + /** + * Array of subfields + * @var array + */ + var $subfields = array(); + /** + * Specify if the Field is a Control field + * @var bool + */ + var $is_control; + /** + * Array of warnings + * @var array + */ + var $warn; + /** + * Value of field, if field is a Control field + * @var string + */ + var $data; + + /** + * ========== ERROR FUNCTIONS ========== + */ + + /** + * Croaking function + * + * Similar to Perl's croak function, which ends parsing and raises an + * user error with a descriptive message. + * @param string The message to display + */ + function _croak($msg) { + trigger_error($msg, E_USER_ERROR); + } + + /** + * Fuction to issue warnings + * + * Warnings will not be displayed unless explicitly accessed, but all + * warnings issued during parse will be stored + * @param string Warning + * @return string Last added warning + */ + function _warn($msg) { + $this->warn[] = $msg; + return $msg; + } + + /** + * Return an array of warnings + */ + function warnings() { + return $this->warn; + } + + /** + * ========== PROCESSING FUNCTIONS ========== + */ + + /** + * Field init function + * + * Create a new Field object from passed arguments + * @param array Array ( tagno, ind1, ind2, subfield_data ) + * @return string Returns warnings if any issued during parse + */ + function field() { + $args = func_get_args(); + + $tagno = array_shift($args); + $this->tagno = $tagno; + + // Check if valid tag + if (!preg_match("/^[0-9A-Za-z]{3}$/", $tagno)) { + return $this->_warn("Tag \"$tagno\" is not a valid tag."); + } + + // Check if field is Control field + $this->is_control = (preg_match("/^\d+$/", $tagno) && $tagno < 10); + if ($this->is_control) { + $this->data = array_shift($args); + } else { + foreach (array("ind1", "ind2") as $indcode) { + $indicator = array_shift($args); + if (!preg_match("/^[0-9A-Za-z ]$/", $indicator)) { + if ($indicator != "") { + $this->_warn("Illegal indicator '$indicator' in field '$tagno' forced to blank"); + } + $indicator = " "; + } + $this->$indcode = $indicator; + } + + $subfields = array_shift($args); + + if (count($subfields) < 1) { + return $this->_warn("Field $tagno must have at least one subfield"); + } else { + $this->add_subfields($subfields); + } + } + } + + /** + * Add subfield + * + * Appends subfields to existing fields last, not in "correct" plase + * @param array Subfield data + * @return string Returns warnings if issued during parse. + */ + function add_subfields() { + // Process arguments + $args = func_get_args(); + if (count($args) == 1 && is_array($args[0])) { + $args = $args[0]; + } + // Add subfields, is appropriate + if ($this->is_control) { + return $this->_warn("Subfields allowed only for tags bigger or equal to 10"); + } else { + $this->subfields = array_merge($this->subfields, $args); + } + + return count($args)/2; + } + + /** + * Return Tag number of Field + */ + function tagno() { + return $this->tagno; + } + + /** + * Set/Get Data of Control field + * + * Sets the Data if argument given, otherwise Data returned + * @param string Data to be set + * @return string Data of Control field if argument not given + */ + function data($data = "") { + if (!$this->is_control) { + $this->_croak("data() is only allowed for tags bigger or equal to 10"); + } + if ($data) { + $this->data = $data; + } else { + return $this->data; + } + } + + /** + * Get values of indicators + * + * @param string Indicator number + */ + function indicator($ind) { + if ($ind == 1) { + return $this->ind1; + } elseif ($ind == 2) { + return $this->ind2; + } else { + $this->_warn("Invalid indicator: $ind"); + } + } + + /** + * Check if Field is Control field + * + * @return bool True or False + */ + function is_control() { + return $this->is_control; + } + + /** + * Get the value of a subfield + * + * Return of the value of the given subfield, if exists + * @param string Name of subfield + * @return string|FALSE Value of the subfield if exists, otherwise FALSE + */ + function subfield($code, $repeatable = FALSE) { + if (array_key_exists($code, $this->subfields)) { + return $repeatable ? $this->subfields[$code] : $this->subfields[$code][0]; + } else { + return $repeatable ? array(): FALSE; + } + } + + /** + * Return array of subfields + * + * @return array Array of subfields + */ + function subfields() { + return $this->subfields; + } + + /** + * Update Field + * + * Update Field with given array of arguments. + * @param array Array of key->value pairs of data + */ + function update() { + // Process arguments + $args = func_get_args(); + if (count($args) == 1 && is_array($args[0])) { + $args = $args[0]; + } + if ($this->is_control) { + $this->data = array_shift($args); + } else { + foreach ($args as $subfield => $value) { + if ($subfield == "ind1") { + $this->ind1 = $value; + } elseif ($subfield == "ind2") { + $this->ind2 = $value; + } else { + $this->subfields[$subfield] = $value; + } + } + } + } + + /** + * Replace Field with given Field + * + * @param Field Field to replace with + */ + function replace_with($obj) { + if (strtolower(get_class($obj)) == "field") { + $this->tagno = $obj->tagno; + $this->ind1 = $obj->ind1; + $this->ind2 = $obj->ind2; + $this->subfields = $obj->subfields; + $this->is_control = $obj->is_control; + $this->warn = $obj->warn; + $this->data = $obj->data; + } else { + $this->_croak(sprintf("Argument must be Field-object, but was '%s'", get_class($obj))); + } + } + + /** + * Clone Field + * + * @return Field Cloned Field object + */ + function make_clone() { + if ($this->is_control) { + return new Field($this->tagno, $this->data); + } else { + return new Field($this->tagno, $this->ind1, $this->ind2, $this->subfields); + } + } + + /** + * ========== OUTPUT FUNCTIONS ========== + */ + + /** + * Return Field formatted + * + * Return Field as string, formatted in a similar fashion to the + * MARC::Record formatted() functio in Perl + * @return string Formatted output of Field + */ + function formatted() { + // Variables + $lines = array(); + // Process + if ($this->is_control) { + return sprintf("%3s %s", $this->tagno, $this->data); + } else { + $pre = sprintf("%3s %1s%1s", $this->tagno, $this->ind1, $this->ind2); + } + // Process subfields + foreach ($this->subfields as $subfield => $value) { + $lines[] = sprintf("%6s _%1s%s", $pre, $subfield, $value); + $pre = ""; + } + + return join("\n", $lines); + } + + /** + * Return Field in Raw MARC + * + * Return the Field formatted in Raw MARC for saving into MARC files + * @return string Raw MARC + */ + function raw() { + if ($this->is_control) { + return $this->data.END_OF_FIELD; + } else { + $subfields = array(); + foreach ($this->subfields as $subfield => $value) { + $subfields[] = SUBFIELD_INDICATOR.$subfield.$value; + } + return $this->ind1.$this->ind2.implode("", $subfields).END_OF_FIELD; + } + } + + /** + * Return Field as String + * + * Return Field formatted as String, with either all subfields or special + * subfields as specified. + * @return string Formatted as String + */ + function string($fields = "") { + $matches = array(); + if ($fields) { + for($i=0; $isubfields)) { + $matches[] = $this->subfields[$fields[$i]]; + } + } + } else { + $matches = $this->subfields; + } + return implode(" ", $matches); + } + +} + +?> \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/pubmed/EntrezClient.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/pubmed/EntrezClient.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,505 @@ +webEnvironment = $webEnvironment; + } + + /** + * Returns the web environment from the previous ESearch results. + * + * This value may change with each utility call. If WebEnv is used, History + * search numbers can be included in an ESummary URL, e.g., + * term=cancer+AND+%23X (where %23 replaces # and X is the History search + * number). + * + * @return string + */ + public function getWebEnvironment() + { + return $this->webEnvironment; + } + + /** + * Sets a history search number. + * + * @param int $key + */ + public function setQueryKey($key) + { + $this->queryKey = $key; + } + + /** + * Returns the history search number from the previous ESearch results. + * + * @return int + */ + public function getQueryKey() + { + return $this->queryKey; + } + + /** + * Sets the entrez database to be queried. + * + * Values available from EInfo, PubMed is the default db. + * + * @param string $database + * @see getAvailableDatabases + */ + public function setDatabase($database) + { + $this->database = strtolower($database); + } + + /** + * Returns the database to be queried in the next search. + * + * @return string + */ + public function getDatabase() + { + return $this->database; + } + + /** + * Returns the available entrez databases from EInfo. + * + * @return array + * @throws Exception + */ + public function getAvailableDatabases() + { + $databases = array(); + + $url = self::BASE_URL . 'einfo.fcgi'; + $result = @simplexml_load_file($url); + + if (!$result) { + throw new Exception('Query ' . $url . ' failed.'); + } + + if (isset($result->DbList->DbName)) { + foreach ($result->DbList->DbName as $name) { + $databases[] = (string)$name; + } + } + + return $databases; + } + + /** + * Sets a string identifying the resource. + * + * A string with no internal spaces that identifies the resource which is + * using Entrez links (e.g., tool=flybase). This argument is used to help + * NCBI provide better service to third parties generating Entrez queries + * from programs. As with any query system, it is sometimes possible to ask + * the same question different ways, with different effects on performance. + * NCBI requests that developers sending batch requests include a constant + * 'tool' argument for all requests using the utilities. + * + * @param string $tool + */ + public function setTool($tool) + { + $this->tool = str_replace(array(" ", "\n", "\r"), '', $tool); + } + + /** + * Returns the resource identifier. + * + * @return string + */ + public function getTool() + { + return $this->tool; + } + + /** + * Sets a contact email address for NCBI. + * + * If you choose to provide an email address, we will use it to contact you + * if there are problems with your queries or if we are changing software + * interfaces that might specifically affect your requests. If you choose + * not to include an email address we cannot provide specific help to you, + * but you can still sign up for utilities-announce to receive general + * announcements. + * + * @param string $email + */ + public function setEmail($email) + { + $this->email = $email; + } + + /** + * Returns the NCBI contact email address. + * + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * Sets the search terms for the next query. + * + * The search command uses terms or phrases with or without Boolean + * operators. See the PubMed or Entrez help for information about search + * field descriptions and tags. Search fields and tags are database specific. + * + * @param string $term + */ + public function setTerm($term) + { + $this->term = $term; + $this->webEnvironment = NULL; + $this->count = NULL; + } + + /** + * Returns the current search terms. + * + * @return string + */ + public function getTerm() + { + return $this->term; + } + + /** + * Sets two specific dates bounding the results. + * + * @param $minDate + * @param $maxDate + * @throws Exception + */ + public function setDateRange($minDate, $maxDate=null) + { + if (is_null($maxDate)) { + $maxDate = date('Y/m/d'); + } else { + $maxDate = date('Y/m/d', strtotime($maxDate)); + } + + $minDate = date('Y/m/d', strtotime($minDate)); + + if ($maxDate < $minDate) { + throw new Exception('First argument must be an earlier date.'); + } + + $this->dateRange = array($minDate, $maxDate); + } + + /** + * Returns the specified date range bounding the results. + * + * @return array + * a pair of dates + */ + public function getDateRange() + { + return $this->dateRange; + } + + /** + * Returns the minimum date of the specified date range bounding the results. + * + * @return string + */ + public function getMinDate() + { + return $this->dateRange[0]; + } + + /** + * Returns the maximum date of the specified date range bounding the results. + * + * @return string + */ + public function getMaxDate() + { + return $this->dateRange[1]; + } + + /** + * Sets the maximum number of items retrieved by a search query. + * + * @param int $number + * @see search + */ + public function setReturnMax($number) + { + $this->returnMax = $number; + } + + /** + * Returns the maximum number of items retrieved by a search query. + * + * @return int + */ + public function getReturnMax() + { + return $this->returnMax; + } + + /** + * Returns the URL of the last executed query. + * + * @return string + */ + public function getLastQuery() + { + return $this->query; + } + + /** + * Returns up to the maximum number of items from the result set starting + * at $retstart. + * + * If this is the first search for a given term a web environment and a query + * key is retrieved from the NCBI server in addition to the result set. + * See http://eutils.ncbi.nlm.nih.gov/corehtml/query/static/esearch_help.html + * + * @param int $retStart + * the sequential number of the first record retrieved - default=0 + * which will retrieve the first record + * @return SimpleXMLElement + * an array of PubMed IDs + * @throws Exception + * @see setReturnMax + * @see setRelativeDate + */ + public function search($retStart=0) + { + if (!is_null($this->webEnvironment)) { + $params['WebEnv'] = $this->webEnvironment; + $params['query_key'] = $this->queryKey; + } else { + $params['usehistory'] = $this->useHistory; + $params['tool'] = $this->getTool(); + $params['email'] = $this->getEmail(); + $params['term'] = $this->getTerm(); + } + + if (isset($this->dateRange)) { + $params['mindate'] = $this->getMinDate(); + $params['maxdate'] = $this->getMaxDate(); + } + + $params['retstart'] = $retStart; + $params['retmax'] = $this->getReturnMax(); + $params['db'] = $this->getDatabase(); + + $this->query = self::BASE_URL . 'esearch.fcgi?' . http_build_query($params); + $result = @simplexml_load_file($this->query); + + if (!$result) { + throw new Exception('Query ' . $this->query . ' failed.'); + } + + if (isset($result->WebEnv)) { + $this->webEnvironment = (string)$result->WebEnv; + $this->queryKey = (int)$result->QueryKey; + $this->count = (int)$result->Count; + } + + return $result; + } + + /** + * Returns the number of results for the previously set search terms. + * + * @return int + * @throws Exception + */ + public function count() + { + if (is_null($this->count)) { + $params['tool'] = $this->getTool(); + $params['email'] = $this->getEmail(); + $params['db'] = $this->getDatabase(); + $params['term'] = $this->getTerm(); + $params['rettype'] = 'count'; + + if (isset($this->dateRange)) { + $params['mindate'] = $this->getMinDate(); + $params['maxdate'] = $this->getMaxDate(); + } + + $this->query = self::BASE_URL . 'esearch.fcgi?' . http_build_query($params); + $result = @simplexml_load_file($this->query); + + if (!$result) { + throw new Exception('Query ' . $this->query . ' failed.'); + } + + if (isset($result->Count)) { + $this->count = (int)$result->Count; + } + } + + return $this->count; + } + + /** + * Returns the document identified by the given PubMed ID as a SimpleXMl + * object. The root element is PubmedArticleSet. + * + * @param int $id + * @return SimpleXMLElement + */ + public function fetch($id) + { + $params['db'] = $this->getDatabase(); + $params['retmode'] = 'xml'; + $params['id'] = $id; + + $this->query = self::BASE_URL . 'efetch.fcgi?' . http_build_query($params); + $request_options = array( + 'method' => 'POST'); + $result = drupal_http_request($this->query, $request_options); + if ($result->code != 200) { + throw new Exception('Query ' . $this->query . ' failed.'); + } + $result = @simplexml_load_string($result->data); + + if (!$result) { + throw new Exception('Query ' . $this->query . ' failed.'); + } + + return $result; + } + public function fetchSummaries($retStart=0) { + return $this->fetchRecords($retStart, TRUE); + } + public function fetchResult($retStart=0) { + return $this->fetchRecords($retStart); + } + + /** + * Returns up to the maximum number of results starting at $retstart + * found by the previous search. + * + * In order to return results this method must be called after search. The + * search method retrieves a web environment and query key from the NCBI + * server which is used to fetch the results. After setting a new search term + * the old web environment is deleted and search must be executed again + * before utilizing this method. + * + * The root element of the returned SimpleXML object is PubmedArticleSet. + * + * @param $retStart + * the sequential number of the first record retrieved - default=0 + * which will retrieve the first record + * @return SimpleXMLElement + * @throws Exception + * @see search + * @see setReturnMax + */ + public function fetchRecords($retStart=0, $summaries = FALSE) { + if (is_null($this->webEnvironment)) { + throw new Exception(t('No web environment set.')); + } + + $params['WebEnv'] = $this->webEnvironment; + $params['query_key'] = $this->queryKey; + $params['retstart'] = $retStart; + $params['retmax'] = $this->getReturnMax(); + $params['db'] = $this->getDatabase(); + $params['retmode'] = 'xml'; + + if (isset($this->dateRange)) { + $params['mindate'] = $this->getMinDate(); + $params['maxdate'] = $this->getMaxDate(); + } + if ($summaries) { + $this->query = self::BASE_URL . 'esummary.fcgi?' . http_build_query($params); + } + else { + $this->query = self::BASE_URL . 'efetch.fcgi?' . http_build_query($params); + } + $request_options = array('method' => 'POST'); + $result = drupal_http_request($this->query, $request_options); + + if ($result->code != 200) { + throw new Exception('Query ' . $this->query . ' failed.'); + } + + $result = @simplexml_load_string($result->data); + + + if (isset($result->body->pre->ERROR)) return FALSE; + + return $result; + } + + public function post($uids) { + $params['db'] = $this->getDatabase(); + $params['id'] = implode(',', $uids); + $this->query = self::BASE_URL . 'epost.fcgi?' . http_build_query($params); + $request_options = array('method' => 'POST'); + $result = drupal_http_request($this->query, $request_options); + + if ($result->code != 200) { + throw new Exception('Query ' . $this->query . ' failed.'); + } + + $result = @simplexml_load_string($result->data); + + if (!$result) { + throw new Exception('Query ' . $this->query . ' failed.'); + } + + if (isset($result->WebEnv)) { + $this->webEnvironment = (string)$result->WebEnv; + $this->queryKey = (int)$result->QueryKey; + $this->count = (int)$result->Count; + } + + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/pubmed/EntrezPubmedArticle.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/pubmed/EntrezPubmedArticle.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,241 @@ +setArticle($pubmedArticle); + } + } + + /** + * Returns the PubMed ID of the article. + * + * @return int + */ + public function setArticle(SimpleXMLElement $pubmedArticle) + { + $this->biblio = array(); + $this->article = $pubmedArticle->MedlineCitation; + $this->pubmeddata = $pubmedArticle->PubmedData; + $this->id = (int)$pubmedArticle->MedlineCitation->PMID; + $this->md5 = md5($pubmedArticle->asXML()); + return $this; + } + public function getId() + { + return $this->id; + } + + /** + * Returns the md5 hash of the serialized XML. + * + * @return string + */ + public function getMd5() + { + return $this->md5; + } + + public function getBiblioAsObject() { + return (object)$this->getBiblio(); + } + + /** + * Returns article elements as an associative array suitable for import into + * a biblio node. + * + * @return array + */ + public function getBiblio() + { + if (empty($this->biblio)) { + if (variable_get('biblio_auto_citekey', 1) ) { + $citekey = ''; + } + else { + $citekey = $this->id; + } + + // Attempts to extract the name of the journal from MedlineTA if + // available. + if (!empty($this->article->MedlineJournalInfo->MedlineTA)) { + $journal = (string)$this->article->MedlineJournalInfo->MedlineTA; + } + elseif (!empty($this->article->Article->Journal->ISOAbbreviation)) { + $journal = (string)$this->article->Article->Journal->ISOAbbreviation; + } + else { + $journal = (string)$this->article->Article->Journal->Title; + } + + $this->biblio = array( + 'title' => (string)$this->article->Article->ArticleTitle, + 'biblio_citekey' => $citekey, + 'biblio_pubmed_id' => $this->id, + 'biblio_pubmed_md5' => $this->md5, + 'biblio_contributors' => $this->contributors(), + // MedlineCitations are always articles from journals or books + 'biblio_type' => 102, + 'biblio_date' => $this->date(), + 'biblio_year' => substr($this->date(), 0, 4), + 'biblio_secondary_title' => $journal, + 'biblio_alternate_title' => (string)$this->article->Article->Journal->ISOAbbreviation, + 'biblio_volume' => (string)$this->article->Article->Journal->JournalIssue->Volume, + 'biblio_issue' => (string)$this->article->Article->Journal->JournalIssue->Issue, + 'biblio_issn' => (string)$this->article->Article->Journal->ISSN, + 'biblio_pages' => (string)$this->article->Article->Pagination->MedlinePgn, + 'biblio_abst_e' => $this->abst(), + 'biblio_custom1' => "http://www.ncbi.nlm.nih.gov/pubmed/{$this->id}?dopt=Abstract", + 'biblio_keywords' => $this->keywords(), + 'biblio_lang' => $this->lang(), + ); + + $doi = $this->article->xpath('.//ELocationID[@EIdType="doi"]/text()'); + if (empty($doi)) { + $doi = $this->pubmeddata->xpath('.//ArticleId[@IdType="doi"]/text()'); + } + if (!empty($doi)) { + $this->biblio['biblio_doi'] = (string)$doi[0]; + } + + $pmcid = $this->pubmeddata->xpath('.//ArticleId[@IdType="pmc"]/text()'); + if (!empty($pmcid)) { + $this->biblio['biblio_pmcid'] = (string)$pmcid[0]; + } + + $grants = $this->grants(); + if (!empty($grants)) { + $this->biblio['biblio_pubmed_grants'] = $grants; + } + } + + return $this->biblio; + } + + /** + * Returns the list of contributors for import obtained from the given + * MedlineCitation element. + * + * @return array + * the contributors of the article + */ + private function contributors() + { + $contributors = array(); + + if (isset($this->article->Article->AuthorList->Author)) { + foreach ($this->article->Article->AuthorList->Author as $author) { + $name = ''; + if (isset($author->CollectiveName)) { + $category = 5; // corporate author + $name = (string)$author->CollectiveName; + } else { + $category = 1; //primary (human) author + $lastname = (string)$author->LastName; + if (isset($author->ForeName)) { + $name = $lastname . ', ' . (string)$author->ForeName; + } elseif (isset($author->FirstName)) { + $name = $lastname . ', ' . (string)$author->FirstName; + } elseif (isset($author->Initials)) { + $name = $lastname . ', ' . (string)$author->Initials; + } + } + if (!empty($name)) { + $contributors[] = array('name' => $name, 'auth_category' => $category); + } + } + } + + return $contributors; + } + + private function grants() { + $grants = array(); + if (isset($this->article->Article->GrantList->Grant)) { + foreach ($this->article->Article->GrantList->Grant as $grant) { + $grants[] = array('grantid' => (string)$grant->GrantID, + 'acronym' => (string)$grant->Acronym, + 'agency' => (string)$grant->Agency, + 'country' => (string)$grant->Country + ); + } + } + return $grants; + } + + /** + * Returns the publication date obtained from the given MedlineCitation's + * PubDate element. See the reference documentation for possible values: + * http://www.nlm.nih.gov/bsd/licensee/elements_descriptions.html#pubdate + * According to the above source it always begins with a four digit year. + * + * @return string + * the publication date of the article + */ + private function date() + { + $pubDate = $this->article->Article->Journal->JournalIssue->PubDate; + + if (isset($pubDate->MedlineDate)) { + $date = (string)$pubDate->MedlineDate; + } else { + $date = implode(' ', (array)$pubDate); + } + + return $date; + } + + private function keywords() { + $keywords = array(); + if (isset($this->article->MeshHeadingList->MeshHeading)) { + foreach ($this->article->MeshHeadingList->MeshHeading as $heading) { + $keywords[] = (string)$heading->DescriptorName; + } + } + return $keywords; + } + + private function lang() { + if (isset($this->article->Article->Language)) { + return (string)$this->article->Article->Language; + } + + } + + private function abst() { + if (isset($this->article->Article->Abstract)) { + $abst = ''; + foreach ($this->article->Article->Abstract->AbstractText as $text) { + $abst .= "

    "; + $attrs = $text->attributes(); + if (isset($attrs['Label'])) { + $abst .= '' . $attrs['Label'] . ': '; + } + $abst .= (string)$text . '

    '; + } + return $abst; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/pubmed/biblio_pm.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/pubmed/biblio_pm.admin.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,101 @@ + 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#group' => 'biblio_settings', + '#title' => t('PubMed'), + '#description' => t('Please select the action to be performed by the PubMed module when it detects changes to an existing entry.'), + '#weight' => 105, + ); + $form['pm_options']['biblio_pm_dup_action'] = array( + '#type' => 'radios', + '#title' => t('Actions'), + '#default_value' => variable_get('biblio_pm_dup_action', 'newrev'), + '#options' => array( + 'newrev' => t('Accept and create a new revision of the existing node.'), + 'replace' => t('Accept and replace the existing node.'), + 'reject' => t('Reject and keep the existing node.'), + ) + ); + $form['pm_options']['biblio_pm_auto_update'] = array( + '#type' => 'checkbox', + '#title' => t('Automatically check for updates via CRON'), + '#return_value' => 1, + '#default_value' => variable_get('biblio_pm_auto_update', 0), + '#description' => t('Entries which were orginally downloaded from PubMed will be periodically checked and updated if the source record has changed.') + ); + $form['pm_options']['pm_cron'] = array( + '#type' => 'fieldset', + '#collapsible' => FALSE, + '#collapsed' => FALSE, +// '#group' => 'pm_options', + '#title' => t('CRON Settings'), + ); + $form['pm_options']['pm_cron']['biblio_pm_update_interval'] = array( + '#type' => 'select', + '#title' => t('PubMed update frequency'), + '#default_value' => variable_get('biblio_pm_update_interval', 3600), + '#options' => array( + 0 => t('Every CRON run'), + 3600 => t('Hourly'), + 86400 => t('Daily'), + 604800 => t('Weeekly'), + ), + '#description' => t('How frequently should we check for updates.'), + '#states' => array( + 'invisible' => array( + 'input[name="biblio_pm_auto_update"]' => array('checked' => FALSE), + ), + ), + ); + $form['pm_options']['pm_cron']['biblio_pm_update_limit'] = array( + '#type' => 'select', + '#title' => t('Number of items to check per CRON run'), + '#default_value' => variable_get('biblio_pm_update_limit', 100), + '#options' => array( + 10 => 10, + 20 => 20, + 50 => 50, + 100 => 100, + 500 => 500, + ), + '#description' => t('The maximum number of items updated in each pass of a cron maintenance task. If necessary, reduce the number of items to prevent timeouts and memory errors while updating.'), + '#states' => array( + 'invisible' => array( + 'input[name="biblio_pm_auto_update"]' => array('checked' => FALSE), + ), + ), + ); + $form['pm_options']['pm_cron']['biblio_pm_age_limit'] = array( + '#type' => 'select', + '#title' => t('Length of time to wait before re-checking for updates'), + '#default_value' => variable_get('biblio_pm_age_limit', 2419200), + '#options' => array( + 86400 => t('1 Day'), + 604800 => t('1 Week'), + 2419200 => t('1 Month'), + 29030400 => t('1 Year'), + ), + '#description' => t('Wait this long before checking for updates on a given node again.'), + '#states' => array( + 'invisible' => array( + 'input[name="biblio_pm_auto_update"]' => array('checked' => FALSE), + ), + ), + ); + + + return ($form); +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/pubmed/biblio_pm.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/pubmed/biblio_pm.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = Biblio - PubMed +description = Provides PubMed import and search to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = EntrezClient.php +files[] = EntrezPubmedArticle.php + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/pubmed/biblio_pm.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/pubmed/biblio_pm.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,158 @@ +fields(array('weight' => 19)) + ->condition('name', 'biblio_pm') + ->execute(); +} + +/** + * Implementation of hook_schema(). + * + * Note: Pro Drupal Development models use of t() to translate 'description' + * for field definitions, but Drupal core does not use them. We follow core. + */ +function biblio_pm_schema() { + $schema = array(); + $schema['biblio_pubmed'] = array( + 'fields' => array( + 'biblio_pubmed_id' => array( + 'type' => 'int', + 'not null' => TRUE + ), + 'biblio_pmcid' => array( + 'type' => 'varchar', + 'length' => 20, + ), + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE + ), + 'biblio_pubmed_md5' => array( + 'type' => 'char', + 'length' => 32, + 'not null' => TRUE + ), + 'biblio_pm_changed' => array( + 'description' => 'The Unix timestamp when the pmid was most recently saved.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0 + ), + ), + 'primary key' => array('nid'), + ); + + $schema['biblio_pubmed_grant_info'] = array( + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'unsigned' => TRUE, + ), + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE + ), + 'biblio_pubmed_id' => array( + 'type' => 'int', + 'not null' => TRUE + ), + 'grantid' => array( + 'type' => 'varchar', + 'length' => 255, + ), + 'acronym' => array( + 'type' => 'varchar', + 'length' => 2, + ), + 'agency' => array( + 'type' => 'varchar', + 'length' => 255, + ), + 'country' => array( + 'type' => 'varchar', + 'length' => 255, + ), + ), + 'primary key' => array('id'), + ); + return $schema; +} + + +/** +* +* UPDATES +* +*/ + + +/** +* +* add two new fields to the biblio_pubmed table +* +*/ + +function biblio_pm_update_7001() { + $spec = array( + 'description' => 'The Unix timestamp when the pmid was most recently saved.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0 + ); + db_add_field('biblio_pubmed', 'biblio_pm_changed', $spec); + $spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('biblio_pubmed', 'biblio_pmcid', $spec); +} +/** +* +* change field type for biblio_pmcid field to the biblio_pubmed table +* +*/ + +function biblio_pm_update_7002() { + $spec = array( + 'type' => 'varchar', + 'length' => 20, + 'not null' => FALSE, + ); + db_change_field('biblio_pubmed', 'biblio_pmcid', 'biblio_pmcid', $spec); +} +/** + * Add a new table 'biblio_pubmed_grant_info' + */ +function biblio_pm_update_7004() { + $schema = biblio_pm_schema(); + $spec = $schema['biblio_pubmed_grant_info']; + db_create_table('biblio_pubmed_grant_info', $spec); +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/pubmed/biblio_pm.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/pubmed/biblio_pm.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,525 @@ += variable_get('biblio_pm_update_next_execution', 0)) { + $ids = array(); + $result = db_select('biblio_pubmed', 'bpm') + ->fields('bpm', array('nid', 'biblio_pubmed_id')) + ->condition('biblio_pm_changed', $age_limit, '<') + ->orderBy('nid', 'ASC') + ->range(0, $count_limit) + ->execute(); + foreach ($result as $pm) { + $ids[$pm->nid] = $pm->biblio_pubmed_id; + } + if (count($ids)) { + + list($nids, $dups) = biblio_pm_import_ids($ids); + + if (count($nids)) { + foreach ($nids as $nid) { + $message = ''; + $message = t('!nid was updated due to changes originating at !url', array( + '!nid' => l($nid, 'node/' . $nid), + '!url' => l(t('PubMed'), 'http://www.ncbi.nlm.nih.gov/pubmed/' . $ids[$nid]), + ) + ); + watchdog('biblio_pm', $message, array(), WATCHDOG_WARNING); + } + } + + if (count($dups)) { + $count = count($dups); + $message = format_plural($count, 'One duplicate PubMed entry was checked, but no changes were found.', + '@count PubMed entries were checked, but no changes were found.'); + watchdog('biblio_pm', $message, array('@count' => $count), WATCHDOG_INFO); + + $now = time(); + + db_update('biblio_pubmed') + ->fields(array('biblio_pm_changed' => $now)) + ->condition('nid', $dups, 'IN') + ->execute(); + } + } + else { + $message = t('There were no PubMed entries older than @age to check.', array( + '@age' => format_interval($age)) + ); + } + watchdog('biblio_pm', $message, array(), WATCHDOG_INFO); + variable_set('biblio_pm_update_next_execution', time() + $interval); + } + } +} + +function biblio_pm_form_biblio_admin_settings_alter(&$form, &$form_state) { + module_load_include('inc', 'biblio_pm', 'biblio_pm.admin'); + $form += biblio_pm_settings_form(); +} + +function biblio_pm_form_biblio_node_form_alter(&$form, &$form_state, $form_id) { + + if (($form_id == 'biblio_node_form') && isset($form_state['biblio_fields'])) { + + if (isset($form_state['values']['biblio_pubmed_id'])) { + $default_pubmed_id = $form_state['values']['biblio_pubmed_id']; + } + elseif (isset($form_state['node']->biblio_pubmed_id)) { + $default_pubmed_id = $form_state['node']->biblio_pubmed_id ; + } + else { + $default_pubmed_id = ''; + } + + if (isset($form_state['values']['biblio_pmcid'])) { + $default_pmcid = $form_state['values']['biblio_pmcid']; + } + elseif (isset($form_state['node']->biblio_pmcid)) { + $default_pmcid = $form_state['node']->biblio_pmcid ; + } + else { + $default_pmcid = ''; + } + + $form['biblio_tabs'][4]['biblio_pubmed_id'] = array( + '#type' => 'textfield', + '#title' => t('PMID'), + '#required' => FALSE, + '#description' => t('PubMed ID'), + '#default_value' => $default_pubmed_id, + '#size' => 50, + '#maxlength' => 50, + ); + + + $form['biblio_tabs'][4]['biblio_pmcid'] = array( + '#type' => 'textfield', + '#title' => t('PMCID'), + '#required' => FALSE, + '#description' => t('PubMed Central ID'), + '#default_value' => $default_pmcid, + '#size' => 50, + '#maxlength' => 50, + ); + + } + if ((!isset($form_state['biblio_type']) || empty($form_state['biblio_type'])) && !isset($form_state['node']->nid)) { + $form['biblio_pubmed_lookup'] = array( + '#type' => 'fieldset', + '#title' => t('PubMed Lookup'), + '#weight' => -20, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['biblio_pubmed_lookup']['PMID'] = array( + '#type' => 'textfield', + '#title' => t('PubMed ID'), + '#required' => FALSE, + '#default_value' => '', + '#description' => t('Enter a PubMed ID'), + '#size' => 60, + '#maxlength' => 255, + '#weight' => -4 + ); + $form['biblio_pubmed_lookup']['pubmed_submit'] = array( + '#type' => 'submit', + '#value' => t('Populate using PubMed'), + '#submit' => array('biblio_pm_form_biblio_node_form_submit') + ); + // $form['#submit'] = array_merge(array('biblio_pm_form_biblio_node_form_submit'), isset($form['#submit'])?$form['#submit']:array()); // put my validator first + } + if (isset($form_state['values']['biblio_pubmed_id'])) { + $form['biblio_pubmed_id'] = array('#type' => 'value', '#value' => $form_state['values']['biblio_pubmed_id']); + } + if (isset($form_state['values']['biblio_pubmed_md5'])) { + $form['biblio_pubmed_md5'] = array('#type' => 'value', '#value' => $form_state['values']['biblio_pubmed_md5']); + } + if (isset($form_state['values']['biblio_pmcid'])) { + $form['biblio_pmcid'] = array('#type' => 'value', '#value' => $form_state['values']['biblio_pmcid']); + } + if (isset($form_state['values']['biblio_pubmed_grants'])) { + $form['biblio_pubmed_grants'] = array('#type' => 'value', '#value' => $form_state['values']['biblio_pubmed_grants']); + } + +} + +function biblio_pm_form_biblio_node_form_submit($form, &$form_state) { + $node_data = array(); + if (strlen($pmid = $form_state['values']['PMID'])) { + if (!($dup = biblio_pm_check_pmid($pmid))) { + module_load_include('php', 'biblio_pm', 'EntrezClient'); + module_load_include('php', 'biblio_pm', 'EntrezPubmedArticle'); + $Eclient = new BiblioEntrezClient; + try { + $result = $Eclient->fetch($pmid); + } catch (Exception $e) { + form_set_error($e->getMessage()); + } + if (!isset($result->PubmedArticle)) { + unset($form_state['values']['biblio_type']); + unset($form_state['post']['biblio_type']); + form_set_error('PMID', 'No data available for PubMed ID: ' . check_plain($pmid)); + return; + } + $data = new BiblioEntrezPubmedArticle($result->PubmedArticle); + $node_data = $data->getBiblio(); + } + else { + $message = t('The PubMed ID that you are trying to import already exists in the database, see !url', array('!url' => l('node/' . $dup, 'node/' . $dup))); + form_set_error('PMID', $message); + $form_state['rebuild'] = TRUE; + $form_state['submitted'] = FALSE; + unset($form_state['values']['biblio_type']); + } + } + if (!empty($node_data)) { + $form_state['values'] = array_merge($form_state['values'], $node_data); + $form_state['input']['biblio_type'] = $form_state['biblio_type'] = $node_data['biblio_type']; + $form_state['input']['biblio_pmcid'] = $form_state['biblio_pmcid'] = isset($node_data['biblio_pmcid']) ? $node_data['biblio_pmcid'] : ''; + $form_state['input']['biblio_pubmed_id'] = $form_state['biblio_pubmed_id'] = $node_data['biblio_pubmed_id']; + $form_state['input']['biblio_pubmed_grants'] = $form_state['biblio_pubmed_grants'] = isset($node_data['biblio_pmcid']) ? $node_data['biblio_pmcid'] : array(); + $form_state['rebuild'] = TRUE; + } + + return; +} + +function biblio_pm_biblio_import_options() { + return array( + 'biblio_pm' => t('PubMed ID List'), + 'biblio_pm_xml' => t('PubMed XML') + ); +} + +function biblio_pm_biblio_import($file, $terms = array(), $batch = FALSE, $session_id = NULL, $save = TRUE, $string = FALSE) { + $nids = array(); + $dups = array(); + $pmids = file($file->uri, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (empty($pmids)) { + drupal_set_message(t("Could not open PubMed ID file"), 'error'); + return; + } + return biblio_pm_import_ids($pmids, $terms, $batch, $session_id); +} + +/** + * Imports article(s) from an XML file (exported) from PubMed Central + * + * @param file object $file + * @param array $terms + * @param bool $batch + * @param string $session_id + * @return array + */ +function biblio_pm_xml_biblio_import($file, $terms = array(), $batch = FALSE, $session_id = NULL) { + libxml_use_internal_errors(true); + $xml = @simplexml_load_file($file->uri); + if (empty($xml) || isset($xml->body->pre->ERROR)) { + drupal_set_message(t("Could not parse file as PubMed XML"), 'error'); + return; + } + return _biblio_pm_create_node_from_xml($xml, $terms, $batch, $session_id); +} + +/** + * Imports multiple PMIDs + * + * @param array $pmids + * @param array $terms + * @param bool $batch + * @param string $session_id + * @return array: + */ +function biblio_pm_import_ids($pmids, $terms = array(), $batch = FALSE, $session_id = NULL) { + module_load_include('php', 'biblio_pm', 'EntrezClient'); + $retmax = 100; + $resmax = count($pmids); + $start = 0; + $Eclient = new BiblioEntrezClient; + $Eclient->post($pmids); + $Eclient->setReturnMax($retmax); + $nids = array(); + $dups = array(); + while ($start < $resmax && ($result = $Eclient->fetchRecords($start))) { + $start += count($result->xpath('//PubmedArticle')); + list($nid, $dup) = _biblio_pm_create_node_from_xml($result, $terms, $batch, $session_id); + $nids = array_merge($nids, $nid); + $dups = array_merge($dups, $dup); + } + + return array($nids, $dups); +} + +/** + * Fetches the article for the given PMID and returns a biblio node object + * + * Returns an empty node object if the PMID is not found. + * + * @param int $pmid + * @return stdClass $node + */ +function biblio_pm_fetch_pmid($pmid) { + $node = new stdClass; + $Eclient = new BiblioEntrezClient; + $paser = new BiblioEntrezPubmedArticle(); + try { + $xml = $Eclient->fetch($pmid); + } + catch (Exception $e) { + return $node; + } + $articles = $xml->xpath('//PubmedArticle'); + if (count($articles)) { + $node = $paser->setArticle($articles[0])->getBiblioAsObject(); + if (!empty($node)) { + $node->type = 'biblio'; + node_object_prepare($node); + } + } + return $node; +} + +function _biblio_pm_create_node_from_xml($xml, $terms, $batch, $session_id) { + module_load_include('php', 'biblio_pm', 'EntrezPubmedArticle'); + $nids = array(); + $dups = array(); + $node = new stdClass(); + $data = new BiblioEntrezPubmedArticle(); + + foreach ($xml->xpath('//PubmedArticle') as $article) { + $node = $data->setArticle($article)->getBiblioAsObject(); + if (isset($node)) { + $dup = biblio_pm_check_md5($node->biblio_pubmed_id, $node->biblio_pubmed_md5); + $action = variable_get('biblio_pm_dup_action', 'newrev'); + if ($dup < 0 && $action == 'newrev') { //entry has be imported before, but may have changed + // Load the node in order to preserve all its data and merge the new + // data from pubmed. + $node = (object) array_merge((array)node_load(-$dup), (array)$node); + $node->nid = -$dup; + $node->revision = 1; + $curr_date = format_date(time()); + $node->log = t('Automatically updated by the Biblio PubMed module on !date due to changes at !url', array( + '!date' => $curr_date, + '!url' => l('PubMed', 'http://www.ncbi.nlm.nih.gov/pubmed/' . $node->biblio_pubmed_id), + )); + $dup = NULL; + } + if ($dup < 0 && $action == 'replace') { //entry has be imported before, but may have changed + $node->nid = -$dup; + $existing_node = db_query("SELECT * FROM {node} WHERE nid=:nid", array(':nid' => $node->nid))->fetchObject(); + $node = (object) array_merge((array)$existing_node, (array)$node); + $dup = NULL; + } + if (!$dup) { + // Allows other modules to alter the node before it is being saved. (Note: $article is a SimpleXML object) + drupal_alter('biblio_pm_node', $node, $article); + biblio_save_node($node, $terms, $batch, $session_id); + if (!empty($node->nid)) $nids[] = $node->nid; + } + else { + $dups[] = $dup; + } + $node = NULL; + } + } + return array($nids, $dups); +} + +function biblio_pm_check_pmid($pmid) { + return db_query("SELECT nid FROM {biblio_pubmed} WHERE biblio_pubmed_id = :pmid", array(':pmid' => $pmid))->fetchField(); +} + +function biblio_pm_biblio_lookup_link_settings() { + return array('pubmed' => t('PubMed')); +} + +function biblio_pm_biblio_lookup_link($node) { + $show_link = variable_get('biblio_lookup_links', array('pubmed' => TRUE)); + if (!isset($show_link['pubmed']) || + !$show_link['pubmed'] || + !isset($node) || + $node->type != 'biblio' || + !isset($node->biblio_pubmed_id)) { + return array(); + } + + $link = 'http://www.ncbi.nlm.nih.gov/pubmed/' . $node->biblio_pubmed_id . '?dopt=Abstract'; + $attrs = array('title' => t("Click to view the PubMed listing for this node")); + if (variable_get('biblio_links_target_new_window', NULL)) { + $attrs = array_merge($attrs, array('target' => '_blank')); + } + return array('biblio_pubmed' => array( + 'title' => t('PubMed'), + 'href' => $link, + 'attributes' => $attrs, + )); +} + +function biblio_pm_node_view($node, $view_mode, $langcode) { + if ($node->type == 'biblio' && isset($node->biblio_pubmed_id)) { + switch ($view_mode) { + case 'full': + case 'teaser': + $node->content['links']['biblio_pubmed'] = array( + '#links' => biblio_pm_biblio_lookup_link($node), + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} + +function biblio_pm_node_delete($node) { + if ($node->type != 'biblio') { + return; + } + db_delete('biblio_pubmed') + ->condition('nid', $node->nid) + ->execute(); + db_delete('biblio_pubmed_grant_info') + ->condition('nid', $node->nid) + ->execute(); +} + + +function biblio_pm_node_insert($node) { + if (isset($node->biblio_pubmed_id) && !empty($node->biblio_pubmed_id)) { + $node->biblio_pm_changed = time(); + drupal_write_record('biblio_pubmed', $node); + } + if (isset($node->biblio_pubmed_grants) && is_array($node->biblio_pubmed_grants)) { + foreach ($node->biblio_pubmed_grants as $grant) { + $info = array( + 'nid' => $node->nid, + 'biblio_pubmed_id' => $node->biblio_pubmed_id + ); + $info += $grant; + drupal_write_record('biblio_pubmed_grant_info', $info); + } + } +} + +function biblio_pm_node_update($node) { + if (isset($node->biblio_pubmed_id) && !empty($node->biblio_pubmed_id)) { + $node->biblio_pm_changed = time(); + drupal_write_record('biblio_pubmed', $node, 'nid'); + } + if (isset($node->biblio_pubmed_grants) && is_array($node->biblio_pubmed_grants) && !empty($node->biblio_pubmed_grants)) { + db_delete('biblio_pubmed_grant_info') + ->condition('nid', $node->nid) + ->execute(); + foreach ($node->biblio_pubmed_grants as $grant) { + $info = array( + 'nid' => $node->nid, + 'biblio_pubmed_id' => $node->biblio_pubmed_id + ); + $info += $grant; + drupal_write_record('biblio_pubmed_grant_info', $info); + } + } +} + +function biblio_pm_node_load($nodes, $types) { + $result = db_select('biblio_pubmed', 'bpm') + ->fields('bpm', array('nid', 'biblio_pubmed_id', 'biblio_pmcid', 'biblio_pubmed_md5')) + ->condition('nid', array_keys($nodes)) + ->execute(); + +// $result = db_query('SELECT biblio_pubmed_id FROM {biblio_pubmed} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes))); + foreach ($result as $record) { + $nodes[$record->nid]->biblio_pubmed_id = $record->biblio_pubmed_id; + $nodes[$record->nid]->biblio_pmcid = $record->biblio_pmcid; + $nodes[$record->nid]->biblio_pubmed_md5 = $record->biblio_pubmed_md5; + } + + $result = db_select('biblio_pubmed_grant_info', 'bpmgi') + ->fields('bpmgi') + ->condition('nid', array_keys($nodes)) + ->execute(); + + foreach ($result as $record) { + $nodes[$record->nid]->biblio_pubmed_grants[] = array( + 'grantid' => $record->grantid, + 'acronym' => $record->acronym, + 'agency' => $record->agency, + 'country' => $record->country); + } +} + +function biblio_pm_check_md5($pmid, $md5) { + static $pm_md5s = array(); + static $pm_nids = array(); + if (empty($pm_md5s)) { + $result = db_query("SELECT * FROM {biblio_pubmed} "); + foreach ($result as $row ) { + $pm_md5s[$row->biblio_pubmed_md5] = $row->nid; + $pm_nids[$row->biblio_pubmed_id] = $row->nid; + } + } + if (isset($pm_nids[$pmid]) && isset($pm_md5s[$md5])) { // must be an exact duplicate of an existing node (return the nid) + return $pm_md5s[$md5]; + } + elseif (isset($pm_nids[$pmid]) && !isset($pm_md5s[$md5])) { //pmid has been save previously but content must have changed (return negative nid) + return -$pm_nids[$pmid]; + } + else { + $pm_md5s[$md5] = TRUE; // gaurd against duplicates in the same import + $pm_nids[$pmid] = TRUE; + return; + } +} + +function biblio_pm_views_api() { + return array('api' => 2); +} + +function biblio_pm_biblio_node_table_rows_alter(&$rows, $node) { + if (isset($node->biblio_pubmed_id) && !empty($node->biblio_pubmed_id)) { + $rows[] = array( + array( + 'data' => t('PubMed ID'), + 'class' => array('biblio-row-title') + ), + array( + 'data' => l($node->biblio_pubmed_id, 'http://www.ncbi.nlm.nih.gov/pubmed/' . $node->biblio_pubmed_id . '?dopt=Abstract') + ) + ); + } + if (isset($node->biblio_pmcid) && !empty($node->biblio_pmcid)) { + $rows[] = array( + array( + 'data' => t('PubMed Central ID'), + 'class' => array('biblio-row-title') + ), + array( + 'data' => check_plain($node->biblio_pmcid) + ) + ); + } + if (isset($node->biblio_pubmed_grants) && is_array($node->biblio_pubmed_grants)) { + + foreach ($node->biblio_pubmed_grants as $grant) { + $list[] = check_plain(implode(' / ', $grant)); + } + $rows[] = array( + array( + 'data' => t('Grant List'), + 'class' => array('biblio-row-title') + ), + array( + 'data' => implode('
    ', $list) + ) + ); + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/pubmed/biblio_pm.views.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/pubmed/biblio_pm.views.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,157 @@ + 'nid', + 'title' => t('PubMed Table'), + 'help' => t('This table contains PubMed Identifiers'), + 'weight' => 10, + ); + + $data['biblio_pubmed']['table']['join'] = array( + 'node' => array( + 'left_field' => 'nid', + 'field' => 'nid', + ), + ); + + $data['biblio_pubmed']['biblio_pubmed_id'] = array( + 'title' => t('PubMed ID'), + 'help' => t('The PubMed ID (http://pubmed.org)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_integer', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_integer', + ), + + ); + $data['biblio_pubmed']['biblio_pmcid'] = array( + 'title' => t('PMCID'), + 'help' => t('The PubMed Central ID (http://www.ncbi.nlm.nih.gov/pmc/)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + + ); + $data['biblio_pubmed_grant_info']['table']['group'] = t('Biblio'); + $data['biblio_pubmed_grant_info']['table']['base'] = array( + 'field' => 'nid', + 'title' => t('PubMed Grant Table'), + 'help' => t('This table contains Grant information for PubMed entries'), + 'weight' => 10, + ); + + $data['biblio_pubmed_grant_info']['table']['join'] = array( + 'node' => array( + 'left_field' => 'nid', + 'field' => 'nid', + ), + ); + $data['biblio_pubmed_grant_info']['biblio_pubmed_id'] = array( + 'title' => t('PubMed ID'), + 'help' => t('The PubMed ID (http://pubmed.org)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_integer', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_integer', + ), + ); + $data['biblio_pubmed_grant_info']['grantid'] = array( + 'title' => t('PubMed Grant ID'), + 'help' => t('The PubMed ID (http://pubmed.org)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + $data['biblio_pubmed_grant_info']['acronym'] = array( + 'title' => t('PubMed Grant Acronym'), + 'help' => t('The PubMed ID (http://pubmed.org)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + $data['biblio_pubmed_grant_info']['agency'] = array( + 'title' => t('PubMed Grant Agency'), + 'help' => t('The PubMed ID (http://pubmed.org)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + $data['biblio_pubmed_grant_info']['country'] = array( + 'title' => t('PubMed Grant Country'), + 'help' => t('The PubMed ID (http://pubmed.org)'), + 'field' => array( + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'handler' => 'views_handler_argument_string', + ), + ); + + return $data; +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/rtf/biblio_rtf.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/rtf/biblio_rtf.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = Biblio - RTF +description = Provides Rich Text Format file export to the Biblio module. +core = 7.x +package = Biblio +dependencies[] = biblio +files[] = rtf_export.inc +files[] = views/biblio_handler_field_export_link_rtf.inc + +; Information added by drupal.org packaging script on 2013-07-20 +version = "7.x-1.0-rc7" +core = "7.x" +project = "biblio" +datestamp = "1374290470" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/rtf/biblio_rtf.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/rtf/biblio_rtf.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,26 @@ +fields(array('weight' => 22)) + ->condition('name', 'biblio_rtf') + ->execute(); +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/rtf/biblio_rtf.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/rtf/biblio_rtf.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,95 @@ + 2, + 'path' => drupal_get_path('module', 'biblio_rtf') . '/views', + ); +} + +function biblio_rtf_biblio_export_options() { + return array('rtf' => t('RTF')); +} + +function biblio_rtf_node_view($node, $view_mode) { + if ($node->type == 'biblio') { + switch ($view_mode) { + case 'full': + case 'teaser': + $links = biblio_rtf_biblio_export_link($node->nid); + $node->content['links']['biblio_rtf'] = array( + '#links' => $links, + '#attributes' => array('class' => array('links', 'inline')), + ); + } + } +} +function biblio_rtf_link($type, $node = NULL, $teaser = FALSE) { + if ($type != 'node' && $node->type != 'biblio') return; + + return biblio_rtf_biblio_export_link($node->nid); +} + +/** + * Creates a link to export a node (or view) in rtf format + * + * @param $base this is the base url (defaults to /biblio) + * @param $nid the node id, if NULL then the current view is exported + * @return a link (rtf) + */ +function biblio_rtf_biblio_export_link($nid = NULL, $filter = array()) { + $show_link = variable_get('biblio_export_links', array('rtf' => TRUE)); + if (!isset($show_link['rtf']) || empty($show_link['rtf']) || !biblio_access('export') ) { + return array(); + } + $base = variable_get('biblio_base', 'biblio'); + + $link = array( + 'attributes' => array( + 'title' => t("Click to download the RTF formatted file"), + 'rel' => 'nofollow') + ); + + $link['href'] = (!empty($nid) ? "$base/export/rtf/$nid" : "$base/export/rtf"); + $link['title'] = t('RTF'); + + if (empty($nid) && !empty($filter)) { // add any filters which may be on the current page + $link['query'] = $filter; + } + return array('biblio_rtf' => $link); +} + +function biblio_rtf_biblio_export($nids) { + $count = 0; + $variables = array( + 'style_name' => biblio_get_style(), + ); + + $nodes = node_load_multiple($nids, array(), TRUE); + foreach ($nodes as $node) { + if (variable_get('biblio_hide_bibtex_braces', 0) ) { + $node->title = biblio_remove_brace($node->title); + } + $count++; + $variables['node'] = $node; + + if ($count == 1) { + $rtf = new rtf(); + $rtf->setPaperSize(5); + $rtf->setPaperOrientation(1); + $rtf->setDefaultFontFace(1); + $rtf->setDefaultFontSize(24); + $rtf->setAuthor("Biblio 7.x"); + $rtf->setOperator(""); + $rtf->setTitle("Biblio RTF Export"); + $rtf->addColour("#000000"); + } + + $rtf->addText(filter_xss(theme('biblio_style', $variables) . '

    ', array('i', 'b', 'br', 'u', 'p', 'strong', 'em', 'sub', 'sup', 'ul', 'li'))); + } + if ($count > 0) $rtf->getDocument(); +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/modules/rtf/rtf_export.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/modules/rtf/rtf_export.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,347 @@ +} {\author } {\operator }} + var $info_table = array(); + var $page_width; + var $page_height; + var $page_size; + var $page_orientation; + var $rtf_version; + var $tab_width; + + var $document; + var $buffer; + var $inch; + var $cm; + var $mm; + + function rtf() { + $this->inch = 1440; + $this->cm = 567; + $this->mm = 56.7; + + $this->fonts_array[] = array( + "name" => "Arial", + "family" => "swiss", + "charset" => 0 + ); + + $this->fonts_array[] = array( + "name" => "Times New Roman", + "family" => "roman", + "charset" => 0 + ); + + $this->fonts_array[] = array( + "name" => "Verdana", + "family" => "swiss", + "charset" => 0 + ); + + $this->fonts_array[] = array( + "name" => "Symbol", + "family" => "roman", + "charset" => 2 + ); + + $this->setDefaultFontFace(0); + $this->setDefaultFontSize(24); + $this->setPaperSize(5); + $this->setPaperOrientation(1); + $this->rtf_version = 1; + $this->tab_width = 360; + } + + function setDefaultFontFace($face) { + $this->font_face = $face; // $font is interger + } + + function setDefaultFontSize($size) { + $this->font_size = $size; + } + + function setTitle($title="") { + $this->info_table["title"] = $title; + } + + function setAuthor($author="") { + $this->info_table["author"] = $author; + } + + function setOperator($operator="") { + $this->info_table["operator"] = $operator; + } + + function setPaperSize($size=0) { + + // 1 => Letter (8.5 x 11 inch) + // 2 => Legal (8.5 x 14 inch) + // 3 => Executive (7.25 x 10.5 inch) + // 4 => A3 (297 x 420 mm) + // 5 => A4 (210 x 297 mm) + // 6 => A5 (148 x 210 mm) + // Orientation considered as Portrait + + switch ($size) { + case 1: + $this->page_width = floor(8.5*$this->inch); + $this->page_height = floor(11*$this->inch); + $this->page_size = 1; + break; + case 2: + $this->page_width = floor(8.5*$this->inch); + $this->page_height = floor(14*$this->inch); + $this->page_size = 5; + break; + case 3: + $this->page_width = floor(7.25*$this->inch); + $this->page_height = floor(10.5*$this->inch); + $this->page_size = 7; + break; + case 4: + $this->page_width = floor(297*$this->mm); + $this->page_height = floor(420*$this->mm); + $this->page_size = 8; + break; + case 5: + default: + $this->page_width = floor(210*$this->mm); + $this->page_height = floor(297*$this->mm); + $this->page_size = 9; + break; + case 6: + $this->page_width = floor(148*$this->mm); + $this->page_height = floor(210*$this->mm); + $this->page_size = 10; + break; + } + } + + function setPaperOrientation($orientation=0) { + // 1 => Portrait + // 2 => Landscape + + switch ($orientation) { + case 1: + default: + $this->page_orientation = 1; + break; + case 2: + $this->page_orientation = 2; + break; + } + } + + function addColour($hexcode) { + // Get the RGB values + $this->hex2rgb($hexcode); + + // Register in the colour table array + $this->colour_table[] = array( + "red" => $this->colour_rgb["red"], + "green" => $this->colour_rgb["green"], + "blue" => $this->colour_rgb["blue"] + ); + } + + // Convert HEX to RGB (#FFFFFF => r255 g255 b255) + function hex2rgb($hexcode) { + $hexcode = str_replace("#", "", $hexcode); + $rgb = array(); + $rgb["red"] = hexdec(substr($hexcode, 0, 2)); + $rgb["green"] = hexdec(substr($hexcode, 2, 2)); + $rgb["blue"] = hexdec(substr($hexcode, 4, 2)); + + $this->colour_rgb = $rgb; + } + + // Convert newlines into \par + function nl2par($text) { + $text = str_replace("\n", "\\par ", $text); + + return $text; + } + + // Add a text string to the document buffer + function addText($text) { + $text = str_replace("\n", "", $text); + $text = str_replace("\t", "", $text); + $text = str_replace("\r", "", $text); + + $this->document .= $text; + } + + // Ouput the RTF file + function getDocument() { + $this->buffer .= "{"; + // Header + $this->buffer .= $this->getHeader(); + // Font table + $this->buffer .= $this->getFontTable(); + // Colour table + $this->buffer .= $this->getColourTable(); + // File Information + $this->buffer .= $this->getInformation(); + // Default font values + $this->buffer .= $this->getDefaultFont(); + // Page display settings + $this->buffer .= $this->getPageSettings(); + // Parse the text into RTF + $this->buffer .= $this->parseDocument(); + $this->buffer .= "}"; + + header("Content-Type: text/enriched\n"); + header("Content-Disposition: attachment; filename=rtf.rtf"); + echo $this->buffer; + } + + // Header + function getHeader() { + $header_buffer = "\\rtf{$this->rtf_version}\\ansi\\deff0\\deftab{$this->tab_width}\n\n"; + + return $header_buffer; + } + + // Font table + function getFontTable() { + + $font_buffer = "{\\fonttbl\n"; + foreach ($this->fonts_array AS $fnum => $farray) { + $font_buffer .= "{\\f{$fnum}\\f{$farray['family']}\\fcharset{$farray['charset']} {$farray['name']}}\n"; + } + $font_buffer .= "}\n\n"; + + return $font_buffer; + } + + // Colour table + function getColourTable() { + $colour_buffer = ""; + if (sizeof($this->colour_table) > 0) { + $colour_buffer = "{\\colortbl;\n"; + foreach ($this->colour_table AS $cnum => $carray) { + $colour_buffer .= "\\red{$carray['red']}\\green{$carray['green']}\\blue{$carray['blue']};\n"; + } + $colour_buffer .= "}\n\n"; + } + + return $colour_buffer; + } + + // Information + function getInformation() { + $info_buffer = ""; + if (sizeof($this->info_table) > 0) { + $info_buffer = "{\\info\n"; + foreach ($this->info_table AS $name => $value) { + $info_buffer .= "{\\{$name} {$value}}"; + } + $info_buffer .= "}\n\n"; + } + + return $info_buffer; + } + + // Default font settings + function getDefaultFont() { + $font_buffer = "\\f{$this->font_face}\\fs{$this->font_size}\n"; + + return $font_buffer; + } + + // Page display settings + function getPageSettings() { + if ($this->page_orientation == 1) + $page_buffer = "\\paperw{$this->page_width}\\paperh{$this->page_height}\n"; + else + $page_buffer = "\\paperw{$this->page_height}\\paperh{$this->page_width}\\landscape\n"; + + $page_buffer .= "\\pgncont\\pgndec\\pgnstarts1\\pgnrestart\n"; + + return $page_buffer; + } + + // Convert special characters to ASCII + function specialCharacters($text) { + $text_buffer = ""; + for ($i = 0; $i < strlen($text); $i++) + $text_buffer .= $this->escapeCharacter($text[$i]); + + return $text_buffer; + } + + // Convert special characters to ASCII + function escapeCharacter($character) { + $escaped = ""; + if (ord($character) >= 0x00 && ord($character) < 0x20) { + $escaped = "\\'" . dechex(ord($character)); + } + + if ((ord($character) >= 0x20 && ord($character) < 0x80) || ord($character) == 0x09 || ord($character) == 0x0A) { + $escaped = $character; + } + + if (ord($character) >= 0x80 and ord($character) < 0xFF) { + $escaped = "\\'" . dechex(ord($character)); + } + + switch (ord($character)) { + case 0x5C: + case 0x7B: + case 0x7D: + $escaped = "\\" . $character; + break; + } + + return $escaped; + } + + // Parse the text input to RTF + function parseDocument() { + //$doc_buffer = $this->specialCharacters(html_entity_decode($this->document)); + $doc_buffer = html_entity_decode($this->document, ENT_QUOTES, "utf-8"); + $doc_buffer = utf8_decode($doc_buffer); + $doc_buffer = $this->specialCharacters($doc_buffer); + if (preg_match("/
      (.*?)<\/ul>/mi", $doc_buffer)) { + $doc_buffer = str_replace("
        ", "", $doc_buffer); + $doc_buffer = str_replace("
      ", "", $doc_buffer); + $doc_buffer = preg_replace("/
    • (.*?)<\/li>/mi", "\\f3\\'B7\\tab\\f{$this->font_face} \\1\\par", $doc_buffer); + } + + $doc_buffer = preg_replace("/

      (.*?)<\/p>/mi", "\\1\\par ", $doc_buffer); + $doc_buffer = preg_replace("/(.*?)<\/strong>/mi", "\\b \\1\\b0 ", $doc_buffer); + $doc_buffer = preg_replace("/(.*?)<\/em>/mi", "\\i \\1\\i0 ", $doc_buffer); + $doc_buffer = preg_replace("/(.*?)<\/i>/mi", "\\i \\1\\i0 ", $doc_buffer); + $doc_buffer = preg_replace("/(.*?)<\/u>/mi", "\\ul \\1\\ul0 ", $doc_buffer); + $doc_buffer = preg_replace("/(.*?)<\/strike>/mi", "\\strike \\1\\strike0 ", $doc_buffer); + $doc_buffer = preg_replace("/(.*?)<\/sub>/mi", "{\\sub \\1}", $doc_buffer); + $doc_buffer = preg_replace("/(.*?)<\/sup>/mi", "{\\super \\1}", $doc_buffer); + + //$doc_buffer = preg_replace("/

      (.*?)<\/H1>/mi", "\\pard\\qc\\fs40 \\1\\par\\pard\\fs{$this->font_size} ", $doc_buffer); + //$doc_buffer = preg_replace("/

      (.*?)<\/H2>/mi", "\\pard\\qc\\fs32 \\1\\par\\pard\\fs{$this->font_size} ", $doc_buffer); + + $doc_buffer = preg_replace("/

      (.*?)<\/h1>/mi", "\\fs48\\b \\1\\b0\\fs{$this->font_size}\\par ", $doc_buffer); + $doc_buffer = preg_replace("/

      (.*?)<\/h2>/mi", "\\fs36\\b \\1\\b0\\fs{$this->font_size}\\par ", $doc_buffer); + $doc_buffer = preg_replace("/

      (.*?)<\/h3>/mi", "\\fs27\\b \\1\\b0\\fs{$this->font_size}\\par ", $doc_buffer); + + + $doc_buffer = preg_replace("//i", "\\brdrb\\brdrs\\brdrw30\\brsp20 \\pard\\par ", $doc_buffer); + $doc_buffer = str_replace("
      ", "\\par ", $doc_buffer); + $doc_buffer = str_replace("
      ", "\\par ", $doc_buffer); + $doc_buffer = str_replace("", "\\tab ", $doc_buffer); + + $doc_buffer = $this->nl2par($doc_buffer); + + return $doc_buffer; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_ama.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_ama.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,511 @@ + and +// Richard Karnesky + +// This is a citation style file (which must reside within the 'cite/styles/' sub-directory of your refbase root directory). It contains a +// version of the 'citeRecord()' function that outputs a reference list from selected records according to the citation style used by +// the American Medical Association (AMA) + +// based on 'cite_Chicago.php' + +// TODO: - abstracts, conference proceedings, patents, reports +// - book/volume/report/etc titles should be formatted in heading caps +// - don't add a dot if the abbreviated journal (or series title) ends with a dot! + +// Modified for use in biblio by Ron Jerome +// +/** + * Get the style information + * + * @return + * The name of the style + */ +function biblio_style_ama_info() { + return array( + 'ama' => 'American Medical Association (AMA)' + ); +} +function biblio_style_ama_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ', ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '', + 'initialsBeforeAuthorFirstAuthor' => FALSE, + 'initialsBeforeAuthorStandard' => FALSE, + 'shortenGivenNames' => TRUE, + 'numberOfAuthorsTriggeringEtAl' => 6, + 'includeNumberOfAuthors' => 3, + 'customStringAfterFirstAuthors' => ', et al.', + 'encodeHTML' => TRUE + ); + + return $author_options; +} + +function biblio_style_ama($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = ''; + $markupPatternsArray = array("italic-prefix" => "", + "italic-suffix" => "<\/i>", + "endash" => '-'); + $author_options = biblio_style_ama_author_options(); + $authors = biblio_get_contributor_category($node->biblio_contributors, 1); + $editors = biblio_get_contributor_category($node->biblio_contributors, 2); + + if (!empty($authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $authors, 'options' => $author_options)); + } + //$editors = theme('biblio_format_authors', array('contributors' => $node->biblio_contributors[2], 'options' => $author_options, 'inline' => $inline)); + //if (empty($authors)) $authors = theme('biblio_authors', $node->biblio_contributors[5], 'mla', 5, $inline); // if no authors substitute corp author if available + //if (empty($authors)) $authors = '[' . t('Anonymous') . ']'; // use anonymous if we still have nothing. + //$output .= '' . $authors . ".  \n"; + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + + switch ($node->biblio_type) { + case 102: //Journal Article + case 105: //Newspaper Article + case 106: //Magazine Article + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ""; + } + + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + $output .= '"' ; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + + // From here on we'll assume that at least either the 'author' or the 'title' field did contain some contents + // if this is not the case, the output string will begin with a space. However, any preceding/trailing whitespace will be removed at the cleanup stage (see below) + + if (!empty($node->biblio_alternate_title)) // abbreviated journal name + $output .= " " . $node->biblio_alternate_title . ""; + + // if there's no abbreviated journal name, we'll use the full journal name + elseif (!empty($node->biblio_secondary_title)) // publication (= journal) name + $output .= " " . $node->biblio_secondary_title . ""; + + if ($node->biblio_type == 105 || $node->biblio_type == 106 AND !preg_match("/^\d+$/", $node->biblio_volume)) // for newspaper articles (and magazine articles if w/o volume number), volume (=month) and issue (=day) information is printed before the year + { + if (!empty($node->biblio_volume)) // volume (=month) + $output .= ". " . $node->biblio_volume; + + if (!empty($node->biblio_issue)) // issue (=day) + $output .= " " . $node->biblio_issue; + + if (!empty($node->biblio_year)) // year + $output .= ", " . $node->biblio_year; + } + else // journal article (or a magazine article with volume numbers) + { + if (!empty($node->biblio_year)) // year + $output .= ". " . $node->biblio_year; + + if (!empty($node->biblio_volume) || !empty($node->biblio_issue)) + $output .= ";"; + + if (!empty($node->biblio_volume)) // volume + $output .= $node->biblio_volume; + + if (!empty($node->biblio_issue)) // issue + $output .= "(" . $node->biblio_issue . ")"; + } + + if (!empty($node->biblio_pages)) // pages + { + if (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) // only add ": " if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= ":"; + + $output .= theme('biblio_page_number', array('orig_page_info' => $node->biblio_pages, 'page_range_delim' => $markupPatternsArray["endash"])); // function 'formatPageInfo()' is defined in 'cite.inc.php' + } + + if (isset($node->online_publication)) // this record refers to an online article + { + // append an optional string (given in 'online_citation') plus the current date and the DOI (or URL): + + $today = date("F j, Y"); + + if (!empty($row['online_citation'])) // online_citation + { + if (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) // only add ":" or "," if either year, volume, issue, abbrev_journal or publication isn't empty + { + if (empty($node->biblio_pages)) + $output .= ":"; // print instead of pages + else + $output .= ","; // append to pages + } + + $output .= $row['online_citation']; + } + + if (!empty($row['doi'])) // doi + { + if (!empty($row['online_citation']) OR (empty($row['online_citation']) AND (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)))) // only add "." if online_citation isn't empty, or else if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= "."; + + if ($encodeHTML) + $output .= " " . encodeHTML("http://dx.doi.org/" . $row['doi']) . ". Accessed " . $today; + else + $output .= " " . "http://dx.doi.org/" . $row['doi'] . ". Accessed " . $today; + } + elseif (!empty($row['url'])) // url + { + if (!empty($row['online_citation']) OR (empty($row['online_citation']) AND (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)))) // only add "." if online_citation isn't empty, or else if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= "."; + + if ($encodeHTML) + $output .= " " . encodeHTML($row['url']) . ". Accessed " . $today; + else + $output .= " " . $row['url'] . ". Accessed " . $today; + } + + } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + + break; + case 101: //Book Chapter + case 103: //Conference Paper + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + + $output .= '"' ; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + $output .= ''; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + + + if (!empty($editors)) { + $editor_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ', ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '', + 'initialsBeforeAuthorFirstAuthor' => FALSE, + 'initialsBeforeAuthorStandard' => FALSE, + 'shortenGivenNames' => TRUE, + 'numberOfAuthorsTriggeringEtAl' => 6, + 'includeNumberOfAuthors' => 3, + 'customStringAfterFirstAuthors' => ' et al.', + 'encodeHTML' => TRUE + ); + + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + + $output .= " In: " . $editor . ", "; + if (count($editors) > 1) // there are at least two editors (separated by ';') + $output .= "eds"; + else // there's only one editor (or the editor field is malformed with multiple editors but missing ';' separator[s]) + $output .= "ed"; + } + + $publication = preg_replace("/[ \r\n]*\(Eds?:[^\)\r\n]*\)/", "", $node->biblio_secondary_title); + if (!empty($publication)) // publication + { + if (!preg_match("/[?!.] *$/", $output)) $output .= "."; + + if (empty($editor)) + $output .= " In:"; + + // TODO: container titles should be formatted in heading caps, however, this doesn't yet work correctly if the publication title contains HTML entities + $output .= " " . $publication . ""; + // $output .= " " . $markupPatternsArray["italic-prefix"] . changeCase("heading", $publication) . $markupPatternsArray["italic-suffix"]; // function 'changeCase()' is defined in 'include.inc.php' + } + + if (!empty($node->biblio_volume)) // volume + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " Vol. " . $node->biblio_volume; + } + + if (!empty($node->biblio_edition) && !preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) + $output .= "."; + + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") + $editionSuffix = "nd"; + elseif ($node->biblio_edition == "3") + $editionSuffix = "rd"; + else + $editionSuffix = "th"; + } + else + $editionSuffix = ""; + + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) + $editionSuffix .= " ed."; + + $output .= " " . $node->biblio_edition . $editionSuffix; + } + + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + if (!empty($node->biblio_place_published)) // place + $output .= " " . $node->biblio_place_published; + + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) + $output .= ":"; + + $output .= " " . $node->biblio_publisher; + } + + if (!empty($node->biblio_year)) // year + $output .= "; " . $node->biblio_year; + + if (!empty($node->biblio_pages)) // pages + $output .= ":" . theme('biblio_page_number', $node->biblio_pages, $markupPatternsArray["endash"]); // function 'formatPageInfo()' is defined in 'cite.inc.php' + + if (!empty($node->biblio_alternate_title) OR !empty($node->biblio_tertiary_title)) // if there's either a full or an abbreviated series title + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " "; + + if (!empty($node->biblio_alternate_title)) + $output .= $node->biblio_alternate_title; // abbreviated series title + + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_tertiary_title)) + $output .= $node->biblio_tertiary_title; // full series title + + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) + $output .= " "; + + if (!empty($node->biblio_volume)) // series volume + $output .= $node->biblio_volume; + + if (!empty($node->biblio_issue)) // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + $output .= "(" . $node->biblio_issue . ")"; // is it correct to format series issues similar to journal article issues? + } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + + break; + + default : // all other types + //TODO + // if (ereg("[ \r\n]*\(ed\)", $node->author)) // single editor + // $author = $author . ", ed"; + // elseif (ereg("[ \r\n]*\(eds\)", $node->author)) // multiple editors + // $author = $author . ", eds"; + + if (!empty($authors)) // author + { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + + if (!empty($node->title)) // title + { + if (!empty($authors)) + $output .= " "; + + $output .= ''; + // TODO: book/volume/report/etc titles should be formatted in heading caps, however, this doesn't yet work correctly if the publication title contains HTML entities + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + $output .= ''; + } + if ($node->biblio_type == "Software") // for software, add software label + $output .= " [computer program]"; + + if (!empty($node->biblio_volume) AND ($node->biblio_type != "Software")) // volume + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) + $output .= "."; + + $output .= " Vol. " . $node->biblio_volume; + } + + if (!empty($node->biblio_edition)) // edition + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) + $output .= "."; + + if ($row['type'] == "Software") // software edition (=version) + { + $output .= " Version " . $node->biblio_edition; + } + elseif (!preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") + $editionSuffix = "nd"; + elseif ($node->biblio_edition == "3") + $editionSuffix = "rd"; + else + $editionSuffix = "th"; + } + else + $editionSuffix = ""; + + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) + $editionSuffix .= " ed."; + + $output .= " " . $node->biblio_edition . $editionSuffix; + } + } + + if (!empty($editors)) // editor (if different from author, see note above regarding the check for ' (ed)' or ' (eds)') + { + + $editor_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ', ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '', + 'initialsBeforeAuthorFirstAuthor' => FALSE, + 'initialsBeforeAuthorStandard' => FALSE, + 'shortenGivenNames' => TRUE, + 'numberOfAuthorsTriggeringEtAl' => 6, + 'includeNumberOfAuthors' => 3, + 'customStringAfterFirstAuthors' => ' et al.', + 'encodeHTML' => TRUE + ); + + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " " . $editor; + if (count($editors) > 1) // there are at least two editors (separated by ';') + $output .= ", eds"; + else // there's only one editor (or the editor field is malformed with multiple editors but missing ';' separator[s]) + $output .= ", ed"; + } + + if (!empty($row['thesis'])) // thesis + // TODO: do we need to use the term "[dissertation]" instead of "[Ph.D. thesis]", etc? What about other thesis types then? + $output .= " [" . $row['thesis'] . "]"; + + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + if (!empty($node->biblio_place_published)) // place + $output .= " " . $node->biblio_place_published; + + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) + $output .= ":"; + + $output .= " " . $node->biblio_publisher; + } + + $output .= ";"; + + if ($node->biblio_type == "Software") // for software, volume (=month) and issue (=day) information is printed before the year (similar to newspaper articles) + { + if (!empty($node->biblio_volume)) // volume (=month) + $output .= " " . $node->biblio_volume; + + if (!empty($node->biblio_issue)) // issue (=day) + $output .= " " . $node->biblio_issue; + + $output .= ","; + } + + if (!empty($node->biblio_year)) // year + $output .= " " . $node->biblio_year; + + if (!empty($node->biblio_alternate_title) OR !empty($node->biblio_tertiary_title)) // if there's either a full or an abbreviated series title + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " "; + + if (!empty($node->biblio_alternate_title)) + $output .= $node->biblio_alternate_title; // abbreviated series title + + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_tertiary_title)) + $output .= $node->biblio_tertiary_title; // full series title + + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) + $output .= " "; + + if (!empty($node->biblio_volume)) // series volume + $output .= $node->biblio_volume; + + if (!empty($node->biblio_issue)) // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + $output .= "(" . $node->biblio_issue . ")"; // is it correct to format series issues similar to journal article issues? + } + +// if ($row['online_publication'] == "yes" || $row['type'] == "Software") // this record refers to an online article, or a computer program/software +// { +// // append an optional string (given in 'online_citation') plus the current date and the DOI (or URL): +// +// $today = date("F j, Y"); +// +// if (!empty($row['online_citation'])) // online_citation +// { +// if (!preg_match("/\. *$/", $output)) $output .= "."; +// +// $output .= $row['online_citation']; +// } +// +// if (!empty($row['doi'])) // doi +// { +// if (!preg_match("/\. *$/", $output)) $output .= "."; +// +// if ($encodeHTML) +// $output .= " " . encodeHTML("http://dx.doi.org/" . $row['doi']) . ". Accessed " . $today; +// else +// $output .= " " . "http://dx.doi.org/" . $row['doi'] . ". Accessed " . $today; +// } +// elseif (!empty($row['url'])) // url +// { +// if (!preg_match("/\. *$/", $output)) $output .= "."; +// +// if ($encodeHTML) +// $output .= " " . encodeHTML($row['url']) . ". Accessed " . $today; +// else +// $output .= " " . $row['url'] . ". Accessed " . $today; +// } +// +// } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + break; + } + + return filter_xss($output, biblio_get_allowed_tags()); +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_apa.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_apa.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,107 @@ + 'American Psychological Association (APA)' + ); +} + +function biblio_style_apa_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' => ', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', & ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ', ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '. ', //9 + 'initialsBeforeAuthorFirstAuthor' => FALSE, //10 + 'initialsBeforeAuthorStandard' => FALSE, //11 + 'shortenGivenNames' => TRUE, //12 + 'numberOfAuthorsTriggeringEtAl' => 6, //13 + 'includeNumberOfAuthors' => 6, //14 + 'customStringAfterFirstAuthors' => ', et al.',//15 + 'encodeHTML' => TRUE + ); + return $author_options; +} + +/** + * Apply a bibliographic style to the node + * + * + * @param $node + * An object containing the node data to render + * The base URL of the biblio module (defaults to /biblio) + * @return + * The styled biblio entry + */ +function biblio_style_apa($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = ''; + $authors = ''; + $author_options = biblio_style_apa_author_options(); + $primary_authors = biblio_get_contributor_category($node->biblio_contributors, 1); + $editors = biblio_get_contributor_category($node->biblio_contributors, 2); + $corp_authors = biblio_get_contributor_category($node->biblio_contributors, 5); + + if (!empty($primary_authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $primary_authors, 'options' => $author_options)); + } + if (empty($authors) && !empty($corp_authors)) {// if no authors substitute corp author if available + foreach ($corp_authors as $rank => $author) { + $authors .= (empty($authors)) ? '' : ', '; + $authors .= (variable_get('biblio_author_links', 1)) ? theme('biblio_author_link', array('author' => $author)) : $author['name']; + } + } + if (empty($authors)) $authors = '[' . t('Anonymous') . ']'; // use anonymous if we still have nothing. + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + $output .= '' . $authors . " \n"; + $output .= (strrpos($authors, '.') == strlen($authors)) ? ".  " : " "; + switch ($node->biblio_type) { + case 1 : // Journal Article + case 2 : //Conference Paper + case 3 : // are all + case 4 : + case 5 : + case 6 : + case 7 : + case 8 : + case 9 : + default : + if (isset ($node->biblio_year)) { + $output .= "(" . check_plain($node->biblio_year) . ").  "; + } + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ". \n"; + $output .= !empty($editors) ? '(' . theme('biblio_format_authors', array('contributors' => $editors, 'options' => $author_options)) . ', Ed.).' : ""; + $output .= ($node->biblio_secondary_title) ? '' . check_plain($node->biblio_secondary_title) . '. ' : ''; + $output .= ($node->biblio_volume) ? check_plain($node->biblio_volume) . ($node->biblio_issue ? '(' . check_plain($node->biblio_issue) . '), ' : ', ') : '
      '; + // $output .= ($node->biblio_issue) ? '('. check_plain($node->biblio_issue).')' :''; + $output .= ($node->biblio_pages) ? check_plain($node->biblio_pages) . '.' : ''; + break; // generic + } + /* if ($node->biblio_date) $output .= ', '. check_plain($node->biblio_date); + if ($node->biblio_number) $output .= ', Number '. check_plain($node->biblio_number); + + if ($node->biblio_place_published) $output .= ', '. check_plain($node->biblio_place_published); + */ + return filter_xss($output, biblio_get_allowed_tags()); +} +/** + * + */ +function _apa_format_author($author) { + $format = $author['prefix'] . ' ' . $author['lastname']; + $format .= !empty ($author['firstname']) ? ' ' . drupal_substr($author['firstname'], 0, 1) . '.' : ''; + $format .= !empty ($author['initials']) ? $author['initials'] . '.' : ''; + return $format; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_chicago.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_chicago.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,473 @@ + and +// Richard Karnesky +// + +// This is a citation style file (which must reside within the 'cite/styles/' sub-directory of your refbase root directory). It contains a +// version of the 'citeRecord()' function that outputs a reference list from selected records according to the citation style documented +// in the "Chicago Manual of Style" (2003), and Kate Turabian's "Manual for Writer's of Term Papers, Theses, and Dissertations" (1996) + +// Modified for use in biblio by Ron Jerome +// +/** + * Get the style information + * + * @return + * The name of the style + */ +function biblio_style_chicago_info() { + return array( + 'chicago' => 'Chicago' + ); +} +function biblio_style_chicago_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' =>', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', and ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ', ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '. ', //9 + 'initialsBeforeAuthorFirstAuthor' => FALSE, //10 + 'initialsBeforeAuthorStandard' => TRUE, //11 + 'shortenGivenNames' => FALSE, //12 + 'numberOfAuthorsTriggeringEtAl' => 10, //13 + 'includeNumberOfAuthors' => 10, //14 + 'customStringAfterFirstAuthors' => ' et al.',//15 + 'encodeHTML' => TRUE + ); + + return $author_options; +} +/** + * Apply a bibliographic style to the node + * + * + * @param $node + * An object containing the node data to render + * @return + * The styled biblio entry + */ +function biblio_style_chicago($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = ''; + $markupPatternsArray = array("italic-prefix" => '', + "italic-suffix" => '<\/i>', + "endash" => '-'); + $author_options = biblio_style_chicago_author_options(); + $authors = $editor = ''; + $primary_authors = biblio_get_contributor_category($node->biblio_contributors, 1); + $editors = biblio_get_contributor_category($node->biblio_contributors, 2); + if (!empty($primary_authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $primary_authors, 'options' => $author_options)); + } + //$editors = theme('biblio_format_authors', array('contributors' => $node->biblio_contributors[2], 'options' => $author_options, 'inline' => $inline)); + //if (empty($authors)) $authors = theme('biblio_authors', $node->biblio_contributors[5], 'mla', 5, $inline); // if no authors substitute corp author if available + //if (empty($authors)) $authors = '[' . t('Anonymous') . ']'; // use anonymous if we still have nothing. + //$output .= '' . $authors . ".  \n"; + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + + switch ($node->biblio_type) { + case 102: //Journal Article + case 105: //Newspaper Article + case 106: //Magazine Article + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + $output .= '"' ; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + + // From here on we'll assume that at least either the 'author' or the 'title' field did contain some contents + // if this is not the case, the output string will begin with a space. However, any preceding/trailing whitespace will be removed at the cleanup stage (see below) + + if (!empty($node->biblio_secondary_title)) // publication (= journal) name + $output .= " $node->biblio_secondary_title"; + + // if there's no full journal name, we'll use the abbreviated journal name + elseif (!empty($node->biblio_alternate_title)) // abbreviated journal name + $output .= " $node->biblio_alternate_title"; + + if (!empty($node->biblio_volume)) // volume + $output .= " " . $node->biblio_volume; + + if (!empty($node->biblio_issue)) // issue + $output .= ", no. " . $node->biblio_issue; + + if (!empty($node->biblio_year)) // year + $output .= " (" . $node->biblio_year . ")"; + + if (!empty($node->biblio_pages)) // pages + { + if (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) // only add ": " if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= ": "; + + $output .= theme('biblio_page_number', array('orig_page_info' => $node->biblio_pages)); // function 'formatPageInfo()' is defined in 'cite.inc.php' + } + + if (isset($node->online_publication)) // this record refers to an online article + { + // append an optional string (given in 'online_citation') plus the current date and the DOI (or URL): + + $today = date("F j, Y"); + + if (!empty($node->online_citation)) // online_citation + { + if (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) // only add "," if either year, volume, issue, abbrev_journal or publication isn't empty + { + if (empty($node->biblio_pages)) + $output .= ":"; // print instead of pages + else + $output .= ","; // append to pages + } + + $output .= " " . $node->online_citation; + } + + if (!empty($node->doi)) // doi + { + if (!empty($node->online_citation) OR (empty($node->online_citation) AND (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)))) // only add "." if online_citation isn't empty, or else if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= "."; // NOTE: some Chicago examples (e.g. ) use a comma here (not sure what's correct) + + if ($encodeHTML) + $output .= " " . encodeHTML("http://dx.doi.org/" . $node->doi) . " (accessed " . $today . ")"; + else + $output .= " " . "http://dx.doi.org/" . $node->doi . " (accessed " . $today . ")"; + } + elseif (!empty($node->biblio_url)) // url + { + if (!empty($node->online_citation) OR (empty($node->online_citation) AND (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)))) // only add "." if online_citation isn't empty, or else if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= "."; // see note for doi + + if ($encodeHTML) + $output .= " " . encodeHTML($node->biblio_url) . " (accessed " . $today . ")"; + else + $output .= " " . $node->biblio_url . " (accessed " . $today . ")"; + } + + } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + + break; + case 101: //Book Chapter + case 103: //Conference Paper + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + + $output .= '"' ; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + $output .= ''; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + + $publication = preg_replace("/[ \r\n]*\(Eds?:[^\)\r\n]*\)/", "", $node->biblio_secondary_title); + if (!empty($publication)) // publication + $output .= " In $publication"; + + + // From here on we'll assume that at least either the 'author' or the 'title' field did contain some contents + // if this is not the case, the output string will begin with a space. However, any preceding/trailing whitespace will be removed at the cleanup stage (see below) + + if (!empty($editors)) // editor + { + $editor_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ' and ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '. ', + 'initialsBeforeAuthorFirstAuthor' => TRUE, + 'initialsBeforeAuthorStandard' => TRUE, + 'shortenGivenNames' => FALSE, + 'numberOfAuthorsTriggeringEtAl' => 10, + 'includeNumberOfAuthors' => 10, + 'customStringAfterFirstAuthors' => ' et al.', + 'encodeHTML' => TRUE + ); + + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + + $output .= ", edited by " . $editor; + } + + if (!empty($node->biblio_pages)) // pages + $output .= ", " . theme('biblio_page_number', array('orig_page_info' => $node->biblio_pages)); // function 'formatPageInfo()' is defined in 'cite.inc.php' + + if (!empty($node->biblio_edition) && !preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + if (!preg_match("/[?!.][ \"]*$/", $output)) $output .= "."; + + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") + $editionSuffix = "nd"; + elseif ($node->biblio_edition == "3") + $editionSuffix = "rd"; + else + $editionSuffix = "th"; + } + else + $editionSuffix = ""; + + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) + $editionSuffix .= " ed."; + + $output .= " " . $node->biblio_edition . $editionSuffix; + } + + if (!empty($node->biblio_volume)) // volume + { + if (!preg_match("/[?!.][ \"]*$/", $output)) $output .= "."; + + $output .= " Vol. " . $node->biblio_volume; + } + + if (!empty($node->biblio_alternate_title) OR !empty($node->biblio_tertiary_title)) // if there's either a full or an abbreviated series title + { + if (!preg_match("/[?!.][ \"]*$/", $output)) $output .= "."; + + $output .= " "; + + if (!empty($node->biblio_alternate_title)) + $output .= $node->biblio_alternate_title; // abbreviated series title + + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_tertiary_title)) + $output .= $node->biblio_tertiary_title; // full series title + + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) + $output .= " "; + + if (!empty($node->biblio_volume)) // series volume + $output .= $node->biblio_volume; + + // if (!empty($node->biblio_issue)) // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + // $output .= ", no. " . $node->biblio_issue; // is it correct to format series issues similar to journal article issues? + } + + if (!preg_match("/[?!.][ \"]*$/", $output)) $output .= "."; + + if (!empty($node->biblio_place_published)) // place + $output .= " " . $node->biblio_place_published; + + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) + $output .= ":"; + + $output .= " " . $node->biblio_publisher; + } + + if (!empty($node->biblio_year)) // year + $output .= ", " . $node->biblio_year; + + if (!preg_match("/\. *$/", $output)) $output .= "."; + + break; + + default : // all other types + //TODO + // if (preg_match("[ \r\n]*\(ed\)", $node->author)) // single editor + // $author = $author . ", ed"; + // elseif (preg_match("[ \r\n]*\(eds\)", $node->author)) // multiple editors + // $author = $author . ", eds"; + + if (!empty($authors)) // author + { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + + if (!empty($node->title)) // title + { + $url = biblio_get_title_url_info($node); + if (!empty($authors)) + $output .= " "; + + if (!empty($node->thesis)) // thesis + { + $output .= ''; + $output .= '"' . l($node->title, $url['link'], $url['options']); + $output .= ""; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + else // not a thesis + $output .= ''; + $output .= ''; + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + $output .= ''; + } + $publication = preg_replace("/[ \r\n]*\(Eds?:[^\)\r\n]*\)/", "", $node->biblio_secondary_title); + if (!empty($publication)) // publication + $output .= " In $publication"; + + if (!empty($editors)) // editor (if different from author, see note above regarding the check for ' (ed)' or ' (eds)') + { + + $editor_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ' and ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '. ', + 'initialsBeforeAuthorFirstAuthor' => TRUE, + 'initialsBeforeAuthorStandard' => TRUE, + 'shortenGivenNames' => FALSE, + 'numberOfAuthorsTriggeringEtAl' => 10, + 'includeNumberOfAuthors' => 10, + 'customStringAfterFirstAuthors' => ' et al.', + 'encodeHTML' => TRUE + ); + + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + $output .= ", Edited by " . $editor; + } + + if (!empty($node->biblio_edition) && !preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + if (!preg_match("/[?!.][ \"]*$/", $output)) $output .= "."; + + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") + $editionSuffix = "nd"; + elseif ($node->biblio_edition == "3") + $editionSuffix = "rd"; + else + $editionSuffix = "th"; + } + else + $editionSuffix = ""; + + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) $editionSuffix .= " ed."; + + $output .= " " . $node->biblio_edition . $editionSuffix; + } + + if (!empty($node->biblio_volume)) // volume + { + if (!preg_match("/[?!.][ \"]*$/", $output)) $output .= "."; + + $output .= " Vol. " . $node->biblio_volume; + } + + if (!empty($node->biblio_alternate_title) OR !empty($node->biblio_tertiary_title)) // if there's either a full or an abbreviated series title + { + if (!preg_match("/[?!.][ \"]*$/", $output)) $output .= "."; + + $output .= " "; + + if (!empty($node->biblio_alternate_title)) + $output .= $node->biblio_alternate_title; // abbreviated series title + + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_tertiary_title)) + $output .= $node->biblio_tertiary_title; // full series title + + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) + $output .= " "; + + if (!empty($node->biblio_volume)) // series volume + $output .= $node->biblio_volume; + + // if (!empty($node->biblio_issue)) // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + // $output .= ", no. " . $node->biblio_issue; // is it correct to format series issues similar to journal article issues? + } + + if (!empty($node->thesis)) // thesis + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " " . $node->thesis; + $output .= ", " . $node->biblio_publisher; + } + else // not a thesis + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + if (!empty($node->biblio_place_published)) // place + $output .= " " . $node->biblio_place_published; + + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) + $output .= ":"; + + $output .= " " . $node->biblio_publisher; + } + } + + if (!empty($node->biblio_year)) // year + $output .= ", ".$node->biblio_year; + +// if ($node->online_publication == "yes") // this record refers to an online article +// { +// // append an optional string (given in 'online_citation') plus the current date and the DOI (or URL): +// +// $today = date("F j, Y"); +// +// if (!empty($node->online_citation)) // online_citation +// { +// if (!preg_match("/\. *$/", $output)) $output .= "."; +// +// $output .= " " . $node->online_citation; +// } +// +// if (!empty($node->doi)) // doi +// { +// if (!preg_match("/\. *$/", $output)) $output .= "."; +// +// if ($encodeHTML) +// $output .= " " . encodeHTML("http://dx.doi.org/" . $node->doi) . " (accessed " . $today . ")"; +// else +// $output .= " " . "http://dx.doi.org/" . $node->doi . " (accessed " . $today . ")"; +// } +// elseif (!empty($node->biblio_url)) // url +// { +// if (!preg_match("/\. *$/", $output)) $output .= "."; +// +// if ($encodeHTML) +// $output .= " " . encodeHTML($node->biblio_url) . " (accessed " . $today . ")"; +// else +// $output .= " " . $node->biblio_url . " (accessed " . $today . ")"; +// } +// +// } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + break; + } + + return filter_xss($output, biblio_get_allowed_tags()); +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_classic.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_classic.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,85 @@ + 'Classic - This is the original biblio style' + ); +} +function biblio_style_classic_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' => ', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', and ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ', ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '. ', //9 + 'initialsBeforeAuthorFirstAuthor' => FALSE, //10 + 'initialsBeforeAuthorStandard' => FALSE, //11 + 'shortenGivenNames' => FALSE, //12 + 'numberOfAuthorsTriggeringEtAl' => 10, //13 + 'includeNumberOfAuthors' => 10, //14 + 'customStringAfterFirstAuthors' => ', et al.',//15 + 'encodeHTML' => TRUE + ); + return $author_options; +} + +/** + * Apply a bibliographic style to the node + * + * + * @param $node + * An object containing the node data to render + * @return + * The styled biblio entry + */ +function biblio_style_classic($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = ''; + $author_options = biblio_style_classic_author_options(); + $authors = $editor = ''; + $primary_authors = biblio_get_contributor_category($node->biblio_contributors, 1); + if(!empty($primary_authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $primary_authors, 'options' => $author_options)); + } + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ", \n"; + $output .= '' . $authors . " \n"; + if ($node->biblio_secondary_title) { + $output .= ', ' . check_plain($node->biblio_secondary_title); + } + if ($node->biblio_date) + $output .= ', ' . check_plain($node->biblio_date); + if ($node->biblio_volume) + $output .= ', Volume ' . check_plain($node->biblio_volume); + if ($node->biblio_issue) + $output .= ', Issue ' . check_plain($node->biblio_issue); + if ($node->biblio_number) + $output .= ', Number ' . check_plain($node->biblio_number); + if ($node->biblio_place_published) + $output .= ', ' . check_plain($node->biblio_place_published); + if ($node->biblio_pages) + $output .= ', p.' . check_plain($node->biblio_pages); + if (isset ($node->biblio_year)) { + $output .= ', (' . check_plain($node->biblio_year) . ")\n"; + } + return filter_xss($output, biblio_get_allowed_tags()); + +} + +function _classic_format_author($author) { + $format = $author['prefix'] . ' ' . $author['lastname'] . ' '; + $format .= !empty ($author['firstname']) ? ' ' . drupal_substr($author['firstname'], 0, 1) : ''; + $format .= !empty ($author['initials']) ? str_replace(' ', '', $author['initials']) : ''; + return $format; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_cse.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_cse.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,90 @@ + 'Council of Science Editors (CSE)' + ); +} +/** + * Apply a bibliographic style to the node + * + * + * @param $node + * An object containing the node data to render + * @return + * The styled biblio entry + */ +function biblio_style_cse_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' =>', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ' ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '', //9 + 'initialsBeforeAuthorFirstAuthor' => FALSE, //10 + 'initialsBeforeAuthorStandard' => FALSE, //11 + 'shortenGivenNames' => TRUE, //12 + 'numberOfAuthorsTriggeringEtAl' => 10, //13 + 'includeNumberOfAuthors' => 10, //14 + 'customStringAfterFirstAuthors' => ' et al.',//15 + 'encodeHTML' => TRUE + ); + return $author_options; +} + +function biblio_style_cse($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = $authors = ''; + if (!empty($node->biblio_contributors)) { + $author_options = biblio_style_cse_author_options(); + $primary_authors = biblio_get_contributor_category($node->biblio_contributors, 1); + $editors = biblio_get_contributor_category($node->biblio_contributors, 2); + $corp_authors = biblio_get_contributor_category($node->biblio_contributors, 5); + if (!empty($primary_authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $primary_authors, 'options' => $author_options)); + } + if (empty($authors) && !empty($corp_authors)) {// if no authors substitute corp author if available + foreach ($corp_authors as $rank => $author) { + $authors .= (empty($authors)) ? '' : ', '; + $authors .= (variable_get('biblio_author_links', 1)) ? theme('biblio_author_link', array('author' => $author)) : $author['name']; + + } + } + } + if (empty($authors)) $authors = '[' . t('Anonymous') . ']'; // use anonymous if we still have nothing. + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + $output .= '' . $authors . ".  \n"; + + switch ($node->biblio_type) { + + default : + if (isset ($node->biblio_year)) { + $output .= check_plain($node->biblio_year) . ".  "; + } + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= (strpos($node->title, '?'))? " " : ". "; // if the title ends in a question mark, don't put a period after it. + $output .= (!empty ($node->biblio_secondary_title)) ? check_plain($node->biblio_secondary_title) . '. ' : ''; + $output .= (!empty ($node->biblio_volume)) ? check_plain($node->biblio_volume) : ''; + $output .= (!empty ($node->biblio_issue)) ? '(' . check_plain($node->biblio_issue) . ')' : ''; + $output .= (!empty ($node->biblio_pages)) ? ':' . str_replace(" ", "", check_plain($node->biblio_pages)) . '.' : ''; + break; // generic + } + /* if ($node->biblio_date) $output .= ', '. check_plain($node->biblio_date); + if ($node->biblio_number) $output .= ', Number '. check_plain($node->biblio_number); + + if ($node->biblio_place_published) $output .= ', '. check_plain($node->biblio_place_published); + */ + + return filter_xss($output, biblio_get_allowed_tags()); + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_ieee.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_ieee.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,111 @@ + 'Institute of Electrical and Electronics Engineers (IEEE)' + ); +} +function biblio_style_ieee_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' => ', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', and ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ', ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '. ', //9 + 'initialsBeforeAuthorFirstAuthor' => FALSE, //10 + 'initialsBeforeAuthorStandard' => TRUE, //11 + 'shortenGivenNames' => TRUE, //12 + 'numberOfAuthorsTriggeringEtAl' => 10, //13 + 'includeNumberOfAuthors' => 10, //14 + 'customStringAfterFirstAuthors' => ', et al.',//15 + 'encodeHTML' => TRUE + ); + return $author_options; +} + +/** + * Apply a bibliographic style to the node + * + * + * @param $node + * An object containing the node data to render + * @return + * The styled biblio entry + */ +function biblio_style_ieee($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = ''; + $author_options = biblio_style_ieee_author_options(); + $primary_authors = biblio_get_contributor_category($node->biblio_contributors, 1); + if (!empty($primary_authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $primary_authors, 'options' => $author_options)); + } + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + + $output .= isset($authors) ? '' . $authors . ", \n" : ''; + switch ($node->biblio_type) { + default : + $url = biblio_get_title_url_info($node); + if (!empty ($node->biblio_secondary_title)) { + $output .= '"'; + $output .= l($node->title, $url['link'], $url['options']); + //$output .= $inline ? l("$node->title", "$base/viewinline/$node->nid") : l("$node->title", "node/$node->nid"); + $output .= "", \n"; + $output .= '' . check_plain($node->biblio_secondary_title) . ''; + } else { + $output .= ''; + $output .= l($node->title, $url['link'], $url['options']); + $output .= ", \n"; + } + if (!empty ($node->biblio_edition)) + $output .= ', ' . check_plain($node->biblio_edition); + if (!empty ($node->biblio_volume)) + $output .= ', vol. ' . check_plain($node->biblio_volume); + if (!empty ($node->biblio_issue)) + $output .= ', issue ' . check_plain($node->biblio_issue); + if (!empty ($node->biblio_number)) + $output .= ', no. ' . check_plain($node->biblio_number); + if (!empty ($node->biblio_place_published)) + $output .= ', ' . check_plain($node->biblio_place_published); + if (!empty ($node->biblio_publisher)) { + $output .= (check_plain($node->biblio_place_published)) ? ', ' : ': '; + $output .= check_plain($node->biblio_publisher); + } + // if a single page instead of a range, should use 'p.' instead of 'pp.' -- ignoring + if (!empty ($node->biblio_pages)) + $output .= ', pp. ' . check_plain($node->biblio_pages); + // if it is a book, year should go before pages instead -- ignoring + // for non-books, should also include month of publication (e.g. "Mar. 2006") -- use date instead of year if available + if (!empty ($node->biblio_date)) { + $output .= ', ' . check_plain($node->biblio_date); + } + if ((!empty ($node->biblio_year) && !empty ($node->biblio_date) && !strstr($node->biblio_date, $node->biblio_year)) || (!empty ($node->biblio_year) && empty ($node->biblio_date))) { + $output .= ', ' . check_plain($node->biblio_year); + } + $output .= ".\n"; + break; // generic + } + /* if ($node->biblio_date) $output .= ', '. check_plain($node->biblio_date); + + */ + return filter_xss($output, biblio_get_allowed_tags()); + +} +function _ieee_format_author($author) { + $format = $author['prefix']; + $format .= !empty ($format) ? ' ' . $author['lastname'] . ' ' : $author['lastname'] . ' '; + $format .= !empty ($author['firstname']) ? drupal_substr($author['firstname'], 0, 1) . '.' : ''; + $format .= !empty ($author['initials']) ? $author['initials'] . '.' : ''; + return $format; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_mla.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_mla.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,507 @@ + and +// Matthias Steffens +// +// Modified for use in biblio by Ron Jerome +// +/** + * Get the style information + * + * @return + * The name of the style + */ +function biblio_style_mla_info() { + return array( + 'mla' => 'Modern Language Association (MLA)' + ); +} +function biblio_style_mla_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' =>', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', and ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ', ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '. ', //9 + 'initialsBeforeAuthorFirstAuthor' => FALSE, //10 + 'initialsBeforeAuthorStandard' => TRUE, //11 + 'shortenGivenNames' => FALSE, //12 + 'numberOfAuthorsTriggeringEtAl' => 3, //13 + 'includeNumberOfAuthors' => 1, //14 + 'customStringAfterFirstAuthors' => ', et al.',//15 + 'encodeHTML' => TRUE + ); + + return $author_options; +} + +/** + * Apply a bibliographic style to the node + * + * + * @param $node + * An object containing the node data to render + * @return + * The styled biblio entry + */ +function biblio_style_mla($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = $authors = $editor = ''; + $author_options = biblio_style_mla_author_options(); + $primary_authors = biblio_get_contributor_category($node->biblio_contributors, 1); + $editors = biblio_get_contributor_category($node->biblio_contributors, 2); + // $corp_authors = biblio_get_contributor_category($node->biblio_contributors, 5); + + if (!empty($primary_authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $primary_authors, 'options' => $author_options)); + } + //if (empty($authors)) $authors = theme('biblio_authors', $node->biblio_contributors->get_category(5), 'mla', 5, $inline); // if no authors substitute corp author if available + //if (empty($authors)) $authors = '[' . t('Anonymous') . ']'; // use anonymous if we still have nothing. + //$output .= '' . $authors . ".  \n"; + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + + switch ($node->biblio_type) { + case 102: //Journal Article + case 105: //Newspaper Article + case 106: //Magazine Article + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + $output .= '"' ; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + + // From here on we'll assume that at least either the 'author' or the 'title' field did contain some contents + // if this is not the case, the output string will begin with a space. However, any preceding/trailing whitespace will be removed at the cleanup stage (see below) + + if (!empty($node->biblio_alternate_title)) { // abbreviated journal name + $output .= " " . '' . $node->biblio_alternate_title . ''; + + // if there's no abbreviated journal name, we'll use the full journal name + } + elseif (!empty($node->biblio_secondary_title)) { // publication (= journal) name + $output .= " " . '' . $node->biblio_secondary_title . ''; + } + if (!empty($node->biblio_volume)) { // volume + if (!empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) { + $output .= "."; + } + $output .= " " . $node->biblio_volume; + } + if (!empty($node->biblio_issue)) { // issue + $output .= "." . $node->biblio_issue; + } + if (!empty($node->biblio_year)) // year + { + $output .= " (".$node->biblio_year . ")"; + } + // FIXME do something with the online pubs section + if (FALSE /*$node->online_publication == "yes"*/) // this record refers to an online article + { + // instead of any pages info (which normally doesn't exist for online publications) we append + // an optional string (given in 'online_citation') plus the current date and the DOI (or URL): + + $today = date("j M. Y"); + + if (!empty($node->online_citation)) // online_citation + { + if (!empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) // only add ":" if either volume, issue, abbrev_journal or publication isn't empty + $output .= ":"; + + $output .= " " . $node->online_citation; + } + + if (!empty($node->doi)) // doi + { + if (!empty($node->online_citation) OR (empty($node->online_citation) AND (!empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)))) // only add "." if online_citation isn't empty, or else if either volume, issue, abbrev_journal or publication isn't empty + $output .= "."; + + if ($encodeHTML) + $output .= " " . $today . encodeHTML(" doi . ">"); + else + $output .= " " . $today . " doi . ">"; + } + elseif (!empty($node->url)) // url + { + if (!empty($node->online_citation) OR (empty($node->online_citation) AND (!empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)))) // only add "." if online_citation isn't empty, or else if either volume, issue, abbrev_journal or publication isn't empty + $output .= "."; + + if ($encodeHTML) + $output .= " " . $today . encodeHTML(" <" . $node->url . ">"); + else + $output .= " " . $today . " <" . $node->url . ">"; + } + + } + else // $node->online_publication == "no" -> this record refers to a printed article, so we append any pages info instead: + { + if (!empty($node->biblio_pages)) // pages + { + if (!empty($node->biblio_year) || + !empty($node->biblio_volume) || + !empty($node->biblio_issue) || + !empty($$node->biblio_alternate_title) || + !empty($node->biblio_secondary_title)) { // only add ": " if either volume, issue, abbrev_journal or publication isn't empty + $output .= ": "; + } + // TODO: FIXME $output .= formatPageInfo($node->biblio_pages, $markupPatternsArray["endash"]); // function 'formatPageInfo()' is defined in 'cite.inc.php' + $output .= theme('biblio_page_number', array('orig_page_info' => $node->biblio_pages)); + } + } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + + break; + case 101: //Book Chapter + case 103: //Conference Paper + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + + //$output .= '"' ; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + // $output .= ''; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + + $publication = preg_replace("/[ \r\n]*\(Eds?:[^\)\r\n]*\)/", "", $node->biblio_secondary_title); + if (!empty($publication)) // publication + $output .= " $publication"; + + + // From here on we'll assume that at least either the 'author' or the 'title' field did contain some contents + // if this is not the case, the output string will begin with a space. However, any preceding/trailing whitespace will be removed at the cleanup stage (see below) + + if (!empty($editors)) // editor + { + $editor_options = array( + 'BetweenAuthorsDelimStandard' =>', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', and ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ' ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '. ', //9 + 'initialsBeforeAuthorFirstAuthor' => TRUE, //10 + 'initialsBeforeAuthorStandard' => TRUE, //11 + 'shortenGivenNames' => FALSE, //12 + 'numberOfAuthorsTriggeringEtAl' => 3, //13 + 'includeNumberOfAuthors' => 1, //14 + 'customStringAfterFirstAuthors' => ', et al.',//15 + 'encodeHTML' => TRUE + ); + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + _period_if_needed($output); + + if (count($editors) > 1) { // there are at least two editors (separated by ';') + $output .= " Eds. " . $editor; + } + else { // there's only one editor (or the editor field is malformed with multiple editors but missing ';' separator[s]) + $output .= " Ed. " . $editor; + } + } + + if (!empty($node->biblio_edition) && !preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + _period_if_needed($output); + + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") { + $editionSuffix = "nd"; + } + elseif ($node->biblio_edition == "3") { + $editionSuffix = "rd"; + } + else { + $editionSuffix = "th"; + } + } + else { + $editionSuffix = ""; + } + + if (preg_match("/^(Rev\.?|Revised)( ed\.?| edition)?$/i", $node->biblio_edition)) { + $node->biblio_edition = "Rev."; + } + elseif (preg_match("/^(Abr\.?|Abridged)( ed\.?| edition)?$/i", $node->biblio_edition)) { + $node->biblio_edition = "Abr."; + } + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) { + $editionSuffix .= " ed."; + } + $output .= " " . $node->biblio_edition . $editionSuffix; + } + + if (!empty($node->biblio_volume)) // volume + { + _period_if_needed($output); + $output .= " Vol. " . $node->biblio_volume; + } + + if (!empty($node->biblio_alternate_title) || !empty($node->biblio_tertiary_title)) // if there's either a full or an abbreviated series title + { + _period_if_needed($output); + $output .= " "; + + if (!empty($node->biblio_alternate_title)) { + $output .= $node->biblio_alternate_title; // abbreviated series title + } + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_tertiary_title)) { + $output .= $node->biblio_tertiary_title; // full series title + } + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) { + $output .= ", "; + } + if (!empty($node->biblio_volume)) { // series volume + $output .= $node->biblio_volume; + } + if (!empty($node->biblio_issue)) { // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + $output .= "." . $node->biblio_issue; // is it correct to format series issues similar to journal article issues? + } + } + + _period_if_needed($output); + if (!empty($node->biblio_place_published)) { // place + $output .= " " . $node->biblio_place_published; + } + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) $output .= ":"; + $output .= " " . $node->biblio_publisher. ", " ; + } + + if (!empty($node->biblio_year)) // year + { + $output .= " " . $node->biblio_year .". "; + } + _period_if_needed($output); + if (!empty($node->biblio_pages)) { // pages + $output .= theme('biblio_page_number', array('orig_page_info' => $node->biblio_pages)); + } + _period_if_needed($output); + //if (!preg_match("/\. *$/", $output)) $output .= "."; + + break; + + default: // all other types + + if (!empty($authors)) // author + { + $output .= ''; + $output .= $authors; + if (!preg_match("/\.*$/", $authors)) $output .= "."; + $output .= ''; + } + + if (!empty($node->title)) // title + { + $url = biblio_get_title_url_info($node); + if (!empty($authors)) + $output .= " "; + + if (!empty($node->thesis)) // thesis + { + $output .= ''; + $output .= '"' . l($node->title, $url['link'], $url['options']); + $output .= ''; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + $output .= '"'; + } + else // not a thesis + $output .= ''; + $output .= ''; + $output .= l($node->title, $url['link'], $url['options']); + $output .= ''; + $output .= ''; + } + + if (!empty($editors)) // editor (if different from author, see note above regarding the check for ' (ed)' or ' (eds)') + { + + $editor_options = array( + 'BetweenAuthorsDelimStandard' =>', ', //4 + 'BetweenAuthorsDelimLastAuthor' => ', and ', //5 + 'AuthorsInitialsDelimFirstAuthor' => ' ', //7 + 'AuthorsInitialsDelimStandard' => ' ', //8 + 'betweenInitialsDelim' => '. ', //9 + 'initialsBeforeAuthorFirstAuthor' => TRUE, //10 + 'initialsBeforeAuthorStandard' => TRUE, //11 + 'shortenGivenNames' => FALSE, //12 + 'numberOfAuthorsTriggeringEtAl' => 3, //13 + 'includeNumberOfAuthors' => 1, //14 + 'customStringAfterFirstAuthors' => ', et al.',//15 + 'encodeHTML' => TRUE + ); + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + _period_if_needed($output); + + if (count($editors) > 1) {// there are at least two editors (separated by ';') + $output .= " Eds. " . $editor; + } + else {// there's only one editor (or the editor field is malformed with multiple editors but missing ';' separator[s]) + $output .= " Ed. " . $editor; + } + } + + if (!empty($node->biblio_edition) && !preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + _period_if_needed($output); + + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") { + $editionSuffix = "nd"; + } + elseif ($node->biblio_edition == "3") { + $editionSuffix = "rd"; + } + else { + $editionSuffix = "th"; + } + } + else { + $editionSuffix = ""; + } + + if (preg_match("/^(Rev\.?|Revised)( ed\.?| edition)?$/i", $node->biblio_edition)) { + $node->biblio_edition = "Rev."; + } + elseif (preg_match("/^(Abr\.?|Abridged)( ed\.?| edition)?$/i", $node->biblio_edition)) { + $node->biblio_edition = "Abr."; + } + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) { + $editionSuffix .= " ed."; + } + $output .= " " . $node->biblio_edition . $editionSuffix; + } + + if (!empty($node->biblio_volume)) // volume + { + _period_if_needed($output); + $output .= " Vol. " . $node->biblio_volume; + } + + if (!empty($node->biblio_alternate_title) OR !empty($node->biblio_secondary_title)) // if there's either a full or an abbreviated series title + { + _period_if_needed($output); + $output .= " "; + + if (!empty($node->biblio_alternate_title)) { + $output .= $node->biblio_alternate_title; // abbreviated series title + } + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_secondary_title)) { + $output .= $node->biblio_secondary_title; // full series title + } + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) { + $output .= ", "; + } + if (!empty($node->biblio_volume)) { // series volume + $output .= $node->biblio_volume; + } + if (!empty($node->biblio_issue)) { // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + $output .= "." . $node->biblio_issue; // is it correct to format series issues similar to journal article issues? + } + } + + if (!empty($node->thesis)) // thesis (unpublished dissertation) + { + // TODO: a published dissertation needs to be formatted differently! + // see e.g. example at: + + _period_if_needed($output); + + // TODO: I've also seen MLA examples that separate thesis name, name of institution and year by dots. ?:-| + // Also, do we need to use the abbreviation "Diss." instead of "Ph.D. thesis"? What about other thesis types then? + // see e.g. + $output .= " " . $node->thesis; + $output .= ", " . $node->biblio_publisher; + } + else // not a thesis + { + _period_if_needed($output); + + if (!empty($node->biblio_place_published)) { // place + $output .= " " . $node->biblio_place_published; + } + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) $output .= ":"; + $output .= " " . $node->biblio_publisher; + } + } + + if (!empty($node->biblio_year)) { // year + $output .= ", ".$node->biblio_year; + } + + if (isset($node->online_publication) && $node->online_publication == "yes") // this record refers to an online article + { + $today = date("j M. Y"); + + if (!empty($node->online_citation)) // online_citation + { + if (!preg_match("/\. *$/", $output)) $output .= "."; + + $output .= " " . $node->online_citation; + } + + if (!empty($node->doi)) // doi + { + if (!preg_match("/\. *$/", $output)) $output .= "."; + + if ($encodeHTML) { + $output .= " " . $today . encodeHTML(" doi . ">"); + } + else { + $output .= " " . $today . " doi . ">"; + } + } + elseif (!empty($node->url)) // url + { + if (!preg_match("/\. *$/", $output)) $output .= "."; + + if ($encodeHTML) { + $output .= " " . $today . encodeHTML(" <" . $node->url . ">"); + } + else { + $output .= " " . $today . " <" . $node->url . ">"; + } + } + } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + break; + } + + return filter_xss($output, biblio_get_allowed_tags()); +} + +function _period_if_needed(&$output) { + if (!preg_match("/[?!.][ \"" . '<\/i>' . "]*$/", $output)) $output .= "."; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/styles/biblio_style_vancouver.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/styles/biblio_style_vancouver.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,537 @@ + + + // This Vancouver style was modeled after these resources: + // + // + // citing: + // + // + + // based on 'cite_AMA.php' + + // NOTES: - In the Vancouver style, the reference list is arranged numerically in the order in which references are cited in the text. + // This isn't currently handled by this style + // - For conference proceedings, you'll currently need to add the place & date of the conference in the proceedings title field + // (e.g. "Proceedings of the 5th Germ Cell Tumour Conference; 2001 Sep 13-15; Leeds, UK"). + + // TODO: - abstracts, newspaper/magazine articles, patents & reports? + // - arrange references numerically + // - for newspaper articles, only the beginning page number of an article should be included (see: ) + // - where to put (and how to format) editors of whole books that also have an author? + // - see also inline comments labeled with TODO (and NOTE) + + +// Modified for use in biblio by Ron Jerome +// +/** + * Get the style information + * + * @return + * The name of the style + */ +function biblio_style_vancouver_info() { + return array( + 'vancouver' => 'Vancouver' + ); +} +function biblio_style_vancouver_author_options() { + $author_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ', ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '', + 'initialsBeforeAuthorFirstAuthor' => FALSE, + 'initialsBeforeAuthorStandard' => FALSE, + 'numberOfInitialsToKeep' => 2, + 'shortenGivenNames' => TRUE, + 'numberOfAuthorsTriggeringEtAl' => 6, + 'includeNumberOfAuthors' => 6, + 'customStringAfterFirstAuthors' => ', et al.', + 'encodeHTML' => TRUE + ); + + return $author_options; +} + +function biblio_style_vancouver($node) { + module_load_include('inc', 'biblio', '/includes/biblio.contributors'); + $output = $authors = $editor = ''; + $markupPatternsArray = array("italic-prefix" => '', + "italic-suffix" => '<\/i>', + "endash" => '-'); + $author_options = biblio_style_vancouver_author_options(); + $primary_authors = biblio_get_contributor_category($node->biblio_contributors, 1); + $editors = biblio_get_contributor_category($node->biblio_contributors, 2); + // $corp_authors = biblio_get_contributor_category($node->biblio_contributors, 5); + if (!empty($primary_authors)) { + $authors = theme('biblio_format_authors', array('contributors' => $primary_authors, 'options' => $author_options)); + } + //$editors = theme('biblio_format_authors', array('contributors' => $node->biblio_contributors->get_category(2), 'options' => $author_options, 'inline' => $inline)); + //if (empty($authors)) $authors = theme('biblio_authors', $node->biblio_contributors->get_category(5), 'mla', 5, $inline); // if no authors substitute corp author if available + //if (empty($authors)) $authors = '[' . t('Anonymous') . ']'; // use anonymous if we still have nothing. + //$output .= '' . $authors . ".  \n"; + if (!empty ($node->biblio_citekey)&&(variable_get('biblio_display_citation_key',0))) { + $output .= '[' . check_plain($node->biblio_citekey) . '] '; + } + + switch ($node->biblio_type) { + case 102: //Journal Article + case 105: //Newspaper Article + case 106: //Magazine Article + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ""; + } + + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + } + + // From here on we'll assume that at least either the 'author' or the 'title' field did contain some contents + // if this is not the case, the output string will begin with a space. However, any preceding/trailing whitespace will be removed at the cleanup stage (see below) + + if (!empty($node->biblio_alternate_title)) // abbreviated journal name + $output .= " " . preg_replace("/\./", "", $node->biblio_alternate_title); // no punctuation marks are used in the abbreviated journal name, just spaces (TODO: smarten regex pattern) + + // if there's no abbreviated journal name, we'll use the full journal name instead: + elseif (!empty($node->biblio_secondary_title)) // publication (= journal) name + $output .= " " . $node->biblio_secondary_title; + + if (isset($node->online_publication)) // this record refers to an online publication + $output .= " [Internet]"; // NOTE: some of the above mentioned resources use "[serial online]", "[serial on the Internet]" or just "[online]" instead + + // NOTE: the formatting of year/volume/issue is meant for journal articles (TODO: newspaper/magazine articles) + if (!empty($node->biblio_year)) // year + $output .= ". " . $node->biblio_year; + + if (isset($node->online_publication)) // append the current date if this record refers to an online publication + $output .= " [cited " . date("Y M j") . "]"; + + if (!empty($node->biblio_volume) || !empty($node->biblio_issue)) + $output .= ";"; + + if (!empty($node->biblio_volume)) // volume (=month) + $output .= $node->biblio_volume; + + if (!empty($node->biblio_issue)) // issue (=day) + $output .= "(" . $node->biblio_issue . ")"; + + if (!empty($node->biblio_pages)) // pages + { + if (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) // only add ": " if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= ":"; + + $output .= theme('biblio_page_number', array('orig_page_info' => $node->biblio_pages, + 'page_range_delim' => $markupPatternsArray["endash"], + 'shorten_page_range_end' => TRUE)); // function 'formatPageInfo()' is defined in 'cite.inc.php' + } + + if (isset($node->online_publication)) // this record refers to an online publication + { + // append an optional string (given in 'online_citation') plus the DOI (or URL): + + if (!empty($node->online_citation)) // online_citation + { + if (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)) // only add ":" or "," if either year, volume, issue, abbrev_journal or publication isn't empty + { + if (empty($node->biblio_pages)) + $output .= ":"; // print instead of pages + else + $output .= ";"; // append to pages (TODO: not sure whether this is correct) + } + + $output .= $node->online_citation; + } + + if (!empty($node->doi) || !empty($node->url)) // doi OR url + { + if (!empty($node->online_citation) OR (empty($node->online_citation) AND (!empty($node->biblio_year) || !empty($node->biblio_volume) || !empty($node->biblio_issue) || !empty($node->biblio_alternate_title) || !empty($node->biblio_secondary_title)))) // only add "." if online_citation isn't empty, or else if either year, volume, issue, abbrev_journal or publication isn't empty + $output .= "."; + + $output .= " Available from: " . $markupPatternsArray["underline-prefix"]; // NOTE: some of the above mentioned resources use "Available from: URL:http://..." instead + + if (!empty($node->doi)) // doi + $uri = "http://dx.doi.org/" . $node->doi; + else // url + $uri = $node->url; + + if ($encodeHTML) + $output .= encodeHTML($uri); + else + $output .= $uri; + + $output .= $markupPatternsArray["underline-suffix"]; + } + } + + if (!preg_match("/\. *$/", $output) AND (!isset($node->online_publication))) + $output .= "."; // NOTE: the examples in the above mentioned resources differ wildly w.r.t. whether the closing period should be omitted for online publications + break; + case 101: //Book Chapter + case 103: //Conference Paper + if (!empty($authors)) { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + if (!empty($node->title)) // title + { + if (!empty($authors)) $output .= " "; + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + if (!preg_match("/[?!.]$/", $node->title)) $output .= "."; + } + + + if (!empty($editors)) // editor + { + $editor_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ', ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '', + 'initialsBeforeAuthorFirstAuthor' => FALSE, + 'initialsBeforeAuthorStandard' => FALSE, + 'shortenGivenNames' => TRUE, + 'numberOfAuthorsTriggeringEtAl' => 6, + 'includeNumberOfAuthors' => 6, + 'customStringAfterFirstAuthors' => ' et al.', + 'encodeHTML' => TRUE + ); + + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + + $output .= " In: " . $editor . ", "; + if (count($editors) > 1) // there are at least two editors (separated by ';') + $output .= "editors"; + else // there's only one editor (or the editor field is malformed with multiple editors but missing ';' separator[s]) + $output .= "editor"; + } + + $publication = preg_replace("/[ \r\n]*\(Eds?:[^\)\r\n]*\)/", "", $node->biblio_secondary_title); + if (!empty($publication)) // publication + { + if (!preg_match("/[?!.] *$/", $output)) $output .= "."; + + if (empty($editor)) $output .= " In:"; + + $output .= " " . $publication; + } + + if (!empty($node->biblio_volume)) // volume + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " Vol " . $node->biblio_volume; // TODO: not sure whether this is correct + } + + if (!empty($node->biblio_edition) && !preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") + $editionSuffix = "nd"; + elseif ($node->biblio_edition == "3") + $editionSuffix = "rd"; + else + $editionSuffix = "th"; + } + else + $editionSuffix = ""; + + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) + $editionSuffix .= " ed."; + + $output .= " " . $node->biblio_edition . $editionSuffix; + } + + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + if (!empty($node->biblio_place_published)) // place + { +// // for places in the USA, format any two-letter postal code for the state (i.e. ensure upper case & wrap in parens, eg. "Boca Raton (FL)"): +// if (preg_match("/(.+?)[$punct$space]+($uspsStateAbbreviations)[$punct$space]*$/i$patternModifiers", $node->biblio_place_published)) +// $output .= " " . preg_replace("/(.+?)[$punct$space]+($uspsStateAbbreviations)[$punct$space]*$/ie$patternModifiers", "'\\1 ('.strtoupper('\\2').')'", $node->biblio_place_published); +// else + $output .= " " . $node->biblio_place_published; + } + + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) + $output .= ":"; + + $output .= " " . $node->biblio_publisher; + } + + if (!empty($node->biblio_year)) // year + $output .= "; " . $node->biblio_year; + + if (!empty($node->biblio_pages)) // pages + $output .= ". " . theme_biblio_page_number($node->biblio_pages, $markupPatternsArray["endash"], "p. ", "p. ", "", "", "", "", TRUE); // function 'formatPageInfo()' is defined in 'cite.inc.php' + $output .= ". " . theme('biblio_page_number', array('orig_page_info' => $node->biblio_pages, + 'page_range_delim' => $markupPatternsArray["endash"], + 'single_page_prefix' => "p. ", + 'page_range_prefix' => "p. ", + 'shorten_page_range_end' => TRUE)); // function 'formatPageInfo()' is defined in 'cite.inc.php' + + if (!empty($node->biblio_alternate_title) OR !empty($node->biblio_tertiary_title)) // if there's either a full or an abbreviated series title + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " ("; + + if (!empty($node->biblio_alternate_title)) // abbreviated series title + $output .= preg_replace("/\./", "", $node->biblio_alternate_title); // no punctuation marks are used in the abbreviated series title, just spaces (TODO: smarten regex pattern) + + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_tertiary_title)) // full series title + $output .= $node->biblio_tertiary_title; + + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) + $output .= "; "; + + if (!empty($node->biblio_volume)) // series volume + $output .= "vol " . $node->biblio_volume; + + if (!empty($node->biblio_volume) && !empty($node->biblio_issue)) + $output .= "; "; // TODO: not sure whether this is correct + + if (!empty($node->biblio_issue)) // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + $output .= "no " . $node->biblio_issue; // since a series volume should be prefixed with "vol", is it correct to prefix series issues with "no"? + + $output .= ")"; + } + + if (!preg_match("/\. *$/", $output)) $output .= "."; + + break; + + default : // all other types + //TODO + // if (preg_match("[ \r\n]*\(ed\)", $node->author)) // single editor + // $author = $author . ", ed"; + // elseif (preg_match("[ \r\n]*\(eds\)", $node->author)) // multiple editors + // $author = $author . ", eds"; + + if (!empty($authors)) // author + { + $output .= ''; + $output .= $authors; + if (!preg_match("/\. *$/", $authors)) $output .= "."; + $output .= ''; + } + + if (!empty($node->title)) // title + { + if (!empty($authors)) + $output .= " "; + + // TODO: book/volume/report/etc titles should be formatted in heading caps, however, this doesn't yet work correctly if the publication title contains HTML entities + $output .= ''; + $url = biblio_get_title_url_info($node); + $output .= l($node->title, $url['link'], $url['options']); + $output .= ""; + } + if ($node->type == "Software") // for software, add software label + $output .= " [computer program]"; + + if (isset($node->online_publication) AND empty($node->thesis)) // this record refers to an online publication (online theses will be handled further down below) + $output .= " [Internet]"; // NOTE: some of the above mentioned resources use "[monograph online]", "[monograph on the Internet]" or just "[online]" instead + + if (!empty($node->biblio_volume) AND ($node->type != "Software")) // volume + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " Vol " . $node->biblio_volume; // TODO: not sure whether this is correct + } + + if (!empty($node->biblio_edition)) // edition + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + if ($node->type == "Software") // software edition (=version) + { + $output .= " Version " . $node->biblio_edition; + } + elseif (!preg_match("/^(1|1st|first|one)( ed\.?| edition)?$/i", $node->biblio_edition)) // edition + { + if (preg_match("/^\d{1,3}$/", $node->biblio_edition)) // if the edition field contains a number of up to three digits, we assume it's an edition number (such as "2nd ed.") + { + if ($node->biblio_edition == "2") + $editionSuffix = "nd"; + elseif ($node->biblio_edition == "3") + $editionSuffix = "rd"; + else + $editionSuffix = "th"; + } + else + $editionSuffix = ""; + + if (!preg_match("/( ed\.?| edition)$/i", $node->biblio_edition)) + $editionSuffix .= " ed."; + + $output .= " " . $node->biblio_edition . $editionSuffix; + } + } + + if (!empty($editors)) // editor (if different from author, see note above regarding the check for ' (ed)' or ' (eds)') + { + + $editor_options = array( + 'BetweenAuthorsDelimStandard' => ', ', + 'BetweenAuthorsDelimLastAuthor' => ', ', + 'AuthorsInitialsDelimFirstAuthor' => ' ', + 'AuthorsInitialsDelimStandard' => ' ', + 'betweenInitialsDelim' => '', + 'initialsBeforeAuthorFirstAuthor' => FALSE, + 'initialsBeforeAuthorStandard' => FALSE, + 'shortenGivenNames' => TRUE, + 'numberOfAuthorsTriggeringEtAl' => 6, + 'includeNumberOfAuthors' => 3, + 'customStringAfterFirstAuthors' => ' et al.', + 'encodeHTML' => TRUE + ); + + $editor = theme('biblio_format_authors', array('contributors' => $editors, 'options' => $editor_options)); + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) + $output .= "."; + + $output .= " " . $editor; + if (count($editors) > 1) // there are at least two editors (separated by ';') + $output .= ", editors"; + else // there's only one editor (or the editor field is malformed with multiple editors but missing ';' separator[s]) + $output .= ", editor"; + } + + if (!empty($node->thesis)) // thesis + { + // TODO: do we need to use the term "[dissertation]" instead of "[Ph.D. thesis]", etc? What about other thesis types then? + $output .= " [" . $node->thesis; + + if (isset($node->online_publication)) // this record refers to an online thesis + $output .= " on the Internet]"; + else + $output .= "]"; + } + + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + if (!empty($node->biblio_place_published)) // place + { +// // for places in the USA, format any two-letter postal code for the state (i.e. ensure upper case & wrap in parentheses, eg. "Boca Raton (FL)"): +// if (preg_match("/(.+?)[$punct$space]+($uspsStateAbbreviations)[$punct$space]*$/i$patternModifiers", $node->biblio_place_published)) +// $output .= " " . preg_replace("/(.+?)[$punct$space]+($uspsStateAbbreviations)[$punct$space]*$/ie$patternModifiers", "'\\1 ('.strtoupper('\\2').')'", $node->biblio_place_published); +// else + $output .= " " . $node->biblio_place_published; + } + + if (!empty($node->biblio_publisher)) // publisher + { + if (!empty($node->biblio_place_published)) + $output .= ":"; + + $output .= " " . $node->biblio_publisher; + } + + $output .= ";"; + + if (!empty($node->biblio_year)) // year + $output .= " " . $node->biblio_year; + + if ($node->type == "Software") // for software, volume (=month) and issue (=day) information is printed after the year (TODO: not sure whether this is correct) + { + if (!empty($node->biblio_volume)) // volume (=month) + $output .= " " . $node->biblio_volume; + + if (!empty($node->biblio_issue)) // issue (=day) + $output .= " " . $node->biblio_issue; + } + + if (isset($node->online_publication)) // append the current date if this record refers to an online publication + $output .= " [cited " . date("Y M j") . "]"; + + if (!empty($node->biblio_alternate_title) OR !empty($node->biblio_tertiary_title)) // if there's either a full or an abbreviated series title + { + if (!preg_match("/[?!.][ \"" . $markupPatternsArray["italic-suffix"] . "]*$/", $output)) $output .= "."; + + $output .= " ("; + + if (!empty($node->biblio_alternate_title)) // abbreviated series title + $output .= preg_replace("/\./", "", $node->biblio_alternate_title); // no punctuation marks are used in the abbreviated series title, just spaces (TODO: smarten regex pattern) + + // if there's no abbreviated series title, we'll use the full series title instead: + elseif (!empty($node->biblio_tertiary_title)) // full series title + $output .= $node->biblio_tertiary_title; + + if (!empty($node->biblio_volume)||!empty($node->biblio_issue)) + $output .= "; "; + + if (!empty($node->biblio_volume)) // series volume + $output .= "vol " . $node->biblio_volume; + + if (!empty($node->biblio_volume) && !empty($node->biblio_issue)) + $output .= "; "; // TODO: not sure whether this is correct + + if (!empty($node->biblio_issue)) // series issue (I'm not really sure if -- for this cite style -- the series issue should be rather omitted here) + $output .= "no " . $node->biblio_issue; // since a series volume should be prefixed with "vol", is it correct to prefix series issues with "no"? + + $output .= ")"; + } + + if (isset($node->online_publication) || $node->type == "Software") // this record refers to an online publication, or a computer program/software + { + // append an optional string (given in 'online_citation') plus the DOI (or URL): + + if (!empty($node->online_citation)) // online_citation + { + if (!preg_match("/\. *$/", $output)) $output .= "."; + + $output .= $node->online_citation; + } + + if (!empty($node->doi) || !empty($node->url)) // doi OR url + { + if (!preg_match("/\. *$/", $output)) $output .= "."; + + $output .= " Available from: " . $markupPatternsArray["underline-prefix"]; // NOTE: some of the above mentioned resources use "Available from: URL:http://..." instead + + if (!empty($node->doi)) // doi + $uri = "http://dx.doi.org/" . $node->doi; + else // url + $uri = $node->url; + + if ($encodeHTML) + $output .= encodeHTML($uri); + else + $output .= $uri; + + $output .= $markupPatternsArray["underline-suffix"]; + } + } + + if (!preg_match("/\. *$/", $output) AND (!isset($node->online_publication)) AND ($node->type != "Software")) + $output .= "."; // NOTE: the examples in the above mentioned resources differ wildly w.r.t. whether the closing period should be omitted for online publications + break; + } + + return filter_xss($output, biblio_get_allowed_tags()); +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/tests/biblio.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/tests/biblio.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,192 @@ +kids)) { +// db_delete('biblio_keyword') +// ->condition('kid', $this->kids, 'IN') +// ->execute(); +// +// db_delete('biblio_keyword_data') +// ->condition('kid', $this->kids, 'IN') +// ->execute(); +// +// } +// +// foreach ($this->nids as $nid) { +// node_delete($nid); +// } +// +// if (!empty($this->cids)) { +// db_delete('biblio_contributor') +// ->condition('cid', $this->cids, 'IN') +// ->execute(); +// +// db_delete('biblio_contributor_data') +// ->condition('cid', $this->cids, 'IN') +// ->execute(); +// +// } +// $this->cids = array(); +// } + + + function createNode($type = 100) { + $schema = drupal_get_schema('biblio'); + foreach ($schema['fields'] as $name => $values) { + if ($values['type'] == 'int') continue; + switch ($values['type']) { + case 'varchar': + $length = $values['length']; + break; + case 'text': + $length = 1000; + break; + } + $biblio_fields["$name"] = $name; + } + $settings = array( + 'title' => 'Biblio Title', + 'type' => 'biblio', // This replaces the default type + 'biblio_type' => $type, // This appends a new field. + 'biblio_year' => 2009, + 'biblio_contributors' => array(0 => array('name' => 'Ron J. Jeromezzzzzz', 'auth_type' => 1, 'auth_category' => 1), + 1 => array('name' => 'John Smithzzzzzz', 'auth_type' => 1, 'auth_category' => 1), + 2 => array('name' => 'George W. Bushzzzzzz', 'auth_type' => 1, 'auth_category' => 1)), + 'biblio_keywords' => array('biblio_keywords') + ); + $settings = array_merge($biblio_fields, $settings); + + $node = $this->drupalCreateNode($settings); + $node = node_load($node->nid, NULL, TRUE); + foreach ($node->biblio_contributors as $author) { + $this->cids[] = $author['cid']; + } + + $this->nids[] = $node->nid; + + return $node; + + } + function assertBiblioFields($node1, $node2, $fields = array()) { + $count = 0; + foreach ($fields as $field) { + if ($field == 'biblio_contributors') { + foreach ($node1->{$field} as $key => $auth) { + unset($node1->{$field}[$key]['nid']); + unset($node1->{$field}[$key]['vid']); + } + foreach ($node2->{$field} as $key => $auth) { + unset($node2->{$field}[$key]['nid']); + unset($node2->{$field}[$key]['vid']); + } + } + if ($node1->$field != $node2->$field) { + $this->assertIdentical($node1->$field, $node2->$field); + $count++; + } + } + $this->assertEqual($count, 0, "There were $count differences between the two nodes"); + } +} + +class BiblioNodeCreationTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Biblio node creation', + 'description' => 'Create a biblio node and test saving it.', + 'group' => 'Biblio', + ); + } + + function setUp() { + parent::setUp('biblio'); + $web_user = $this->drupalCreateUser(array('create biblio content')); + $this->drupalLogin($web_user); + } + + /** + * Create a biblio node and verify its consistency in the database. + */ + function testBiblioNodeCreation() { + // Create a node. + $edit = array(); + $edit["biblio_type"] = '101'; + $this->drupalPost('node/add/biblio', $edit, t('Next')); + + // Check that the next step of the form appears. + $this->assertOptionSelected('edit-biblio-type', '101'); + $this->assertFieldById('edit-title'); + $this->assertFieldById('edit-biblio-year'); + + $edit = array( + 'title' => $this->randomString(32), + 'biblio_year' => '2009', + 'biblio_contributors[0][name]' => 'Kevin Brown', + 'biblio_contributors[1][name]' => 'Martin Clark', + 'biblio_contributors[2][name]' => 'George Wei', + 'biblio_keywords' => 'architecture, building, wood', + ); + $this->drupalPost(NULL, $edit, t('Save')); + + // Check that the Basic page has been created. + $this->assertRaw(t('!post %title has been created.', array('!post' => 'Biblio', '%title' => $edit["title"])), t('Biblio entry created.')); + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit['title']); + + $keywordstring = implode(', ', $node->biblio_keywords); + $this->assertIdentical($keywordstring, 'architecture, building, wood', t('Keywords are present on the biblio node.')); + $this->assertTrue($node, t('Node found in database.')); + } +} + +class BiblioPageViewTestCase extends BiblioWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Biblio node view and edit permissions', + 'description' => 'Create a biblio node and test view / edit permissions.', + 'group' => 'Biblio', + ); + } + + function setUp() { + parent::setUp('biblio'); + } + + /** + * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + */ + function testBiblioPageView() { + // Create a node to view. + $node = $this->createNode('101'); + $this->assertTrue(node_load($node->nid), t('Node created.')); + + // Try to edit with anonymous user. + $html = $this->drupalGet("node/$node->nid/edit"); + $this->assertResponse(403); + + // Create a user without permission to edit node. + $web_user = $this->drupalCreateUser(array('access content')); + $this->drupalLogin($web_user); + + // Attempt to access edit page. + $this->drupalGet("node/$node->nid/edit"); + $this->assertResponse(403); + + // Create user with permission to edit node. + $web_user = $this->drupalCreateUser(array('edit any biblio content')); + $this->drupalLogin($web_user); + + // Attempt to access edit page. + $this->drupalGet("node/$node->nid/edit"); + $this->assertResponse(200); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/tests/contributor.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/tests/contributor.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,129 @@ + 'Biblio contributor unit tests', + 'description' => 'Unit tests for contributor functions.', + 'group' => 'Biblio', + ); + } + + function testGrabSurname() { + + $surname = 'van der Plus'; + list ($surname, $prefix) = _grabSurname($surname); + $this->assertIdentical($surname, 'Plus' ); + $this->assertIdentical($prefix, 'van der' ); + $surname = 'Van den Bussche'; + list ($surname, $prefix) = _grabSurname($surname); + $this->assertIdentical($surname, 'Van den Bussche' ); + $this->assertIdentical($prefix, FALSE ); + } + function testGrabFirstnameInitials() { + + $string = "Ron"; + list($firstname, $initials, $prefix) = _grabFirstnameInitials($string); + $this->assertIdentical($firstname, 'Ron' ); + $this->assertIdentical($initials, '' ); + $string = "Ron J."; + list($firstname, $initials, $prefix) = _grabFirstnameInitials($string); + $this->assertIdentical($firstname, 'Ron' ); + $this->assertIdentical($initials, 'J' ); + $string = "sir Ron J."; + list($firstname, $initials, $prefix) = _grabFirstnameInitials($string); + $this->assertIdentical($firstname, 'Ron' ); + $this->assertIdentical($initials, 'J' ); + $this->assertIdentical($prefix, 'sir' ); + $string = "R J"; + list($firstname, $initials, $prefix) = _grabFirstnameInitials($string); + $this->assertIdentical($firstname, '' ); + $this->assertIdentical($initials, 'R J' ); + $string = "R. J."; + list($firstname, $initials, $prefix) = _grabFirstnameInitials($string); + $this->assertIdentical($firstname, '' ); + $this->assertIdentical($initials, 'R J' ); + $string = "R.J."; + list($firstname, $initials, $prefix) = _grabFirstnameInitials($string); + $this->assertIdentical($firstname, '' ); + $this->assertIdentical($initials, 'R J' ); + + } + + function testBiblioParseAuthor() { + + $author['name'] = 'Bush, Jr. III, George W'; + $author['auth_category'] = 1; + $author = biblio_parse_author($author); + $this->assertIdentical($author['firstname'], 'George', 'Test biblio_parse_author($author), firstname' ); + $this->assertIdentical($author['lastname'], 'Bush', 'Test biblio_parse_author($author), lastname'); + $this->assertIdentical($author['initials'], 'W', 'Test biblio_parse_author($author), initials' ); + $this->assertIdentical($author['suffix'], 'Jr. III', 'Test biblio_parse_author($author), suffix' ); + + } + + function testBiblioUpdateContributors() { + + $node = $this->createNode(); + $nid = $node->nid; + $vid1 = $node->vid; + $this->assertIdentical($node->biblio_contributors[2]['firstname'], 'George', 'Test biblio_insert_contributors($node), firstname'); + $this->assertIdentical($node->biblio_contributors[2]['lastname'], 'Bushzzzzzz', 'Test biblio_insert_contributors($node), lastname' ); + unset($node->biblio_contributors[2]); + $node->revision = TRUE; + node_save($node); + + $node = node_load($nid, NULL, TRUE); + $this->assertFalse(isset($node->biblio_contributors[2]), 'Test removing an author and updating the node'); + + biblio_delete_contributor_revision($node->biblio_contributors[1]['cid'], $node->vid); + $node = node_load($nid, NULL, TRUE); + $this->assertEqual(count($node->biblio_contributors), 1, 'Test biblio_delete_contributor_revision($cid, $vid)'); + + $node = node_load($nid, $vid1, TRUE); + + $this->assertEqual(count($node->biblio_contributors), 3, 'Test load original vid, still three authors'); + + biblio_delete_contributors($node); + $node = node_load($nid, NULL, TRUE); + $this->assertFalse(count($node->biblio_contributors), 'Test biblio_delete_contributors($node), should be zero authors on reload'); + + } + + function testBiblioDeleteOrphanAuthors() { + $orphan_authors = array(); + $orphan_authors = biblio_get_orphan_authors(); // first save any existing orphans so we can put them back + $orphan_count = biblio_count_orphan_authors(); + $author = array('name' => 'Ron J. Jeromezzzzzz', 'auth_type' => 1); + biblio_save_contributor($author); // create a new orphan so we will have at least one + $before_count = biblio_count_orphan_authors(); + $this->assertTrue($before_count != 0, "There are $before_count orphans to delete"); + biblio_delete_orphan_authors(TRUE); + $after_count = biblio_count_orphan_authors(); + $this->assertEqual($after_count, 0, "There are now $after_count orphans"); + + + foreach ($orphan_authors as $author) { + biblio_save_contributor($author); + } + $restored_count = biblio_count_orphan_authors(); + $this->assertEqual($orphan_count, $restored_count, "Restored $restored_count of $orphan_count original orphans"); + + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/tests/import.export.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/tests/import.export.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,119 @@ + 'Biblio import/export unit tests', + 'description' => 'Unit tests for import/export functions.', + 'group' => 'Biblio', + ); + } + function getTaggedString() { + return "%0 Book\r\n%B biblio_secondary_title\r\n%D 2009\r\n%T Biblio Title\r\n%A Ron J. Jeromezzzzzz\r\n%A John Smithzzzzzz\r\n%A George W. Bushzzzzzz\r\n%K biblio_keywords\r\n%X biblio_abst_e\r\n%B biblio_secondary_title\r\n%S biblio_tertiary_title\r\n%7 biblio_edition\r\n%I biblio_publisher\r\n%C biblio_place_published\r\n%V biblio_volume\r\n%P biblio_pages\r\n%8 biblio_date\r\n%@ biblio_isbn\r\n%G biblio_lang\r\n%U biblio_url\r\n%N biblio_issue\r\n%9 biblio_type_of_work\r\n%M biblio_accession_number\r\n%L biblio_call_number\r\n%1 biblio_custom1\r\n%2 biblio_custom2\r\n%3 biblio_custom3\r\n%4 biblio_custom4\r\n%# biblio_custom5\r\n%$ biblio_custom6\r\n%] biblio_custom7\r\n%< biblio_research_notes\r\n%6 biblio_number_of_volumes\r\n%! biblio_short_title\r\n%( biblio_original_publication\r\n%) biblio_reprint_edition\r\n%& biblio_section\r\n%R biblio_doi\r\n%F biblio_label\r\n\r\n"; + } + + function getBibTexString() { + return "@book {biblio_citekey,\n\ttitle = {Biblio Title},\n\tseries = {biblio_secondary_title},\n\tvolume = {biblio_volume},\n\tnumber = {biblio_number},\n\tyear = {2009},\n\tnote = {biblio_notes},\n\tmonth = {biblio_date},\n\tpages = {biblio_pages},\n\tpublisher = {biblio_publisher},\n\torganization = {biblio_publisher},\n\ttype = {biblio_type_of_work},\n\tedition = {biblio_edition},\n\tchapter = {biblio_section},\n\taddress = {biblio_place_published},\n\tabstract = {biblio_abst_e},\n\tkeywords = {biblio_keywords},\n\tisbn = {biblio_isbn},\n\tissn = {biblio_issn},\n\tdoi = {biblio_doi},\n\turl = {biblio_url},\n\tauthor = {Ron J. Jeromezzzzzz and John Smithzzzzzz and George W. Bushzzzzzz}\n}\n"; + } + + function getXMLString() { + return 'Drupal-Biblio6<style face="normal" font="default" size="100%">Biblio Title</style>
      '; + } + + function getRISString() { + return "TY - BOOK\r\nTI - Biblio Title\r\nY1 - 2009\r\nN1 - biblio_notes\r\nAU - Ron J. Jeromezzzzzz\r\nAU - John Smithzzzzzz\r\nAU - George W. Bushzzzzzz\r\nKW - biblio_keywords\r\nSP - 1\r\nEP - 2\r\nJO - biblio_short_title\r\nVL - biblio_volume\r\nIS - biblio_issue\r\nT2 - biblio_secondary_title\r\nCY - biblio_place_published\r\nPB - biblio_publisher\r\nU1 - biblio_custom1\r\nU2 - biblio_custom2\r\nU3 - biblio_custom3\r\nU4 - biblio_custom4\r\nU5 - biblio_custom5\r\nT3 - biblio_tertiary_title\r\nAB - biblio_abst_e\r\nSN - biblio_isbn\r\nUR - biblio_url\r\nER - \r\n\r\n"; + } + + function testBiblioNodeExport() { + module_load_include('inc', 'biblio_xml', 'endnote8_export'); + $node = $this->createNode(); + $this->assertEqual(_biblio_tagged_export($node), $this->getTaggedString());//, 'Export a node in EndNote Tagged format'); + $this->assertEqual(_biblio_bibtex_export($node), $this->getBibTexString(), 'Export a node in BibTex format'); + $xml = _endnote8_XML_export('', 'begin'); + $xml .= _endnote8_XML_export($node); + $xml .= _endnote8_XML_export('', 'end'); + $this->assertEqual($xml, $this->getXMLString());//, 'Export a node in EndNote XML format'); + + } + + function testBiblioRISFileImport() { + $file = file_save_data($this->getRISString()); + $context = array(); + biblio_import($file, 'biblio_ris', 1, NULL, FALSE, NULL, $context); + $nids = $context['results']['nids']; + array_merge($this->nids, $nids); + $this->assertEqual(count($nids), 1, 'Imported 1 RIS entry'); + file_delete($file); + $node = $this->createNode(); + $fields = array_unique(array_filter(biblio_get_map('field_map', 'ris'))); + $fields += array('title', 'biblio_contributors'); + foreach ($nids as $nid) { + $this->nids[] = $nid; + $imported_node = node_load($nid); + $this->assertBiblioFields($node, $imported_node, $fields); + } + } + + function testBiblioXMLFileImport() { + $file = file_save_data($this->getXMLString()); + $context = array(); + biblio_import($file, 'biblio_xml', 1, NULL, FALSE, NULL, $context); + $nids = $context['results']['nids']; + array_merge($this->nids, $nids); + $this->assertEqual(count($nids), 1, 'Imported 1 EndNote XML entry'); + file_delete($file); + $node = $this->createNode(); + $fields = array_filter(biblio_get_map('field_map', 'endnote8')); + $fields += array('title', 'biblio_contributors', 'biblio_keywords'); + foreach ($nids as $nid) { + $this->nids[] = $nid; + $imported_node = node_load($nid); + $this->assertBiblioFields($node, $imported_node, $fields); + } + } + + function testBiblioTaggedFileImport() { + $file = file_save_data($this->getTaggedString()); + $context = array(); + biblio_import($file, 'biblio_tagged', 1, NULL, FALSE, NULL, $context); + $nids = $context['results']['nids']; + array_merge($this->nids, $nids); + $this->assertEqual(count($nids), 1, 'Imported 1 Tagged entry'); + file_delete($file); + $node = $this->createNode(); + $fields = array_filter(biblio_get_map('field_map', 'tagged')); + $fields += array('title', 'biblio_contributors', 'biblio_keywords'); + foreach ($nids as $nid) { + $this->nids[] = $nid; + $imported_node = node_load($nid); + $this->assertBiblioFields($node, $imported_node, $fields); + } + } + + function testBiblioBibtexFileImport() { + $file = file_save_data($this->getBibTexString()); + $context = array(); + biblio_import($file, 'biblio_bibtex', 1, NULL, FALSE, NULL, $context); + $nids = $context['results']['nids']; + array_merge($this->nids, $nids); + $this->assertEqual(count($nids), 1, 'Imported 1 Bibtex entry'); + file_delete($file); + $node = $this->createNode(); + $fields = array_filter(biblio_get_map('field_map', 'bibtex')); + $fields += array('title', 'biblio_contributors'); + foreach ($nids as $nid) { + $this->nids[] = $nid; + $imported_node = node_load($nid); + $this->assertBiblioFields($node, $imported_node, $fields); + } + } + + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/tests/keyword.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/tests/keyword.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,149 @@ +randomName(); + biblio_save_keyword($keyword); + $this->kids[] = $keyword['kid']; + return $keyword; + } + +} + +/** + * Unit tests for keyword functions. + */ +class BiblioKeywordUnitTest extends BiblioKeywordWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Biblio keyword unit tests', + 'description' => 'Unit tests for keyword functions.', + 'group' => 'Biblio', + ); + } + function testBiblioSaveKeyword() { + $keyword = $this->createKeyword(); + $this->assertTrue($keyword['kid'], t('Created and saved a single keyword')); + } + function testBiblioDeleteKeyword() { + $keyword = $this->createKeyword(); + $num_deleted = biblio_delete_keyword($keyword['kid']); + $this->assertEqual($num_deleted, 1, t('Deleted a single keyword')); + } + function testBiblioGetKeywordById() { + $keyword = $this->createKeyword(); + $word = (array) biblio_get_keyword_by_id($keyword['kid']); + $this->assertEqual($keyword, $word, 'Get keyword by ID'); + } + function testBiblioGetKeywordByName() { + $keyword = $this->createKeyword(); + + // Load the term with the exact name. + $word = biblio_get_keyword_by_name($keyword['word']); + $this->assertEqual($word->word, $keyword['word'], t('Keyword loaded using exact name.')); + + // Load the term with space concatenated. + $word = biblio_get_keyword_by_name(' ' . $keyword['word'] . ' '); + $this->assertEqual($word->word, trim(' ' . $keyword['word'] . ' '), t('Keyword loaded with extra whitespace.')); + + // Load the term with name uppercased. + $word = biblio_get_keyword_by_name(strtoupper($keyword['word'])); + $this->assertEqual($word->word, $keyword['word'], t('Keyword loaded with uppercased name.')); + + // Load the term with name lowercased. + $word = biblio_get_keyword_by_name(strtolower($keyword['word'])); + $this->assertEqual($word->word, $keyword['word'], t('Keyword loaded with lowercased name.')); + + // Try to load an invalid term name. + $word = biblio_get_keyword_by_name('Banana'); + $this->assertFalse($word, t('Tried to load an invalid keyword')); + + // Try to load the term using a substring of the name. + $word = biblio_get_keyword_by_name(drupal_substr($keyword['word'], 2)); + $this->assertFalse($word, t('Tried to load a keyword using a substring of the word')); + } + + + function testBiblioUpdateKeywords() { + $term1 = $this->createKeyword(); + $node = $this->createNode(); + + $node->biblio_keywords = array($term1['word']); + $old_vid = $node->vid; + + $node->biblio_keywords[0] .= 'xxx'; + $node->revision = TRUE; + node_save($node); + + $node = node_load($node->nid, NULL, TRUE); + + foreach ($node->biblio_keywords as $kid => $value) { + $this->assertEqual($value, $term1['word'] . 'xxx', 'Loaded updated keyword'); + $this->kids[] = $kid; // add the new kids to the global array so we can delete them on tear down. + } + $node = node_load($node->nid, $old_vid, TRUE); + foreach ($node->biblio_keywords as $kid => $value) { + $this->assertEqual($value, 'biblio_keywords', 'Loaded previous revision prior to update'); + $this->kids[] = $kid; // add the new kids to the global array so we can delete them on tear down. + } + biblio_delete_keywords($node); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertFalse(count($node->biblio_keywords), "Loaded node which no longer has any keywords"); + } + + function testBiblioDeleteOrphanKeywords() { + $this->createKeyword(); + $this->createKeyword(); + // $count = count($this->kids); + $num_records_before = db_query('SELECT COUNT(*) FROM {biblio_keyword_data} WHERE kid NOT IN (SELECT DISTINCT(kid) FROM {biblio_keyword})')->fetchField(); + biblio_delete_orphan_keywords(TRUE); + $num_records_after = db_query('SELECT COUNT(*) FROM {biblio_keyword_data} WHERE kid NOT IN (SELECT DISTINCT(kid) FROM {biblio_keyword})')->fetchField(); + $this->assertEqual($num_records_before, $num_records_after+$num_records_before, "Deleted $num_records_before orphan keywords"); + } + + function testBiblioExplodeKeywords() { + $keywords = array(); + $exploded = array(); + $words = array(); + $sep = variable_get('biblio_keyword_sep', ','); + $wrong_sep = ($sep == ',') ? ';' : ','; + for ($i=0; $i < 4; $i++) { + $words[] = $this->randomName(); + } + $string = implode($sep, $words); + $exploded = biblio_explode_keywords($string); + $this->assertIdentical($words, $exploded, 'Exploded keyword string with correct separator'); + $string = implode($wrong_sep, $words); + $exploded = biblio_explode_keywords($string); + $this->assertNotIdentical($words, $exploded, 'Tried to explode keyword string with incorrect separator'); + $words[] = '"' . 'word1' . $sep . ' word2' . '"'; + $string = implode($sep, $words); + $exploded = biblio_explode_keywords($string); + $words[4] = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $words[4]))); // strip the quotes becuase that's the way it comes back + + $this->assertEqual($words, $exploded, "Exploded a keyword string which contains and escaped separator"); + } + +} + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/tests/test.bib --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/tests/test.bib Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,23 @@ +@book {biblio_citekey, + title = {Biblio Title}, + series = {biblio_secondary_title}, + volume = {biblio_volume}, + number = {biblio_number}, + year = {2009}, + note = {biblio_notes}, + month = {biblio_date}, + pages = {biblio_pages}, + publisher = {biblio_publisher}, + organization = {biblio_publisher}, + type = {biblio_type_of_work}, + edition = {biblio_edition}, + chapter = {biblio_section}, + address = {biblio_place_published}, + abstract = {biblio_abst_e}, + keywords = {biblio_keywords}, + isbn = {biblio_isbn}, + issn = {biblio_issn}, + doi = {biblio_doi}, + url = {biblio_url}, + author = {Ron J. Jeromezzzzzz and John Smithzzzzzz and George W. Bushzzzzzz} +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/tests/test.ens --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/tests/test.ens Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,36 @@ +%0 Book +%B biblio_secondary_title +%D 2009 +%T Biblio Title +%A Ron J. Jeromezzzzzz +%A John Smithzzzzzz +%A George W. Bushzzzzzz +%K biblio_keywords +%X biblio_abst_e +%B biblio_secondary_title +%S biblio_tertiary_title +%7 biblio_edition +%I biblio_publisher +%C biblio_place_published +%V biblio_volume +%P biblio_pages +%8 biblio_date +%@ biblio_isbn +%G biblio_lang +%U biblio_url +%N biblio_issue +%9 biblio_type_of_work +%M biblio_accession_number +%L biblio_call_number +%1 biblio_custom1 +%2 biblio_custom2 +%3 biblio_custom3 +%4 biblio_custom4 +%# biblio_custom5 +%$ biblio_custom6 +%] biblio_custom7 +%< biblio_research_notes +%6 biblio_number_of_volumes +%R biblio_doi +%F biblio_label + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/tests/test.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/tests/test.xml Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1 @@ +Drupal-Biblio6<style face="normal" font="default" size="100%">Biblio Title</style>
      \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/todo.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/todo.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,23 @@ + todo list (no particular order) + +* check if upgrade from 5.x is clobbering other biblio related taxonomy http://drupal.org/node/361589 +* add sorting to the argument list you can pass to biblio_db_search() http://drupal.org/node/360624 +* put titles in body text to avoid long title problems http://drupal.org/node/358286 +* check author parsing on names containing UTF-8 chars. +* look at validating the DOI input http://drupal.org/node/359318 +* add descriptions to publication types (http://drupal.org/node/358158) +* add the option to change subsorts (i.e. sort by year then within year sort by...) +* fix display when sorting by author +* bibtex handeling of curly braces http://drupal.org/node/314488 +* add image field http://drupal.org/node/314249 +* check assignment of taxonomy terms http://drupal.org/node/336344 +* check BOM breaking RIS parsing http://drupal.org/node/330901 +* create "author editor" UI to find and combine duplcate authors (variations on a name who are actually the same person) +* add UNICODE to LATEX conversion to bibtex export http://drupal.org/node/183517 +* fix Biblio user warning: empty (sub)expression' from regexp query http://drupal.org/node/349777 +* ubercart integration... http://drupal.org/node/354373 +* Footnote repetition handling http://drupal.org/comment/reply/134748 +(DONE) add pathAuto support http://drupal.org/node/89038 +(DONE) fix author handeling (in contributor widget) http://drupal.org/node/313264 +(DONE) make citekey database field longer (varchar 255) http://drupal.org/node/282896 +(DONE) display attachment description rather than file name http://drupal.org/node/314395 diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio.views.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio.views.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,441 @@ + array( +// 'biblio' => array( +// 'title' => t('Biblio Page'), +// 'help' => t(''), +// 'handler' => 'views_plugin_display_page_biblio', +// 'path' => drupal_get_path('module', 'biblio') . '/views', // not necessary for most modules +//// 'theme' => 'views_view_row_node', +// 'base' => array('node'), // only works with 'node' as base. +// 'uses options' => TRUE, +// 'type' => 'normal', +// ), +// ), +// 'style' => array( +// // ... list of style plugins, +// ), +// 'row' => array( +// // ... list of row style plugins, +// ), +// 'argument default' => array( +// // ... list of argument default plugins, +// ), +// 'argument validator' => array( +// // ... list of argument validator plugins, +// ), +// 'access' => array( +// // ... list of access plugins, +// ), +// ); +//} + +/** + * Implementation of hook_views_data(). + * + * Exposes all fields to the views system. + */ +function biblio_views_data() { + $viewsdata = array(); + + /**************** biblio table **************/ + $data = array(); + // everything belongs to the Biblio group + $data['table']['group'] = t('Biblio'); + + $data['citation'] = array( + 'title' => t('Biblio Citation'), + 'help' => t("Display the complete citation for a given node"), + 'field' => array( + 'handler' => 'biblio_handler_citation', + ), + ); + $result = db_query('SELECT f.name,f.type,ftd.title,ft.ftdid FROM {biblio_fields} f + INNER JOIN {biblio_field_type} AS ft ON ft.fid = f.fid + INNER JOIN {biblio_field_type_data} ftd ON ft.ftdid = ftd.ftdid + WHERE ft.tid = 0'); + + foreach ($result as $field) { + $data[$field->name] = array( + 'title' => $field->title, + 'help' => "Display the " . $field->title, + 'field' => array('handler' => 'biblio_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'filter' => array('handler' => 'views_handler_filter_string'), + 'argument' => array('handler' => 'views_handler_argument_string') + ); + + // for contrib_widgets we use a special handler: + if ($field->type == 'contrib_widget') { + $data[$field->name]['field'] = array( + 'handler' => 'biblio_handler_field_contributor', + 'auth_category' => $field->ftdid, + ); + unset($data[$field->name]['sort']); + unset($data[$field->name]['filter']); + unset($data[$field->name]['argument']); + } + if ($field->type == 'text_format') { + $data[$field->name]['field']['handler'] = 'biblio_handler_field_markup'; + } + } + $data['biblio_year']['argument'] = array('handler' => 'views_handler_argument_numeric'); // biblio_year is an int + $data['biblio_year']['filter'] = array('handler' => 'views_handler_filter_numeric'); // biblio_year is an int + + $data['biblio_keywords']['field'] = array('handler' => 'biblio_handler_field_keyword'); + $data['biblio_keywords']['title'] = 'Keywords'; + $data['biblio_keywords']['help'] = t('All Keywords associated with the biblio node'); + + + $data['biblio_sort_title'] = array( + 'title' => 'Sort Title', + 'help' => t('Sort Title is a normalized version of the Title with leading special characters and stop words removed, use for sorting only.'), + 'field' => array('handler' => 'biblio_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'filter' => array('handler' => 'views_handler_filter_string'), + 'argument' => array('handler' => 'views_handler_argument_string') + ); + + $data['table']['join'] = array( + 'node' => array( + // links directly to node via vid + 'left_field' => 'vid', + 'field' => 'vid', + 'type' => 'left', + ), + 'node_revision' => array( + 'left_field' => 'vid', + 'field' => 'vid', + 'type' => 'left', + ), + ); + + $viewsdata['biblio'] = $data; + + /**************** biblio_types table *********************/ + + $data = array(); + $data['table']['group'] = t('Biblio'); + $data['table']['join'] = array( + 'node' => array( + 'left_table' => 'biblio', + 'left_field' => 'biblio_type', + 'field' => 'tid', + ), + 'node_revision' => array( + 'left_table' => 'biblio', + 'left_field' => 'biblio_type', + 'field' => 'tid', + ), + ); + $data['name'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Type of Publication'), + 'help' => t('The type of publication: Journal, Book, Conference Paper, etc.') + ); + $data['tid'] = array( + 'argument' => array( + 'handler' => 'biblio_handler_argument_many_to_one', + 'name table' => 'biblio_types', + 'name field' => 'name', + 'empty name field' => t('No Type'), + 'numeric' => TRUE, + ), + 'filter' => array('handler' => 'biblio_handler_filter_biblio_type'), + 'title' => t('Type of Publication'), + 'help' => t('The type of publication: Journal, Book, Conference Paper, etc.') + ); + + $viewsdata['biblio_types'] = $data; + + /**************** biblio contributors table **************/ + + $data = array(); + $data['table']['group'] = t('Biblio'); + $data['table']['join'] = array( + 'node' => array( + 'left_field' => 'vid', + 'field' => 'vid', + 'type' => 'inner', + ), + 'node_revision' => array( + 'left_field' => 'vid', + 'field' => 'vid', + ), + // This is provided for many_to_one argument + 'biblio' => array( + 'field' => 'vid', + 'left_field' => 'vid', + 'type' => 'inner', + ), + 'users' => array( + 'left_table' => 'biblio_contributor_data', + 'field' => 'cid', + 'left_field' => 'cid', + ), + ); + $data['cid'] = array( + 'title' => t('Author ID'), + 'help' => t('Filter by author id.'), + 'argument' => array( + 'handler' => 'biblio_handler_argument_many_to_one', + 'name table' => 'biblio_contributor_data', + 'name field' => 'lastname', + 'empty name field' => t('No Author'), + 'numeric' => TRUE, + ), + 'filter' => array( + 'handler' => 'biblio_handler_filter_contributor', + ) + ); + $data['rank'] = array( + 'title' => t('Author Rank'), + 'help' => t('Rank defines the author order "0" being the first author, "1" the second and so on.'), + 'filter' => array( + 'handler' => 'views_handler_filter_numeric', + ) + ); + $data['auth_type'] = array( + 'title' => t('Author Type'), + 'help' => t('Rank defines the type of author Author, Editor, Translator and so on.'), + 'filter' => array( + 'handler' => 'biblio_handler_filter_biblio_contributor_auth_type', + ) + ); + + $viewsdata['biblio_contributor'] = $data; + +/**************** biblio_contributor_data table ***********/ + $data = array(); + $data['table']['group'] = t('Biblio'); + $data['table']['base'] = array( + 'field' => 'cid', + 'title' => t('Biblio Authors'), + ); + $data['table']['join'] = array( + 'biblio_contributor' => array( + 'left_field' => 'cid', + 'field' => 'cid', + ), + 'node' => array( + 'left_table' => 'biblio_contributor', + 'left_field' => 'cid', + 'field' => 'cid', + ), + 'users' => array( + 'left_field' => 'uid', + 'field' => 'drupal_uid', + ), + ); + $data['drupal_uid'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'filter' => array('handler' => 'biblio_handler_filter_contributor_uid'), + 'argument' => array('handler' => 'views_handler_argument_numeric'), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'base' => 'users', + 'base field' => 'uid', + 'label' => t('user'), + ), + + 'title' => t('Drupal UserID'), + 'help' => t('This is the Drupal user associated with the Biblio Author.') + ); + $data['cid'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'biblio_handler_sort_contributor_lastname'), + 'filter' => array('handler' => 'biblio_handler_filter_contributor_lastname'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Author id'), + 'help' => t('Author id') + ); + $data['lastname'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'biblio_handler_sort_contributor_lastname'), + 'filter' => array('handler' => 'biblio_handler_filter_contributor_lastname'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Author last name'), + 'help' => t('Author last name') + ); + $data['firstname'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'filter' => array('handler' => 'views_handler_filter'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Author first name'), + 'help' => t('Author first name') + ); + $data['name'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'filter' => array('handler' => 'views_handler_filter'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Author full name'), + 'help' => t('Author full name') + ); + $data['prefix'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'filter' => array('handler' => 'views_handler_filter'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Author preix'), + 'help' => t('Attributes which typically proceed the authors first name') + ); + $data['suffix'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'filter' => array('handler' => 'views_handler_filter'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Author suffix'), + 'help' => t('Attributes which typically follow the authors last name.') + ); + $data['affiliation'] = array( + 'field' => array('handler' => 'views_handler_field'), + 'sort' => array('handler' => 'views_handler_sort'), + 'filter' => array('handler' => 'views_handler_filter'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'title' => t('Author affiliation'), + 'help' => t('Organization the author is affiliated with') + ); + + $viewsdata['biblio_contributor_data'] = $data; + + + +/***************** Describe the keyword table *************/ + + $data = array(); + $data['table']['group'] = t('Biblio'); + $data['table']['join'] = array( + 'node' => array( + 'left_field' => 'vid', + 'field' => 'vid', + ), + 'node_revision' => array( + 'left_field' => 'vid', + 'field' => 'vid', + ), + 'biblio_keyword_data' => array( + 'field' => 'kid', + 'left_field' => 'kid', + ), + ); + $data['kid'] = array( + 'title' => t('Keyword ID'), + 'help' => t('The Biblio keyword ID'), + 'field' => array( + 'title' => t('All keywords'), + 'help' => t('Display all keywords associated with a node.'), + 'handler' => 'biblio_handler_field_biblio_keyword_kid', + ), + 'argument' => array( + 'handler' => 'biblio_handler_argument_many_to_one', + 'name table' => 'biblio_keyword_data', + 'name field' => 'word', + 'empty name field' => t('No Keyword'), + 'numeric' => TRUE, + ), + 'filter' => array( + 'title' => t('Keyword ID'), + 'handler' => 'biblio_handler_filter_biblio_keyword_kid', + 'numeric' => TRUE, + ), + 'skip base' => array('node', 'node_revision'), + ); + + $viewsdata['biblio_keyword'] = $data; + + $viewsdata['biblio_keyword']['nid'] = array( + 'title' => t('Node'), + 'help' => t('Get all nodes tagged with a keyword.'), + 'relationship' => array( + 'handler' => 'views_handler_relationship', + 'base' => 'node', + 'base field' => 'nid', + 'label' => t('node'), + ), + ); + + +/***************** Describe the keyword_data table ***********/ + + $data = array(); + $data['table']['group'] = t('Biblio'); + $data['table']['base'] = array( + 'field' => 'kid', + 'title' => t('Biblio Keywords'), + ); + + $data['table']['join'] = array( + 'biblio_keyword' => array( + 'left_field' => 'kid', + 'field' => 'kid', + ), + 'node' => array( + 'left_table' => 'biblio_keyword', + 'left_field' => 'kid', + 'field' => 'kid', + ), + 'biblio' => array( + 'left_table' => 'biblio_keyword', + 'left_field' => 'kid', + 'field' => 'kid', + ), + ); + $data['word'] = array( + 'field' => array('handler' => 'biblio_handler_field_biblio_keyword_data_word'), + 'filter' => array('handler' => 'views_handler_filter_string'), + 'argument' => array('handler' => 'views_handler_argument_string'), + 'sort' => array('handler' => 'views_handler_sort'), + 'title' => t('Keyword'), + 'help' => t('A single keyword related to a biblio node'), + //'skip base' => array('node', 'node_revision'), + ); + $data['kid'] = array( + 'field' => array('handler' => 'views_handler_field_numeric'), + // 'filter' => array('handler' => 'views_handler_filter'), + // 'argument' => array('handler' => 'views_handler_argument'), + 'title' => t('Keyword ID'), + 'skip base' => array('node', 'node_revision'), + ); + + + $viewsdata['biblio_keyword_data'] = $data; + + $export_links = module_invoke_all('biblio_export_options'); + foreach ($export_links as $type => $name) { + $viewsdata['biblio'][$type.'_export'] = array( + 'title' => t('Export link - ' . $name), + 'help' => t("Provides a link to export the data in ") . $name . t(" format"), + 'field' => array( + 'handler' => 'biblio_handler_field_export_link', + 'group' => t('Biblio'), + 'format' => $type, + 'format name' => $name, + ), + ); + + } + + return $viewsdata; +} + +function template_preprocess_views_view_unformatted__biblio_year(&$vars) { + $view = $vars['view']; + $rows = $vars['rows']; + + $vars['classes'] = array(); + // Set up striping values. + foreach ($rows as $id => $row) { + $vars['classes'][$id] = 'views-row'; + $vars['classes'][$id] .= ' views-row-' . ($id + 1); + $vars['classes'][$id] .= ' views-row-' . ($id % 2 ? 'even' : 'odd'); + if ($id == 0) { + $vars['classes'][$id] .= ' views-row-first'; + } + } + $vars['classes'][$id] .= ' views-row-last'; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio.views_default.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio.views_default.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,642 @@ +name = 'biblio_views'; + $view->description = ''; + $view->tag = ''; + $view->view_php = ''; + $view->base_table = 'node'; + $view->is_cacheable = FALSE; + $view->api_version = 2; + $view->disabled = TRUE; /* Edit this to TRUE to make a default view disabled initially */ + $handler = $view->new_display('default', 'Defaults', 'default'); + $handler->override_option('fields', array( + 'citation' => array( + 'label' => '', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'style_name' => 'vancouver', + 'exclude' => 0, + 'id' => 'citation', + 'table' => 'biblio', + 'field' => 'citation', + 'relationship' => 'none', + ), + )); + $handler->override_option('sorts', array( + 'biblio_year' => array( + 'order' => 'DESC', + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + 'relationship' => 'none', + ), + 'name' => array( + 'id' => 'name', + 'table' => 'biblio_types', + 'field' => 'name', + ), + )); + $handler->override_option('arguments', array( + 'biblio_year' => array( + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + ), + )); + $handler->override_option('filters', array( + 'status' => array( + 'operator' => '=', + 'value' => '1', + 'group' => '0', + 'exposed' => FALSE, + 'expose' => array( + 'operator' => FALSE, + 'label' => '', + ), + 'id' => 'status', + 'table' => 'node', + 'field' => 'status', + 'relationship' => 'none', + ), + 'type' => array( + 'operator' => 'in', + 'value' => array( + 'biblio' => 'biblio', + ), + 'group' => '0', + 'exposed' => FALSE, + 'expose' => array( + 'operator' => FALSE, + 'label' => '', + ), + 'id' => 'type', + 'table' => 'node', + 'field' => 'type', + 'relationship' => 'none', + ), + )); + $handler->override_option('access', array( + 'type' => 'none', + )); + $handler->override_option('cache', array( + 'type' => 'none', + )); + $handler->override_option('header_format', '1'); + $handler->override_option('header_empty', 0); + $handler->override_option('empty', 'Nothing found'); + $handler->override_option('empty_format', '1'); + $handler->override_option('use_pager', '1'); + $handler->override_option('style_options', array( + 'grouping' => 'name', + )); + $handler->override_option('row_options', array( + 'inline' => array(), + 'separator' => '', + )); + $handler = $view->new_display('page', 'Group by Year', 'page_1'); + $handler->override_option('fields', array( + 'citation' => array( + 'label' => '', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'style_name' => 'vancouver', + 'exclude' => 0, + 'id' => 'citation', + 'table' => 'biblio', + 'field' => 'citation', + 'relationship' => 'none', + ), + 'biblio_year' => array( + 'label' => '', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'biblio_label' => 0, + 'exclude' => 1, + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + 'override' => array( + 'button' => 'Override', + ), + 'relationship' => 'none', + ), + )); + $handler->override_option('arguments', array( + 'biblio_year' => array( + 'default_action' => 'ignore', + 'style_plugin' => 'default_summary', + 'style_options' => array(), + 'wildcard' => 'all', + 'wildcard_substitution' => 'All', + 'title' => '', + 'breadcrumb' => '', + 'default_argument_type' => 'fixed', + 'default_argument' => '', + 'validate_type' => 'none', + 'validate_fail' => 'not found', + 'glossary' => 0, + 'limit' => '0', + 'case' => 'none', + 'path_case' => 'none', + 'transform_dash' => 0, + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + 'validate_user_argument_type' => 'uid', + 'validate_user_roles' => array( + '2' => 0, + ), + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + 'default_options_div_prefix' => '', + 'default_argument_user' => 0, + 'default_argument_fixed' => '', + 'default_argument_php' => '', + 'validate_argument_node_type' => array( + 'image' => 0, + 'biblio' => 0, + 'book' => 0, + 'feed' => 0, + 'page' => 0, + 'story' => 0, + 'test' => 0, + ), + 'validate_argument_node_access' => 0, + 'validate_argument_nid_type' => 'nid', + 'validate_argument_vocabulary' => array( + '2' => 0, + '3' => 0, + '4' => 0, + ), + 'validate_argument_type' => 'tid', + 'validate_argument_transform' => 0, + 'validate_user_restrict_roles' => 0, + 'image_size' => array( + '_original' => '_original', + 'thumbnail' => 'thumbnail', + 'preview' => 'preview', + ), + 'validate_argument_php' => '', + ), + )); + $handler->override_option('style_options', array( + 'grouping' => 'biblio_year', + )); + $handler->override_option('path', 'view/biblio/year'); + $handler->override_option('menu', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + 'name' => 'navigation', + )); + $handler->override_option('tab_options', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + )); + $handler = $view->new_display('page', 'Group by Type', 'page_2'); + $handler->override_option('fields', array( + 'citation' => array( + 'label' => '', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'style_name' => 'vancouver', + 'exclude' => 0, + 'id' => 'citation', + 'table' => 'biblio', + 'field' => 'citation', + 'relationship' => 'none', + ), + 'name' => array( + 'label' => '', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'exclude' => 1, + 'id' => 'name', + 'table' => 'biblio_types', + 'field' => 'name', + 'relationship' => 'none', + ), + )); + $handler->override_option('sorts', array( + 'name' => array( + 'order' => 'ASC', + 'id' => 'name', + 'table' => 'biblio_types', + 'field' => 'name', + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + ), + 'biblio_year' => array( + 'order' => 'DESC', + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + 'relationship' => 'none', + ), + )); + $handler->override_option('path', 'view/biblio/type'); + $handler->override_option('menu', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + 'name' => 'navigation', + )); + $handler->override_option('tab_options', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + )); + $handler = $view->new_display('page', 'Group by Title', 'page_3'); + $handler->override_option('fields', array( + 'citation' => array( + 'label' => '', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'style_name' => 'vancouver', + 'exclude' => 0, + 'id' => 'citation', + 'table' => 'biblio', + 'field' => 'citation', + 'relationship' => 'none', + ), + 'biblio_year' => array( + 'label' => 'Year of Publication', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'biblio_label' => 1, + 'exclude' => 1, + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + ), + )); + $handler->override_option('sorts', array( + 'biblio_year' => array( + 'order' => 'DESC', + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + 'relationship' => 'none', + ), + 'title' => array( + 'order' => 'ASC', + 'id' => 'title', + 'table' => 'node', + 'field' => 'title', + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + ), + )); + $handler->override_option('style_options', array( + 'grouping' => 'biblio_year', + )); + $handler->override_option('path', 'view/biblio/title'); + $handler->override_option('menu', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + 'name' => 'navigation', + )); + $handler->override_option('tab_options', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + )); + $handler = $view->new_display('page', 'Filter by Keyword ID', 'page_4'); + $handler->override_option('arguments', array( + 'kid' => array( + 'default_action' => 'ignore', + 'style_plugin' => 'default_summary', + 'style_options' => array(), + 'wildcard' => 'all', + 'wildcard_substitution' => 'All', + 'title' => 'Contains the keyword %1', + 'breadcrumb' => '', + 'default_argument_type' => 'fixed', + 'default_argument' => '', + 'validate_type' => 'none', + 'validate_fail' => 'not found', + 'break_phrase' => 1, + 'add_table' => 0, + 'require_value' => 0, + 'reduce_duplicates' => 0, + 'id' => 'kid', + 'table' => 'biblio_keyword', + 'field' => 'kid', + 'validate_user_argument_type' => 'uid', + 'validate_user_roles' => array( + '2' => 0, + ), + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + 'default_options_div_prefix' => '', + 'default_argument_user' => 0, + 'default_argument_fixed' => '', + 'default_argument_php' => '', + 'validate_argument_node_type' => array( + 'image' => 0, + 'biblio' => 0, + 'book' => 0, + 'feed' => 0, + 'page' => 0, + 'story' => 0, + 'test' => 0, + ), + 'validate_argument_node_access' => 0, + 'validate_argument_nid_type' => 'nid', + 'validate_argument_vocabulary' => array( + '2' => 0, + '3' => 0, + '4' => 0, + ), + 'validate_argument_type' => 'tid', + 'validate_argument_transform' => 0, + 'validate_user_restrict_roles' => 0, + 'image_size' => array( + '_original' => '_original', + 'thumbnail' => 'thumbnail', + 'preview' => 'preview', + ), + 'validate_argument_php' => '', + ), + )); + $handler->override_option('path', 'view/biblio/keyword'); + $handler->override_option('menu', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + 'name' => 'navigation', + )); + $handler->override_option('tab_options', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + )); + $handler = $view->new_display('page', 'Filter by Author ID', 'page_5'); + $handler->override_option('arguments', array( + 'cid' => array( + 'default_action' => 'ignore', + 'style_plugin' => 'default_summary', + 'style_options' => array(), + 'wildcard' => 'all', + 'wildcard_substitution' => 'All', + 'title' => '', + 'breadcrumb' => '', + 'default_argument_type' => 'fixed', + 'default_argument' => '', + 'validate_type' => 'none', + 'validate_fail' => 'not found', + 'break_phrase' => 1, + 'add_table' => 0, + 'require_value' => 0, + 'reduce_duplicates' => 0, + 'id' => 'cid', + 'table' => 'biblio_contributor', + 'field' => 'cid', + 'validate_user_argument_type' => 'uid', + 'validate_user_roles' => array( + '2' => 0, + ), + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + 'default_options_div_prefix' => '', + 'default_argument_user' => 0, + 'default_argument_fixed' => '', + 'default_argument_php' => '', + 'validate_argument_node_type' => array( + 'image' => 0, + 'biblio' => 0, + 'book' => 0, + 'feed' => 0, + 'page' => 0, + 'story' => 0, + 'test' => 0, + ), + 'validate_argument_node_access' => 0, + 'validate_argument_nid_type' => 'nid', + 'validate_argument_vocabulary' => array( + '2' => 0, + '3' => 0, + '4' => 0, + ), + 'validate_argument_type' => 'tid', + 'validate_argument_transform' => 0, + 'validate_user_restrict_roles' => 0, + 'image_size' => array( + '_original' => '_original', + 'thumbnail' => 'thumbnail', + 'preview' => 'preview', + ), + 'validate_argument_php' => '', + ), + )); + $handler->override_option('path', 'view/biblio/author'); + $handler->override_option('menu', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + 'name' => 'navigation', + )); + $handler->override_option('tab_options', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + )); + $handler = $view->new_display('page', 'Browse by Year', 'page_6'); + $handler->override_option('fields', array( + 'biblio_year' => array( + 'label' => '', + 'alter' => array( + 'alter_text' => 0, + 'text' => '', + 'make_link' => 0, + 'path' => '', + 'link_class' => '', + 'alt' => '', + 'prefix' => '', + 'suffix' => '', + 'help' => '', + 'trim' => 0, + 'max_length' => '', + 'word_boundary' => 1, + 'ellipsis' => 1, + 'strip_tags' => 0, + 'html' => 0, + ), + 'biblio_label' => 0, + 'exclude' => 0, + 'id' => 'biblio_year', + 'table' => 'biblio', + 'field' => 'biblio_year', + 'override' => array( + 'button' => 'Use default', + ), + 'relationship' => 'none', + ), + )); + $handler->override_option('sorts', array()); + $handler->override_option('arguments', array()); + $handler->override_option('distinct', 0); + $handler->override_option('style_plugin', 'grid'); + $handler->override_option('style_options', array( + 'grouping' => '', + 'columns' => '4', + 'alignment' => 'horizontal', + )); + $handler->override_option('path', 'view/biblio/years'); + $handler->override_option('menu', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + 'name' => 'navigation', + )); + $handler->override_option('tab_options', array( + 'type' => 'none', + 'title' => '', + 'description' => '', + 'weight' => 0, + )); + + $views[$view->name] = $view; + return $views; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_argument_many_to_one.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_argument_many_to_one.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,21 @@ +name_field . ' FROM {' . $this->name_table + . '} WHERE ' . $this->real_field . ' IN (:vids)', array(':vids' => implode(', ', $this->value))); + foreach ($result as $row) { + $names[] = $row->{$this->name_field}; + } + return !empty($names) ? $names : array(t('Invalid input')); + } +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_citation.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_citation.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,128 @@ +additional_fields['nid'] = array('table' => 'node', 'field' => 'nid'); + } + + function query() { + $this->field_alias = 'nid'; + $this->add_additional_fields(); + } + + function option_definition() { + $options = parent::option_definition(); + $options['style_name'] = array('default' => biblio_get_style()); + $options['export_links'] = array('default' => 1); + $options['file_attachments'] = array('default' => 1); + $options['open_url_link'] = array('default' => 1); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['style_name'] = array( + '#type' => 'select', + '#title' => t('Style'), + '#default_value' => $this->options['style_name'], + '#options' => biblio_get_styles(), + '#description' => t('Define the layout of citation.') + ); + $form['export_links'] = array( + '#type' => 'checkbox', + '#title' => t('Show export links'), + '#default_value' => $this->options['export_links'], + '#description' => t('This will add a set of links to export the entry in various file formats such as Bibtex or RIS.') + ); + $form['file_attachments'] = array( + '#type' => 'checkbox', + '#title' => t('Show download links for file attachments'), + '#default_value' => $this->options['file_attachments'], + '#description' => t('If there are files attached to the entry, this will add a download link for each file attached.') + ); + $form['open_url_link'] = array( + '#type' => 'checkbox', + '#title' => t('Show OpenURL links'), + '#default_value' => $this->options['open_url_link'], + '#description' => t('This will add an !openurl link to the entry, assuming you have competed the OpenURL configuration on the Biblio !settings page.', array('!openurl' => l('OpenURL', "http://en.wikipedia.org/wiki/OpenURL"), '!settings' => l('settings', 'admin/config/content/biblio') )), + ); + } + + function pre_render(&$values) { + $nids = array(); + $nodes = array(); + + foreach ($values as $result) { + if (!empty($result->{$this->aliases['nid']})) { + $nids[] = $result->{$this->aliases['nid']}; + } + } + + if ($nids) { + $langcode = $GLOBALS['language_content']->language; + $nodes = node_load_multiple($nids); + if (!empty($nodes)) { + field_attach_prepare_view('node', $nodes, 'full', $langcode); + entity_prepare_view('node', $nodes, $langcode); + + foreach ($values as $key => $result) { + if (isset($result->{$this->aliases['nid']})) { + $values[$key]->node = $nodes[$result->{$this->aliases['nid']}]; + } + } + } + } + } + + function render($values) { + $output = ''; + + if (!isset($values->node) || $values->node->type != 'biblio') { + return; + } + + if (empty($this->biblio_base)) { + $this->biblio_base = variable_get('biblio_base', 'biblio'); + } + + $item = $values->node; + + if (isset($item->biblio_year)) $item->biblio_year = _biblio_text_year($item->biblio_year); + if (variable_get('biblio_hide_bibtex_braces', 0)) { + $item->title = biblio_remove_brace($item->title); + } + + if (!$item->status) { + $output .= '
      '; + } + + // first add the styled entry... + $output .= theme('biblio_style', array('node' => $item, 'style_name' => $this->options['style_name'])); + + $annotation_field = variable_get('biblio_annotations', 'none'); + if ($annotation_field != 'none' && $item-> $annotation_field) { + $output .= '
      '; + $output .= filter_xss($item->$annotation_field, biblio_get_allowed_tags()); + $output .= '
      '; + } + + $openurl_base = variable_get('biblio_baseopenurl', ''); + if (!empty($openurl_base) && $this->options['open_url_link']) { + $output .= theme('biblio_openurl', array('openURL' => biblio_openurl($item))); + } + + if (biblio_access('export') && $this->options['export_links']) { + $output .= theme('biblio_export_links', array('node' => $item)); + } + + if (biblio_access('download', $item) && $this->options['file_attachments']) { + $output .= theme('biblio_download_links', array('node' => $item)); + } + if (!$item->status) { + $output .= '
      '; + } + + return $output; + } + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,71 @@ +options['biblio_label']) return; + $this->definition['click sortable'] = array('default' => TRUE); + + $result = db_query("SELECT bft.tid, bftd.title FROM {biblio_field_type} bft + INNER JOIN {biblio_fields} bf ON bft.fid=bf.fid AND bf.name = :name + INNER JOIN {biblio_field_type_data} bftd ON bftd.ftdid=bft.ftdid", + array(':name' => $options['field'])) + ; + foreach ($result as $label) { + $this->labels[$label->tid] = $label->title; + } + } + + function query() { + // add the biblio_type field as tid + $this->ensure_my_table(); + if ($this->options['biblio_label']) { + $this->query->add_field($this->table_alias, 'biblio_type', 'biblio_tid'); + } + parent::query(); + } + + function option_definition() { + $options = parent::option_definition(); + $options['biblio_label'] = array('default' => TRUE); + + return $options; + } + function options_form(&$form, &$form_state) { + $form['biblio_label'] = array( + '#type' => 'checkbox', + '#title' => t('Use label specific to biblio type'), + '#description' => 'Check this option to use the type-specific field labels as defined in ' + . l(t('biblio settings'), 'admin/config/content/biblio/fields/type'), + '#default_value' => $this->options['biblio_label'], + ); + parent::options_form($form, $form_state); + $form['label'] += array( + '#process' => array('ctools_dependent_process'), + '#dependency' => array( + 'edit-options-biblio-label' => array(0), + ), + ); + } + function set_label(&$values) { + if (!$this->options['biblio_label']) return; + $tid = $values->biblio_tid; + $this->options['label'] = isset($this->labels[$tid]) ? $this->labels[$tid] : $this->labels[0]; + } + + function pre_render(&$values) { + foreach ($values as $result) { + if (!empty($result->biblio_biblio_year)) { // this converts values like 9999 or 9998 to "Submitted" and "In Press" + $result->biblio_biblio_year = _biblio_text_year($result->biblio_biblio_year); + } + } + return $values; + } + + function render($values) { + $this->set_label($values); + return parent::render($values); + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field_biblio_keyword_data_word.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field_biblio_keyword_data_word.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,60 @@ +additional_fields['kid'] = 'kid'; + } + + function option_definition() { + $options = parent::option_definition(); + $options['link_to_keyword'] = array( + 'default' => FALSE + ); + $options['multi_type'] = array( + 'default' => 'separator' + ); + $options['separator'] = array( + 'default' => ', ' + ); + + return $options; + } + + /** + * Provide link to taxonomy option + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['link_to_keyword'] = array( + '#title' => t('Link this field to its keyword page'), + '#description' => t('This will override any other link you have set.'), + '#type' => 'checkbox', + '#default_value' => !empty($this->options['link_to_keyword']), + ); + } + /** + * Render whatever the data is as a link to the taxonomy. + * + * Data should be made XSS safe prior to calling this function. + */ + function render_link($data, $values) { + if (!empty($this->options['link_to_keyword']) && !empty($values->{$this->aliases['kid']}) && $data !== NULL && $data !== '') { + $kid = $values->{$this->aliases['kid']}; + $base = variable_get('biblio_base', 'biblio'); + $this->options['alter']['make_link'] = TRUE; + $this->options['alter']['path'] = $base .'/keyword/' . $kid; + } + return $data; + } + + function render($values) { + return $this->render_link(check_plain($values->{$this->field_alias}), $values); + + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field_biblio_keyword_kid.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field_biblio_keyword_kid.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,40 @@ +multiple = TRUE; + if ($view->base_table == 'node_revision') { + $this->additional_fields['vid'] = array('table' => 'node_revision', 'field' => 'vid'); + } + else { + $this->additional_fields['vid'] = array('table' => 'node', 'field' => 'vid'); + } + } + + + function pre_render($values) { + $this->field_alias = $this->aliases['vid']; + $vids = array(); + foreach ($values as $result) { + if (!empty($result->{$this->aliases['vid']})) { + $vids[] = $result->{$this->aliases['vid']}; + } + } + if ($vids) { + $query = db_select('biblio_keyword_data', 'bkd'); + $query->innerJoin('biblio_keyword', 'bk', 'bkd.kid = bk.kid'); + $query->fields('bkd'); + $query->addField('bk', 'vid', 'node_vid'); + $query->orderby('bkd.word'); + $query->condition('bk.vid', $vids); + $query->addMetaData('base_table', 'biblio_keyword'); + $query->addTag('node_access'); + $result = $query->execute(); + + foreach ($result as $term) { + $this->items[$term->node_vid][$term->kid] = check_plain($term->word); + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field_biblio_type.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field_biblio_type.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,41 @@ +base_table == 'node_revision') { + $this->additional_fields['vid'] = array('table' => 'node_revision', 'field' => 'vid'); + } + else { + $this->additional_fields['vid'] = array('table' => 'node', 'field' => 'vid'); + } + } + + + function pre_render($values) { + $this->field_alias = $this->aliases['vid']; + $vids = array(); + foreach ($values as $result) { + if (!empty($result->{$this->aliases['vid']})) { + $vids[] = $result->{$this->aliases['vid']}; + } + } + //print_r($values); + if ($vids) { + + //$result = db_query("SELECT bt.name AS node_vid, bkd.* FROM {biblio_keyword_data} bkd INNER JOIN {biblio_keyword} bk ON bkd.kid = bk.kid WHERE bk.vid IN (" . implode(', ', $vids) . ") ORDER BY bkd.word"); + $result = db_query("SELECT name, tid + FROM {biblio_types} t + WHERE IN (:vids)", array(':vids' => implode(', ', $vids))); + + foreach ($result as $term) { +// if (empty($this->options['link_to_taxonomy'])) { + $this->items[$term->node_vid][$term->kid] = check_plain($term->word); +// } +// else { +// $this->items[$term->node_vid][$term->kid] = l($term->word, taxonomy_term_path($term)); +// } + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field_contributor.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field_contributor.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,279 @@ +multiple = TRUE; + $this->additional_fields['vid'] = array('table' => 'biblio', 'field' => 'vid'); + } + + function option_definition() { + $options = parent::option_definition(); + $options['auth_category'] = array('default' => 1); + $options['auth_links'] = array('default' => 0); + $options['initialize'] = array('default' => TRUE); + $options['initialize_with'] = array('default' => '.'); + $options['initialize_with_hyphen'] = array('default' => FALSE); + $options['separator'] = array('default' => '; '); + $options['sort_separator'] = array('default' => ', '); + $options['short_form'] = array('default' => 0); + $options['name_order'] = array('default' => 'first-last'); + + return $options; + } + + function options_form(&$form, &$form_state) { + $form['auth_category'] = array( + '#type' => 'select', + '#title' => t('Category of Author'), + '#default_value' => $this->options['auth_category'], + '#options' => array( + 0 => t('All categories'), + 1 => t('Primary'), + 2 => t('Secondary'), + 3 => t('Tertiary'), + 4 => t('Subsidiary'), + 5 => t('Corporate/Institutional') + ), + + ); + $form['formatting'] = array( + '#type' => 'fieldset', + '#title' => t('Author format'), + '#collapsible' => TRUE, + ); + $form['name_order'] = array( + '#type' => 'select', + '#title' => t('Name order'), + '#default_value' => $this->options['name_order'], + '#options' => array( + 'first-last' => t('First name first'), + 'last-first' => t('Last name first'), + ), + '#description' => t('The order that first and last names appear for each author.'), + '#fieldset' => 'formatting', + ); + $form['initialize'] = array( + '#type' => 'checkbox', + '#title' => t('Shorten given names'), + '#default_value' => $this->options['initialize'], + '#description' => t('Shorten given names to single initial each.'), + '#fieldset' => 'formatting', + ); + $form['short_form'] = array( + '#type' => 'checkbox', + '#title' => t('Show only "family" names'), + '#default_value' => $this->options['short_form'], + '#description' => t('Show only family name, no first name or initials.'), + '#fieldset' => 'formatting', + ); + $form['separators'] = array( + '#type' => 'fieldset', + '#title' => t('Separators'), + '#collapsible' => TRUE, + ); + $form['initialize_with'] = array( + '#type' => 'textfield', + '#size' => 5, + '#title' => t('Initial separator'), + '#default_value' => $this->options['initialize_with'], + '#description' => t('Enter the character (if any) which will be used to separate the initials.'), + '#fieldset' => 'separators', + ); + $form['sort_separator'] = array( + '#type' => 'textfield', + '#size' => 5, + '#title' => t('Sort separator'), + '#default_value' => $this->options['sort_separator'], + '#description' => t('Enter the character which will be used to separate the last name from the first name (or initials) when displayed last name first (Smith, John).'), + '#fieldset' => 'separators', + ); + $form['separator'] = array( + '#type' => 'textfield', + '#size' => 5, + '#title' => t('Author separator'), + '#default_value' => $this->options['separator'], + '#required' => TRUE, + '#description' => t('Enter the character which will be used to separate the authors (Smith, John; Doe, Jane).'), + '#fieldset' => 'separators', + ); + parent::options_form($form, $form_state); + + } + + function query() { + $this->add_additional_fields(); + $this->field_alias = $this->aliases['vid']; + } + + function post_execute(&$values) { + $vids = array(); + $this->items = array(); + $filter = ''; + + foreach ($values as $result) { + // Don't add empty results to the array. + if (isset($this->aliases['vid']) && !empty($result->{$this->aliases['vid']})) { + $vids[] = $result->{$this->aliases['vid']}; + } + } + + if (count($vids)) { + $this->items = biblio_load_contributors_multiple($vids, $this->options['auth_category']); + } + } + + function render($values) { + parent::set_label($values); + $vid = $values->{$this->field_alias}; + if (!isset($this->items[$vid])) return NULL; + return $this->render_contriubutors($this->items[$vid]); + } + + function render_contriubutors($contributors) { + $authors = array(); + if (!isset($this->alnum)) { + list($this->alnum, $this->alpha, $this->cntrl, $this->dash, + $this->digit, $this->graph, $this->lower, $this->print, + $this->punct, $this->space, $this->upper, $this->word, + $this->patternModifiers) = $this->get_regex_patterns(); + } + + foreach ($contributors as $rank => $author) { + $author = (object)$author; + if ($author->literal == 1) { + $authors[] = $author->name; + } + else { + if (!empty($author->firstname) && $this->options['initialize'] == 1) { + $author->firstname = preg_replace("/([$this->upper])[$this->lower]+/$this->patternModifiers", '\\1', $author->firstname); + $author->firstname = preg_replace("/(?<=[-$this->upper]) +(?=[-$this->upper])/$this->patternModifiers", "", $author->firstname); + $author->initials = $author->firstname . $author->initials; + } + if (isset($author->initials)) { + // within initials, remove any dots: + $author->initials = preg_replace("/([$this->upper])\.+/$this->patternModifiers", "\\1", $author->initials); + // within initials, remove any spaces *between* initials: + $author->initials = preg_replace("/(?<=[-$this->upper]) +(?=[-$this->upper])/$this->patternModifiers", "", $author->initials); + if ($this->options['initialize_with_hyphen'] === FALSE) { + $author->initials = preg_replace("/-/", '', $author->initials); + } + // within initials, add a space after a hyphen, but only if ... + if (preg_match("/ $/", $this->options['initialize_with'])) {// ... the delimiter that separates initials ends with a space + $author->initials = preg_replace("/-(?=[$this->upper])/$this->patternModifiers", "- ", $author->initials); + } + // then, separate initials with the specified delimiter: + $init_sep = $this->options['initialize_with']; + $author->initials = preg_replace("/([$this->upper])(?=[^$this->lower]+|$)/$this->patternModifiers", "\\1$init_sep", $author->initials); + + // $shortenInitials = (isset($options['numberOfInitialsToKeep)) ? $options['numberOfInitialsToKeep : FALSE; + // if ($shortenInitials) $given = drupal_substr($given, 0, $shortenInitials); + + if ($this->options['initialize'] == 1) { + $author->firstname = $author->initials; + // if ($shortenInitials) $author->firstname = drupal_substr($author->firstname, 0, $shortenInitials); + } + elseif (!empty($author->firstname)) { + $author->firstname = $author->firstname . ' ' . $author->initials; + } + elseif (empty($author->firstname)) { + $author->firstname = $author->initials; + } + } + + if (isset($author->lastname)) { + if ($this->options['short_form'] == 1) { + $authors[] = $author->lastname; + } + else { + switch ($this->options['name_order']) { + case 'last-first': + $authors[] = $author->lastname . $this->options['sort_separator'] . $author->firstname; + break; + default: + $authors[] = $author->firstname . ' ' . $author->lastname ; + } + } + } + } + } + + return implode($this->options['separator'], $authors); + } + function get_regex_patterns() { + // Checks if PCRE is compiled with UTF-8 and Unicode support + if (!@preg_match('/\pL/u', 'a')) { + // probably a broken PCRE library + return $this->get_latin1_regex(); + } + else { + // Unicode safe filter for the value + return $this->get_utf8_regex(); + } + } + + function get_latin1_regex() { + $alnum = "[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 letters: + $alpha = "[:alpha:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 control characters: + $cntrl = "[:cntrl:]"; + // Matches ISO-8859-1 dashes & hyphens: + $dash = "-–"; + // Matches ISO-8859-1 digits: + $digit = "[\d]"; + // Matches ISO-8859-1 printing characters (excluding space): + $graph = "[:graph:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 lower case letters: + $lower = "[:lower:]äåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 printing characters (including space): + $print = "[:print:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Matches ISO-8859-1 punctuation: + $punct = "[:punct:]"; + // Matches ISO-8859-1 whitespace (separating characters with no visual representation): + $space = "[\s]"; + // Matches ISO-8859-1 upper case letters: + $upper = "[:upper:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆ"; + // Matches ISO-8859-1 "word" characters: + $word = "_[:alnum:]ÄÅÁÀÂÃÇÉÈÊËÑÖØÓÒÔÕÜÚÙÛÍÌÎÏÆäåáàâãçéèêëñöøóòôõüúùûíìîïæÿß"; + // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables: + // More info: + $patternModifiers = ""; + + return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers); + + } + function get_utf8_regex() { + // Matches Unicode letters & digits: + $alnum = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:alnum:]" + // Matches Unicode letters: + $alpha = "\p{Ll}\p{Lu}\p{Lt}\p{Lo}"; // Unicode-aware equivalent of "[:alpha:]" + // Matches Unicode control codes & characters not in other categories: + $cntrl = "\p{C}"; // Unicode-aware equivalent of "[:cntrl:]" + // Matches Unicode dashes & hyphens: + $dash = "\p{Pd}"; + // Matches Unicode digits: + $digit = "\p{Nd}"; // Unicode-aware equivalent of "[:digit:]" + // Matches Unicode printing characters (excluding space): + $graph = "^\p{C}\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:graph:]" + // Matches Unicode lower case letters: + $lower = "\p{Ll}\p{M}"; // Unicode-aware equivalent of "[:lower:]" + // Matches Unicode printing characters (including space): + $print = "\P{C}"; // same as "^\p{C}", Unicode-aware equivalent of "[:print:]" + // Matches Unicode punctuation (printing characters excluding letters & digits): + $punct = "\p{P}"; // Unicode-aware equivalent of "[:punct:]" + // Matches Unicode whitespace (separating characters with no visual representation): + $space = "\t\n\f\r\p{Z}"; // Unicode-aware equivalent of "[:space:]" + // Matches Unicode upper case letters: + $upper = "\p{Lu}\p{Lt}"; // Unicode-aware equivalent of "[:upper:]" + // Matches Unicode "word" characters: + $word = "_\p{Ll}\p{Lu}\p{Lt}\p{Lo}\p{Nd}"; // Unicode-aware equivalent of "[:word:]" (or "[:alnum:]" plus "_") + // Defines the PCRE pattern modifier(s) to be used in conjunction with the above variables: + // More info: + $patternModifiers = "u"; // the "u" (PCRE_UTF8) pattern modifier causes PHP/PCRE to treat pattern strings as UTF-8 + return array($alnum, $alpha, $cntrl, $dash, $digit, $graph, $lower, + $print, $punct, $space, $upper, $word, $patternModifiers); + } + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field_export_link.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field_export_link.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,43 @@ +additional_fields['nid'] = array('table' => 'node', 'field' => 'nid'); + } + + function query() { + $this->add_additional_fields(); + } + + function option_definition() { + $options = parent::option_definition(); + + $options['text'] = array('default' => '', 'translatable' => TRUE); + $options['label'] = array('default' => ''); + + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $text = !empty($this->options['text']) ? $this->options['text'] : $this->definition['format name']; + $form['text'] = array( + '#type' => 'textfield', + '#title' => t('Text to display'), + '#default_value' => $text, + ); + } + + function render($values) { + if (user_access('show export links')) { + $format = $this->definition['format']; + $base = variable_get('biblio_base', 'biblio'); + $nid = $this->get_value($values, 'nid'); + $this->options['alter']['make_link'] = TRUE; + $this->options['alter']['path'] = "$base/export/$format/$nid"; + $text = !empty($this->options['text']) ? $this->options['text'] : $this->definition['format name']; + return $text; + } + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field_keyword.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field_keyword.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,61 @@ +multiple = TRUE; + $this->additional_fields['vid'] = array('table' => 'biblio', 'field' => 'vid'); + } + + function option_definition() { + $options = parent::option_definition(); + $options['separator'] = array('default' => '; '); + + return $options; + } + + function options_form(&$form, &$form_state) { + $form['separator'] = array( + '#type' => 'textfield', + '#size' => 5, + '#title' => t('Keyword separator'), + '#default_value' => $this->options['separator'], + '#required' => TRUE, + '#description' => t('Enter the character which will be used to separate the keywords.'), + '#fieldset' => 'separators', + ); + parent::options_form($form, $form_state); + + } + + function query() { + $this->add_additional_fields(); + $this->field_alias = $this->aliases['vid']; + } + + function post_execute(&$values) { + $vids = array(); + $this->items = array(); + $filter = ''; + + foreach ($values as $result) { + // Don't add empty results to the array. + if (isset($this->aliases['vid']) && !empty($result->{$this->aliases['vid']})) { + $vids[] = $result->{$this->aliases['vid']}; + } + } + + if (count($vids)) { + $this->items = biblio_load_keywords_multiple($vids); + } + } + + function render($values) { + parent::set_label($values); + $vid = $values->{$this->field_alias}; + if (!isset($this->items[$vid])) return NULL; + return implode($this->options['separator'], $this->items[$vid]); + } + + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_field_markup.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_field_markup.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,27 @@ +additional_fields = array(); + $this->additional_fields['biblio_formats'] = 'biblio_formats'; + + } + + function render($values) { + $value = $this->get_value($values); + $formats = $this->get_value($values, 'biblio_formats'); + $format = filter_default_format(); + if (!empty($formats) ) { + $formats = unserialize($formats); + $format = isset($formats[$this->field]) ? $formats[$this->field] : $format; + } + if ($value) { + $value = str_replace('', '', $value); + return check_markup($value, $format, ''); + } + } + + + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_filter_biblio_contributor_auth_type.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_filter_biblio_contributor_auth_type.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,23 @@ +definition['numeric'] = TRUE; + } + + function get_value_options() { + if (!isset($this->value_options)) { + $result = db_query("SELECT title, auth_type + FROM {biblio_contributor_type_data} + ORDER by auth_type"); + $this->value_title = t('Author type'); + $options = array(); + + foreach ($result as $row) { + $options[$row->auth_type] = $row->title; + } + $this->value_options = $options; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_filter_biblio_keyword_kid.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_filter_biblio_keyword_kid.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,117 @@ +value_options[$term->kid] = $term->word; + } + + } + + function option_definition() { + $options = parent::option_definition(); + + $options['type'] = array('default' => 'textfield'); + $options['limit'] = array('default' => TRUE); + $options['kid'] = array('default' => 0); + + return $options; + } + + function extra_options_form(&$form, &$form_state) { + + if ($this->options['limit']) { + // We only do this when the form is displayed so this query doesn't run + // unnecessarily just when the object is constructed. + + $form['type'] = array( + '#type' => 'radios', + '#title' => t('Selection type'), + '#options' => array('select' => t('Dropdown'), 'textfield' => t('Autocomplete')), + '#default_value' => $this->options['type'], + ); + } + } + function value_form(&$form, &$form_state) { + if ($this->options['type'] == 'textfield') { + $default = ''; + if ($this->value) { + $result = db_query("SELECT * FROM {biblio_keyword} bk WHERE bk.kid IN (:kids)", + array(':kids' => implode(', ', $this->value))); + + foreach ($result as $term) { + if ($default) { + $default .= ', '; + } + $default .= $term->word; + } + } + + $form['value'] = array( + '#title' => t('Select keywords'), + '#type' => 'textfield', + '#default_value' => $default, + ); + + if ($this->options['limit']) { + $form['value']['#autocomplete_path'] = 'biblio/autocomplete/biblio_keywords'; + } + } + else { + $options = array(); + $result = db_query("SELECT kd.* FROM {biblio_keyword_data} kd ORDER BY kd.word"); + foreach ($result as $term) { + $options[$term->kid] = $term->word; + } + + $default_value = (array) $this->value; + + if (!empty($form_state['exposed'])) { + $identifier = $this->options['expose']['identifier']; + + if (!empty($this->options['expose']['reduce'])) { + $options = $this->reduce_value_options($options); + + if (empty($this->options['expose']['single']) && !empty($this->options['expose']['optional'])) { + $default_value = array(); + } + } + + if (!empty($this->options['expose']['single'])) { + if (!empty($this->options['expose']['optional']) && (empty($default_value) || !empty($this->options['expose']['reduce']))) { + $default_value = 'All'; + } + elseif (empty($default_value)) { + $keys = array_keys($options); + $default_value = array_shift($keys); + } + else { + $copy = $default_value; + $default_value = array_shift($copy); + } + } + } + $form['value'] = array( + '#type' => 'select', + '#title' => t('Select keywords'), + '#multiple' => TRUE, + '#options' => $options, + '#size' => min(9, count($options)), + '#default_value' => $default_value, + ); + + if (!empty($form_state['exposed']) && !isset($form_state['input'][$identifier])) { + $form_state['input'][$identifier] = $default_value; + } + } + + + if (empty($form_state['exposed'])) { + // Retain the helper option + $this->helper->options_form($form, $form_state); + } + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_filter_biblio_keyword_word.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_filter_biblio_keyword_word.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,72 @@ +value_options[$term->kid] = $term->word; + } + + } + + function option_definition() { + $options = parent::option_definition(); + + $options['type'] = array('default' => 'textfield'); + $options['limit'] = array('default' => TRUE); + $options['kid'] = array('default' => 0); + + return $options; + } + + function extra_options_form(&$form, &$form_state) { + + if ($this->options['limit']) { + // We only do this when the form is displayed so this query doesn't run + // unnecessarily just when the object is constructed. + + $form['type'] = array( + '#type' => 'radios', + '#title' => t('Selection type'), + '#options' => array('select' => t('Dropdown'), 'textfield' => t('Autocomplete')), + '#default_value' => $this->options['type'], + ); + } + } + function value_form(&$form, &$form_state) { + parent::value_form(&$form, &$form_state); + if ($this->options['type'] == 'textfield') { +/* $default = ''; + if ($this->value) { + $result = db_query("SELECT * FROM {biblio_keyword} bk WHERE bk.kid IN (:kids)", + array(':kids' => implode(', ', $this->value))); + + foreach ($result as $term) { + if ($default) { + $default .= ', '; + } + $default .= $term->word; + } + } + + $form['value'] = array( + '#title' => t('Select keywords'), + '#type' => 'textfield', + '#default_value' => $default, + ); + + if ($this->options['limit']) { + $form['value']['#autocomplete_path'] = 'biblio/autocomplete/biblio_keywords'; + } + */ + } + + + if (empty($form_state['exposed'])) { + // Retain the helper option + // $this->helper->options_form($form, $form_state); + } + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_filter_biblio_type.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_filter_biblio_type.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,23 @@ +definition['numeric'] = TRUE; + } + + function get_value_options() { + if (!isset($this->value_options)) { + $result = db_query("SELECT name, tid + FROM {biblio_types} t + WHERE t.tid > 0 AND t.visible=1 + ORDER by name"); + $this->value_title = t('Publication type'); + $options = array(); + foreach ($result as $row) { + $options[$row->tid] = $row->name; + } + $this->value_options = $options; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_filter_contributor.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_filter_contributor.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,15 @@ +value_options = array(); + foreach ($result as $row) { + $this->value_options[$row->cid] = "$row->lastname, $row->firstname $row->initials"; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_filter_contributor_lastname.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_filter_contributor_lastname.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,15 @@ +value_options = array(); + foreach ($result as $row) { + $this->value_options[$row->lastname] = "$row->lastname, $row->firstname $row->initials"; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_filter_contributor_uid.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_filter_contributor_uid.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,17 @@ + 0 + ORDER by lastname, firstname"); + $this->value_options = array(); + foreach ($result as $row) { + $this->value_options[$row->drupal_uid] = "$row->lastname, $row->firstname $row->initials ($row->name)"; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio/views/biblio_handler_sort_contributor_lastname.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio/views/biblio_handler_sort_contributor_lastname.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,39 @@ + 0); + return $options; + } + function admin_summary() { + $order = parent::admin_summary(); + $rank = $this->rank_options(); + return $rank[$this->options['rank']] . ' ' . t('Author') . ', ' . $order; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $form['op_val_start'] = array('#value' => '
      '); + $form['rank'] = array( + '#title' => t('Sort by which author?'), + '#type' => 'select', + '#options' => $this->rank_options(), + '#default_value' => $this->options['rank'], + ); + $form['op_val_end'] = array('#value' => '
      '); + + } + + function rank_options() { + return array('1st', '2nd', '3rd', '4th', '5th'); + } + function query() { + parent::query(); + $this->query->add_where(0, "biblio_contributor.rank", $this->options['rank'], '='); + } + +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/.gitignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/.gitignore Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,3 @@ +/.settings +/.buildpath +/.project diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/CHANGELOG.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/CHANGELOG.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,44 @@ +Biblio Advanced Import 7.x-1.0-alpha3, 2013-07-04 +------------------------------------------------- +[#2027691] cspitzlay: respect the pubmed ID setting when detecting duplicates and when creating citekeys + + +Biblio Advanced Import 7.x-1.0-alpha2, 2013-06-28 +------------------------------------------------- +[ ] cspitzlay: Minor fixes for notices and deprecation warnings + + +Biblio Advanced Import 7.x-1.0-alpha1, 2013-06-26 +------------------------------------------------- +[ ] cspitzlay: straight port to D7 + + +Biblio Advanced Import 6.x-1.0-rc1, 2013-04-05 +------------------------------------------------- +[ ] mkalkbrenner: special handling for biblio's "year" 9999 + + +Biblio Advanced Import 6.x-1.0-beta1, 2011-09-06 +------------------------------------------------- +[ ] mkalkbrenner: force Unified Cite ID Generation if import parser already created one + + +Biblio Advanced Import 6.x-1.0-alpha3, 2011-06-20 +------------------------------------------------- +[#1057650] mkalkbrenner: Unified Cite ID Generation +[ ] mkalkbrenner: added workarounds for different biblio import pitfalls + + +Biblio Advanced Import 6.x-1.0-alpha2, 2011-02-15 +------------------------------------------------- +[ ] mkalkbrenner: combine different duplicate detection strategies +[ ] mkalkbrenner: fixed bug merge always overrides + + +Biblio Advanced Import 6.x-1.0-alpha1, 2011-02-14 +------------------------------------------------- +[ ] mkalkbrenner: first public release +[#1057646] mkalkbrenner: Create new revision on update +[#1058992] mkalkbrenner: Configurable merge strategy +[ ] mkalkbrenner: don't create a new revision if no data changed in record +[#1058998] mkalkbrenner: Update all existing hashes if duplicate detection strategy changes diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,41 @@ + +Biblio Advanced Import +====================== + +Name: biblio_advanced_import +Authors: Markus Kalkbrenner | bio.logis + Christian Spitzlay | bio.logis +Drupal: 7.x +Sponsors: Cocomore AG - http://www.cocomore.com + bio.logis - http://www.biologis.com + +About +===== + +"Biblio Advanced Import" is an add-on for the Bibliography Module, +which can be found at http://drupal.org/project/biblio + +Instead of creating duplicate biblio records during imports, +existing ones can be updated, or the import can be skipped +depending on a configurable duplicate detection strategy. + + + +Installation +============ + +1. Install Bibliography Module itself from + http://drupal.org/project/biblio + +2. Place whole biblio_advanced_import folder into your Drupal + modules/ directory, or better, your sites/x/modules directory. + +3. Enable the "Biblio Advanced Import" module at /admin/build/modules + + + +Usage +===== + +Configure your duplicate detection strategy at +/admin/config/content/biblio/advanced_import diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/biblio_advanced_import.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/biblio_advanced_import.admin.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,324 @@ + t('Duplicate Detection and Merge Strategy'), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $options = array( + 'create duplicate' => t('Create duplicate record.'), + 'skip import' => t('Skip import of a record.'), + 'update latest' => t('Update the latest existing duplicate record. (See "Merge Strategy" below.)'), + 'update oldest' => t('Update the oldest existing duplicate record. (See "Merge Strategy" below.)'), + 'update all' => t('Update all existing duplicate records. (See "Merge Strategy" below.)'), + 'new rev latest' => t('Create a new revision of the latest existing duplicate record. (See "Merge Strategy" below.)'), + 'new rev oldest' => t('Create a new revision of the oldest existing duplicate record. (See "Merge Strategy" below.)'), + 'new rev all' => t('Create a new revisions of all existing duplicate records. (See "Merge Strategy" below.)'), + ); + + $form['biblio_advanced_import_duplicate_merge_fieldset']['biblio_advanced_import_duplicate_strategy'] = array( + '#title' => t('Duplicate Strategy'), + '#description' => t('Create a duplicate or skip a single record of an import or update existing records, if the import detects an already exiting biblio record.'), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_duplicate_strategy', 'create duplicate'), + '#required' => TRUE, + ); + + $options = array( + 'md5' => t('Hashing over configurable biblio fields. (See "Hash Settings" below.)'), + 'isbn' => t('ISBN'), + 'issn' => t('ISSN'), + 'doi' => t('DOI'), + ); + + if (module_exists('biblio_pm')) { + $options['pubmed'] = t('PubMed ID'); + } + + $form['biblio_advanced_import_duplicate_merge_fieldset']['biblio_advanced_import_detect_duplicate_strategy'] = array( + '#title' => t('Detect Duplicate Strategy'), + '#description' => t('These fields are used to decide if a new biblio record is a duplicate of an existing one. If you select more than one, only one of the fields has to be identical to declare two records as duplicate. P.e. if you choose "Hash" and "ISBN" duplicates will be found in general using the hash, but if available in both records, ISBN will be used.'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_detect_duplicate_strategy', array('md5' => 'md5')), + ); + + $form['biblio_advanced_import_duplicate_merge_fieldset']['biblio_advanced_import_duplicate_criteria_fieldset'] = array( + '#title' => t('Hash Settings'), + '#description' => t('Select the criteria to create hashes to detect duplicate biblio records. But think twice about your selection. P.e. the ISBN is a perfect criteria. But if you import the same record from two different sources an one contains the ISBN while the other does not, they will not be considered as duplicates.'), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $field_options = array('title' => t('Title')); + if (module_exists('biblio_pm')) { + // this key needs to be the name of the node property + $field_options['biblio_pubmed_id'] = t('Pubmed ID'); + } + $result = db_query("SELECT name FROM {biblio_fields}"); + while ($row = $result->fetchObject()) { + $field_options[$row->name] = str_replace('biblio_', '', $row->name); + } + + $form['biblio_advanced_import_duplicate_merge_fieldset']['biblio_advanced_import_duplicate_criteria_fieldset']['biblio_advanced_import_duplicate_criteria'] = array( + '#type' => 'checkboxes', + '#options' => $field_options, + '#default_value' => variable_get('biblio_advanced_import_duplicate_criteria', array('title' => 'title', 'biblio_year' => 'biblio_year')), + ); + + $options = array( + 'override' => t('Override the existing record.'), + 'override but keep additional' => t('Override the existing record but keep non empty values that do not exist in the record to be imported.'), + 'add new' => t('Only add new values that are empty in the existing record.'), + 'override existing non empty' => t('Only override non empty existing values.'), + 'override existing non empty with non empty' => t('Only override non empty existing values with new non empty existing values.'), + ); + + $form['biblio_advanced_import_duplicate_merge_fieldset']['biblio_advanced_import_merge_strategy'] = array( + '#title' => t('Merge Strategy'), + '#description' => t('If you did not choose "Create duplicate record." or "Skip import of a record." as Duplicate Strategy, you have to select a Merge Strategy as well.'), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_merge_strategy', 'override'), + ); + + $form['biblio_advanced_import_citekey_creation_fieldset'] = array( + '#title' => t('Cite ID Creation Strategy'), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $options = array( + 'biblio' => t('Use biblio default automatic Cite ID creation.'), + 'fields' => t('Use configurable biblio fields. (See "Cite ID Creation Settings" below.)'), + ); + + $form['biblio_advanced_import_citekey_creation_fieldset']['biblio_advanced_import_citekey_creation_strategy'] = array( + '#title' => t('Automatic Cite ID Creation Strategy'), + '#description' => t("Note: Existing Cite IDs won't be changed."), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_citekey_creation_strategy', 'biblio'), + ); + + $form['biblio_advanced_import_citekey_creation_fieldset']['biblio_advanced_import_citekey_creation_fieldset'] = array( + '#title' => t('Cite ID Creation Settings'), + '#description' => t("Select the fields to automatically create Cite IDs. Note: Existing Cite IDs won't be changed."), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['biblio_advanced_import_citekey_creation_fieldset']['biblio_advanced_import_citekey_creation_fieldset']['biblio_advanced_import_citekey_creation'] = array( + '#type' => 'checkboxes', + '#options' => $field_options, + '#default_value' => variable_get('biblio_advanced_import_citekey_creation', array('title' => 'title', 'biblio_year' => 'biblio_year')), + ); + + $options = array( + 'skip' => t('Skip automatic Cite ID creation.'), + 'append counter' => t('Append counter.'), + ); + + $form['biblio_advanced_import_citekey_creation_fieldset']['biblio_advanced_import_duplicate_citekey_strategy'] = array( + '#title' => t('Duplicate Cite ID Strategy'), + '#description' => t("Note: Existing Cite IDs won't be changed."), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_duplicate_citekey_strategy', 'skip'), + ); + + $form['biblio_advanced_import_workarounds_fieldset'] = array( + '#title' => t('Biblio Import Pitfall Workarounds'), + '#type' => 'fieldset', + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $options = array( + 'as is' => t('Store field ISBN as is'), + 'remove' => t('Remove invalid ISBN'), + 'convert 13' => t('Convert ISBN-10 into ISBN-13 and normalize ISBN-13 and GTIN-14'), + ); + + $form['biblio_advanced_import_workarounds_fieldset']['biblio_advanced_import_fix_isbn'] = array( + '#title' => t('ISBN'), + '#description' => t("There are three different formats ISBN-10, ISBN-13, GTIN-14."), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_fix_isbn', 'as is'), + ); + + $options = array( + 'as is' => t('Store field ISSN as is'), + 'normalize' => t('Normalize (numbers only)'), + 'normalize from isbn' => t('Copy from ISBN if necessary and normalize (numbers only)'), + ); + + $form['biblio_advanced_import_workarounds_fieldset']['biblio_advanced_import_fix_issn'] = array( + '#title' => t('ISSN'), + '#description' => t('RIS format does not distinguish between ISBN and ISSN, so biblio imports ISSN as ISBN by default.'), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_fix_issn', 'as is'), + ); + + $options = array( + 'as is' => t('Store field DOI as is'), + 'one valid' => t('Only store the first valid DOI if additional text or a list of DOIs is provided'), + ); + + $form['biblio_advanced_import_workarounds_fieldset']['biblio_advanced_import_fix_doi'] = array( + '#title' => t('DOI'), + '#description' => t("Some import formats include multiple DOIs which is not supported by biblio."), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_fix_doi', 'as is'), + ); + + $options = array( + 'as is' => t('Store field URL as is'), + 'one valid' => t('Only store the first valid URL if additional text or a list of URLs is provided'), + ); + + $form['biblio_advanced_import_workarounds_fieldset']['biblio_advanced_import_fix_url'] = array( + '#title' => t('URL'), + '#description' => t("Some import formats include multiple URLs which is not supported by biblio."), + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_fix_url', 'as is'), + ); + + $options = array( + 'as is' => t('Store field Title as is'), + 'mendeley bibtex' => t('Remove {} from Title exported by Mendeley using BibTex'), + ); + + $form['biblio_advanced_import_workarounds_fieldset']['biblio_advanced_import_fix_title'] = array( + '#title' => t('Title'), + '#description' => '', + '#type' => 'radios', + '#options' => $options, + '#default_value' => variable_get('biblio_advanced_import_fix_title', 'as is'), + ); + + $form['#submit'] = array('biblio_advanced_import_settings_form_submit'); + + return system_settings_form($form); +} + + +/** + * Validation of biblio_advanced_import_settings_form(). + */ +function biblio_advanced_import_settings_form_validate($form, &$form_state) { + if ('create duplicate' != $form_state['values']['biblio_advanced_import_duplicate_strategy']) { + $no_detect_duplicate_strategy = TRUE; + foreach ($form_state['values']['biblio_advanced_import_detect_duplicate_strategy'] as $value) { + if ($value) { + $no_detect_duplicate_strategy = FALSE; + break; + } + } + if ($no_detect_duplicate_strategy) { + form_set_error('biblio_advanced_import_detect_duplicate_strategy', t('You need to specifiy at least one criteria.')); + } + + if (in_array('md5', $form_state['values']['biblio_advanced_import_detect_duplicate_strategy'], TRUE)) { + $no_duplicate_criteria = TRUE; + foreach ($form_state['values']['biblio_advanced_import_duplicate_criteria'] as $value) { + if ($value) { + $no_duplicate_criteria = FALSE; + break; + } + } + if ($no_duplicate_criteria) { + form_set_error('biblio_advanced_import_duplicate_criteria', t('You need to specifiy at least one criteria.')); + } + } + + if (empty($form_state['values']['biblio_advanced_import_merge_strategy'])) { + form_set_error('biblio_advanced_import_merge_strategy', t('You need to specifiy a merge strategy.')); + } + } +} + + +/** + * Submit handler of biblio_advanced_import_settings_form(). + * + * Checks if duplicate strategy has been changed which requires + * an update of all hashes of all existing biblio nodes. + * If required, a batch process will be started to recalculate + * all hashes. + */ +function biblio_advanced_import_settings_form_submit($form, &$form_state) { + if ($form_state['values']['biblio_advanced_import_citekey_creation_strategy'] == 'fields' + && variable_get('biblio_advanced_import_citekey_creation_strategy', 'biblio') != 'fields') { + + // save existing custom citekey creation php code + variable_set('biblio_advanced_import_old_citekey_phpcode', variable_get('biblio_citekey_phpcode', '')); + // insert biblio_advanced_import citekey creation php code + // @see biblio_advanced_import_create_citekey() + variable_set('biblio_citekey_phpcode', 'return biblio_advanced_import_create_citekey($node);'); + } + elseif ($form_state['values']['biblio_advanced_import_citekey_creation_strategy'] != 'fields' + && variable_get('biblio_advanced_import_citekey_creation_strategy', 'biblio') == 'fields') { + + variable_set('biblio_citekey_phpcode', variable_get('biblio_advanced_import_old_citekey_phpcode', '')); + } + + if ($form_state['values']['biblio_advanced_import_duplicate_strategy'] != 'create duplicate') { + $update_hashes = FALSE; + if (variable_get('biblio_advanced_import_duplicate_strategy', 'create duplicate') == 'create duplicate') { + $update_hashes = TRUE; + } + else { + $old_values = variable_get('biblio_advanced_import_duplicate_criteria', array('title' => 'title', 'biblio_year' => 'biblio_year')); + foreach ($form_state['values']['biblio_advanced_import_duplicate_criteria'] as $key => $value) { + if ($value) { + if (!array_key_exists($key, $old_values) || ((string) $old_values[$key] != (string) $value)) { + $update_hashes = TRUE; + break; + } + } + } + } + + if ($update_hashes) { + $batch = array( + 'title' => t('Re-hashing existing biblio nodes'), + 'operations' => array( + array('biblio_advanced_import_update_hashes_batch', array()), + ), + 'finished' => 'biblio_advanced_import_update_hashes_batch_finished', + 'file' => 'biblio_advanced_import.batch.inc', + ); + batch_set($batch); + + // TODO block import and creation of new biblio nodes until batch is finished + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/biblio_advanced_import.batch.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/biblio_advanced_import.batch.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,76 @@ +fetchField(); + $context['sandbox']['num_nids'] = db_query('SELECT COUNT(DISTINCT nid) FROM {biblio}')->fetchField(); + $context['sandbox']['limit'] = $context['sandbox']['num_nids'] > 20 ? 5 : 1; + } + + if ($context['sandbox']['current_nid'] >= $context['sandbox']['max_nid']) { + $context['sandbox']['progress'] = $context['sandbox']['num_nids']; + $context['finished'] = 1; + } + else { + $query = db_select('biblio', 'b'); + $alias = $query->innerJoin('node', 'n', 'b.nid = n.nid AND b.vid = n.vid'); + $query->fields('b', array('nid')) + ->condition('b.nid', $context['sandbox']['current_nid'], '>') + ->condition('b.nid', $context['sandbox']['max_nid'], '<=') + ->orderBy('b.nid', 'ASC'); + $result = $query->execute(); + while ($row = $result->fetchObject()) { + $node = node_load($row['nid'], NULL, TRUE); + biblio_advanced_import_update_hash($node); + + $context['results'][] = $node->nid . ' : ' . $node->biblio_md5; + $context['sandbox']['progress']++; + $context['sandbox']['current_nid'] = $node->nid; + $context['message'] = $node->nid . ' : ' . $node->title; + } + + if ($context['sandbox']['progress'] != $context['sandbox']['num_nids']) { + $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['num_nids']; + } + } +} + + +/** + * Clean-up after hashes of all biblio nodes have been updated + * in a batch process. + * + * @see biblio_advanced_import_settings_form_submit() + */ +function biblio_advanced_import_update_hashes_batch_finished($success, $results, $operations) { + if ($success) { + $message = format_plural(count($results), 'Updated one biblio hash.', 'Updates @count biblio hashes.'); + } + else { + $message = t('Finished with an error.'); + } + drupal_set_message($message); + + // TODO re-enable import and creation of new biblio nodes +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/biblio_advanced_import.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/biblio_advanced_import.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = "Biblio Advanced Import" +description = "Identification of duplicates across different import formats, updating of existing nodes, cite id generation." +core = 7.x +package = Biblio +dependencies[] = biblio + +files[] = lib/isbntest.class.php + +; Information added by drupal.org packaging script on 2013-07-04 +version = "7.x-1.0-alpha3" +core = "7.x" +project = "biblio_advanced_import" +datestamp = "1372929652" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/biblio_advanced_import.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/biblio_advanced_import.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,476 @@ + 'Advanced Import', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('biblio_advanced_import_settings_form'), + 'access arguments' => array('administer biblio'), + 'file' => 'biblio_advanced_import.admin.inc', + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + + return $items; +} + + +/** + * Implements hook_node_presave(). + */ +function biblio_advanced_import_node_presave($node) { + if ('biblio' == $node->type && empty($node->nid)) { + + biblio_advanced_import_pitfall_workarounds($node); + + if (variable_get('biblio_auto_citekey', 1)) { + // on new entries, override citekeys generated by parses, depending on settings + $citekey = biblio_advanced_import_create_citekey($node); + if ($citekey) { + $node->biblio_citekey = $citekey; + } + } + + $query = db_select('biblio', 'b'); + $alias = $query->innerJoin('node', 'n', 'b.nid = n.nid AND b.vid = n.vid'); + $query->fields('b', array('nid')); + $skip = FALSE; + $revision = FALSE; + + switch (variable_get('biblio_advanced_import_duplicate_strategy', 'create duplicate')) { + case 'create duplicate': + return; + + case 'skip import': + // There's no way to stop an already running node_save() + // in a safe way without breaking a batch process. + // So we do a little trick to realize the 'skip import': + // We simply replace the current node to be saved by the + // unmodified oldest duplicate and save this one instead + $skip = TRUE; + $query->orderBy('b.nid', 'DESC') + ->range(0, 1); + break; + + case 'new rev latest': + $revision = TRUE; + case 'update latest': + $query->orderBy('b.nid', 'DESC') + ->range(0, 1); + break; + + case 'new rev oldest': + $revision = TRUE; + case 'update oldest': + $query->orderBy('b.nid', 'ASC') + ->range(0, 1); + break; + + case 'new rev all': + $revision = TRUE; + case 'update all': + break; + } + + $condition_exists = FALSE; + $or_condition = db_or(); + foreach (variable_get('biblio_advanced_import_detect_duplicate_strategy', array('md5' => 'md5')) as $field) { + switch ((string) $field) { + case 'md5': + $or_condition->condition('b.biblio_md5', biblio_advanced_import_hash($node)); + $condition_exists = TRUE; + break; + case 'isbn': + case 'issn': + case 'doi': + $field_property = 'biblio_' . $field; + if (!empty($node->$field_property)) { + $or_condition->condition('b.' . $field_property, $node->$field_property); + $condition_exists = TRUE; + } + break; + + case 'pubmed': + if (module_exists('biblio_pm') && !empty($node->biblio_pubmed_id)) { + $query->innerJoin('biblio_pubmed', 'bp', 'b.nid = bp.nid'); + $or_condition->condition('bp.biblio_pubmed_id', $node->biblio_pubmed_id); + $condition_exists = TRUE; + } + break; + } + } + + if ($condition_exists) { + $query->condition($or_condition); + + $result = $query->execute(); + $is_first_duplicate = TRUE; + $node_new = (Array) $node; + + while ($row = $result->fetchObject()) { + // there are duplicates: + $node_old = node_load($row->nid); + // we need to set this or the node module will throw notices + // (if this node becomes the one to be really saved instead of the original one) + $node_old->is_new = FALSE; + + if (!$skip) { + // update an existing biblio node with new data + $merge = FALSE; + foreach ($node_new as $key => $value) { + if (strpos($key, 'biblio') === 0 || strpos($key, 'contributors') === 0 || 'title' == $key) { + $strategy = variable_get('biblio_advanced_import_merge_strategy', 'override'); + if ('override' == $strategy + || ('override but keep additional' == $strategy && !empty($value)) + || ('add new' == $strategy && !empty($value) && empty($node_old->$key)) + || ('override existing non empty' == $strategy && !empty($node_old->$key)) + || ('override existing non empty with non empty' == $strategy && !empty($value) && !empty($node_old->$key)) + ) { + if (!property_exists($node_old, $key) || $node_old->$key != $value) { + $node_old->$key = $value; + $merge = TRUE; + } + } + } + } + + if ($revision && $merge) { + $node_old->revision = TRUE; + $node_old->log = t('New revision created automatically by Biblio Advanced Import.'); + } + } + else { + // There's no way to stop an already running node_save() + // in a safe way without breaking the batch process. + // So we use a little trick to implement the 'skip import': + // We replace the current node to be saved with the + // unmodified first duplicate and let drupal save that one instead. + } + + if ($is_first_duplicate) { + // the content of the node being saved gets replaced with the values from the first duplicate node + // (replacing the whole object with the loaded node did not seem to work + // so we do it property by property ...) + $is_first_duplicate = FALSE; + // clear existing object + foreach (get_object_vars($node) as $key => $value) { + unset($node->$key); + } + // copy values over + foreach (get_object_vars($node_old) as $key => $value) { + $node->$key = $value; + } + } + else { + // save any other existing duplicates, with values updated + node_save($node_old); + } + } + } + } +} + +/** + * Implements hook_node_insert(). + */ +function biblio_advanced_import_node_insert($node) { + if ('biblio' == $node->type) { + biblio_advanced_import_update_hash($node); + } +} + +/** + * Implements hook_node_update(). + */ +function biblio_advanced_import_node_update($node) { + if ('biblio' == $node->type) { + biblio_advanced_import_update_hash($node); + } +} + + +/** + * Helper function to create a hash from a biblio node + * depending on a configurable duplicate detection + * strategy. + * + * @see biblio_advanced_import_settings_form() + * + * @param $node + * a biblio node + * + * @return + * a md5 hash + */ +function biblio_advanced_import_hash($node) { + $hash_string = ''; + $duplicate_criteria = variable_get('biblio_advanced_import_duplicate_criteria', + array('title' => 'title', 'biblio_year' => 'biblio_year')); + + foreach ($duplicate_criteria as $field) { + if ($field) { + $field_value = ''; + + if (isset($node->$field)) { + $field_value = $node->$field; + } + + if ('biblio_year' == $field && empty($field_value)) { + // If the year is empty, it will be set to 9999 by biblio on save. + // 9999 => "Submitted" + // Therefore we have to do the same here to not break duplicate detection. + $field_value = 9999; + } + + if ($field_value) { + if (is_array($field_value) || is_object($field_value)) { + $hash_string .= serialize($field_value); + } + else { + $hash_string .= preg_replace("/\s+/", '', mb_strtolower(mb_substr($field_value, 0, 256))); + } + } + } + } + + return md5(strtolower($hash_string)); +} + + +/** + * Helper function to update the hash of a biblio node. + * + * @see biblio_advanced_import_settings_form() + * + * @param $node + * a biblio node + */ +function biblio_advanced_import_update_hash(&$node) { + $node->biblio_md5 = biblio_advanced_import_hash($node); + db_update('biblio') + ->fields(array( + 'biblio_md5' => $node->biblio_md5, + )) + ->condition('nid', $node->nid) + ->condition('vid', $node->vid) + ->execute(); +} + + +/** + * Helper function to create a configurable biblio node citekey. + * + * @see biblio_advanced_import_settings_form() + * @see biblio_advanced_import_settings_form_submit() + * + * @param $node + * a biblio node + */ +function biblio_advanced_import_create_citekey($node) { + $citekey = ''; + + switch (variable_get('biblio_advanced_import_citekey_creation_strategy', 'biblio')) { + case 'fields': + $citekey_parts = array(); + $prefix = variable_get('biblio_citekey_prefix', ''); + if (!empty($prefix)) { + $citekey_parts[] = $prefix; + } + foreach (variable_get('biblio_advanced_import_citekey_creation', array('title' => 'title', 'biblio_year' => 'biblio_year')) as $field) { + if (!empty($field) && !empty($node->$field)) { + $citekey_parts[] = $node->$field; + } + } + $citekey = implode('|', $citekey_parts); + // biblio stores citekey as varchar(255), we need to make sure it fits + // or a PDO Exception is thrown + $citekey = mb_substr($citekey, 0, 255); + // strip trailing pipe symbol, if any + $citekey = preg_replace('@\|+$@', '', $citekey); + $citekey = trim($citekey); + break; + } + + if ($citekey) { + if (db_query("SELECT 1 FROM {biblio} WHERE biblio_citekey = :biblio_citekey", array(':biblio_citekey' => $citekey))->fetchField()) { + switch (variable_get('biblio_advanced_import_citekey_creation_strategy', 'skip')) { + case 'skip': + $citekey = ''; + break; + + case 'append counter': + $counter = variable_get('biblio_advanced_import_citekey_creation_counter', 0) + 1; + variable_set('biblio_advanced_import_citekey_creation_counter', $counter); + // biblio stores citekey as varchar(255), so we have to ensure that the counter is saved + $citekey = mb_substr($citekey, 0, 254 - strlen($counter)) . '|' . $counter; + } + } + } + + return $citekey; +} + + +/** + * @todo Please document this function. + * @see http://drupal.org/node/1354 + */ +function biblio_advanced_import_form_biblio_admin_settings_alter(&$form, &$form_state) { + if ('fields' == variable_get('biblio_advanced_import_citekey_creation_strategy', 'biblio')) { + $form['citekey']['biblio_citekey_field1']['#type'] = 'value'; + $form['citekey']['biblio_citekey_field1']['#value'] = $form['citekey']['biblio_citekey_field1']['#default_value']; + $form['citekey']['biblio_citekey_field2']['#type'] = 'value'; + $form['citekey']['biblio_citekey_field2']['#value'] = $form['citekey']['biblio_citekey_field2']['#default_value']; + $form['citekey']['biblio_citekey_phpcode']['#type'] = 'value'; + $form['citekey']['biblio_citekey_phpcode']['#value'] = $form['citekey']['biblio_citekey_phpcode']['#default_value']; + } +} + + +/** + * This function implements some optional data cleanup / normalization + * that can be activated on the "advanced import" tab. + */ +function biblio_advanced_import_pitfall_workarounds(&$node) { + switch (variable_get('biblio_advanced_import_fix_issn', 'as is')) { + case 'as is': + break; + + case 'normalize from isbn': + if (empty($node->biblio_issn) || !empty($node->biblio_isbn)) { + // RIS format does not distinguish between ISBN and ISSN + $node->biblio_issn = $node->biblio_isbn; + } + // no break + case 'normalize': + // @see http://en.wikipedia.org/wiki/International_Standard_Serial_Number + if (!empty($node->biblio_issn)) { + if (preg_match("@\b([0-9]{4})-?([0-9X]{4})\b@i", $node->biblio_issn, $matches)) { + $issn = strtoupper($matches[1] . $matches[2]); + $sum = 0; + for ($i = 0; $i < 7; $i++) { + $sum += $issn[$i] * (8 - $i); + } + $checksum = 11 - ($sum % 11); + if ($checksum == $issn[7] || (10 == $checksum && 'X' == $issn[7])) { + $node->biblio_issn = $issn; + } + else { + unset($node->biblio_issn); + } + } + else { + unset($node->biblio_issn); + } + } + break; + } + + switch (variable_get('biblio_advanced_import_fix_isbn', 'as is')) { + case 'as is': + break; + + case 'remove': + // @see http://en.wikipedia.org/wiki/International_Standard_Book_Number + if (!empty($node->biblio_isbn)) { + module_load_include('class.php', 'biblio_advanced_import', 'lib/isbntest'); + $currISBN = new ISBNtest(); + $currISBN->set_isbn($matches[0]); + if ($currISBN->valid_isbn10() || $currISBN->valid_isbn13() || $currISBN->valid_gtin14()) { + $node->biblio_isbn = $currISBN->get_gtin14(); + } + else { + unset($node->biblio_isbn); + } + } + break; + + case 'convert 13': + // @see http://en.wikipedia.org/wiki/International_Standard_Book_Number + if (!empty($node->biblio_isbn)) { + if (preg_match("@[0-9\-]{10,}@", $node->biblio_isbn, $matches)) { + module_load_include('class.php', 'biblio_advanced_import', 'lib/isbntest'); + $currISBN = new ISBNtest(); + $currISBN->set_isbn($matches[0]); + if ($currISBN->valid_isbn13()) { + $node->biblio_isbn = $currISBN->get_isbn13(); + } + elseif ($currISBN->valid_gtin14()) { + $node->biblio_isbn = $currISBN->get_gtin14(); + } + else { + unset($node->biblio_isbn); + } + } + else { + unset($node->biblio_isbn); + } + } + break; + } + + switch (variable_get('biblio_advanced_import_fix_doi', 'as is')) { + case 'as is': + break; + + case 'one valid': + // @see http://en.wikipedia.org/wiki/Digital_object_identifier + if (!empty($node->biblio_doi)) { + if (preg_match("@10\.\d{4,}/[^\s]+@i", $node->biblio_doi, $matches)) { + $node->biblio_doi = $matches[0]; + } + else { + unset($node->biblio_doi); + } + } + break; + } + + switch (variable_get('biblio_advanced_import_fix_title', 'as is')) { + case 'as is': + break; + + case 'mendeley bibtex': + if (!empty($node->title)) { + $node->title = trim($node->title, '{}'); + } + break; + } + + switch (variable_get('biblio_advanced_import_fix_title', 'as is')) { + case 'as is': + break; + + case 'one valid': + if (!empty($node->biblio_url)) { + if (preg_match("@(http|https)://[^\s]+@i", $node->biblio_url, $matches)) { + // ris import runs together lists of urls without a delimiter + $urls = explode('http', str_replace(array('HTTP:', 'HTTPS:'), array('http:', 'https:'), $matches[0])); + $node->biblio_url = 'http' . $urls[1]; + } + else { + unset($node->biblio_url); + } + } + break; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_advanced_import/lib/isbntest.class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_advanced_import/lib/isbntest.class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,353 @@ +isbn10) != 10) + { + return FALSE; + $this->error = "Given ISBN-10 is not 10 digits (" . $this->isbn10 . ")"; + } + /* + * this checkdigit calculation could probably be expressed in less + * space using a lop, but this keeps it very clear what the math + * involved is + */ + $checkdigit = 11 - ( ( 10 * substr($this->isbn10,0,1) + 9 * substr($this->isbn10,1,1) + 8 * substr($this->isbn10,2,1) + 7 * substr($this->isbn10,3,1) + 6 * substr($this->isbn10,4,1) + 5 * substr($this->isbn10,5,1) + 4 * substr($this->isbn10,6,1) + 3 * substr($this->isbn10,7,1) + 2 * substr($this->isbn10,8,1) ) % 11); + /* + * convert the numeric check value + * into the single char version + */ + switch ( $checkdigit ) + { + case 10: + $checkdigit = "X"; + break; + case 11: + $checkdigit = 0; + break; + default: + } + return $checkdigit; + } + /***********************************************/ + + private function get_isbn13_checkdigit() + // calculate the checkdigit for ISBN-10 + { + if (strlen($this->isbn13) != 13) + { + return FALSE; + $this->error = "Given ISBN-13 is not 10 digits (" . $this->isbn13 . ")"; + } + /* + * this checkdigit calculation could probably be expressed in less + * space using a lop, but this keeps it very clear what the math + * involved is + */ + $checkdigit = 10 - ( ( 1 * substr($this->isbn13,0,1) + 3 * substr($this->isbn13,1,1) + 1 * substr($this->isbn13,2,1) + 3 * substr($this->isbn13,3,1) + 1 * substr($this->isbn13,4,1) + 3 * substr($this->isbn13,5,1) + 1 * substr($this->isbn13,6,1) + 3 * substr($this->isbn13,7,1) + 1 * substr($this->isbn13,8,1) + 3 * substr($this->isbn13,9,1) + 1 * substr($this->isbn13,10,1) + 3 * substr($this->isbn13,11,1) ) % 10 ); + /* + * convert the numeric check value + * into the single char version + */ + if ( $checkdigit == 10 ) + { + $checkdigit = "0"; + } + return $checkdigit; + } + /***********************************************/ + + private function get_gtin14_checkdigit() + // calculate the checkdigit for GTIN + { + if (strlen($this->gtin14) != 14) + { + return FALSE; + $this->error = "Given GTIN is not 14 digits (" . $this->gtin14 . ")"; + } + $checkdigit = 10 - ( ( 3 * substr($this->gtin14,0,1) + 1 * substr($this->gtin14,1,1) + 3 * substr($this->gtin14,2,1) + 1 * substr($this->gtin14,3,1) + 3 * substr($this->gtin14,4,1) + 1 * substr($this->gtin14,5,1) + 3 * substr($this->gtin14,6,1) + 1 * substr($this->gtin14,7,1) + 3 * substr($this->gtin14,8,1) + 1 * substr($this->gtin14,9,1) + 3 * substr($this->gtin14,10,1) + 1 * substr($this->gtin14,11,1) + 3 * substr($this->gtin14,12,1) ) % 10 ); + /* + * convert the numeric check value + * into the single char version + */ + if ( $checkdigit == 10 ) + { + $checkdigit = "0"; + } + return $checkdigit; + } + /***********************************************/ + + public function set_isbn10($isbn) + { + $isbn = preg_replace("@[^0-9X]@","",strtoupper($isbn)); // strip to the basic ISBN + if (strlen($isbn)==10) + { + $this->isbn10 = $isbn; + } + else + { + $this->error = "ISBN-10 given is not 10 digits ($isbn)"; + return FALSE; + } + } + /***********************************************/ + + public function set_isbn13($isbn) + { + $isbn = preg_replace("@[^0-9]@","",strtoupper($isbn)); // strip to the basic ISBN + if (strlen($isbn)==13) + { + $this->isbn13 = $isbn; + } + else + { + $this->error = "ISBN-13 given is not 13 digits ($isbn)"; + return FALSE; + } + } + /***********************************************/ + + public function set_gtin14($isbn) + { + $isbn = preg_replace("@[^0-9]@","",strtoupper($isbn)); // strip to the basic ISBN + if (strlen($isbn)==14) + { + $this->gtin14 = $isbn; + } + else + { + $this->error = "GTIN given is not 14 digits ($isbn)"; + return FALSE; + } + } + /***********************************************/ + + public function set_isbn($isbn) + // trying to provide a common interface here so it's possible to cope + // if you don't know for sure what you have -- provided the data is valid + { + $isbn = preg_replace("@[^0-9X]@","",strtoupper($isbn)); // strip to the basic ISBN + if (strlen($isbn)==14) + { + $this->set_gtin14($isbn); + return TRUE; + } + if (strlen($isbn)==13) + { + $this->set_isbn13($isbn); + return TRUE; + } + elseif (strlen($isbn)==10) + { + $this->isbn10 = $isbn; + return TRUE; + } + else + { + $this->error = "ISBN given is not 10, 13, or 14 digits ($isbn)"; + return FALSE; + } + } + /***********************************************/ + + public function valid_isbn10($isbn="") + // report on the validity of the ISBN-10 we have or are given + { + if ($isbn != "") // If we've been given a new ISBN then use it. + { + $this->set_isbn10($isbn); + } + if ( FALSE === $this->isbn10 && FALSE !== $this->isbn13 ) + { + if ( TRUE === $this->valid_isbn13() ) + { + $this->get_isbn10(); + } + } + if ( FALSE === $this->isbn10 || strlen($this->isbn10) != 10 ) + { + $this->error = "ISBN-10 is not set"; + return FALSE; + } + if ( (string) substr($this->isbn10,9,1) === (string) $this->get_isbn10_checkdigit() ) + { + return TRUE; + } + else + { + $this->error = "Checkdigit failure"; + return FALSE; + } + } + /***********************************************/ + + public function valid_isbn13($isbn="") + // report on the validity of the ISBN-13 we have or are given + { + if ($isbn != "") // if we've been given an isbn here, use it + { + $this->set_isbn13($isbn); + } + if ( FALSE === $this->isbn13 && FALSE !== $this->isbn10 ) + { + if ( TRUE === $this->valid_isbn10() ) + { + $this->get_isbn13(); + } + } + if ( FALSE === $this->isbn13 || strlen($this->isbn13) != 13 ) + { + $this->error = "ISBN-13 is not set"; + return FALSE; + } + if ( (string) substr($this->isbn13,12,1) === (string) $this->get_isbn13_checkdigit() ) + { + return TRUE; + } + else + { + $this->error = "Checkdigit failure"; + return FALSE; + } + } + /***********************************************/ + + public function valid_gtin14($isbn="") + // report on the validity of the GTIN we have or are given + { + if ($isbn != "") // if we've been given an ISBN here, use it + { + $this->set_gtin14($isbn); + } + if ( FALSE === $this->gtin14 || strlen($this->gtin14) != 14 ) + { + $this->error = "GTIN-14 is not set"; + return FALSE; + } + if ( substr($this->gtin14,13,1) === $this->get_gtin14_checkdigit() ) + { + return TRUE; + } + else + { + $this->error = "Checkdigit failure"; + return FALSE; + } + } + /***********************************************/ + public function valid_isbn($isbn="") + // trying to provide a common interface here so it's possible to cope + // if you don't know for sure what you have -- provided the data is valid + { + if ($isbn != "") // if we've been given an ISBN then use it + { + $this->set_isbn($isbn); + } + if ( (isset($this->gtin14) && $this->valid_gtin14() == TRUE) || (isset($this->isbn13) && $this->valid_isbn13() == TRUE) || (isset($this->isbn10) && $this->valid_isbn10() == TRUE) ) // in this routine, we don't care what kind it is, only that it's valid. + { + return TRUE; + } + else + { + $this->error = "Checkdigit failure"; + return FALSE; + } + } + /***********************************************/ + + public function get_isbn10() + // return the ISBN-10 that has been set or create one if we have a valid ISBN-13 + { + if ( $this->isbn10 != FALSE ) + { + return $this->isbn10; + } + elseif ( $this->valid_isbn13() != FALSE ) + { + if ( preg_match("@979@i", $this->isbn13) ) + { + $this->error = "979 Bookland EAN values can't be converted to ISBN-10"; + return FALSE; // if it's a 979 prefix it can't be downgraded + } + else + { + $this->set_isbn10(substr($this->isbn13, 3, 10)); // invalid ISBN used as a temp value for next step + $checkdigit = $this->get_isbn10_checkdigit(); + $this->set_isbn10(substr($this->isbn13, 3, 9) . $checkdigit); // true value (I hope) + return $this->isbn10; + } + } + else + { + $this->error = "No ISBN-10 value set or calculable"; + return FALSE; + } + } + /*********************************************/ + + public function get_isbn13() + // return the ISBN-13 that has been set or create one if we have a valid ISBN-10 + { + if ( $this->isbn13 != FALSE ) + { + return $this->isbn13; + } + elseif ( $this->valid_isbn10() != FALSE ) + { + $this->set_isbn13("978" . substr($this->isbn10, 0, 9) . "0"); // invalid ISBN used as a temp value for next step + $checkdigit = (string) $this->get_isbn13_checkdigit(); + $this->set_isbn13("978" . substr($this->isbn10, 0, 9) . $checkdigit); // true value (I hope) + return $this->isbn13; + } + else + { + $this->error = "No ISBN-10 value set or calculable"; + return FALSE; + } + } + /*********************************************/ + + public function get_gtin14() + // return the ISBN-13 that has been set or create one if we have a valid ISBN-10 + { + if ( $this->gtin14 != FALSE ) + { + return $this->gtin14; + } + else + { + $this->error = "No GTIN-14 value set or calculable"; + return FALSE; + } + } + /*********************************************/ + + public function get_error() + // return the error message + { + return $this->error; + } + /*********************************************/ + + } +?> \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/biblio_autocomplete.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/biblio_autocomplete.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,12 @@ +; $Id$ +name = Biblio Autocomplete +description = Allows other modules to provide autocomplete functionality to Biblio nodes +core = 7.x +dependencies[] = biblio +package = Biblio +; Information added by drupal.org packaging script on 2013-04-18 +version = "7.x-1.4" +core = "7.x" +project = "biblio_autocomplete" +datestamp = "1366294251" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/biblio_autocomplete.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/biblio_autocomplete.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,92 @@ + $data){ + $items['biblio_autocomplete/'.$field] = array( + 'title' => t('Biblio autocomplete for ').$field, + 'page callback' => 'biblio_autocomplete_json', + 'page arguments' => array($field, 2), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + } + return $items; +} + +function biblio_autocomplete_json($biblio_field, $string){ + $autocompletes = module_invoke_all('biblio_autocomplete_info'); + $matches = array(); + foreach ($autocompletes as $field => $data){ + if ($field == $biblio_field){ + if (!is_array($data['function'])){ + $data['function'] = array($data['function']); + } + foreach($data['function'] as $function){ + $matches = array_merge($matches, $function($string) ); + } + } + } + asort($matches); + + $return_matches = array(); + $i = 0; + foreach ($matches as $key => $data){ + if ($data['key'] != '' && $data['description'] != ''){ + $i++; + $return_matches[$data['key']] = $data['provider'].': '.$data['description']; + if ($i >= 10){ + break; + } + } + } + + print drupal_json_output($return_matches); +} + +/** + * Implemets hook_form_alter(). + * + * @param unknown_type $form + * @param unknown_type $form_state + * @param unknown_type $form_id + */ +function biblio_autocomplete_form_alter(&$form, &$form_state, $form_id){ + if ($form_id == 'biblio_node_form'){ + $autocompletes = module_invoke_all('biblio_autocomplete_info'); + foreach ($autocompletes as $field => $values){ + if (in_array($field, $form)){ + $key = array_tree_search_key($form, $field); + if (!is_null($key)){ + array_tree_update_autocomplete($form, $key, 'biblio_autocomplete/'.$field); + } + } + } + } +} + +function array_tree_search_key($a, $subkey) { +if (is_array($a)){ + foreach (array_keys($a) as $i=>$k) { + if ($k === $subkey) { + return array($k); + } + elseif ($pos = array_tree_search_key($a[$k], $subkey)) { + return array_merge(array($k), $pos); + } + } +} +} + +function array_tree_update_autocomplete(&$a, $key_array, $autocomplete){ + if (count($key_array) == 0){ + $a['#autocomplete_path'] = $autocomplete; + } else { + $a_next = array_shift($key_array); + array_tree_update_autocomplete($a[$a_next], $key_array, $autocomplete); + } +} + + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/plugins/biblio_ipni/biblio_ipni.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/plugins/biblio_ipni/biblio_ipni.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +; $Id$ +name = Biblio IPNI autocomplete +description = Allows autocompletion of Biblio fields from IPNI. +core = 7.x +dependencies[] = biblio_autocomplete +package = Biblio + + +; Information added by drupal.org packaging script on 2013-04-18 +version = "7.x-1.4" +core = "7.x" +project = "biblio_autocomplete" +datestamp = "1366294251" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/plugins/biblio_ipni/biblio_ipni.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/plugins/biblio_ipni/biblio_ipni.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,202 @@ + array('function' => 'autocomplete_callback_function') + */ +function biblio_ipni_biblio_autocomplete_info(){ + $fields = array( + 'biblio_secondary_title' => array('function' => 'biblio_ipni_autocomplete_publication'), + 'biblio_alternate_title' => array('function' => 'biblio_ipni_autocomplete_publication'), + 'biblio_original_publication' => array('function' => 'biblio_ipni_autocomplete_publication'), + 'biblio_short_title' => array('function' => 'biblio_ipni_autocomplete_short_publication'), + 'title' => array('function' => 'biblio_ipni_autocomplete_publication'), + 'biblio_authors' => array('function' => 'biblio_ipni_autocomplete_author_short'), + ); + return $fields; +} + +/** + * + * Gets a list of potential publication matches for the autocomplete + * + * @param $string + * Text string to try and match + * + * @return $return_matches + * An array of autocomplete results for use in biblio_autocomple_json(). + * Format: + * array( + * 'key' => 'value to put in biblio field', + * 'description' => 'can be the same as key or contain extra information to help use decide', + * 'provider' => 'source of the autocmplete information', + * ) + */ +function biblio_ipni_autocomplete_publication($string){ + $ipni_matches = biblio_ipni_get_publication_data($string); + $return_matches = array(); + foreach($ipni_matches as $result){ + $return_matches[] = array( + 'key' => $result[3], + 'description' => $result[3], + 'provider' => 'IPNI', + ); + } + return $return_matches; +} + +/** + * + * Gets a list of potential short (abbreviated) publication matches for the autocomplete + * + * @param $string + * Text string to try and match + * + * @return $return_matches + * An array of autocomplete results for use in biblio_autocomple_json(). + * Format: + * array( + * 'key' => 'value to put in biblio field', + * 'description' => 'can be the same as key or contain extra information to help use decide', + * 'provider' => 'source of the autocmplete information', + * ) + */ +function biblio_ipni_autocomplete_short_publication($string){ + $ipni_matches = biblio_ipni_get_publication_data_short($string); + foreach($ipni_matches as $result){ + $return_matches[] = array( + 'key' => $result[2], + 'description' => $result[2] . ' | ' . $result[3], + 'provider' => 'IPNI', + ); + + + } + return $return_matches; +} + +/** + * + * Function to get publication data from IPNI + * + * @param $string + * Text string to try and match + * + * @return $ipni_matches + * An array of matched data from IPNI + */ +function biblio_ipni_get_publication_data($string){ + $ipni_result = file_get_contents('http://www.ipni.org/ipni/advPublicationSearch.do?find_title=' . str_replace(' ', '+', $string) . '&output_format=delimited'); + $ipni_matches = biblio_ipni_process_file($ipni_result); + return $ipni_matches; +} + +/** + * + * Function to get short publication data from IPNI + * + * @param $string + * Text string to try and match + * + * @return $ipni_matches + * An array of matched data from IPNI + */ +function biblio_ipni_get_publication_data_short($string){ + $ipni_result = file_get_contents('http://www.ipni.org/ipni/advPublicationSearch.do?find_abbreviation=' . str_replace(' ', '+', $string) . '&output_format=delimited'); + $ipni_matches = biblio_ipni_process_file($ipni_result); + return $ipni_matches; +} + +/** + * + * Gets a list of potential author matches for the autocomplete + * + * @param $string + * Text string to try and match + * + * @return $return_matches + * An array of autocomplete results for use in biblio_autocomple_json(). + * Format: + * array( + * 'key' => 'value to put in biblio field', + * 'description' => 'can be the same as key or contain extra information to help use decide', + * 'provider' => 'source of the autocmplete information', + * ) + */ +function biblio_ipni_autocomplete_author_short($string){ + $ipni_matches = biblio_ipni_get_author_data($string); + foreach($ipni_matches as $result){ + $return_matches[$result[5]] = $result[2]; + $return_matches[] = array( + 'key' => $result[5], + 'description' => $result[2], + 'provider' => 'IPNI', + ); + } + return $return_matches; +} + +/** + * + * Function to get author data from IPNI + * + * @param $string + * Text string to try and match + * + * @return $ipni_matches + * An array of matched data from IPNI + */ +function biblio_ipni_get_author_data($string){ + $ipni_result = file_get_contents('http://www.ipni.org/ipni/advAuthorSearch.do?find_abbreviation=' . str_replace(' ', '+', $string) . '&output_format=delimited'); + $ipni_matches = biblio_ipni_process_file($ipni_result); + return $ipni_matches; +} + +/** + * + * IPNI data is delimited by newlines and the % character, this function parses + * this format into an array + * + * @param $file + * Input file from IPNI + * + * @return $ipni_matches + * An array of data parsed from $file + */ +function biblio_ipni_process_file($file){ + $ipni_result = explode("\n", $file); + $i = 0; + $ipni_matches = array(); + foreach($ipni_result as $result){ + $i++; + $ipni_matches[] = explode('%', $result); + if($i > 10){ + break; + } + } + array_shift($ipni_matches); + array_walk($ipni_matches, 'biblio_ipni_clean'); + return $ipni_matches; +} + + +/** + * + * Function called by array_walk() in biblio_ipni_process_file() to remove + * artefacts from the IPNI data + * + * @param $value + */ +function biblio_ipni_clean(&$value){ + if(is_array($value)){ + array_walk($value, 'biblio_ipni_clean'); + }else{ + if(substr($value, 0, 1) == '>'){ + $value = substr($value, 1); + } + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/plugins/biblio_self/biblio_self.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/plugins/biblio_self/biblio_self.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +; $Id$ +name = Biblio self autocomplete +description = Allows autocompletion of Biblio fields from previously used entries. +core = 7.x +dependencies[] = biblio_autocomplete +package = Biblio + + +; Information added by drupal.org packaging script on 2013-04-18 +version = "7.x-1.4" +core = "7.x" +project = "biblio_autocomplete" +datestamp = "1366294251" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/plugins/biblio_self/biblio_self.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/plugins/biblio_self/biblio_self.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,76 @@ + array('function' => 'autocomplete_callback_function') + */ +function biblio_self_biblio_autocomplete_info(){ + $fields = array( + 'biblio_secondary_title' => array('function' => 'biblio_self_autocomplete_secondary_title'), + 'biblio_tertiary_title' => array('function' => 'biblio_self_autocomplete_tertiary_title'), + 'biblio_alternate_title' => array('function' => 'biblio_self_autocomplete_alternate_title'), + 'biblio_original_publication' => array('function' => 'biblio_self_autocomplete_original_publication'), + 'biblio_short_title' => array('function' => 'biblio_self_autocomplete_short_title'), + 'biblio_publisher' => array('function' => 'biblio_self_autocomplete_publisher'), + 'biblio_place_published' => array('function' => 'biblio_self_autocomplete_place_published'), + 'biblio_type_of_work' => array('function' => 'biblio_self_autocomplete_type_of_work'), + 'biblio_translated_title' => array('function' => 'biblio_self_autocomplete_translated_title'), + ); + return $fields; +} + +function biblio_self_autocomplete_secondary_title($string){ + return biblio_self_autocomplete($string, 'secondary_title'); +} + +function biblio_self_autocomplete_tertiary_title($string){ + return biblio_self_autocomplete($string, 'tertiary_title'); +} + +function biblio_self_autocomplete_alternate_title($string){ + return biblio_self_autocomplete($string, 'alternate_title'); +} + +function biblio_self_autocomplete_original_publication($string){ + return biblio_self_autocomplete($string, 'original_publication'); +} + +function biblio_self_autocomplete_short_title($string){ + return biblio_self_autocomplete($string, 'short_title'); +} + +function biblio_self_autocomplete_publisher($string){ + return biblio_self_autocomplete($string, 'publisher'); +} + +function biblio_self_autocomplete_place_published($string){ + return biblio_self_autocomplete($string, 'place_published'); +} + +function biblio_self_autocomplete_type_of_work($string){ + return biblio_self_autocomplete($string, 'type_of_work'); +} + +function biblio_self_autocomplete_translated_title($string){ + return biblio_self_autocomplete($string, 'translated_title'); +} + + +function biblio_self_autocomplete($string, $field){ + $field = 'biblio_'.$field; + $sql = 'SELECT DISTINCT '.$field.' FROM biblio WHERE '.$field." LIKE '%".$string."%';"; + $result = db_query($sql)->fetchAll(); + $results = array(); + foreach ($result as $match){ + $results[] = array( + 'key' => $match->$field, + 'description' => $match->$field, + 'provider' => 'Biblio', + ); + } + return $results; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/plugins/biblio_zoobank/biblio_zoobank.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/plugins/biblio_zoobank/biblio_zoobank.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +; $Id$ +name = Biblio ZooBank autocomplete +description = Allows autocompletion of Biblio fields using ZooBank +core = 7.x +dependencies[] = biblio_autocomplete +package = Biblio + + +; Information added by drupal.org packaging script on 2013-04-18 +version = "7.x-1.4" +core = "7.x" +project = "biblio_autocomplete" +datestamp = "1366294251" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_autocomplete/plugins/biblio_zoobank/biblio_zoobank.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_autocomplete/plugins/biblio_zoobank/biblio_zoobank.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,440 @@ + array('function' => 'autocomplete_callback_function') + */ +function biblio_zoobank_biblio_autocomplete_info(){ + $fields = array( + 'biblio_secondary_title' => array('module' => 'biblio_zoobank', 'function' => 'biblio_zoobank_autocomplete_publication'), + 'biblio_alternate_title' => array('module' => 'biblio_zoobank', 'function' => 'biblio_zoobank_autocomplete_publication'), + 'biblio_original_publication' => array('module' => 'biblio_zoobank', 'function' => 'biblio_zoobank_autocomplete_publication'), + 'title' => array('module' => 'biblio_zoobank', 'function' => 'biblio_zoobank_autocomplete_publication'), + 'biblio_authors' => array('module' => 'biblio_zoobank', 'function' => 'biblio_zoobank_autocomplete_author_short'), + ); + return $fields; +} + +/** + * + * Gets a list of potential publication matches for the autocomplete + * + * @param $string + * Text string to try and match + * + * @return $return_matches + * An array of autocomplete results for use in biblio_autocomple_json(). + * Format: + * array( + * 'key' => 'value to put in biblio field', + * 'description' => 'can be the same as key or contain extra information to help use decide', + * 'provider' => 'source of the autocmplete information', + * ) + */ +function biblio_zoobank_autocomplete_publication($string){ + $zoobank_matches = biblio_zoobank_get_publication_data($string); + $return_matches = array(); + foreach($zoobank_matches as $result => $data){ + $return_matches[] = array( + 'key' => strip_tags($data->title), + 'description' => $data->label, + 'provider' => 'ZooBank', + ); + } + return $return_matches; +} + +/** + * + * Function to get publication data from ZooBank + * + * @param $string + * Text string to try and match + * + * @return $zoobank_result + * An array of data objects from ZooBank + */ +function biblio_zoobank_get_publication_data($string){ + $zoobank_result = file_get_contents('http://test.zoobank.org/References.json?term=' . urlencode($string)); + $zoobank_result = json_decode($zoobank_result); + + return $zoobank_result; +} + + +//Code below here relates to ZooBank biblio import function (not yet implemented) +// +// + +/** + * Implemets hook_menu(). + * + * @return $items + * Array of menu_items + */ +function biblio_zoobank_menu(){ + $items['biblio_autocomplete/zoobank_import'] = array( + 'title' => t('ZooBank import lookup'), + 'page callback' => 'biblio_zoobank_import_lookup', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + return $items; +} + +function biblio_zoobank_import_lookup($string){ + $matches = biblio_zoobank_get_publication_data($string); + $return_matches = array(); + $i = 0; + foreach ($matches as $result => $data){ + $i++; + $return_matches[$data->referenceuuid] = $data->label; + if ($i >=10){ + break; + } + } + print drupal_json_output($return_matches); +} + + +function biblio_zoobank_form_biblio_node_form_alter(&$form, &$form_state, $form_id) { + if ((!isset($form_state['biblio_type']) || empty($form_state['biblio_type'])) && !isset($form_state['node']->nid)) { + $form['biblio_zoobank_lookup'] = array( + '#type' => 'fieldset', + '#title' => t('ZooBank Lookup'), + '#weight' => -20, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + + $form['biblio_zoobank_lookup']['referenceuuid'] = array( + '#type' => 'textfield', + '#title' => t('Lookup reference'), + '#required' => TRUE, + '#default_value' => '', + '#description' => t('Search for a publication in ZooBank'), + '#size' => 60, + '#maxlength' => 255, + '#weight' => -4, + '#autocomplete_path' => 'biblio_autocomplete/zoobank_import', + ); + $form['biblio_zoobank_lookup']['zoobank_submit'] = array( + '#type' => 'submit', + '#value' => t('Populate from ZooBank'), + '#submit' => array('biblio_zoobank_form_biblio_node_form_submit') + ); + } +} + +function biblio_zoobank_form_biblio_node_form_submit($form, &$form_state){ + $url = 'http://test.zoobank.org/References.json/427D7953-E8FC-41E8-BEA7-8AE644E6DE77'; + $url = temp_zoobank_example(); + $data = file_get_contents($url); + $data = json_decode($data); + + + //TODO: Fails here as ZooBank JSON is invalid + $i =0; + drupal_set_message('Node (would have been) created', 'status'); +} + +function temp_zoobank_example(){ + $json='[ + { + "columns": [ + "pkid", + "uuid", + "referencetype", + "authors", + "year", + "numericyear", + "fulltitle", + "volume", + "number", + "edition", + "publisher", + "placepublished", + "pagination", + "startpage", + "endpage", + "startdateof", + "enddateof", + "startdateon", + "enddateon", + "datepublished", + "language", + "languageid", + "parentreferenceid", + "parentreferenceuuid", + "parentreference", + "formattedparentreference", + "typework", + "formatted", + "cleandisplay", + "citationdetails" + ], + "data": [ + [ + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + "Journal Article", + "Randall, John E. & Richard L. Pyle.", + "2001", + 2001, + "Four new serranid fishes of the anthiine genus Pseudanthias from the South Pacific", + "49", + "1", + "", + "", + "", + "19-34", + "", + "", + "2001-01-01", + "2001-12-31", + "2001-01-01", + "2001-12-31", + "", + "English", + 19691433, + 19703683, + "0089FB37-AA19-41B8-8622-011CCA4EF778", + "Raffles Bulletin of Zoology", + "Raffles Bulletin of Zoology", + "", + "Randall, John E. & Richard L. Pyle. 2001. Four new serranid fishes of the anthiine genus Pseudanthias from the South Pacific. Raffles Bulletin of Zoology 49(1): 19-34.", + "Randall, John E. & Richard L. Pyle. 2001. Four new serranid fishes of the anthiine genus Pseudanthias from the South Pacific. Raffles Bulletin of Zoology 49(1): 19-34.", + "Raffles Bulletin of Zoology 49(1): 19-34." + ] + ] + }, + { + "columns": [ + "pkid", + "uuid", + "protonymid", + "protonymuuid", + "rankgroup", + "taxonnamerankid", + "referenceid", + "referenceuuid", + "originalreferenceid", + "originalreferenceuuid", + "startpage", + "illustration", + "validusageid", + "validusageuuid", + "parentusageid", + "parentusageuuid", + "parentname", + "originalparentusageid", + "originalparentusageuuid", + "genderid", + "isfossil", + "types", + "typelocality", + "authors", + "year", + "namestring", + "scientificname", + "cleandisplay", + "formatteddisplay", + "formattedprotonym", + "usageauthors", + "usageyear", + "cleanprotonym", + "isprotonym" + ], + "data": [ + [ + 20195759, + "B1FBF746-782B-4AE8-9817-F4E3B8C03FFF", + 20231068, + "F77E4029-F731-4B62-829E-A050365DF383", + "Genus", + 60, + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + 19714690, + "FDA2DE2F-54D6-43AE-9FB0-733138334F18", + "", + "", + 20195759, + "B1FBF746-782B-4AE8-9817-F4E3B8C03FFF", + 0, + "00000000-0000-0000-0000-000000000000", + "", + 0, + "00000000-0000-0000-0000-000000000000", + 1, + 0, + "", + "", + "Bleeker", + "1871", + "Pseudanthias", + "Pseudanthias Bleeker in Bleeker 1871", + "Pseudanthias Bleeker in Bleeker 1871", + "Pseudanthias Bleeker in Bleeker 1871", + "Pseudanthias Bleeker in Bleeker 1871", + "Randall & Pyle", + "2001", + "Pseudanthias Bleeker in Bleeker 1871", + 0 + ], + [ + 20130879, + "6EA8BB2A-A57B-47C1-953E-042D8CD8E0E2", + 20130879, + "6EA8BB2A-A57B-47C1-953E-042D8CD8E0E2", + "Species", + 70, + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + "20", + "Figs. 1-4", + 20130879, + "6EA8BB2A-A57B-47C1-953E-042D8CD8E0E2", + 20195759, + "B1FBF746-782B-4AE8-9817-F4E3B8C03FFF", + "Pseudanthias", + 20231068, + "F77E4029-F731-4B62-829E-A050365DF383", + 0, + 0, + "", + "", + "Randall & Pyle", + "2001", + "carlsoni", + "Pseudanthias carlsoni Randall & Pyle 2001", + "Pseudanthias carlsoni Randall & Pyle 2001", + "Pseudanthias carlsoni Randall & Pyle 2001", + "Pseudanthias carlsoni Randall & Pyle 2001", + "Randall & Pyle", + "2001", + "carlsoni Randall & Pyle 2001 (as a species of Pseudanthias)", + 1 + ], + [ + 20428317, + "07DAC21C-21CE-42E9-B835-686B03E98E81", + 20428317, + "07DAC21C-21CE-42E9-B835-686B03E98E81", + "Species", + 70, + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + "23", + "Figs. 5-8", + 20428317, + "07DAC21C-21CE-42E9-B835-686B03E98E81", + 20195759, + "B1FBF746-782B-4AE8-9817-F4E3B8C03FFF", + "Pseudanthias", + 20231068, + "F77E4029-F731-4B62-829E-A050365DF383", + 0, + 0, + "", + "", + "Randall & Pyle", + "2001", + "flavicauda", + "Pseudanthias flavicauda Randall & Pyle 2001", + "Pseudanthias flavicauda Randall & Pyle 2001", + "Pseudanthias flavicauda Randall & Pyle 2001", + "Pseudanthias flavicauda Randall & Pyle 2001", + "Randall & Pyle", + "2001", + "flavicauda Randall & Pyle 2001 (as a species of Pseudanthias)", + 1 + ], + [ + 20070742, + "BB0F5636-525B-4B1F-BDAD-E4C4FA7EB03B", + 20070742, + "BB0F5636-525B-4B1F-BDAD-E4C4FA7EB03B", + "Species", + 70, + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + "25", + "Figs. 9-11", + 20070742, + "BB0F5636-525B-4B1F-BDAD-E4C4FA7EB03B", + 20195759, + "B1FBF746-782B-4AE8-9817-F4E3B8C03FFF", + "Pseudanthias", + 20231068, + "F77E4029-F731-4B62-829E-A050365DF383", + 0, + 0, + "", + "", + "Randall & Pyle", + "2001", + "hiva", + "Pseudanthias hiva Randall & Pyle 2001", + "Pseudanthias hiva Randall & Pyle 2001", + "Pseudanthias hiva Randall & Pyle 2001", + "Pseudanthias hiva Randall & Pyle 2001", + "Randall & Pyle", + "2001", + "hiva Randall & Pyle 2001 (as a species of Pseudanthias)", + 1 + ], + [ + 20230739, + "C7D36E15-93A9-43CD-A1FE-D734B676D66D", + 20230739, + "C7D36E15-93A9-43CD-A1FE-D734B676D66D", + "Species", + 70, + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + 19692512, + "427D7953-E8FC-41E8-BEA7-8AE644E6DE77", + "28", + "Fig. 12", + 20230739, + "C7D36E15-93A9-43CD-A1FE-D734B676D66D", + 20195759, + "B1FBF746-782B-4AE8-9817-F4E3B8C03FFF", + "Pseudanthias", + 20231068, + "F77E4029-F731-4B62-829E-A050365DF383", + 0, + 0, + "", + "", + "Randall & Pyle", + "2001", + "privitera", + "Pseudanthias privitera Randall & Pyle 2001", + "Pseudanthias privitera Randall & Pyle 2001", + "Pseudanthias privitera Randall & Pyle 2001", + "Pseudanthias privitera Randall & Pyle 2001", + "Randall & Pyle", + "2001", + "privitera Randall & Pyle 2001 (as a species of Pseudanthias)", + 1 + ] + ] + } +]'; + + $json = preg_replace('/\s+/', '', $json); + + return $json; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_scholar/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_scholar/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_scholar/biblio_scholar.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_scholar/biblio_scholar.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +; $Id$ +name = Biblio Scholar +description = Adds Google Scholar metatags to Biblio nodes. +core = 7.x +dependencies[] = biblio +package = Biblio + + +; Information added by drupal.org packaging script on 2012-11-22 +version = "7.x-1.6" +core = "7.x" +project = "biblio_scholar" +datestamp = "1353587456" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/biblio_scholar/biblio_scholar.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/biblio_scholar/biblio_scholar.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,105 @@ + 'meta', + '#attributes' => array( + 'name' => 'citation_title', + 'content' => $variables['title'] + ) + ), 'biblio_scholar_title'); + //Sort out the authors + $i = 0; + foreach($variables['biblio_contributors'] as $author){ + $author_types = array(1,2,3,4); + if(in_array($author['auth_type'], $author_types) ){ + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_author', + 'content' => $author['name'] + ), + '#weight' => $i, + ), 'biblio_scholar_author' . $i); + $i++; + } + } + if($variables['biblio_year'] != ''){ + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_publication_date', + 'content' => $variables['biblio_year'] + ) + ), 'biblio_scholar_publication_date'); + } + if($variables['biblio_secondary_title'] != ''){ + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_journal_title', + 'content' => $variables['biblio_secondary_title'] + ) + ), 'biblio_scholar_secondary_title'); + } + if($variables['biblio_volume'] != ''){ + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_volume', + 'content' => $variables['biblio_volume'] + ) + ), 'biblio_scholar_volume'); + } + if($variables['biblio_issue'] != ''){ + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_issue', + 'content' => $variables['biblio_issue'] + ) + ), 'biblio_scholar_issue'); + } + if($variables['biblio_isbn'] != ''){ + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_isbn', + 'content' => $variables['biblio_isbn'] + ) + ), 'biblio_scholar_isbn'); + } + if($variables['biblio_issn'] != ''){ + // drupal_set_html_head(''); + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_issn', + 'content' => $variables['biblio_issn'] + ) + ), 'biblio_scholar_issn'); + } + + foreach ($variables as $key => $field){ + if (is_array($field) && isset($field[0]['fid'])){ + foreach ($field as $file){ + if ($file['filemime'] == 'application/pdf'){ + drupal_add_html_head(array( + '#tag' => 'meta', + '#attributes' => array( + 'name' => 'citation_pdf_url', + 'content' => url(file_create_url($file['uri']), array('absolute' => TRUE)) + ) + ), 'biblio_scholar_url'); + } + } + } + } + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,39 @@ +Description: + The main target of the Bundle Inherit module is to allow users to inherit + bundles of different entity types from any other bundles of the same entity + type. Inheritance could be performed while creating new bundle of some entity + type (for example new content type). There are two types (modes) of inherit + available: + - Soft: All field instances from existing (parent) bundle will be cloned and + attached to the newly created bundle. As for the soft mode it is all. + - Strict: All field instances from existing (parent) bundle will be cloned and + attached to the newly created bundle. After that you will not be able to + directly edit inherited field instances in the children bundles and they + will be always kept synchronized. + +Structure: + Consist of two modules. + First module only Provide API for other modules to implement inheritance logic + on their (or other modules) entity types. + Second one extends node module and allow end users to inherit new content + types from already created. + + Support for other entity types (like commerce products, etc) could be easily + added by writing appropriate module. You can look at Bundle Inherit Node + module (included in Bundle Inherit module basic package, presented on this + page) for example of bundle inheritance logic implementation. + +Demo + Demo site: http://demo.etreyd.com/ + Demo login: demo + Demo password: demo + +Module project page: + http://drupal.org/project/bundle_inherit + +To submit bug reports and feature suggestions, or to track changes: + http://drupal.org/project/issues/bundle_inherit + +-- MAINTAINERS -- + +lemark - http://drupal.org/user/317870 \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/bundle_inherit.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/bundle_inherit.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,10 @@ +name = Bundle Inheritance +description = Provide API for modules to implement bundle inheritance logic +core = 7.x +files[] = bundle_inherit.test +; Information added by drupal.org packaging script on 2011-11-05 +version = "7.x-1.0-alpha2" +core = "7.x" +project = "bundle_inherit" +datestamp = "1320523829" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/bundle_inherit.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/bundle_inherit.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ + 'Holds info about hierarchy relations between entity types.', + 'fields' => array( + 'entity_type' => array( + 'description' => 'The entity type of the bundles.', + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + ), + 'bundle' => array( + 'description' => 'Child bundle name.', + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + ), + 'bundle_parent' => array( + 'description' => 'Parent bundle name.', + 'type' => 'varchar', + 'length' => '255', + 'not null' => TRUE, + ), + ), + 'primary key' => array('entity_type', 'bundle'), + 'indexes' => array( + 'parent' => array('entity_type', 'bundle_parent'), + ), + ); + + + return $schema; +} + + +/** + * Implements hook_uninstall(). + * + * We should remove 'locked' state from all inherited fields instances. + * + * @todo + * Maybe some better way to implement this function exists (talking about + * performance). But anyway we are getting info about instances from cache + * so I think that this implementation is more stable and logicaly loocking. + */ +function bundle_inherit_uninstall() { + $records = db_select('bundle_hierarchy', 'bh') + ->fields('bh') + ->execute(); + foreach ($records as $record) { + $instances = field_info_instances($record->entity_type, $record->bundle_parent); + foreach ($instances as $instance) { + $inherited_instance = field_info_instance($record->entity_type, $instance['field_name'], $record->bundle); + $inherited_instance['locked'] = FALSE; + field_update_instance($inherited_instance); + } + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/bundle_inherit.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/bundle_inherit.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,294 @@ +addField('fci', 'id'); + $query->condition('fci.bundle', $bundle); + $new_instance['id'] = $query->execute()->fetchField(); + } + // Check if we perform strict inheritance. + if ($strict) { + db_insert('bundle_hierarchy') + ->fields(array( + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'bundle_parent' => $bundle_parent + )) + ->execute(); + watchdog('bundle_inherit', 'The %bundle bundle of the entity %type was STRICTLY inherited from %parent_bundle bundle.', array('%bundle' => $bundle, '%bundle_parent' => $bundle_parent, '%type' => $entity_type)); + drupal_static_reset('bundle_inherit_bundle_get_children'); + } + else{ + watchdog('bundle_inherit', 'The %bundle bundle of the entity %type was SOFTLY inherited from %parent_bundle bundle.', array('%bundle' => $bundle, '%bundle_parent' => $bundle_parent, '%type' => $entity_type)); + } +} + +/** + * Implements hook_field_create_instance(). + */ +function bundle_inherit_field_create_instance($instance) { + $children = bundle_inherit_bundle_get_children($instance['entity_type'], $instance['bundle']); + foreach ($children as $bundle) { + $new_instance = $instance; + unset($new_instance['id']); + $new_instance['bundle'] = $bundle; + $new_instance['locked'] = TRUE; + field_create_instance($new_instance); + } +} + +/** + * Implements hook_field_update_instance(). + */ +function bundle_inherit_field_update_instance($instance, $prior_instance) { + $children = bundle_inherit_bundle_get_children($prior_instance['entity_type'], $prior_instance['bundle']); + + foreach ($children as $bundle) { + $old_instance = field_info_instance($instance['entity_type'], $instance['field_name'], $bundle); + + $new_instance = array( + 'id' => $old_instance['id'], + 'bundle' => $old_instance['bundle'], + 'locked' => TRUE + ); + $new_instance += $instance; + + field_update_instance($new_instance); + } +} + +/** + * Implements hook_field_delete_instance(). + */ +function bundle_inherit_field_delete_instance($instance) { + $children = bundle_inherit_bundle_get_children($instance['entity_type'], $instance['bundle']); + foreach ($children as $bundle) { + $new_instance = $instance; + $new_instance['bundle'] = $bundle; + $new_instance['locked'] = FALSE; + try { + field_update_instance($new_instance); + } + catch (Exception $e) { + drupal_set_message($e->getMessage(), 'error'); + } + } +} + +/** + * Implements hook_form_FORMID_alter(). + * + * Attach additional validation callback to the field_ui_field_overview_form. + * When adding new field instance to the parent we should check that all of it + * childrens hase not that field instances. + */ +function bundle_inherit_form_field_ui_field_overview_form_alter(&$form, &$form_instance, $form_id) { + $form['#validate'][] = 'bundle_inherit_validate_field_instance_creation'; +} + +/** + * Additional validation function to the field_ui_field_overview_form. + * + * While adding existing field instance, get this form is created for and set + * form error if any of this children has instance of this field. + */ +function bundle_inherit_validate_field_instance_creation($form, &$form_state) { + $form_values = $form_state['values']['fields']; + if (!empty($form_values['_add_existing_field']['field_name'])) { + $children = bundle_inherit_bundle_get_children_all($form['#entity_type'], $form['#bundle']); + $bundles_with_instance = array(); + foreach ($children as $child) { + $prior_instance = field_info_instance($form['#entity_type'], $form_values['_add_existing_field']['field_name'], $child); + if (!empty($prior_instance)) { + $bundles_with_instance[] = $prior_instance['bundle']; + } + } + if (count($bundles_with_instance) > 0) { + $string = implode(", ", $bundles_with_instance); + form_set_error('fields][_add_existing_field', t("Instance of the field %field can't be attached to %bundle bundle because this field instances are already attached to some of this bundle children bundles: %children", array('%bundle' => $form['#bundle'], '%field' => $form_values['_add_existing_field']['field_name'], '%children' => $string))); + } + } +} + +/** + * Get direct children bundles of the selected entity bundle. + */ +function bundle_inherit_bundle_get_children($entity_type, $bundle_parent) { + $children = &drupal_static(__FUNCTION__); + if (!isset($children[$entity_type][$bundle_parent])) { + $children[$entity_type][$bundle_parent] = db_select('bundle_hierarchy', 'bh') + ->fields('bh', array('bundle')) + ->condition('bundle_parent', $bundle_parent) + ->condition('entity_type', $entity_type) + ->execute() + ->fetchCol(); + } + return $children[$entity_type][$bundle_parent]; +} + +/** + * Get all children bundles of the selected entity bundle. + */ +function bundle_inherit_bundle_get_children_all($entity_type, $bundle_parent) { + $children = array(); + $children = bundle_inherit_bundle_get_children($entity_type, $bundle_parent); + foreach ($children as $child) { + $children = array_merge($children, bundle_inherit_bundle_get_children_all($entity_type, $child)); + } + return $children; +} + +/** + * Get parent of the selected entity bundle. + * + * @return + * Entity type parent type. + */ +function bundle_inherit_bundle_get_parent($entity_type, $bundle) { + $parent = &drupal_static(__FUNCTION__); + if (!isset($parent[$entity_type][$bundle])) { + $parent[$entity_type][$bundle] = db_select('bundle_hierarchy', 'bh') + ->fields('bh', array('bundle_parent')) + ->condition('bh.bundle', $bundle) + ->execute() + ->fetchField(); + if (!$parent[$entity_type][$bundle]) $parent[$entity_type][$bundle] = ''; + } + return $parent[$entity_type][$bundle]; +} + +/** + * Attach ineritance form to selected form element. + * + * @param $form + * Parent form element to attach inheritance form to. + * @param $form_state + * From state from the parent form. + * @param $entity_type + * Entity for which bundle is creating for. + * @param $bundle + * If editing existing bundle value for this argument should be provided. + */ +function bundle_inherit_attach_inherit_form(&$form, &$form_state, $entity_type, $bundle = '') { + $entity = entity_get_info($entity_type); + if (count($entity['bundles']) > 0) { + if (empty($bundle)) { + $form['bundle_inherit'] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + '#title' => t('Inheritance') + ); + $form['bundle_inherit']['entity_type'] = array('#type' => 'value', '#value' => $entity_type); + $form['bundle_inherit']['#parents'] = array('bundle_inherit'); + $form['bundle_inherit']['inherit'] = array( + '#type' => 'checkbox', + '#title' => t('Inherit from other') + ); + + foreach ($entity['bundles'] as $bundle_name => $bundle) { + $options[$bundle_name] = $bundle['label']; + } + $form['bundle_inherit']['parent_type'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('Parent'), + '#states' => array( + // Hide the inheritance settings when inherit checkbox is disabled. + 'invisible' => array( + 'input[name="bundle_inherit[inherit]"]' => array('checked' => FALSE), + ), + ), + ); + $form['bundle_inherit']['mode'] = array( + '#type' => 'radios', + '#options' => array( + 'strict' => t('Strict inherit'), + 'soft' => t('Soft inherit'), + ), + '#default_value' => 'strict', + '#required' => TRUE, + '#states' => array( + // Hide the inheritance settings when inherit checkbox is disabled. + 'invisible' => array( + 'input[name="bundle_inherit[inherit]"]' => array('checked' => FALSE), + ), + ), + '#title' => t('Inheritance mode') + ); + } + else { + $parent_bundle_name = bundle_inherit_bundle_get_parent($entity_type, $bundle); + if (!empty($parent_bundle_name)) { + $form['bundle_inherit'] = array( + '#type' => 'fieldset', + '#tree' => TRUE, + '#title' => t('Inheritance') + ); + $form['bundle_inherit']['message'] = array( + '#markup' => t('This bundle was inherited from !parent_bundle bundle.', array('!parent_bundle' => l($entity['bundles'][$parent_bundle_name]['label'], $entity['bundles'][$parent_bundle_name]['admin']['real path'].'/fields'))) + ); + } + } + } +} + +/** + * Should be executed when entity creation form is submiting. + * + * @param $bundle + * Newly created bundle name. + */ +function bundle_inherit_attach_inherit_form_submit($bundle, &$form, &$form_state) { + if (isset($form_state['values']['bundle_inherit']) && $form_state['values']['bundle_inherit']['inherit']) { + $bundle_inherit_values = $form_state['values']['bundle_inherit']; + bundle_inherit_perform($bundle_inherit_values['entity_type'], $bundle, $bundle_inherit_values['parent_type'], $bundle_inherit_values['mode'] == 'strict' ? TRUE : FALSE); + } +} + +/** + * Implements hook_field_attach_delete_bundle(). + */ +function bundle_inherit_field_attach_delete_bundle($entity_type, $bundle, $instances) { + db_delete('bundle_hierarchy') + ->condition('entity_type', $entity_type) + ->condition(db_or()->condition('bundle_parent', $bundle)->condition('bundle', $bundle)) + ->execute(); +} + +/** + * Implements hook_field_attach_rename_bundle(). + */ +function bundle_inherit_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { + db_update('bundle_hierarchy') + ->condition('entity_type', $entity_type) + ->condition('bundle', $bundle_old) + ->fields(array( + 'bundle' => $bundle_new + )) + ->execute(); + db_update('bundle_hierarchy') + ->condition('entity_type', $entity_type) + ->condition('bundle_parent', $bundle_old) + ->fields(array( + 'bundle_parent' => $bundle_new + )) + ->execute(); +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/bundle_inherit.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/bundle_inherit.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,127 @@ + 'Bundle Inherit', + 'description' => 'Ensure that the Bundle Inherit module works properly', + 'group' => 'Bundle Inherit' + ); + } + + public function setUp($modules = array()) { + $modules += array('bundle_inherit', 'node'); + parent::setUp($modules); + + // Add new content type (for example we'll create node type, but generaly, + // kind of the entity type does not matter here) + $this->parentType = $this->drupalCreateContentType(); + $this->childType = $this->drupalCreateContentType(); + // Create custom field + do { + $field_name = drupal_strtolower($this->randomName()); + } while (field_info_field($field_name)); + // Add 5 custom fields + for ($i = 0; $i < 5; $i++) { + do { + $field_name = drupal_strtolower($this->randomName()); + } while (field_info_field($field_name)); + $this->customFields[$field_name]['field'] = field_create_field(array( + 'field_name' => $field_name, + 'type' => 'text' + )); + field_create_instance(array( + 'field_name' => $field_name, + 'entity_type' => 'node', + 'bundle' => $this->parentType->type + )); + $this->customFields[$field_name]['instance'] = field_info_instance('node', $field_name, $this->parentType->type); + } + do { + $field_name = drupal_strtolower($this->randomName()); + } while (field_info_field($field_name)); + $this->extraField['field'] = field_create_field(array( + 'field_name' => $field_name, + 'type' => 'text' + )); + } + + public function inheritPerform() { + // When implementing inheritance on node entity type we should delete body + // field first. + if ($body_field_instance = field_info_instance('node', 'body', $this->childType->type)) { + field_delete_instance($body_field_instance); + } + + // Perform inherit operations + bundle_inherit_perform('node', $this->childType->type, $this->parentType->type, TRUE); + + $type_inherited = db_query('SELECT 1 FROM {bundle_hierarchy} WHERE entity_type = :entity_type AND bundle = :bundle AND bundle_parent = :bundle_parent', array(':entity_type' => 'node', ':bundle' => $this->childType->type, ':bundle_parent' => $this->parentType->type))->fetchField(); + $this->assertTrue($type_inherited, t('Type was inherited.')); + + // Check if fields instances was succesfuly inherited + $n = 1; + foreach ($this->customFields as $field_name => $field) { + $parent_type_field_instance = $this->customFields[$field_name]['instance']; + $child_type_field_instance = field_info_instance('node', $field_name, $this->childType->type); + $this->assertTrue($this->compareInstances($parent_type_field_instance, $child_type_field_instance), t('Child field instance %n is equal to the parent one', array('%n' => $n++))); + } + } + + public function fieldInstanceCreate() { + field_create_instance(array( + 'field_name' => $this->extraField['field']['field_name'], + 'entity_type' => 'node', + 'bundle' => $this->parentType->type + )); + $this->extraField['instance'] = field_info_instance('node', $this->extraField['field']['field_name'], $this->parentType->type); + $parent_type_field_instance = $this->extraField['instance']; $child_type_field_instance = field_info_instance('node', $this->extraField['field']['field_name'], $this->childType->type); + $this->assertTrue($this->compareInstances($parent_type_field_instance, $child_type_field_instance), t('The field instance attached to the parent type was succesfuly attached to the child type and equal to the parent one.')); + } + + public function fieldInstanceUpdate() { + $this->extraField['instance']['label'] = $this->randomString(); + $this->extraField['instance']['required'] = TRUE; + field_update_instance($this->extraField['instance']); + $child_type_field_instance = field_info_instance('node', $this->extraField['field']['field_name'], $this->childType->type); + $this->assertTrue($this->compareInstances($this->extraField['instance'], $child_type_field_instance), t('Child type field instance is equal to the parent one after updating.')); + } + + public function fieldInstanceDelete() { + field_delete_instance($this->extraField['instance']); + $child_type_field_instance = field_info_instance('node', $this->extraField['field']['field_name'], $this->childType->type); + $this->assertTrue($child_type_field_instance['locked'] == FALSE, t('Child type field instance was succesfuly deleted when the parent type one was deleted.')); + } + + /** + * Run complex CRUD test based on node entity type. + */ + public function testCRUD() { + $this->inheritPerform(); + $this->fieldInstanceCreate(); + $this->fieldInstanceUpdate(); + $this->fieldInstanceDelete(); + } + + /** + * Helper function to compare two instances indeferent of their 'id', + * 'bundle'and 'locked' fields. + * @param $a + * First instance. + * @param $b + * Second instance. + * @return + * TRUE if $a and $b are equal. + */ + private function compareInstances($a, $b) { + unset($a['id'], $a['bundle'], $a['locked']); + unset($b['id'], $b['bundle'], $b['locked']); + return $a == $b; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/bundle_inherit_node/bundle_inherit_node.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/bundle_inherit_node/bundle_inherit_node.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,11 @@ +name = Bundle Inheritance Node +description = Allow to inherit one node type from other +dependencies[] = bundle_inherit +files[] = bundle_inherit_node.test +core = 7.x +; Information added by drupal.org packaging script on 2011-11-05 +version = "7.x-1.0-alpha2" +core = "7.x" +project = "bundle_inherit" +datestamp = "1320523829" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/bundle_inherit_node/bundle_inherit_node.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/bundle_inherit_node/bundle_inherit_node.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,129 @@ + 0) { + // Attach inheritance form + if (empty($form['#node_type']->type)) { + bundle_inherit_attach_inherit_form($form, $form_state, 'node'); + } + // If editing existing node type we should provide it + else { + bundle_inherit_attach_inherit_form($form, $form_state, 'node', $form['#node_type']->type); + } + if (!empty($form['bundle_inherit'])) { + $form['bundle_inherit']['#group'] = 'additional_settings'; + // We should add submit callback only if we are creating new content type + if (empty($form['#node_type']->type)) { + $form['#submit'][] = 'bundle_inherit_node_form_submit'; + } + } + } +} + +/** + * + * Additional submit handler to the 'node_type_form' form. Perform inherit + * operations. + */ +function bundle_inherit_node_form_submit(&$form, &$form_state) { + // Check if body field instance already exists. + if (isset($form_state['values']['bundle_inherit']['inherit']) && $form_state['values']['bundle_inherit']['inherit']) { + if ($instance = field_info_instance('node', 'body', trim($form_state['values']['type']))) { + field_delete_instance($instance); + } + bundle_inherit_attach_inherit_form_submit(trim($form_state['values']['type']), $form, $form_state); + drupal_set_message(t('Node type %type was inherited from %parent_type. All fields from %parent_type type was attached to %type type.', + array('%type' => $form_state['values']['name'], '%parent_type' => $form['bundle_inherit']['parent_type']['#options'][$form_state['values']['bundle_inherit']['parent_type']]))); + } +} + +/** + * Implements hook_menu_alter(). + */ +function bundle_inherit_menu_alter(&$items) { + $items['admin/structure/types']['page callback'] = 'bundle_inherit_node_overview_types'; +} + +/** + * Redeclare content types admin page. Sort and add indention to the inherited types. + */ +function bundle_inherit_node_overview_types() { + $types = node_type_get_types(); + $names = node_type_get_names(); + $field_ui = module_exists('field_ui'); + $header = array(t('Name'), array('data' => t('Operations'), 'colspan' => $field_ui ? '4' : '2')); + $rows = array(); + + // Firstly we need to iterate through node types wich has children types + $top_items = $child_items = array(); + foreach ($names as $key => $name) { + $type = $types[$key]; + if (node_hook($type->type, 'form')) { + $type_url_str = str_replace('_', '-', $type->type); + $row = array(theme('node_admin_overview', array('name' => $name, 'type' => $type))); + // Set the edit column. + $row[] = array('data' => l(t('edit'), 'admin/structure/types/manage/' . $type_url_str)); + + if ($field_ui) { + // Manage fields. + $row[] = array('data' => l(t('manage fields'), 'admin/structure/types/manage/' . $type_url_str . '/fields')); + + // Display fields. + $row[] = array('data' => l(t('manage display'), 'admin/structure/types/manage/' . $type_url_str . '/display')); + } + + // Set the delete column. + if ($type->custom) { + $row[] = array('data' => l(t('delete'), 'admin/structure/types/manage/' . $type_url_str . '/delete')); + } + else { + $row[] = array('data' => ''); + } + + $parent_type = bundle_inherit_bundle_get_parent('node', $type->type); + if (empty($parent_type)) { + $top_items[$type->type] = $row; + } + else { + $child_items[$parent_type][$type->type] = $row; + } + } + } + + foreach ($top_items as $type => $row) { + $rows[] = $row; + _bundle_inherit_node_overview_types_populate_rows($child_items, $type, $rows); + } + + $build['node_table'] = array( + '#theme' => 'table', + '#header' => $header, + '#rows' => $rows, + '#empty' => t('No content types available. Add content type.', array('@link' => url('admin/structure/types/add'))), + ); + + return $build; +} + +/** + * Helper function for bundle_inherit_node_overview_types(). + */ +function _bundle_inherit_node_overview_types_populate_rows($child_items, $parent, &$rows, $level = 1) { + if (array_key_exists($parent, $child_items)) { + foreach ($child_items[$parent] as $type => $row) { + $row[0] = str_repeat('-  -  ', $level) . $row[0]; + $rows[] = $row; + _bundle_inherit_node_overview_types_populate_rows($child_items, $type, $rows, $level + 1); + } + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/bundle_inherit/bundle_inherit_node/bundle_inherit_node.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/bundle_inherit/bundle_inherit_node/bundle_inherit_node.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ + 'Bundle Inherit Node', + 'description' => 'Ensure that the Bundle Inherit Node module works properly', + 'group' => 'Bundle Inherit' + ); + } + + public function setUp() { + parent::setUp(array('bundle_inherit_node', 'field_ui', 'text', 'node')); + + $this->privelegedUser = $this->drupalCreateUser(array('administer content types')); + $this->childTypeName = drupal_strtolower($this->randomName()); + $this->drupalLogin($this->privelegedUser); + + // Add new content type. + $this->parentType = $this->drupalCreateContentType(); + + // Add 5 custom fields. + for ($i = 0; $i < 5; $i++) { + do{ + $field_name = drupal_strtolower($this->randomName()); + } while (field_info_field($field_name)); + $field = field_create_field(array( + 'field_name' => $field_name, + 'type' => 'text' + )); + field_create_instance(array( + 'field_name' => $field_name, + 'entity_type' => 'node', + 'bundle' => $this->parentType->type + )); + } + } + + /** + * Create new content type. Strictly inherit it from content type created in + * setUp(). + */ + public function testInherit() { + $edit = array( + 'name' => $this->childTypeName, + 'type' => $this->childTypeName, + 'bundle_inherit[inherit]' => TRUE, + 'bundle_inherit[parent_type]' => $this->parentType->type, + 'bundle_inherit[mode]' => 'strict' + ); + + $this->drupalPost('admin/structure/types/add', $edit, t('Save content type')); + $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $this->childTypeName))->fetchField(); + $this->assertTrue($type_exists, t('Type was created.')); + $type_inherited = db_query('SELECT 1 FROM {bundle_hierarchy} WHERE entity_type = :entity_type AND bundle = :bundle AND bundle_parent = :bundle_parent', array(':entity_type' => 'node', ':bundle' => $this->childTypeName, ':bundle_parent' => $this->parentType->type))->fetchField(); + $this->assertTrue($type_inherited, t('Type was inherited.')); + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,156 @@ + +Entity API module +----------------- +by Wolfgang Ziegler, nuppla@zites.net + +This module extends the entity API of Drupal core in order to provide a unified +way to deal with entities and their properties. Additionally, it provides an +entity CRUD controller, which helps simplifying the creation of new entity types. + + +This is an API module. You only need to enable it if a module depends on it or +you are interested in using it for development. + +This README is for interested developers. If you are not interested in +developing, you may stop reading now. + +-------------------------------------------------------------------------------- + Entity API +-------------------------------------------------------------------------------- + + * The module provides API functions allowing modules to create, save, delete + or to determine access for entities based on any entity type, for which the + necessary metadata is available. The module comes with integration for all + core entity types, as well as for entities provided via the Entity CRUD API + (see below). However, for any other entity type implemented by a contrib + module, the module integration has to be provided by the contrib module + itself. + + * Thus the module provides API functions like entity_save(), entity_create(), + entity_delete(), entity_revision_delete(), entity_view() and entity_access() + among others. + entity_load(), entity_label() and entity_uri() are already provided by + Drupal core. + + * For more information about how to provide this metadata, have a look at the + API documentation, i.e. entity_metadata_hook_entity_info(). + +-------------------------------------------------------------------------------- + Entity CRUD API - Providing new entity types +-------------------------------------------------------------------------------- + + * This API helps you when defining a new entity type. It provides an entity + controller, which implements full CRUD functionality for your entities. + + * To make use of the CRUD functionality you may just use the API functions + entity_create(), entity_delete() and entity_save(). + + Alternatively you may specify a class to use for your entities, for which the + "Entity" class is provided. In particular, it is useful to extend this class + in order to easily customize the entity type, e.g. saving. + + * The controller supports fieldable entities and revisions. There is also a + controller which supports implementing exportable entities. + + * The Entity CRUD API helps with providing additional module integration too, + e.g. exportable entities are automatically integrated with the Features + module. These module integrations are implemented in separate controller + classes, which may be overridden and deactivated on their own. + + * There is also an optional ui controller class, which assists with providing + an administrative UI for managing entities of a certain type. + + * For more details check out the documentation in the drupal.org handbook + http://drupal.org/node/878804 as well as the API documentation, i.e. + entity_crud_hook_entity_info(). + + + Basic steps to add a new entity type: +--------------------------------------- + + * You might want to study the code of the "entity_test.module". + + * Describe your entities db table as usual in hook_schema(). + + * Just use the "Entity" directly or extend it with your own class. + To see how to provide a separate class have a look at the "EntityClass" from + the "entity_test.module". + + * Implement hook_entity_info() for your entity. At least specifiy the + controller class (EntityAPIController, EntityAPIControllerExportable or your + own), your db table and your entity's keys. + Again just look at "entity_test.module"'s hook_entity_info() for guidance. + + * If you want your entity to be fieldable just set 'fieldable' in + hook_entity_info() to TRUE. The field API attachers are then called + automatically in the entity CRUD functions. + + * The entity API is able to deal with bundle objects too (e.g. the node type + object). For that just specify another entity type for the bundle objects + and set the 'bundle of' property for it. + Again just look at "entity_test.module"'s hook_entity_info() for guidance. + + * Schema fields marked as 'serialized' are automatically unserialized upon + loading as well as serialized on saving. If the 'merge' attribute is also + set to TRUE the unserialized data is automatically "merged" into the entity. + + * Further details can be found at http://drupal.org/node/878804. + + + +-------------------------------------------------------------------------------- + Entity Properties & Entity metadata wrappers +-------------------------------------------------------------------------------- + + * This module introduces a unique place for metadata about entity properties: + hook_entity_property_info(), whereas hook_entity_property_info() may be + placed in your module's {YOUR_MODULE}.info.inc include file. For details + have a look at the API documentation, i.e. hook_entity_property_info() and + at http://drupal.org/node/878876. + + * The information about entity properties contains the data type and callbacks + for how to get and set the data of the property. That way the data of an + entity can be easily re-used, e.g. to export it into other data formats like + XML. + + * For making use of this information (metadata) the module provides some + wrapper classes which ease getting and setting values. The wrapper supports + chained usage for retrieving wrappers of entity properties, e.g. to get a + node author's mail address one could use: + + $wrapper = entity_metadata_wrapper('node', $node); + $wrapper->author->mail->value(); + + To update the user's mail address one could use + + $wrapper->author->mail->set('sepp@example.com'); + + or + + $wrapper->author->mail = 'sepp@example.com'; + + The wrappers always return the data as described in the property + information, which may be retrieved directly via entity_get_property_info() + or from the wrapper: + + $mail_info = $wrapper->author->mail->info(); + + In order to force getting a textual value sanitized for output one can use, + e.g. + + $wrapper->title->value(array('sanitize' => TRUE)); + + to get the sanitized node title. When a property is already returned + sanitized by default, like the node body, one possibly wants to get the + not-sanitized data as it would appear in a browser for other use-cases. + To do so one can enable the 'decode' option, which ensures for any sanitized + data the tags are stripped and HTML entities are decoded before the property + is returned: + + $wrapper->body->value->value(array('decode' => TRUE)); + + That way one always gets the data as shown to the user. However if you + really want to get the raw, unprocessed value, even for sanitized textual + data, you can do so via: + + $wrapper->body->value->raw(); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/ctools/content_types/entity_view.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/ctools/content_types/entity_view.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,133 @@ + t('Rendered entity'), + 'defaults' => array('view_mode' => 'full'), + 'content type' => 'entity_entity_view_content_type_info', +); + +/** + * Get the entity content type info. + */ +function entity_entity_view_content_type_info($entity_type) { + $types = entity_entity_view_content_type_content_types(); + if (isset($types[$entity_type])) { + return $types[$entity_type]; + } +} + +/** + * Implements hook_PLUGIN_content_type_content_types(). + * + * Rendered entity use entity types machine name as subtype name. + */ +function entity_entity_view_content_type_content_types() { + $types = array(); + $entities = entity_get_info(); + + foreach ($entities as $entity_type => $info) { + if (entity_type_supports($entity_type, 'view')) { + $types[$entity_type] = array( + 'title' => t('Rendered @entity_type', array('@entity_type' => $info['label'])), + 'category' => t('Entity'), + 'required context' => new ctools_context_required(t('Entity'), $entity_type), + ); + } + } + + return $types; +} + +/** + * Returns an edit form for a entity. + * + * Rendered entity use entity types machine name as subtype name. + * + * @see entity_entity_view_get_content_types() + */ +function entity_entity_view_content_type_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + $entity_type = $form_state['subtype_name']; + $entity_info = entity_get_info($entity_type); + + $options = array(); + if (!empty($entity_info['view modes'])) { + foreach ($entity_info['view modes'] as $mode => $settings) { + $options[$mode] = $settings['label']; + } + } + + if (count($options) > 1) { + $form['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('View mode'), + '#default_value' => $conf['view_mode'], + ); + } + else { + $form['view_mode_info'] = array( + '#type' => 'item', + '#title' => t('View mode'), + '#description' => t('Only one view mode is available for this entity type.'), + '#markup' => $options ? current($options) : t('Default'), + ); + + $form['view_mode'] = array( + '#type' => 'value', + '#value' => $options ? key($options) : 'default', + ); + } + + return $form; +} + +/** + * Save selected view mode. + */ +function entity_entity_view_content_type_edit_form_submit(&$form, &$form_state) { + if (isset($form_state['values']['view_mode'])) { + $form_state['conf']['view_mode'] = $form_state['values']['view_mode']; + } +} + +/** + * Implements hook_PLUGIN_content_type_render(). + * + * Ctools requires us to return a block. + * + * @see ctools_content_render() + */ +function entity_entity_view_content_type_render($entity_type, $conf, $panel_args, $context) { + if ($context->empty) { + return; + } + $block = new stdClass(); + $block->module = 'entity'; + $block->delta = $entity_type . '-' . str_replace('-', '_', $conf['view_mode']); + + $entity_id = $context->argument; + $entity = entity_load_single($entity_type, $entity_id); + $block->content = entity_view($entity_type, array($entity_id => $entity), $conf['view_mode']); + return $block; +} + +/** + * Implements hook_PLUGIN_content_type_admin_title(). + * + * Returns the administrative title for a type. + */ +function entity_entity_view_content_type_admin_title($entity_type, $conf, $contexts) { + $entity_info = entity_get_info($entity_type); + $view_mode = $conf['view_mode']; + if (isset($entity_info['view modes'][$view_mode])) { + $view_mode = $entity_info['view modes'][$view_mode]['label']; + } + return t('Rendered @entity_type using view mode "@view_mode"', array('@entity_type' => $entity_info['label'], '@view_mode' => $view_mode)); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.api.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,466 @@ + array( + 'label' => t('Test Entity'), + 'entity class' => 'Entity', + 'controller class' => 'EntityAPIController', + 'base table' => 'entity_test', + 'module' => 'entity_test', + 'fieldable' => TRUE, + 'entity keys' => array( + 'id' => 'pid', + 'name' => 'name', + 'bundle' => 'type', + ), + 'bundles' => array(), + ), + ); + foreach (entity_test_get_types() as $name => $info) { + $return['entity_test']['bundles'][$name] = array( + 'label' => $info['label'], + ); + } + return $return; +} + +/** + * Provide additional metadata for entities. + * + * This is a placeholder for describing further keys for hook_entity_info(), + * which are introduced the entity API in order to support any entity type; e.g. + * to make entity_save(), entity_create(), entity_view() and others work. + * See entity_crud_hook_entity_info() for the documentation of additional keys + * for hook_entity_info() as introduced by the entity API for providing new + * entity types with the entity CRUD API. + * + * Additional keys are: + * - plural label: (optional) The human-readable, plural name of the entity + * type. As 'label' it should start capitalized. + * - description: (optional) A human-readable description of the entity type. + * - access callback: (optional) Specify a callback that returns access + * permissions for the operations 'create', 'update', 'delete' and 'view'. + * The callback gets optionally the entity and the user account to check for + * passed. See entity_access() for more details on the arguments and + * entity_metadata_no_hook_node_access() for an example. + * - creation callback: (optional) A callback that creates a new instance of + * this entity type. See entity_metadata_create_node() for an example. + * - save callback: (optional) A callback that permanently saves an entity of + * this type. + * - deletion callback: (optional) A callback that permanently deletes an + * entity of this type. + * - revision deletion callback: (optional) A callback that deletes a revision + * of the entity. + * - view callback: (optional) A callback to render a list of entities. + * See entity_metadata_view_node() as example. + * - form callback: (optional) A callback that returns a fully built edit form + * for the entity type. + * - token type: (optional) A type name to use for token replacements. Set it + * to FALSE if there aren't any token replacements for this entity type. + * - configuration: (optional) A boolean value that specifies whether the entity + * type should be considered as configuration. Modules working with entities + * may use this value to decide whether they should deal with a certain entity + * type. Defaults to TRUE to for entity types that are exportable, else to + * FALSE. + * + * @see hook_entity_info() + * @see entity_crud_hook_entity_info() + * @see entity_access() + * @see entity_create() + * @see entity_save() + * @see entity_delete() + * @see entity_view() + * @see entity_form() + */ +function entity_metadata_hook_entity_info() { + return array( + 'node' => array( + 'label' => t('Node'), + 'access callback' => 'entity_metadata_no_hook_node_access', + // ... + ), + ); +} + +/** + * Allow modules to define metadata about entity properties. + * + * Modules providing properties for any entities defined in hook_entity_info() + * can implement this hook to provide metadata about this properties. + * For making use of the metadata have a look at the provided wrappers returned + * by entity_metadata_wrapper(). + * For providing property information for fields see entity_hook_field_info(). + * + * @return + * An array whose keys are entity type names and whose values are arrays + * containing the keys: + * - properties: The array describing all properties for this entity. Entries + * are keyed by the property name and contain an array of metadata for each + * property. The name may only contain alphanumeric lowercase characters + * and underscores. Known keys are: + * - label: A human readable, translated label for the property. + * - description: (optional) A human readable, translated description for + * the property. + * - type: The data type of the property. To make the property actually + * useful it is important to map your properties to one of the known data + * types, which currently are: + * - text: Any text. + * - token: A string containing only lowercase letters, numbers, and + * underscores starting with a letter; e.g. this type is useful for + * machine readable names. + * - integer: A usual PHP integer value. + * - decimal: A PHP float or integer. + * - date: A full date and time, as timestamp. + * - duration: A duration as number of seconds. + * - boolean: A usual PHP boolean value. + * - uri: An absolute URI or URL. + * - entities - You may use the type of each entity known by + * hook_entity_info(), e.g. 'node' or 'user'. Internally entities are + * represented by their identifieres. In case of single-valued + * properties getter callbacks may return full entity objects as well, + * while a value of FALSE is interpreted like a NULL value as "property + * is not set". + * - entity: A special type to be used generically for entities where the + * entity type is not known beforehand. The entity has to be + * represented using an EntityMetadataWrapper. + * - struct: This as well as any else not known type may be used for + * supporting arbitrary data structures. For that additional metadata + * has to be specified with the 'property info' key. New type names + * have to be properly prefixed with the module name. + * - list: A list of values, represented as numerically indexed array. + * The list notation may be used to specify the type of the + * contained items, where TYPE may be any valid type expression. + * - bundle: (optional) If the property is an entity, you may specify the + * bundle of the referenced entity. + * - options list: (optional) A callback that returns a list of possible + * values for the property. The callback has to return an array as + * used by hook_options_list(). + * Note that it is possible to return a different set of options depending + * whether they are used in read or in write context. See + * EntityMetadataWrapper::optionsList() for more details on that. + * - getter callback: (optional) A callback used to retrieve the value of + * the property. Defaults to entity_property_verbatim_get(). + * It is important that your data is represented, as documented for your + * data type, e.g. a date has to be a timestamp. Thus if necessary, the + * getter callback has to do the necessary conversion. In case of an empty + * or not set value, the callback has to return NULL. + * - setter callback: (optional) A callback used to set the value of the + * property. In many cases entity_property_verbatim_set() can be used. + * - validation callback: (optional) A callback that returns whether the + * passed data value is valid for the property. May be used to implement + * additional validation checks, such as to ensure the value is a valid + * mail address. + * - access callback: (optional) An access callback to allow for checking + * 'view' and 'edit' access for the described property. If no callback + * is specified, a 'setter permission' may be specified instead. + * - setter permission: (optional) A permission that describes whether + * a user has permission to set ('edit') this property. This permission + * is only be taken into account, if no 'access callback' is given. + * - schema field: (optional) In case the property is directly based upon + * a field specified in the entity's hook_schema(), the name of the field. + * - queryable: (optional) Whether a property is queryable with + * EntityFieldQuery. Defaults to TRUE if a 'schema field' is specified, or + * if the deprecated 'query callback' is set to + * 'entity_metadata_field_query'. Otherwise it defaults to FALSE. + * - query callback: (deprecated) A callback for querying for entities + * having the given property value. See entity_property_query(). + * Generally, properties should be queryable via EntityFieldQuery. If + * that is the case, just set 'queryable' to TRUE. + * - required: (optional) Whether this property is required for the creation + * of a new instance of its entity. See + * entity_property_values_create_entity(). + * - field: (optional) A boolean indicating whether a property is stemming + * from a field. + * - computed: (optional) A boolean indicating whether a property is + * computed, i.e. the property value is not stored or loaded by the + * entity's controller but determined on the fly by the getter callback. + * Defaults to FALSE. + * - entity views field: (optional) If enabled, the property is + * automatically exposed as views field available to all views query + * backends listing this entity-type. As the property value will always be + * generated from a loaded entity object, this is particularly useful for + * 'computed' properties. Defaults to FALSE. + * - sanitized: (optional) For textual properties only, whether the text is + * already sanitized. In this case you might want to also specify a raw + * getter callback. Defaults to FALSE. + * - sanitize: (optional) For textual properties, that are not sanitized + * yet, specify a function for sanitizing the value. Defaults to + * check_plain(). + * - raw getter callback: (optional) For sanitized textual properties, a + * separate callback which can be used to retrieve the raw, unprocessed + * value. + * - clear: (optional) An array of property names, of which the cache should + * be cleared too once this property is updated. As a rule of thumb any + * duplicated properties should be avoided though. + * - property info: (optional) An array of info for an arbitrary data + * structure together with any else not defined type, see data type + * 'struct'. Specify metadata in the same way as defined for this hook. + * - property info alter: (optional) A callback for altering the property + * info before it is used by the metadata wrappers. + * - property defaults: (optional) An array of property info defaults for + * each property derived of the wrapped data item (e.g. an entity). + * Applied by the metadata wrappers. + * - auto creation: (optional) Properties of type 'struct' may specify + * this callback which is used to automatically create the data structure + * (e.g. an array) if necessary. This is necessary in order to support + * setting a property of a not yet initialized data structure. + * See entity_metadata_field_file_callback() for an example. + * - translatable: (optional) Whether the property is translatable, defaults + * to FALSE. + * - entity token: (optional) If Entity tokens module is enabled, the + * module provides a token for the property if one does not exist yet. + * Specify FALSE to disable this functionality for the property. + * - bundles: An array keyed by bundle name containing further metadata + * related to the bundles only. This array may contain the key 'properties' + * with an array of info about the bundle specific properties, structured in + * the same way as the entity properties array. + * + * @see hook_entity_property_info_alter() + * @see entity_get_property_info() + * @see entity_metadata_wrapper() + */ +function hook_entity_property_info() { + $info = array(); + $properties = &$info['node']['properties']; + + $properties['nid'] = array( + 'label' => t("Content ID"), + 'type' => 'integer', + 'description' => t("The unique content ID."), + ); + return $info; +} + +/** + * Allow modules to alter metadata about entity properties. + * + * @see hook_entity_property_info() + */ +function hook_entity_property_info_alter(&$info) { + $properties = &$info['node']['bundles']['poll']['properties']; + + $properties['poll-votes'] = array( + 'label' => t("Poll votes"), + 'description' => t("The number of votes that have been cast on a poll node."), + 'type' => 'integer', + 'getter callback' => 'entity_property_poll_node_get_properties', + ); +} + +/** + * Provide entity property information for fields. + * + * This is a placeholder for describing further keys for hook_field_info(), + * which are introduced by the entity API. + * + * For providing entity property info for fields each field type may specify a + * property type to map to using the key 'property_type'. With that info in + * place useful defaults are generated, which suffice for a lot of field + * types. + * However it is possible to specify further callbacks that may alter the + * generated property info. To do so use the key 'property_callbacks' and set + * it to an array of function names. Apart from that any property info provided + * for a field instance using the key 'property info' is added in too. + * + * @see entity_field_info_alter() + * @see entity_metadata_field_text_property_callback() + */ +function entity_hook_field_info() { + return array( + 'text' => array( + 'label' => t('Text'), + 'property_type' => 'text', + // ... + ), + ); +} + +/** + * Alter the handlers used by the data selection tables provided by this module. + * + * @param array $field_handlers + * An array of the field handler classes to use for specific types. The keys + * are the types, mapped to their respective classes. Contained types are: + * - All primitive types known by the entity API (see + * hook_entity_property_info()). + * - options: Special type for fields having an options list. + * - field: Special type for Field API fields. + * - entity: Special type for entity-valued fields. + * - relationship: Views relationship handler to use for relationships. + * Values for all specific entity types can be additionally added. + * + * @see entity_views_field_definition() + * @see entity_views_get_field_handlers() + */ +function hook_entity_views_field_handlers_alter(array &$field_handlers) { + $field_handlers['duration'] = 'example_duration_handler'; + $field_handlers['node'] = 'example_node_handler'; +} + +/** + * @} End of "addtogroup hooks". + */ diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.features.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.features.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,174 @@ + 'EntityDefaultFeaturesController'); + $static[$type] = $info['features controller class'] ? new $info['features controller class']($type) : FALSE; + } + return $static[$type]; +} + +/** + * Default controller handling features integration. + */ +class EntityDefaultFeaturesController { + + protected $type, $info; + + public function __construct($type) { + $this->type = $type; + $this->info = entity_get_info($type); + $this->info['entity keys'] += array('module' => 'module', 'status' => 'status'); + $this->statusKey = $this->info['entity keys']['status']; + $this->moduleKey = $this->info['entity keys']['module']; + if (!empty($this->info['bundle of'])) { + $entity_info = entity_get_info($this->info['bundle of']); + $this->bundleKey = $entity_info['bundle keys']['bundle']; + } + } + + /** + * Defines the result for hook_features_api(). + */ + public function api() { + return array( + // The entity type has to be the features component name. + $this->type => array( + 'name' => $this->info['label'], + 'feature_source' => TRUE, + 'default_hook' => isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type, + // Use the provided component callbacks making use of the controller. + 'base' => 'entity', + 'file' => drupal_get_path('module', 'entity') . '/entity.features.inc', + ), + ); + } + + /** + * Generates the result for hook_features_export_options(). + */ + public function export_options() { + $options = array(); + foreach (entity_load_multiple_by_name($this->type, FALSE) as $name => $entity) { + $options[$name] = entity_label($this->type, $entity); + } + return $options; + } + + /** + * Generates the result for hook_features_export(). + */ + public function export($data, &$export, $module_name = '') { + $pipe = array(); + foreach (entity_load_multiple_by_name($this->type, $data) as $name => $entity) { + // If this entity is provided by a different module, add it as dependency. + if (($entity->{$this->statusKey} & ENTITY_IN_CODE) && $entity->{$this->moduleKey} != $module_name) { + $module = $entity->{$this->moduleKey}; + $export['dependencies'][$module] = $module; + } + // Otherwise export the entity. + else { + $export['features'][$this->type][$name] = $name; + + // If this is a bundle of a fieldable entity, add its fields to the pipe. + if (!empty($this->info['bundle of'])) { + $fields = field_info_instances($this->info['bundle of'], $entity->{$this->bundleKey}); + foreach ($fields as $name => $field) { + $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; + } + } + } + } + // Add the module providing the entity type as dependency. + if ($data && !empty($this->info['module'])) { + $export['dependencies'][$this->info['module']] = $this->info['module']; + // In case entity is not already an indirect dependency, add it. + // We can do so without causing redundant dependencies because, + // if entity is an indirect dependency, Features will filter it out. + $export['dependencies']['entity'] = 'entity'; + } + return $pipe; + } + + /** + * Generates the result for hook_features_export_render(). + */ + function export_render($module, $data, $export = NULL) { + $output = array(); + $output[] = ' $items = array();'; + foreach (entity_load_multiple_by_name($this->type, $data) as $name => $entity) { + $export = " \$items['$name'] = entity_import('{$this->type}', '"; + // Make sure to escape the characters \ and '. + $export .= addcslashes(entity_export($this->type, $entity, ' '), '\\\''); + $export .= "');"; + $output[] = $export; + } + $output[] = ' return $items;'; + $output = implode("\n", $output); + + $hook = isset($this->info['export']['default hook']) ? $this->info['export']['default hook'] : 'default_' . $this->type; + return array($hook => $output); + } + + /** + * Generates the result for hook_features_revert(). + */ + function revert($module = NULL) { + if ($defaults = features_get_default($this->type, $module)) { + foreach ($defaults as $name => $entity) { + entity_delete($this->type, $name); + } + } + } +} + +/** + * Implements of hook_features_api(). + */ +function entity_features_api() { + $items = array(); + foreach (entity_crud_get_info() as $type => $info) { + if (!empty($info['exportable']) && $controller = entity_features_get_controller($type)) { + $items += $controller->api(); + } + } + return $items; +} + +/** + * Features component callback. + */ +function entity_features_export_options($entity_type) { + return entity_features_get_controller($entity_type)->export_options(); +} + +/** + * Features component callback. + */ +function entity_features_export($data, &$export, $module_name = '', $entity_type) { + return entity_features_get_controller($entity_type)->export($data, $export, $module_name); +} + +/** + * Features component callback. + */ +function entity_features_export_render($module, $data, $export = NULL, $entity_type) { + return entity_features_get_controller($entity_type)->export_render($module, $data, $export); +} + +/** + * Features component callback. + */ +function entity_features_revert($module = NULL, $entity_type) { + return entity_features_get_controller($entity_type)->revert($module); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.i18n.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.i18n.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,210 @@ + $info) { + entity_i18n_controller($entity_type); + } + return array_filter($static); + } + + if (!isset($static[$type])) { + $info = entity_get_info($type); + // Do not activate it by default. Modules have to explicitly enable it by + // specifying EntityDefaultI18nStringController or their customization. + $class = isset($info['i18n controller class']) ? $info['i18n controller class'] : FALSE; + $static[$type] = $class ? new $class($type, $info) : FALSE; + } + + return $static[$type]; +} + +/** + * Implements hook_i18n_string_info(). + */ +function entity_i18n_string_info() { + $groups = array(); + foreach (entity_i18n_controller() as $entity_type => $controller) { + $groups += $controller->hook_string_info(); + } + return $groups; +} + +/** + * Implements hook_i18n_object_info(). + */ +function entity_i18n_object_info() { + $info = array(); + foreach (entity_i18n_controller() as $entity_type => $controller) { + $info += $controller->hook_object_info(); + } + return $info; +} + +/** + * Implements hook_i18n_string_objects(). + */ +function entity_i18n_string_objects($type) { + if ($controller = entity_i18n_controller($type)) { + return $controller->hook_string_objects(); + } +} + +/** + * Default controller handling i18n integration. + * + * Implements i18n string translation for all non-field properties marked as + * 'translatable' and having the flag 'i18n string' set. This translation + * approach fits in particular for translating configuration, i.e. exportable + * entities. + * + * Requirements for the default controller: + * - The entity type providing module must be specified using the 'module' key + * in hook_entity_info(). + * - An 'entity class' derived from the provided class 'Entity' must be used. + * - Properties must be declared as 'translatable' and the 'i18n string' flag + * must be set to TRUE using hook_entity_property_info(). + * - i18n must be notified about changes manually by calling + * i18n_string_object_update(), i18n_string_object_remove() and + * i18n_string_update_context(). Ideally, this is done in a small integration + * module depending on the entity API and i18n_string. Look at the provided + * testing module "entity_test_i18n" for an example. + * - If the entity API admin UI is used, the "translate" tab will be + * automatically enabled and linked from the UI. + * - There are helpers for getting translated values which work regardless + * whether the i18n_string module is enabled, i.e. entity_i18n_string() + * and Entity::getTranslation(). + * + * Current limitations: + * - Translatable property values cannot be updated via the metadata wrapper, + * however reading works fine. See Entity::getTranslation(). + */ +class EntityDefaultI18nStringController { + + protected $entityType, $entityInfo; + + /** + * The i18n textgroup we are using. + */ + protected $textgroup; + + public function __construct($type) { + $this->entityType = $type; + $this->entityInfo = entity_get_info($type); + // By default we go with the module name as textgroup. + $this->textgroup = $this->entityInfo['module']; + } + + /** + * Implements hook_i18n_string_info() via entity_i18n_string_info(). + */ + public function hook_string_info() { + $list = system_list('module_enabled'); + $info = $list[$this->textgroup]->info; + + $groups[$this->textgroup] = array( + 'title' => $info['name'], + 'description' => !empty($info['description']) ? $info['description'] : NULL, + 'format' => FALSE, + 'list' => TRUE, + ); + return $groups; + } + + /** + * Implements hook_i18n_object_info() via entity_i18n_object_info(). + * + * Go with the same default values as the admin UI as far as possible. + */ + public function hook_object_info() { + $wildcard = $this->menuWildcard(); + $id_key = !empty($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->entityInfo['entity keys']['id']; + + $info[$this->entityType] = array( + // Generic object title. + 'title' => $this->entityInfo['label'], + // The object key field. + 'key' => $id_key, + // Placeholders for automatic paths. + 'placeholders' => array( + $wildcard => $id_key, + ), + + // Properties for string translation. + 'string translation' => array( + // Text group that will handle this object's strings. + 'textgroup' => $this->textgroup, + // Object type property for string translation. + 'type' => $this->entityType, + // Translatable properties of these objects. + 'properties' => $this->translatableProperties(), + ), + ); + + // Integrate the translate tab into the admin-UI if enabled. + if ($base_path = $this->menuBasePath()) { + $info[$this->entityType] += array( + // To produce edit links automatically. + 'edit path' => $base_path . '/manage/' . $wildcard, + // Auto-generate translate tab. + 'translate tab' => $base_path . '/manage/' . $wildcard . '/translate', + ); + $info[$this->entityType]['string translation'] += array( + // Path to translate strings to every language. + 'translate path' => $base_path . '/manage/' . $wildcard . '/translate/%i18n_language', + ); + } + return $info; + } + + /** + * Defines the menu base path used by self::hook_object_info(). + */ + protected function menuBasePath() { + return !empty($this->entityInfo['admin ui']['path']) ? $this->entityInfo['admin ui']['path'] : FALSE; + } + + /** + * Defines the menu wildcard used by self::hook_object_info(). + */ + protected function menuWildcard() { + return isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object'; + } + + /** + * Defines translatable properties used by self::hook_object_info(). + */ + protected function translatableProperties() { + $list = array(); + foreach (entity_get_all_property_info($this->entityType) as $name => $info) { + if (!empty($info['translatable']) && !empty($info['i18n string'])) { + $list[$name] = array( + 'title' => $info['label'], + ); + } + } + return $list; + } + + /** + * Implements hook_i18n_string_objects() via entity_i18n_string_objects(). + */ + public function hook_string_objects() { + return entity_load_multiple_by_name($this->entityType, FALSE); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,33 @@ +name = Entity API +description = Enables modules to work with any entity type and to provide entities. +core = 7.x +files[] = entity.features.inc +files[] = entity.i18n.inc +files[] = entity.info.inc +files[] = entity.rules.inc +files[] = entity.test +files[] = includes/entity.inc +files[] = includes/entity.controller.inc +files[] = includes/entity.ui.inc +files[] = includes/entity.wrapper.inc +files[] = views/entity.views.inc +files[] = views/handlers/entity_views_field_handler_helper.inc +files[] = views/handlers/entity_views_handler_area_entity.inc +files[] = views/handlers/entity_views_handler_field_boolean.inc +files[] = views/handlers/entity_views_handler_field_date.inc +files[] = views/handlers/entity_views_handler_field_duration.inc +files[] = views/handlers/entity_views_handler_field_entity.inc +files[] = views/handlers/entity_views_handler_field_field.inc +files[] = views/handlers/entity_views_handler_field_numeric.inc +files[] = views/handlers/entity_views_handler_field_options.inc +files[] = views/handlers/entity_views_handler_field_text.inc +files[] = views/handlers/entity_views_handler_field_uri.inc +files[] = views/handlers/entity_views_handler_relationship_by_bundle.inc +files[] = views/handlers/entity_views_handler_relationship.inc +files[] = views/plugins/entity_views_plugin_row_entity_view.inc +; Information added by drupal.org packaging script on 2013-08-14 +version = "7.x-1.2" +core = "7.x" +project = "entity" +datestamp = "1376493705" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,262 @@ + $info) { + // Automatically enable the controller only if the module does not implement + // the hook itself. + if (!isset($info['metadata controller class']) && !empty($info['base table']) && (!isset($info['module']) || !module_hook($info['module'], 'entity_property_info'))) { + $info['metadata controller class'] = 'EntityDefaultMetadataController'; + } + if (!empty($info['metadata controller class'])) { + $controller = new $info['metadata controller class']($type); + $items += $controller->entityPropertyInfo(); + } + } + // Add in info for all core entities. + foreach (_entity_metadata_core_modules() as $module) { + module_load_include('inc', 'entity', "modules/$module.info"); + if (function_exists($function = "entity_metadata_{$module}_entity_property_info")) { + if ($return = $function()) { + $items = array_merge_recursive($items, $return); + } + } + } + return $items; +} + +/** + * Implements hook_entity_property_info_alter(). + */ +function entity_entity_property_info_alter(&$entity_info) { + // Add in info for all core entities. + foreach (_entity_metadata_core_modules() as $module) { + module_load_include('inc', 'entity', "modules/$module.info"); + if (function_exists($function = "entity_metadata_{$module}_entity_property_info_alter")) { + $function($entity_info); + } + } +} + +function _entity_metadata_core_modules() { + return array_filter(array('book', 'comment', 'field', 'locale', 'node', 'taxonomy', 'user', 'system', 'statistics'), 'module_exists'); +} + +/** + * Default controller for generating some basic metadata for CRUD entity types. + */ +class EntityDefaultMetadataController { + + protected $type, $info; + + public function __construct($type) { + $this->type = $type; + $this->info = entity_get_info($type); + } + + public function entityPropertyInfo() { + $entity_label = drupal_strtolower($this->info['label']); + + // Provide defaults based on the schema. + $info['properties'] = $this->convertSchema(); + foreach ($info['properties'] as $name => &$property) { + // Add a description. + $property['description'] = t('@entity "@property" property.', array('@entity' => drupal_ucfirst($entity_label), '@property' => $name)); + } + + // Set better metadata for known entity keys. + $id_key = $this->info['entity keys']['id']; + + if (!empty($this->info['entity keys']['name']) && $key = $this->info['entity keys']['name']) { + $info['properties'][$key]['type'] = 'token'; + $info['properties'][$key]['label'] = t('Machine-readable name'); + $info['properties'][$key]['description'] = t('The machine-readable name identifying this @entity.', array('@entity' => $entity_label)); + $info['properties'][$id_key]['label'] = t('Internal, numeric @entity ID', array('@entity' => $entity_label)); + $info['properties'][$id_key]['description'] = t('The ID used to identify this @entity internally.', array('@entity' => $entity_label)); + } + else { + $info['properties'][$id_key]['label'] = t('@entity ID', array('@entity' => drupal_ucfirst($entity_label))); + $info['properties'][$id_key]['description'] = t('The unique ID of the @entity.', array('@entity' => $entity_label)); + } + // Care for the bundle. + if (!empty($this->info['entity keys']['bundle']) && $key = $this->info['entity keys']['bundle']) { + $info['properties'][$key]['type'] = 'token'; + $info['properties'][$key]['options list'] = array(get_class($this), 'bundleOptionsList'); + } + // Care for the label. + if (!empty($this->info['entity keys']['label']) && $key = $this->info['entity keys']['label']) { + $info['properties'][$key]['label'] = t('Label'); + $info['properties'][$key]['description'] = t('The human readable label.'); + } + + // Add a computed property for the entity URL and expose it to views. + if (empty($info['properties']['url']) && !empty($this->info['uri callback'])) { + $info['properties']['url'] = array( + 'label' => t('URL'), + 'description' => t('The URL of the entity.'), + 'getter callback' => 'entity_metadata_entity_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + 'entity views field' => TRUE, + ); + } + + return array($this->type => $info); + } + + /** + * A options list callback returning all bundles for an entity type. + */ + public static function bundleOptionsList($name, $info) { + if (!empty($info['parent']) && $type = $info['parent']) { + $entity_info = $info['parent']->entityInfo(); + $options = array(); + foreach ($entity_info['bundles'] as $name => $bundle_info) { + $options[$name] = $bundle_info['label']; + } + return $options; + } + } + + /** + * Return a set of properties for an entity based on the schema definition + */ + protected function convertSchema() { + return entity_metadata_convert_schema($this->info['base table']); + } +} + +/** + * Converts the schema information available for the given table to property info. + * + * @param $table + * The name of the table as used in hook_schema(). + * @return + * An array of property info as suiting for hook_entity_property_info(). + */ +function entity_metadata_convert_schema($table) { + $schema = drupal_get_schema($table); + $properties = array(); + foreach ($schema['fields'] as $name => $info) { + if ($type = _entity_metadata_convert_schema_type($info['type'])) { + $properties[$name] = array( + 'type' => $type, + 'label' => drupal_ucfirst($name), + 'schema field' => $name, + // As we cannot know about any setter access, leave out the setter + // callback. For getting usually no further access callback is needed. + ); + if ($info['type'] == 'serial') { + $properties[$name]['validation callback'] = 'entity_metadata_validate_integer_positive'; + } + } + } + return $properties; +} + +function _entity_metadata_convert_schema_type($type) { + switch ($type) { + case 'int': + case 'serial': + return 'integer'; + case 'float': + case 'numeric': + return 'decimal'; + case 'char': + case 'varchar': + case 'text': + return 'text'; + } +} + +/** + * Interface for extra fields controller. + * + * Note: Displays extra fields exposed by this controller are rendered by + * default by the EntityAPIController. + */ +interface EntityExtraFieldsControllerInterface { + + /** + * Returns extra fields for this entity type. + * + * @see hook_field_extra_fields(). + */ + public function fieldExtraFields(); +} + +/** + * Default controller for generating extra fields based on property metadata. + * + * By default a display extra field for each property not being a field, ID or + * bundle is generated. + */ +class EntityDefaultExtraFieldsController implements EntityExtraFieldsControllerInterface { + + /** + * @var string + */ + protected $entityType; + + /** + * @var array + */ + protected $entityInfo; + + /** + * Constructor. + */ + public function __construct($type) { + $this->entityType = $type; + $this->entityInfo = entity_get_info($type); + $this->propertyInfo = entity_get_property_info($type); + } + + /** + * Implements EntityExtraFieldsControllerInterface::fieldExtraFields(). + */ + public function fieldExtraFields() { + $extra = array(); + foreach ($this->propertyInfo['properties'] as $name => $property_info) { + // Skip adding the ID or bundle. + if ($this->entityInfo['entity keys']['id'] == $name || $this->entityInfo['entity keys']['bundle'] == $name) { + continue; + } + $extra[$this->entityType][$this->entityType]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info); + } + + // Handle bundle properties. + $this->propertyInfo += array('bundles' => array()); + foreach ($this->propertyInfo['bundles'] as $bundle_name => $info) { + foreach ($info['properties'] as $name => $property_info) { + $extra[$this->entityType][$bundle_name]['display'][$name] = $this->generateExtraFieldInfo($name, $property_info); + } + } + return $extra; + } + + /** + * Generates the display field info for a given property. + */ + protected function generateExtraFieldInfo($name, $property_info) { + $info = array( + 'label' => $property_info['label'], + 'weight' => 0, + ); + if (!empty($property_info['description'])) { + $info['description'] = $property_info['description']; + } + return $info; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,28 @@ + 'view callback', + 'create' => 'creation callback', + 'delete' => 'deletion callback', + 'revision delete' => 'revision deletion callback', + 'save' => 'save callback', + 'access' => 'access callback', + 'form' => 'form callback' + ); + if (isset($info[$keys[$op]])) { + return TRUE; + } + if ($op == 'revision delete') { + return in_array('EntityAPIControllerInterface', class_implements($info['controller class'])); + } + if ($op == 'form') { + return (bool) entity_ui_controller($entity_type); + } + if ($op != 'access') { + return in_array('EntityAPIControllerInterface', class_implements($info['controller class'])); + } + return FALSE; +} + +/** + * Menu loader function: load an entity from its path. + * + * This can be used to load entities of all types in menu paths: + * + * @code + * $items['myentity/%entity_object'] = array( + * 'load arguments' => array('myentity'), + * 'title' => ..., + * 'page callback' => ..., + * 'page arguments' => array(...), + * 'access arguments' => array(...), + * ); + * @endcode + * + * @param $entity_id + * The ID of the entity to load, passed by the menu URL. + * @param $entity_type + * The type of the entity to load. + * @return + * A fully loaded entity object, or FALSE in case of error. + */ +function entity_object_load($entity_id, $entity_type) { + $entities = entity_load($entity_type, array($entity_id)); + return reset($entities); +} + +/** + * Page callback to show links to add an entity of a specific bundle. + * + * Entity modules that provide a further description to their bundles may wish + * to implement their own version of this to show these. + * + * @param $entity_type + * The type of the entity. + */ +function entity_ui_bundle_add_page($entity_type) { + // Set the title, as we're a MENU_LOCAL_ACTION and hence just get tab titles. + module_load_include('inc', 'entity', 'includes/entity.ui'); + drupal_set_title(entity_ui_get_action_title('add', $entity_type)); + + // Get entity info for our bundles. + $info = entity_get_info($entity_type); + $items = array(); + foreach ($info['bundles'] as $bundle_name => $bundle_info) { + // Create an empty entity with just the bundle set to check for access. + $dummy_entity = entity_create($entity_type, array( + 'bundle' => $bundle_name, + )); + // If modules use a uid, they can default to the current-user + // in their create() method on the storage controller. + if (entity_access('create', $entity_type, $dummy_entity, $account = NULL)) { + $add_path = $info['admin ui']['path'] . '/add/' . $bundle_name; + $items[] = l(t('Add @label', array('@label' => $bundle_info['label'])), $add_path); + } + } + return theme('item_list', array('items' => $items)); +} + +/** + * Page callback to add an entity of a specific bundle. + * + * @param $entity_type + * The type of the entity. + * @param $bundle_name + * The bundle machine name. + */ +function entity_ui_get_bundle_add_form($entity_type, $bundle_name) { + $info = entity_get_info($entity_type); + $bundle_key = $info['entity keys']['bundle']; + + // Make a stub entity of the right bundle to pass to the entity_ui_get_form(). + $values = array( + $bundle_key => $bundle_name, + ); + $entity = entity_create($entity_type, $values); + + return entity_ui_get_form($entity_type, $entity, 'add'); +} + +/** + * A wrapper around entity_load() to load a single entity by name or numeric id. + * + * @todo: Re-name entity_load() to entity_load_multiple() in d8 core and this + * to entity_load(). + * + * @param $entity_type + * The entity type to load, e.g. node or user. + * @param $id + * The entity id, either the numeric id or the entity name. In case the entity + * type has specified a name key, both the numeric id and the name may be + * passed. + * + * @return + * The entity object, or FALSE. + * + * @see entity_load() + */ +function entity_load_single($entity_type, $id) { + $entities = entity_load($entity_type, array($id)); + return reset($entities); +} + +/** + * A wrapper around entity_load() to return entities keyed by name key if existing. + * + * @param $entity_type + * The entity type to load, e.g. node or user. + * @param $names + * An array of entity names or ids, or FALSE to load all entities. + * @param $conditions + * (deprecated) An associative array of conditions on the base table, where + * the keys are the database fields and the values are the values those + * fields must have. Instead, it is preferable to use EntityFieldQuery to + * retrieve a list of entity IDs loadable by this function. + * + * @return + * An array of entity objects indexed by their names (or ids if the entity + * type has no name key). + * + * @see entity_load() + */ +function entity_load_multiple_by_name($entity_type, $names = FALSE, $conditions = array()) { + $entities = entity_load($entity_type, $names, $conditions); + $info = entity_get_info($entity_type); + if (!isset($info['entity keys']['name'])) { + return $entities; + } + return entity_key_array_by_property($entities, $info['entity keys']['name']); +} + +/** + * Permanently save an entity. + * + * In case of failures, an exception is thrown. + * + * @param $entity_type + * The type of the entity. + * @param $entity + * The entity to save. + * + * @return + * For entity types provided by the CRUD API, SAVED_NEW or SAVED_UPDATED is + * returned depending on the operation performed. If there is no information + * how to save the entity, FALSE is returned. + * + * @see entity_type_supports() + */ +function entity_save($entity_type, $entity) { + $info = entity_get_info($entity_type); + if (method_exists($entity, 'save')) { + return $entity->save(); + } + elseif (isset($info['save callback'])) { + $info['save callback']($entity); + } + elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + return entity_get_controller($entity_type)->save($entity); + } + else { + return FALSE; + } +} + +/** + * Permanently delete the given entity. + * + * In case of failures, an exception is thrown. + * + * @param $entity_type + * The type of the entity. + * @param $id + * The uniform identifier of the entity to delete. + * + * @return + * FALSE, if there were no information how to delete the entity. + * + * @see entity_type_supports() + */ +function entity_delete($entity_type, $id) { + return entity_delete_multiple($entity_type, array($id)); +} + +/** + * Permanently delete multiple entities. + * + * @param $entity_type + * The type of the entity. + * @param $ids + * An array of entity ids of the entities to delete. In case the entity makes + * use of a name key, both the names or numeric ids may be passed. + * @return + * FALSE if the given entity type isn't compatible to the CRUD API. + */ +function entity_delete_multiple($entity_type, $ids) { + $info = entity_get_info($entity_type); + if (isset($info['deletion callback'])) { + foreach ($ids as $id) { + $info['deletion callback']($id); + } + } + elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + entity_get_controller($entity_type)->delete($ids); + } + else { + return FALSE; + } +} + +/** + * Loads an entity revision. + * + * @param $entity_type + * The type of the entity. + * @param $revision_id + * The id of the revision to load. + * + * @return + * The entity object, or FALSE if there is no entity with the given revision + * id. + */ +function entity_revision_load($entity_type, $revision_id) { + $info = entity_get_info($entity_type); + if (!empty($info['entity keys']['revision'])) { + $entity_revisions = entity_load($entity_type, FALSE, array($info['entity keys']['revision'] => $revision_id)); + return reset($entity_revisions); + } + return FALSE; +} + +/** + * Deletes an entity revision. + * + * @param $entity_type + * The type of the entity. + * @param $revision_id + * The revision ID to delete. + * + * @return + * TRUE if the entity revision could be deleted, FALSE otherwise. + */ +function entity_revision_delete($entity_type, $revision_id) { + $info = entity_get_info($entity_type); + if (isset($info['revision deletion callback'])) { + return $info['revision deletion callback']($revision_id, $entity_type); + } + elseif (in_array('EntityAPIControllerRevisionableInterface', class_implements($info['controller class']))) { + return entity_get_controller($entity_type)->deleteRevision($revision_id); + } + return FALSE; +} + +/** + * Checks whether the given entity is the default revision. + * + * Note that newly created entities will always be created in default revision, + * thus TRUE is returned for not yet saved entities. + * + * @param $entity_type + * The type of the entity. + * @param $entity + * The entity object to check. + * + * @return boolean + * A boolean indicating whether the entity is in default revision is returned. + * If the entity is not revisionable or is new, TRUE is returned. + * + * @see entity_revision_set_default() + */ +function entity_revision_is_default($entity_type, $entity) { + $info = entity_get_info($entity_type); + if (empty($info['entity keys']['revision'])) { + return TRUE; + } + // Newly created entities will always be created in default revision. + if (!empty($entity->is_new) || empty($entity->{$info['entity keys']['id']})) { + return TRUE; + } + if (in_array('EntityAPIControllerRevisionableInterface', class_implements($info['controller class']))) { + $key = !empty($info['entity keys']['default revision']) ? $info['entity keys']['default revision'] : 'default_revision'; + return !empty($entity->$key); + } + else { + // Else, just load the default entity and compare the ID. Usually, the + // entity should be already statically cached anyway. + $default = entity_load_single($entity_type, $entity->{$info['entity keys']['id']}); + return $default->{$info['entity keys']['revision']} == $entity->{$info['entity keys']['revision']}; + } +} + +/** + * Sets a given entity revision as default revision. + * + * Note that the default revision flag will only be supported by entity types + * based upon the EntityAPIController, i.e. implementing the + * EntityAPIControllerRevisionableInterface. + * + * @param $entity_type + * The type of the entity. + * @param $entity + * The entity revision to update. + * + * @see entity_revision_is_default() + */ +function entity_revision_set_default($entity_type, $entity) { + $info = entity_get_info($entity_type); + if (!empty($info['entity keys']['revision'])) { + $key = !empty($info['entity keys']['default revision']) ? $info['entity keys']['default revision'] : 'default_revision'; + $entity->$key = TRUE; + } +} + +/** + * Create a new entity object. + * + * @param $entity_type + * The type of the entity. + * @param $values + * An array of values to set, keyed by property name. If the entity type has + * bundles the bundle key has to be specified. + * @return + * A new instance of the entity type or FALSE if there is no information for + * the given entity type. + * + * @see entity_type_supports() + */ +function entity_create($entity_type, array $values) { + $info = entity_get_info($entity_type); + if (isset($info['creation callback'])) { + return $info['creation callback']($values, $entity_type); + } + elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + return entity_get_controller($entity_type)->create($values); + } + return FALSE; +} + +/** + * Exports an entity. + * + * Note: Currently, this only works for entity types provided with the entity + * CRUD API. + * + * @param $entity_type + * The type of the entity. + * @param $entity + * The entity to export. + * @param $prefix + * An optional prefix for each line. + * @return + * The exported entity as serialized string. The format is determined by the + * respective entity controller, e.g. it is JSON for the EntityAPIController. + * The output is suitable for entity_import(). + */ +function entity_export($entity_type, $entity, $prefix = '') { + if (method_exists($entity, 'export')) { + return $entity->export($prefix); + } + $info = entity_get_info($entity_type); + if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + return entity_get_controller($entity_type)->export($entity, $prefix); + } +} + +/** + * Imports an entity. + * + * Note: Currently, this only works for entity types provided with the entity + * CRUD API. + * + * @param $entity_type + * The type of the entity. + * @param string $export + * The string containing the serialized entity as produced by + * entity_export(). + * @return + * The imported entity object not yet saved. + */ +function entity_import($entity_type, $export) { + $info = entity_get_info($entity_type); + if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + return entity_get_controller($entity_type)->import($export); + } +} + +/** + * Checks whether an entity type is fieldable. + * + * @param $entity_type + * The type of the entity. + * + * @return + * TRUE if the entity type is fieldable, FALSE otherwise. + */ +function entity_type_is_fieldable($entity_type) { + $info = entity_get_info($entity_type); + return !empty($info['fieldable']); +} + +/** + * Builds a structured array representing the entity's content. + * + * The content built for the entity will vary depending on the $view_mode + * parameter. + * + * Note: Currently, this only works for entity types provided with the entity + * CRUD API. + * + * @param $entity_type + * The type of the entity. + * @param $entity + * An entity object. + * @param $view_mode + * A view mode as used by this entity type, e.g. 'full', 'teaser'... + * @param $langcode + * (optional) A language code to use for rendering. Defaults to the global + * content language of the current request. + * @return + * The renderable array. + */ +function entity_build_content($entity_type, $entity, $view_mode = 'full', $langcode = NULL) { + $info = entity_get_info($entity_type); + if (method_exists($entity, 'buildContent')) { + return $entity->buildContent($view_mode, $langcode); + } + elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + return entity_get_controller($entity_type)->buildContent($entity, $view_mode, $langcode); + } +} + +/** + * Returns the entity identifier, i.e. the entities name or numeric id. + * + * Unlike entity_extract_ids() this function returns the name of the entity + * instead of the numeric id, in case the entity type has specified a name key. + * + * @param $entity_type + * The type of the entity. + * @param $entity + * An entity object. + * + * @see entity_extract_ids() + */ +function entity_id($entity_type, $entity) { + if (method_exists($entity, 'identifier')) { + return $entity->identifier(); + } + $info = entity_get_info($entity_type); + $key = isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id']; + return isset($entity->$key) ? $entity->$key : NULL; +} + +/** + * Generate an array for rendering the given entities. + * + * Entities being viewed, are generally expected to be fully-loaded entity + * objects, thus have their name or id key set. However, it is possible to + * view a single entity without any id, e.g. for generating a preview during + * creation. + * + * @param $entity_type + * The type of the entity. + * @param $entities + * An array of entities to render. + * @param $view_mode + * A view mode as used by this entity type, e.g. 'full', 'teaser'... + * @param $langcode + * (optional) A language code to use for rendering. Defaults to the global + * content language of the current request. + * @param $page + * (optional) If set will control if the entity is rendered: if TRUE + * the entity will be rendered without its title, so that it can be embeded + * in another context. If FALSE the entity will be displayed with its title + * in a mode suitable for lists. + * If unset, the page mode will be enabled if the current path is the URI + * of the entity, as returned by entity_uri(). + * This parameter is only supported for entities which controller is a + * EntityAPIControllerInterface. + * @return + * The renderable array, keyed by the entity type and by entity identifiers, + * for which the entity name is used if existing - see entity_id(). If there + * is no information on how to view an entity, FALSE is returned. + */ +function entity_view($entity_type, $entities, $view_mode = 'full', $langcode = NULL, $page = NULL) { + $info = entity_get_info($entity_type); + if (isset($info['view callback'])) { + $entities = entity_key_array_by_property($entities, $info['entity keys']['id']); + return $info['view callback']($entities, $view_mode, $langcode, $entity_type); + } + elseif (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + return entity_get_controller($entity_type)->view($entities, $view_mode, $langcode, $page); + } + return FALSE; +} + +/** + * Determines whether the given user has access to an entity. + * + * @param $op + * The operation being performed. One of 'view', 'update', 'create' or + * 'delete'. + * @param $entity_type + * The entity type of the entity to check for. + * @param $entity + * Optionally an entity to check access for. If no entity is given, it will be + * determined whether access is allowed for all entities of the given type. + * @param $account + * The user to check for. Leave it to NULL to check for the global user. + * + * @return boolean + * Whether access is allowed or not. If the entity type does not specify any + * access information, NULL is returned. + * + * @see entity_type_supports() + */ +function entity_access($op, $entity_type, $entity = NULL, $account = NULL) { + if (($info = entity_get_info()) && isset($info[$entity_type]['access callback'])) { + return $info[$entity_type]['access callback']($op, $entity, $account, $entity_type); + } +} + +/** + * Gets the edit form for any entity. + * + * This helper makes use of drupal_get_form() and the regular form builder + * function of the entity type to retrieve and process the form as usual. + * + * In order to use this helper to show an entity add form, the new entity object + * can be created via entity_create() or entity_property_values_create_entity(). + * + * @param $entity_type + * The type of the entity. + * @param $entity + * The entity to show the edit form for. + * @return + * The renderable array of the form. If there is no entity form or missing + * metadata, FALSE is returned. + * + * @see entity_type_supports() + */ +function entity_form($entity_type, $entity) { + $info = entity_get_info($entity_type); + if (isset($info['form callback'])) { + return $info['form callback']($entity, $entity_type); + } + // If there is an UI controller, the providing module has to implement the + // entity form using entity_ui_get_form(). + elseif (entity_ui_controller($entity_type)) { + return entity_metadata_form_entity_ui($entity, $entity_type); + } + return FALSE; +} + +/** + * Converts an array of entities to be keyed by the values of a given property. + * + * @param array $entities + * The array of entities to convert. + * @param $property + * The name of entity property, by which the array should be keyed. To get + * reasonable results, the property has to have unique values. + * + * @return array + * The same entities in the same order, but keyed by their $property values. + */ +function entity_key_array_by_property(array $entities, $property) { + $ret = array(); + foreach ($entities as $entity) { + $key = isset($entity->$property) ? $entity->$property : NULL; + $ret[$key] = $entity; + } + return $ret; +} + +/** + * Get the entity info for the entity types provided via the entity CRUD API. + * + * @return + * An array in the same format as entity_get_info(), containing the entities + * whose controller class implements the EntityAPIControllerInterface. + */ +function entity_crud_get_info() { + $types = array(); + foreach (entity_get_info() as $type => $info) { + if (isset($info['controller class']) && in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + $types[$type] = $info; + } + } + return $types; +} + +/** + * Checks if a given entity has a certain exportable status. + * + * @param $entity_type + * The type of the entity. + * @param $entity + * The entity to check the status on. + * @param $status + * The constant status like ENTITY_CUSTOM, ENTITY_IN_CODE, ENTITY_OVERRIDDEN + * or ENTITY_FIXED. + * + * @return + * TRUE if the entity has the status, FALSE otherwise. + */ +function entity_has_status($entity_type, $entity, $status) { + $info = entity_get_info($entity_type); + $status_key = empty($info['entity keys']['status']) ? 'status' : $info['entity keys']['status']; + return isset($entity->{$status_key}) && ($entity->{$status_key} & $status) == $status; +} + +/** + * Export a variable. Copied from ctools. + * + * This is a replacement for var_export(), allowing us to more nicely + * format exports. It will recurse down into arrays and will try to + * properly export bools when it can. + */ +function entity_var_export($var, $prefix = '') { + if (is_array($var)) { + if (empty($var)) { + $output = 'array()'; + } + else { + $output = "array(\n"; + foreach ($var as $key => $value) { + $output .= " '$key' => " . entity_var_export($value, ' ') . ",\n"; + } + $output .= ')'; + } + } + elseif (is_bool($var)) { + $output = $var ? 'TRUE' : 'FALSE'; + } + else { + $output = var_export($var, TRUE); + } + + if ($prefix) { + $output = str_replace("\n", "\n$prefix", $output); + } + return $output; +} + +/** + * Export a variable in pretty formatted JSON. + */ +function entity_var_json_export($var, $prefix = '') { + if (is_array($var) && $var) { + // Defines whether we use a JSON array or object. + $use_array = ($var == array_values($var)); + $output = $use_array ? "[" : "{"; + + foreach ($var as $key => $value) { + if ($use_array) { + $values[] = entity_var_json_export($value, ' '); + } + else { + $values[] = entity_var_json_export((string) $key, ' ') . ' : ' . entity_var_json_export($value, ' '); + } + } + // Use several lines for long content. However for objects with a single + // entry keep the key in the first line. + if (strlen($content = implode(', ', $values)) > 70 && ($use_array || count($values) > 1)) { + $output .= "\n " . implode(",\n ", $values) . "\n"; + } + elseif (strpos($content, "\n") !== FALSE) { + $output .= " " . $content . "\n"; + } + else { + $output .= " " . $content . ' '; + } + $output .= $use_array ? ']' : '}'; + } + else { + $output = drupal_json_encode($var); + } + + if ($prefix) { + $output = str_replace("\n", "\n$prefix", $output); + } + return $output; +} + +/** + * Rebuild the default entities provided in code. + * + * Exportable entities provided in code get saved to the database once a module + * providing defaults in code is activated. This allows module and entity_load() + * to easily deal with exportable entities just by relying on the database. + * + * The defaults get rebuilt if the cache is cleared or new modules providing + * defaults are enabled, such that the defaults in the database are up to date. + * A default entity gets updated with the latest defaults in code during rebuild + * as long as the default has not been overridden. Once a module providing + * defaults is disabled, its default entities get removed from the database + * unless they have been overridden. In that case the overridden entity is left + * in the database, but its status gets updated to 'custom'. + * + * @param $entity_types + * (optional) If specified, only the defaults of the given entity types are + * rebuilt. + */ +function entity_defaults_rebuild($entity_types = NULL) { + if (!isset($entity_types)) { + $entity_types = array(); + foreach (entity_crud_get_info() as $type => $info) { + if (!empty($info['exportable'])) { + $entity_types[] = $type; + } + }; + } + foreach ($entity_types as $type) { + _entity_defaults_rebuild($type); + } +} + +/** + * Actually rebuild the defaults of a given entity type. + */ +function _entity_defaults_rebuild($entity_type) { + if (lock_acquire('entity_rebuild_' . $entity_type)) { + $info = entity_get_info($entity_type); + $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type; + $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']); + + // Check for the existence of the module and status columns. + if (!in_array($keys['status'], $info['schema_fields_sql']['base table']) || !in_array($keys['module'], $info['schema_fields_sql']['base table'])) { + trigger_error("Missing database columns for the exportable entity $entity_type as defined by entity_exportable_schema_fields(). Update the according module and run update.php!", E_USER_WARNING); + return; + } + + // Invoke the hook and collect default entities. + $entities = array(); + foreach (module_implements($hook) as $module) { + foreach ((array) module_invoke($module, $hook) as $name => $entity) { + $entity->{$keys['name']} = $name; + $entity->{$keys['module']} = $module; + $entities[$name] = $entity; + } + } + drupal_alter($hook, $entities); + + // Check for defaults that disappeared. + $existing_defaults = entity_load_multiple_by_name($entity_type, FALSE, array($keys['status'] => array(ENTITY_OVERRIDDEN, ENTITY_IN_CODE, ENTITY_FIXED))); + + foreach ($existing_defaults as $name => $entity) { + if (empty($entities[$name])) { + $entity->is_rebuild = TRUE; + if (entity_has_status($entity_type, $entity, ENTITY_OVERRIDDEN)) { + $entity->{$keys['status']} = ENTITY_CUSTOM; + entity_save($entity_type, $entity); + } + else { + entity_delete($entity_type, $name); + } + unset($entity->is_rebuild); + } + } + + // Load all existing entities. + $existing_entities = entity_load_multiple_by_name($entity_type, array_keys($entities)); + + foreach ($existing_entities as $name => $entity) { + if (entity_has_status($entity_type, $entity, ENTITY_CUSTOM)) { + // If the entity already exists but is not yet marked as overridden, we + // have to update the status. + if (!entity_has_status($entity_type, $entity, ENTITY_OVERRIDDEN)) { + $entity->{$keys['status']} |= ENTITY_OVERRIDDEN; + $entity->{$keys['module']} = $entities[$name]->{$keys['module']}; + $entity->is_rebuild = TRUE; + entity_save($entity_type, $entity); + unset($entity->is_rebuild); + } + + // The entity is overridden, so we do not need to save the default. + unset($entities[$name]); + } + } + + // Save defaults. + $originals = array(); + foreach ($entities as $name => $entity) { + if (!empty($existing_entities[$name])) { + // Make sure we are updating the existing default. + $entity->{$keys['id']} = $existing_entities[$name]->{$keys['id']}; + unset($entity->is_new); + } + // Pre-populate $entity->original as we already have it. So we avoid + // loading it again. + $entity->original = !empty($existing_entities[$name]) ? $existing_entities[$name] : FALSE; + // Keep original entities for hook_{entity_type}_defaults_rebuild() + // implementations. + $originals[$name] = $entity->original; + + $entity->{$keys['status']} |= ENTITY_IN_CODE; + $entity->is_rebuild = TRUE; + entity_save($entity_type, $entity); + unset($entity->is_rebuild); + } + + // Invoke an entity type-specific hook so modules may apply changes, e.g. + // efficiently rebuild caches. + module_invoke_all($entity_type . '_defaults_rebuild', $entities, $originals); + + lock_release('entity_rebuild_' . $entity_type); + } +} + +/** + * Implements hook_modules_enabled(). + */ +function entity_modules_enabled($modules) { + foreach (_entity_modules_get_default_types($modules) as $type) { + _entity_defaults_rebuild($type); + } +} + +/** + * Implements hook_modules_disabled(). + */ +function entity_modules_disabled($modules) { + foreach (_entity_modules_get_default_types($modules) as $entity_type) { + $info = entity_get_info($entity_type); + + // Do nothing if the module providing the entity type has been disabled too. + if (isset($info['module']) && in_array($info['module'], $modules)) { + return; + } + + $keys = $info['entity keys'] + array('module' => 'module', 'status' => 'status', 'name' => $info['entity keys']['id']); + // Remove entities provided in code by one of the disabled modules. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $entity_type, '=') + ->propertyCondition($keys['module'], $modules, 'IN') + ->propertyCondition($keys['status'], array(ENTITY_IN_CODE, ENTITY_FIXED), 'IN'); + $result = $query->execute(); + if (isset($result[$entity_type])) { + $entities = entity_load($entity_type, array_keys($result[$entity_type])); + entity_delete_multiple($entity_type, array_keys($entities)); + } + + // Update overridden entities to be now custom. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $entity_type, '=') + ->propertyCondition($keys['module'], $modules, 'IN') + ->propertyCondition($keys['status'], ENTITY_OVERRIDDEN, '='); + $result = $query->execute(); + if (isset($result[$entity_type])) { + foreach (entity_load($entity_type, array_keys($result[$entity_type])) as $name => $entity) { + $entity->{$keys['status']} = ENTITY_CUSTOM; + $entity->{$keys['module']} = NULL; + entity_save($entity_type, $entity); + } + } + + // Rebuild the remaining defaults so any alterations of the disabled modules + // are gone. + _entity_defaults_rebuild($entity_type); + } +} + +/** + * Gets all entity types for which defaults are provided by the $modules. + */ +function _entity_modules_get_default_types($modules) { + $types = array(); + foreach (entity_crud_get_info() as $entity_type => $info) { + if (!empty($info['exportable'])) { + $hook = isset($info['export']['default hook']) ? $info['export']['default hook'] : 'default_' . $entity_type; + foreach ($modules as $module) { + if (module_hook($module, $hook) || module_hook($module, $hook . '_alter')) { + $types[] = $entity_type; + } + } + } + } + return $types; +} + +/** + * Defines schema fields required for exportable entities. + * + * Warning: Do not call this function in your module's hook_schema() + * implementation or update functions. It is not safe to call functions of + * dependencies at this point. Instead of calling the function, just copy over + * the content. + * For more details see the issue http://drupal.org/node/1122812. + */ +function entity_exportable_schema_fields($module_col = 'module', $status_col = 'status') { + return array( + $status_col => array( + 'type' => 'int', + 'not null' => TRUE, + // Set the default to ENTITY_CUSTOM without using the constant as it is + // not safe to use it at this point. + 'default' => 0x01, + 'size' => 'tiny', + 'description' => 'The exportable status of the entity.', + ), + $module_col => array( + 'description' => 'The name of the providing module if the entity has been defined in code.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ); +} + +/** + * Implements hook_flush_caches(). + */ +function entity_flush_caches() { + entity_property_info_cache_clear(); + // Re-build defaults in code, however skip it on the admin modules page. In + // case of enabling or disabling modules we already rebuild defaults in + // entity_modules_enabled() and entity_modules_disabled(), so we do not need + // to do it again. + if (current_path() != 'admin/modules/list/confirm') { + entity_defaults_rebuild(); + } +} + +/** + * Implements hook_theme(). + */ +function entity_theme() { + // Build a pattern in the form of "(type1|type2|...)(\.|__)" such that all + // templates starting with an entity type or named like the entity type + // are found. + // This has to match the template suggestions provided in + // template_preprocess_entity(). + $types = array_keys(entity_crud_get_info()); + $pattern = '(' . implode('|', $types) . ')(\.|__)'; + + return array( + 'entity_status' => array( + 'variables' => array('status' => NULL, 'html' => TRUE), + 'file' => 'theme/entity.theme.inc', + ), + 'entity' => array( + 'render element' => 'elements', + 'template' => 'entity', + 'pattern' => $pattern, + 'path' => drupal_get_path('module', 'entity') . '/theme', + 'file' => 'entity.theme.inc', + ), + 'entity_property' => array( + 'render element' => 'elements', + 'file' => 'theme/entity.theme.inc', + ), + 'entity_ui_overview_item' => array( + 'variables' => array('label' => NULL, 'entity_type' => NULL, 'url' => FALSE, 'name' => FALSE), + 'file' => 'includes/entity.ui.inc' + ), + ); +} + +/** + * Label callback that refers to the entity classes label method. + */ +function entity_class_label($entity) { + return $entity->label(); +} + +/** + * URI callback that refers to the entity classes uri method. + */ +function entity_class_uri($entity) { + return $entity->uri(); +} + +/** + * Implements hook_file_download_access() for entity types provided by the CRUD API. + */ +function entity_file_download_access($field, $entity_type, $entity) { + $info = entity_get_info($entity_type); + if (in_array('EntityAPIControllerInterface', class_implements($info['controller class']))) { + return entity_access('view', $entity_type, $entity); + } +} + +/** + * Determines the UI controller class for a given entity type. + * + * @return EntityDefaultUIController + * If a type is given, the controller for the given entity type. Else an array + * of all enabled UI controllers keyed by entity type is returned. + */ +function entity_ui_controller($type = NULL) { + $static = &drupal_static(__FUNCTION__); + + if (!isset($type)) { + // Invoke the function for each type to ensure we have fully populated the + // static variable. + foreach (entity_get_info() as $entity_type => $info) { + entity_ui_controller($entity_type); + } + return array_filter($static); + } + + if (!isset($static[$type])) { + $info = entity_get_info($type); + $class = isset($info['admin ui']['controller class']) ? $info['admin ui']['controller class'] : 'EntityDefaultUIController'; + $static[$type] = (isset($info['admin ui']['path']) && $class) ? new $class($type, $info) : FALSE; + } + + return $static[$type]; +} + +/** + * Implements hook_menu(). + * + * @see EntityDefaultUIController::hook_menu() + */ +function entity_menu() { + $items = array(); + foreach (entity_ui_controller() as $controller) { + $items += $controller->hook_menu(); + } + return $items; +} + +/** + * Implements hook_forms(). + * + * @see EntityDefaultUIController::hook_forms() + * @see entity_ui_get_form() + */ +function entity_forms($form_id, $args) { + // For efficiency only invoke an entity types controller, if a form of it is + // requested. Thus if the first (overview and operation form) or the third + // argument (edit form) is an entity type name, add in the types forms. + if (isset($args[0]) && is_string($args[0]) && entity_get_info($args[0])) { + $type = $args[0]; + } + elseif (isset($args[2]) && is_string($args[2]) && entity_get_info($args[2])) { + $type = $args[2]; + } + if (isset($type) && $controller = entity_ui_controller($type)) { + return $controller->hook_forms(); + } +} + +/** + * A wrapper around drupal_get_form() that helps building entity forms. + * + * This function may be used by entities to build their entity form. It has to + * be used instead of calling drupal_get_form(). + * Entity forms built with this helper receive useful defaults suiting for + * editing a single entity, whereas the special cases of adding and cloning + * of entities are supported too. + * + * While this function is intended to be used to get entity forms for entities + * using the entity ui controller, it may be used for entity types not using + * the ui controller too. + * + * @param $entity_type + * The entity type for which to get the form. + * @param $entity + * The entity for which to return the form. + * If $op is 'add' the entity has to be either initialized before calling this + * function, or NULL may be passed. If NULL is passed, an entity will be + * initialized with empty values using entity_create(). Thus entities, for + * which this is problematic have to care to pass in an initialized entity. + * @param $op + * (optional) One of 'edit', 'add' or 'clone'. Defaults to edit. + * @param $form_state + * (optional) A pre-populated form state, e.g. to add in form include files. + * See entity_metadata_form_entity_ui(). + * + * @return + * The fully built and processed form, ready to be rendered. + * + * @see EntityDefaultUIController::hook_forms() + * @see entity_ui_form_submit_build_entity() + */ +function entity_ui_get_form($entity_type, $entity, $op = 'edit', $form_state = array()) { + if (isset($entity)) { + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + } + $form_id = (!isset($bundle) || $bundle == $entity_type) ? $entity_type . '_form' : $entity_type . '_edit_' . $bundle . '_form'; + + if (!isset($entity) && $op == 'add') { + $entity = entity_create($entity_type, array()); + } + + // Do not use drupal_get_form(), but invoke drupal_build_form() ourself so + // we can prepulate the form state. + $form_state['wrapper_callback'] = 'entity_ui_main_form_defaults'; + $form_state['entity_type'] = $entity_type; + form_load_include($form_state, 'inc', 'entity', 'includes/entity.ui'); + + // Handle cloning. We cannot do that in the wrapper callback as it is too late + // for changing arguments. + if ($op == 'clone') { + $entity = entity_ui_clone_entity($entity_type, $entity); + } + + // We don't pass the entity type as first parameter, as the implementing + // module knows the type anyway. However, in order to allow for efficient + // hook_forms() implementiations we append the entity type as last argument, + // which the module implementing the form constructor may safely ignore. + // @see entity_forms() + $form_state['build_info']['args'] = array($entity, $op, $entity_type); + return drupal_build_form($form_id, $form_state); +} + +/** + * Helper for using i18n_string(). + * + * @param $name + * Textgroup and context glued with ':'. + * @param $default + * String in default language. Default language may or may not be English. + * @param $langcode + * (optional) The code of a certain language to translate the string into. + * Defaults to the i18n_string() default, i.e. the current language. + * + * @see i18n_string() + */ +function entity_i18n_string($name, $default, $langcode = NULL) { + return function_exists('i18n_string') ? i18n_string($name, $default, array('langcode' => $langcode)) : $default; +} + +/** + * Implements hook_views_api(). + */ +function entity_views_api() { + return array( + 'api' => '3.0-alpha1', + 'path' => drupal_get_path('module', 'entity') . '/views', + ); +} + +/** + * Implements hook_field_extra_fields(). + */ +function entity_field_extra_fields() { + // Invoke specified controllers for entity types provided by the CRUD API. + $items = array(); + foreach (entity_crud_get_info() as $type => $info) { + if (!empty($info['extra fields controller class'])) { + $items = array_merge_recursive($items, entity_get_extra_fields_controller($type)->fieldExtraFields()); + } + } + return $items; +} + +/** + * Gets the extra field controller class for a given entity type. + * + * @return EntityExtraFieldsControllerInterface|false + * The controller for the given entity type or FALSE if none is specified. + */ +function entity_get_extra_fields_controller($type = NULL) { + $static = &drupal_static(__FUNCTION__); + + if (!isset($static[$type])) { + $static[$type] = FALSE; + $info = entity_get_info($type); + if (!empty($info['extra fields controller class'])) { + $static[$type] = new $info['extra fields controller class']($type); + } + } + return $static[$type]; +} + +/** + * Returns a property wrapper for the given data. + * + * If an entity is wrapped, the wrapper can be used to retrieve further wrappers + * for the entitity properties. For that the wrapper support chaining, e.g. you + * can use a node wrapper to get the node authors mail address: + * + * @code + * echo $wrappedNode->author->mail->value(); + * @endcode + * + * @param $type + * The type of the passed data. + * @param $data + * The data to wrap. It may be set to NULL, so the wrapper can be used + * without any data for getting information about properties. + * @param $info + * (optional) Specify additional information for the passed data: + * - langcode: (optional) If the data is language specific, its langauge + * code. Defaults to NULL, what means language neutral. + * - bundle: (optional) If an entity is wrapped but not passed, use this key + * to specify the bundle to return a wrapper for. + * - property info: (optional) May be used to use a wrapper with an arbitrary + * data structure (type 'struct'). Use this key for specifying info about + * properties in the same structure as used by hook_entity_property_info(). + * - property info alter: (optional) A callback for altering the property + * info before it is utilized by the wrapper. + * - property defaults: (optional) An array of defaults for the info of + * each property of the wrapped data item. + * @return EntityMetadataWrapper + * Dependend on the passed data the right wrapper is returned. + */ +function entity_metadata_wrapper($type, $data = NULL, array $info = array()) { + if ($type == 'entity' || (($entity_info = entity_get_info()) && isset($entity_info[$type]))) { + // If the passed entity is the global $user, we load the user object by only + // passing on the user id. The global user is not a fully loaded entity. + if ($type == 'user' && is_object($data) && $data == $GLOBALS['user']) { + $data = $data->uid; + } + return new EntityDrupalWrapper($type, $data, $info); + } + elseif ($type == 'list' || entity_property_list_extract_type($type)) { + return new EntityListWrapper($type, $data, $info); + } + elseif (isset($info['property info'])) { + return new EntityStructureWrapper($type, $data, $info); + } + else { + return new EntityValueWrapper($type, $data, $info); + } +} + +/** + * Returns a metadata wrapper for accessing site-wide properties. + * + * Although there is no 'site' entity or such, modules may provide info about + * site-wide properties using hook_entity_property_info(). This function returns + * a wrapper for making use of this properties. + * + * @return EntityMetadataWrapper + * A wrapper for accessing site-wide properties. + * + * @see entity_metadata_system_entity_property_info() + */ +function entity_metadata_site_wrapper() { + $site_info = entity_get_property_info('site'); + $info['property info'] = $site_info['properties']; + return entity_metadata_wrapper('site', FALSE, $info); +} + +/** + * Implements hook_module_implements_alter(). + * + * Moves the hook_entity_info_alter() implementation to the bottom so it is + * invoked after all modules relying on the entity API. + * That way we ensure to run last and clear the field-info cache after the + * others added in their bundle information. + * + * @see entity_entity_info_alter() + */ +function entity_module_implements_alter(&$implementations, $hook) { + if ($hook == 'entity_info_alter') { + // Move our hook implementation to the bottom. + $group = $implementations['entity']; + unset($implementations['entity']); + $implementations['entity'] = $group; + } +} + +/** + * Implements hook_entity_info_alter(). + * + * @see entity_module_implements_alter() + */ +function entity_entity_info_alter(&$entity_info) { + _entity_info_add_metadata($entity_info); + + // Populate a default value for the 'configuration' key of all entity types. + foreach ($entity_info as $type => $info) { + if (!isset($info['configuration'])) { + $entity_info[$type]['configuration'] = !empty($info['exportable']); + } + } +} + +/** + * Adds metadata and callbacks for core entities to the entity info. + */ +function _entity_info_add_metadata(&$entity_info) { + // Set plural labels. + $entity_info['node']['plural label'] = t('Nodes'); + $entity_info['user']['plural label'] = t('Users'); + $entity_info['file']['plural label'] = t('Files'); + + // Set descriptions. + $entity_info['node']['description'] = t('Nodes represent the main site content items.'); + $entity_info['user']['description'] = t('Users who have created accounts on your site.'); + $entity_info['file']['description'] = t('Uploaded file.'); + + // Set access callbacks. + $entity_info['node']['access callback'] = 'entity_metadata_no_hook_node_access'; + $entity_info['user']['access callback'] = 'entity_metadata_user_access'; + // File entity has it's own entity_access function. + if (!module_exists('file_entity')) { + $entity_info['file']['access callback'] = 'entity_metadata_file_access'; + } + + // CRUD function callbacks. + $entity_info['node']['creation callback'] = 'entity_metadata_create_node'; + $entity_info['node']['save callback'] = 'node_save'; + $entity_info['node']['deletion callback'] = 'node_delete'; + $entity_info['node']['revision deletion callback'] = 'node_revision_delete'; + $entity_info['user']['creation callback'] = 'entity_metadata_create_object'; + $entity_info['user']['save callback'] = 'entity_metadata_user_save'; + $entity_info['user']['deletion callback'] = 'user_delete'; + $entity_info['file']['save callback'] = 'file_save'; + $entity_info['file']['deletion callback'] = 'entity_metadata_delete_file'; + + // Form callbacks. + $entity_info['node']['form callback'] = 'entity_metadata_form_node'; + $entity_info['user']['form callback'] = 'entity_metadata_form_user'; + + // View callbacks. + $entity_info['node']['view callback'] = 'entity_metadata_view_node'; + $entity_info['user']['view callback'] = 'entity_metadata_view_single'; + + if (module_exists('comment')) { + $entity_info['comment']['plural label'] = t('Comments'); + $entity_info['comment']['description'] = t('Remark or note that refers to a node.'); + $entity_info['comment']['access callback'] = 'entity_metadata_comment_access'; + $entity_info['comment']['creation callback'] = 'entity_metadata_create_comment'; + $entity_info['comment']['save callback'] = 'comment_save'; + $entity_info['comment']['deletion callback'] = 'comment_delete'; + $entity_info['comment']['view callback'] = 'entity_metadata_view_comment'; + $entity_info['comment']['form callback'] = 'entity_metadata_form_comment'; + } + if (module_exists('taxonomy')) { + $entity_info['taxonomy_term']['plural label'] = t('Taxonomy terms'); + $entity_info['taxonomy_term']['description'] = t('Taxonomy terms are used for classifying content.'); + $entity_info['taxonomy_term']['access callback'] = 'entity_metadata_taxonomy_access'; + $entity_info['taxonomy_term']['creation callback'] = 'entity_metadata_create_object'; + $entity_info['taxonomy_term']['save callback'] = 'taxonomy_term_save'; + $entity_info['taxonomy_term']['deletion callback'] = 'taxonomy_term_delete'; + $entity_info['taxonomy_term']['view callback'] = 'entity_metadata_view_single'; + $entity_info['taxonomy_term']['form callback'] = 'entity_metadata_form_taxonomy_term'; + + $entity_info['taxonomy_vocabulary']['plural label'] = t('Taxonomy vocabularies'); + $entity_info['taxonomy_vocabulary']['description'] = t('Vocabularies contain related taxonomy terms, which are used for classifying content.'); + $entity_info['taxonomy_vocabulary']['access callback'] = 'entity_metadata_taxonomy_access'; + $entity_info['taxonomy_vocabulary']['creation callback'] = 'entity_metadata_create_object'; + $entity_info['taxonomy_vocabulary']['save callback'] = 'taxonomy_vocabulary_save'; + $entity_info['taxonomy_vocabulary']['deletion callback'] = 'taxonomy_vocabulary_delete'; + $entity_info['taxonomy_vocabulary']['form callback'] = 'entity_metadata_form_taxonomy_vocabulary'; + // Token type mapping. + $entity_info['taxonomy_term']['token type'] = 'term'; + $entity_info['taxonomy_vocabulary']['token type'] = 'vocabulary'; + } +} + +/** + * Implements hook_ctools_plugin_directory(). + */ +function entity_ctools_plugin_directory($module, $plugin) { + if ($module == 'ctools' && $plugin == 'content_types') { + return 'ctools/content_types'; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.rules.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.rules.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,118 @@ +type = $type; + $this->info = entity_get_info($type); + } + + public function eventInfo() { + $info = $this->info; + $type = $this->type; + + $label = $info['label']; + $defaults = array( + 'module' => isset($info['module']) ? $info['module'] : 'entity', + 'group' => $label, + 'access callback' => 'entity_rules_integration_event_access', + ); + + $items[$type . '_insert'] = $defaults + array( + 'label' => t('After saving a new @entity', array('@entity' => drupal_strtolower($label))), + 'variables' => entity_rules_events_variables($type, t('created @entity', array('@entity' => drupal_strtolower($label)))), + ); + $items[$type . '_update'] = $defaults + array( + 'label' => t('After updating an existing @entity', array('@entity' => drupal_strtolower($label))), + 'variables' => entity_rules_events_variables($type, t('updated @entity', array('@entity' => drupal_strtolower($label))), TRUE), + ); + $items[$type . '_presave'] = $defaults + array( + 'label' => t('Before saving a @entity', array('@entity' => drupal_strtolower($label))), + 'variables' => entity_rules_events_variables($type, t('saved @entity', array('@entity' => drupal_strtolower($label))), TRUE), + ); + $items[$type . '_delete'] = $defaults + array( + 'label' => t('After deleting a @entity', array('@entity' => drupal_strtolower($label))), + 'variables' => entity_rules_events_variables($type, t('deleted @entity', array('@entity' => drupal_strtolower($label)))), + ); + if (count($info['view modes'])) { + $items[$type . '_view'] = $defaults + array( + 'label' => t('@entity is viewed', array('@entity' => $label)), + 'variables' => entity_rules_events_variables($type, t('viewed @entity', array('@entity' => drupal_strtolower($label)))) + array( + 'view_mode' => array( + 'type' => 'text', + 'label' => t('view mode'), + 'options list' => 'rules_get_entity_view_modes', + // Add the entity-type for the options list callback. + 'options list entity type' => $type, + ), + ), + ); + } + // Specify that on presave the entity is saved anyway. + $items[$type . '_presave']['variables'][$type]['skip save'] = TRUE; + return $items; + } + +} + +/** + * Returns some parameter info suiting for the specified entity type. + */ +function entity_rules_events_variables($type, $label, $update = FALSE) { + $args = array( + $type => array('type' => $type, 'label' => $label), + ); + if ($update) { + $args += array( + $type . '_unchanged' => array( + 'type' => $type, + 'label' => t('unchanged entity'), + 'handler' => 'rules_events_entity_unchanged', + ), + ); + } + return $args; +} + +/** + * Implements hook_rules_event_info(). + */ +function entity_rules_event_info() { + $items = array(); + foreach (entity_crud_get_info() as $type => $info) { + // By default we enable the controller only for non-configuration. + $configuration = !empty($info['configuration']) || !empty($info['exportable']); + $info += array('rules controller class' => $configuration ? FALSE : 'EntityDefaultRulesController'); + if ($info['rules controller class']) { + $controller = new $info['rules controller class']($type); + $items += $controller->eventInfo(); + } + } + return $items; +} + +/** + * Rules integration access callback. + */ +function entity_rules_integration_event_access($type, $event_name) { + // Cut of _insert/_update/.. from the event name. + $entity_type = substr($event_name, 0, strrpos($event_name, '_')); + + $result = entity_access('view', $entity_type); + // If no access callback is given, just grant access for viewing. + return isset($result) ? $result : TRUE; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1672 @@ + $this->randomName(), + 'machine_name' => drupal_strtolower($this->randomName()), + 'description' => $this->randomName(), + )); + entity_save('taxonomy_vocabulary', $vocab); + return $vocab; + } + + /** + * Creates a random file of the given type. + */ + protected function createFile($file_type = 'text') { + // Create a managed file. + $file = current($this->drupalGetTestFiles($file_type)); + + // Set additional file properties and save it. + $file->filemime = file_get_mimetype($file->filename); + $file->uid = 1; + $file->timestamp = REQUEST_TIME; + $file->filesize = filesize($file->uri); + $file->status = 0; + file_save($file); + return $file; + } +} + +/** + * Test basic API. + */ +class EntityAPITestCase extends EntityWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Entity CRUD', + 'description' => 'Tests basic CRUD API functionality.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp('entity', 'entity_test'); + } + + /** + * Tests CRUD. + */ + function testCRUD() { + module_enable(array('entity_feature')); + + $user1 = $this->drupalCreateUser(); + // Create test entities for the user1 and unrelated to a user. + $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid)); + $entity->save(); + $entity = entity_create('entity_test', array('name' => 'test2', 'uid' => $user1->uid)); + $entity->save(); + $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL)); + $entity->save(); + + $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test'))); + + $this->assertEqual($entities[0]->name, 'test', 'Created and loaded entity.'); + $this->assertEqual($entities[1]->name, 'test', 'Created and loaded entity.'); + + $results = entity_test_load_multiple(array($entity->pid)); + $loaded = array_pop($results); + $this->assertEqual($loaded->pid, $entity->pid, 'Loaded the entity unrelated to a user.'); + + $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2'))); + $entities[0]->delete(); + $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2'))); + $this->assertEqual($entities, array(), 'Entity successfully deleted.'); + + $entity->save(); + $this->assertEqual($entity->pid, $loaded->pid, 'Entity successfully updated.'); + + // Try deleting multiple test entities by deleting all. + $pids = array_keys(entity_test_load_multiple(FALSE)); + entity_test_delete_multiple($pids); + } + + /** + * Tests CRUD for entities supporting revisions. + */ + function testCRUDRevisisions() { + module_enable(array('entity_feature')); + + // Add text field to entity. + $field_info = array( + 'field_name' => 'field_text', + 'type' => 'text', + 'entity_types' => array('entity_test2'), + ); + field_create_field($field_info); + + $instance = array( + 'label' => 'Text Field', + 'field_name' => 'field_text', + 'entity_type' => 'entity_test2', + 'bundle' => 'entity_test2', + 'settings' => array(), + 'required' => FALSE, + ); + field_create_instance($instance); + + // Create a test entity. + $entity_first_revision = entity_create('entity_test2', array('title' => 'first revision', 'name' => 'main', 'uid' => 1)); + $entity_first_revision->field_text[LANGUAGE_NONE][0]['value'] = 'first revision text'; + entity_save('entity_test2', $entity_first_revision); + + $entities = array_values(entity_load('entity_test2', FALSE)); + $this->assertEqual(count($entities), 1, 'Entity created.'); + $this->assertTrue($entities[0]->default_revision, 'Initial entity revision is marked as default revision.'); + + // Saving the entity in revision mode should create a new revision. + $entity_second_revision = clone $entity_first_revision; + $entity_second_revision->title = 'second revision'; + $entity_second_revision->is_new_revision = TRUE; + $entity_second_revision->default_revision = TRUE; + $entity_second_revision->field_text[LANGUAGE_NONE][0]['value'] = 'second revision text'; + + entity_save('entity_test2', $entity_second_revision); + $this->assertNotEqual($entity_second_revision->revision_id, $entity_first_revision->revision_id, 'Saving an entity in new revision mode creates a revision.'); + $this->assertTrue($entity_second_revision->default_revision, 'New entity revision is marked as default revision.'); + + // Check the saved entity. + $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE)); + $this->assertNotEqual($entity->title, $entity_first_revision->title, 'Default revision was changed.'); + + // Create third revision that is not default. + $entity_third_revision = clone $entity_first_revision; + $entity_third_revision->title = 'third revision'; + $entity_third_revision->is_new_revision = TRUE; + $entity_third_revision->default_revision = FALSE; + $entity_third_revision->field_text[LANGUAGE_NONE][0]['value'] = 'third revision text'; + entity_save('entity_test2', $entity_third_revision); + $this->assertNotEqual($entity_second_revision->revision_id, $entity_third_revision->revision_id, 'Saving an entity in revision mode creates a revision.'); + $this->assertFalse($entity_third_revision->default_revision, 'Entity revision is not marked as default revision.'); + + $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE)); + $this->assertEqual($entity->title, $entity_second_revision->title, 'Default revision was not changed.'); + $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], $entity_second_revision->field_text[LANGUAGE_NONE][0]['value'], 'Default revision text field was not changed.'); + + // Load not default revision. + $revision = entity_revision_load('entity_test2', $entity_third_revision->revision_id); + $this->assertEqual($revision->revision_id, $entity_third_revision->revision_id, 'Revision successfully loaded.'); + $this->assertFalse($revision->default_revision, 'Entity revision is not marked as default revision after loading.'); + + // Save not default revision. + $entity_third_revision->title = 'third revision updated'; + $entity_third_revision->field_text[LANGUAGE_NONE][0]['value'] = 'third revision text updated'; + entity_save('entity_test2', $entity_third_revision); + + // Ensure that not default revision has been changed. + $entity = entity_revision_load('entity_test2', $entity_third_revision->revision_id); + $this->assertEqual($entity->title, 'third revision updated', 'Not default revision was updated successfully.'); + $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], 'third revision text updated', 'Not default revision field was updated successfully.'); + + // Ensure that default revision has not been changed. + $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE)); + $this->assertEqual($entity->title, $entity_second_revision->title, 'Default revision was not changed.'); + + // Try to delete default revision. + $result = entity_revision_delete('entity_test2', $entity_second_revision->revision_id); + $this->assertFalse($result, 'Default revision cannot be deleted.'); + + // Make sure default revision is still set after trying to delete it. + $entity = current(entity_load('entity_test2', array($entity_first_revision->pid), array(), TRUE)); + $this->assertEqual($entity->revision_id, $entity_second_revision->revision_id, 'Second revision is still default.'); + + // Delete first revision. + $result = entity_revision_delete('entity_test2', $entity_first_revision->revision_id); + $this->assertTrue($result, 'Not default revision deleted.'); + + $entity = entity_revision_load('entity_test2', $entity_first_revision->revision_id); + $this->assertFalse($entity, 'First revision deleted.'); + + // Delete the entity and make sure third revision has been deleted as well. + entity_delete('entity_test2', $entity_second_revision->pid); + $entity_info = entity_get_info('entity_test2'); + $result = db_select($entity_info['revision table']) + ->condition('revision_id', $entity_third_revision->revision_id) + ->countQuery() + ->execute() + ->fetchField(); + $this->assertEqual($result, 0, 'Entity deleted with its all revisions.'); + } + + /** + * Tests CRUD API functions: entity_(create|delete|save) + */ + function testCRUDAPIfunctions() { + module_enable(array('entity_feature')); + + $user1 = $this->drupalCreateUser(); + // Create test entities for the user1 and unrelated to a user. + $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid)); + entity_save('entity_test', $entity); + $entity = entity_create('entity_test', array('name' => 'test2', 'uid' => $user1->uid)); + entity_save('entity_test', $entity); + $entity = entity_create('entity_test', array('name' => 'test', 'uid' => NULL)); + entity_save('entity_test', $entity); + + $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test'))); + $this->assertEqual($entities[0]->name, 'test', 'Created and loaded entity.'); + $this->assertEqual($entities[1]->name, 'test', 'Created and loaded entity.'); + + // Test getting the entity label, which is the used test-type's label. + $label = entity_label('entity_test', $entities[0]); + $this->assertEqual($label, 'label', 'Default label returned.'); + + $results = entity_test_load_multiple(array($entity->pid)); + $loaded = array_pop($results); + $this->assertEqual($loaded->pid, $entity->pid, 'Loaded the entity unrelated to a user.'); + + $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2'))); + + entity_delete('entity_test', $entities[0]->pid); + $entities = array_values(entity_test_load_multiple(FALSE, array('name' => 'test2'))); + $this->assertEqual($entities, array(), 'Entity successfully deleted.'); + + entity_save('entity_test', $entity); + $this->assertEqual($entity->pid, $loaded->pid, 'Entity successfully updated.'); + + // Try deleting multiple test entities by deleting all. + $pids = array_keys(entity_test_load_multiple(FALSE)); + entity_delete_multiple('entity_test', $pids); + } + + /** + * Test loading entities defined in code. + */ + function testExportables() { + module_enable(array('entity_feature')); + + $types = entity_load_multiple_by_name('entity_test_type', array('test2', 'test')); + + $this->assertEqual(array_keys($types), array('test2', 'test'), 'Entities have been loaded in the order as specified.'); + $this->assertEqual($types['test']->label, 'label', 'Default type loaded.'); + $this->assertTrue($types['test']->status & ENTITY_IN_CODE && !($types['test']->status & ENTITY_CUSTOM), 'Default type status is correct.'); + + // Test using a condition, which has to be applied on the defaults. + $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'label')); + $this->assertEqual($types['test']->label, 'label', 'Condition to default type applied.'); + + $types['test']->label = 'modified'; + $types['test']->save(); + + // Ensure loading the changed entity works. + $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'modified')); + $this->assertEqual($types['test']->label, 'modified', 'Modified type loaded.'); + + // Clear the cache to simulate a new page load. + entity_get_controller('entity_test_type')->resetCache(); + + // Test loading using a condition again, now they default may not appear any + // more as it's overridden by an entity with another label. + $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'label')); + $this->assertTrue(empty($types), 'Conditions are applied to the overridden entity only.'); + + // But the overridden entity has to appear with another condition. + $types = entity_load_multiple_by_name('entity_test_type', FALSE, array('label' => 'modified')); + $this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by condition.'); + + $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2')); + $this->assertEqual($types['test']->label, 'modified', 'Modified default type loaded by id.'); + $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.'); + + // Test rebuilding the defaults and make sure overridden entities stay. + entity_defaults_rebuild(); + $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2')); + $this->assertEqual($types['test']->label, 'modified', 'Overridden entity is still overridden.'); + $this->assertTrue(entity_has_status('entity_test_type', $types['test'], ENTITY_OVERRIDDEN), 'Status of overridden type is correct.'); + + // Test reverting. + $types['test']->delete(); + $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2')); + $this->assertEqual($types['test']->label, 'label', 'Entity has been reverted.'); + + // Test loading an exportable by its numeric id. + $result = entity_load_multiple_by_name('entity_test_type', array($types['test']->id)); + $this->assertTrue(isset($result['test']), 'Exportable entity loaded by the numeric id.'); + + // Test exporting an entity to JSON. + $serialized_string = $types['test']->export(); + $data = drupal_json_decode($serialized_string); + $this->assertNotNull($data, 'Exported entity is valid JSON.'); + $import = entity_import('entity_test_type', $serialized_string); + $this->assertTrue(get_class($import) == get_class($types['test']) && $types['test']->label == $import->label, 'Successfully exported entity to code.'); + $this->assertTrue(!isset($import->status), 'Exportable status has not been exported to code.'); + + // Test disabling the module providing the defaults in code. + $types = entity_load_multiple_by_name('entity_test_type', array('test', 'test2')); + $types['test']->label = 'modified'; + $types['test']->save(); + + module_disable(array('entity_feature')); + + // Make sure the overridden entity stays and the other one is deleted. + entity_get_controller('entity_test_type')->resetCache(); + $test = entity_load_single('entity_test_type', 'test'); + $this->assertTrue(!empty($test) && $test->label == 'modified', 'Overidden entity is still available.'); + $this->assertTrue(!empty($test) && !entity_has_status('entity_test_type', $test, ENTITY_IN_CODE) && entity_has_status('entity_test_type', $test, ENTITY_CUSTOM), 'Overidden entity is now marked as custom.'); + + $test2 = entity_load_single('entity_test_type', 'test2'); + $this->assertFalse($test2, 'Default entity has disappeared.'); + } + + /** + * Make sure insert() and update() hooks for exportables are invoked. + */ + function testExportableHooks() { + $_SESSION['entity_hook_test'] = array(); + // Enabling the module should invoke the enabled hook for the other + // entities provided in code. + module_enable(array('entity_feature')); + + $insert = array('main', 'test', 'test2'); + $this->assertTrue($_SESSION['entity_hook_test']['entity_insert'] == $insert, 'Hook entity_insert has been invoked.'); + $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_insert'] == $insert, 'Hook entity_test_type_insert has been invoked.'); + + // Load a default entity and make sure the rebuilt logic only ran once. + entity_load_single('entity_test_type', 'test'); + $this->assertTrue(!isset($_SESSION['entity_hook_test']['entity_test_type_update']), '"Entity-test-type" defaults have been rebuilt only once.'); + + // Add a new test entity in DB and make sure the hook is invoked too. + $test3 = entity_create('entity_test_type', array( + 'name' => 'test3', + 'label' => 'label', + 'weight' => 0, + )); + $test3->save(); + + $insert[] = 'test3'; + $this->assertTrue($_SESSION['entity_hook_test']['entity_insert'] == $insert, 'Hook entity_insert has been invoked.'); + $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_insert'] == $insert, 'Hook entity_test_type_insert has been invoked.'); + + // Now override the 'test' entity and make sure it invokes the update hook. + $result = entity_load_multiple_by_name('entity_test_type', array('test')); + $result['test']->label = 'modified'; + $result['test']->save(); + + $this->assertTrue($_SESSION['entity_hook_test']['entity_update'] == array('test'), 'Hook entity_update has been invoked.'); + $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_update'] == array('test'), 'Hook entity_test_type_update has been invoked.'); + + // 'test' has to remain enabled, as it has been overridden. + $delete = array('main', 'test2'); + module_disable(array('entity_feature')); + + $this->assertTrue($_SESSION['entity_hook_test']['entity_delete'] == $delete, 'Hook entity_deleted has been invoked.'); + $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_delete'] == $delete, 'Hook entity_test_type_deleted has been invoked.'); + + // Now make sure 'test' is not overridden any more, but custom. + $result = entity_load_multiple_by_name('entity_test_type', array('test')); + $this->assertTrue(!$result['test']->hasStatus(ENTITY_OVERRIDDEN), 'Entity is not marked as overridden any more.'); + $this->assertTrue(entity_has_status('entity_test_type', $result['test'], ENTITY_CUSTOM), 'Entity is marked as custom.'); + + // Test deleting the remaining entities from DB. + entity_delete_multiple('entity_test_type', array('test', 'test3')); + $delete[] = 'test'; + $delete[] = 'test3'; + $this->assertTrue($_SESSION['entity_hook_test']['entity_delete'] == $delete, 'Hook entity_deleted has been invoked.'); + $this->assertTrue($_SESSION['entity_hook_test']['entity_test_type_delete'] == $delete, 'Hook entity_test_type_deleted has been invoked.'); + } + + /** + * Tests determining changes. + */ + function testChanges() { + module_enable(array('entity_feature')); + $types = entity_load_multiple_by_name('entity_test_type'); + + // Override the default entity, such it gets saved in the DB. + $types['test']->label ='test_changes'; + $types['test']->save(); + + // Now test an update without applying any changes. + $types['test']->save(); + $this->assertEqual($types['test']->label, 'test_changes', 'No changes have been determined.'); + + // Apply changes. + $types['test']->label = 'updated'; + $types['test']->save(); + + // The hook implementations entity_test_entity_test_type_presave() and + // entity_test_entity_test_type_update() determine changes and change the + // label. + $this->assertEqual($types['test']->label, 'updated_presave_update', 'Changes have been determined.'); + + // Test the static load cache to be cleared. + $types = entity_load_multiple_by_name('entity_test_type'); + $this->assertEqual($types['test']->label, 'updated_presave', 'Static cache has been cleared.'); + } + + + /** + * Tests viewing entites. + */ + function testRendering() { + module_enable(array('entity_feature')); + + $user1 = $this->drupalCreateUser(); + // Create test entities for the user1 and unrelated to a user. + $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid)); + + $render = $entity->view(); + $output = drupal_render($render); + // The entity class adds the user name to the output. Verify it is there. + $this->assertTrue(strpos($output, format_username($user1)) !== FALSE, 'Entity has been rendered'); + } + + /** + * Test uninstall of the entity_test module. + */ + function testUninstall() { + // Add a test type and add a field instance, uninstall, then re-install and + // make sure the field instance can be re-created. + $test_type = entity_create('entity_test_type', array( + 'name' => 'test', + 'label' => 'label', + 'weight' => 0, + )); + $test_type->save(); + + $field = array( + 'field_name' => 'field_test_fullname', + 'type' => 'text', + 'cardinality' => 1, + 'translatable' => FALSE, + ); + field_create_field($field); + + $instance = array( + 'entity_type' => 'entity_test', + 'field_name' => 'field_test_fullname', + 'bundle' => 'test', + 'label' => 'Full name', + 'description' => 'Specify your first and last name.', + 'widget' => array( + 'type' => 'text_textfield', + 'weight' => 0, + ), + ); + field_create_instance($instance); + + // Uninstallation has to remove all bundles, thus also field instances. + module_disable(array('entity_test')); + require_once DRUPAL_ROOT . '/includes/install.inc'; + drupal_uninstall_modules(array('entity_test')); + + // Make sure the instance has been deleted. + $instance_read = field_read_instance('entity_test', 'field_test_fullname', 'test', array('include_inactive' => 1)); + $this->assertFalse((bool) $instance_read, 'Field instance has been deleted.'); + + // Ensure re-creating the same instance now works. + module_enable(array('entity_test')); + $test_type = entity_create('entity_test_type', array( + 'name' => 'test', + 'label' => 'label', + 'weight' => 0, + )); + $test_type->save(); + field_create_field($field); + field_create_instance($instance); + + $instance_read = field_info_instance('entity_test', 'field_test_fullname', 'test'); + $this->assertTrue((bool) $instance_read, 'Field instance has been re-created.'); + } +} + +/** + * Test the generated Rules integration. + */ +class EntityAPIRulesIntegrationTestCase extends EntityWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Entity CRUD Rules integration', + 'description' => 'Tests the Rules integration provided by the Entity CRUD API.', + 'group' => 'Entity API', + 'dependencies' => array('rules'), + ); + } + + function setUp() { + parent::setUp('entity', 'entity_test', 'rules'); + // Make sure the logger is enabled so the debug log is saved. + variable_set('rules_debug_log', 1); + } + + /** + * Test the events. + */ + function testEvents() { + $rule = rules_reaction_rule(); + $rule->event('entity_test_presave'); + $rule->event('entity_test_insert'); + $rule->event('entity_test_update'); + $rule->event('entity_test_delete'); + $rule->action('drupal_message', array('message' => 'hello!')); + $rule->save(); + rules_clear_cache(TRUE); + + // Let the events occur. + $user1 = $this->drupalCreateUser(); + RulesLog::logger()->clear(); + + $entity = entity_create('entity_test', array('name' => 'test', 'uid' => $user1->uid)); + $entity->save(); + $entity->name = 'update'; + $entity->save(); + $entity->delete(); + + // Now there should have been 5 events, 2 times presave and once insert, + // update and delete. + $count = substr_count(RulesLog::logger()->render(), '0 ms Reacting on event'); + $this->assertTrue($count == 5, 'Events have been properly invoked.'); + RulesLog::logger()->checkLog(); + } +} + +/** + * Tests comments with node access. + */ +class EntityAPICommentNodeAccessTestCase extends CommentHelperCase { + + public static function getInfo() { + return array( + 'name' => 'Entity API comment node access', + 'description' => 'Test viewing comments on nodes with node access.', + 'group' => 'Entity API', + ); + } + + function setUp() { + DrupalWebTestCase::setUp('comment', 'entity', 'node_access_test'); + node_access_rebuild(); + + // Create test node and user with simple node access permission. The + // 'node test view' permission is implemented and granted by the + // node_access_test module. + $this->accessUser = $this->drupalCreateUser(array('access comments', 'post comments', 'edit own comments', 'node test view')); + $this->noAccessUser = $this->drupalCreateUser(array('administer comments')); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $this->accessUser->uid)); + } + + /** + * Tests comment access when node access is enabled. + */ + function testCommentNodeAccess() { + // Post comment. + $this->drupalLogin($this->accessUser); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), 'Comment found.'); + $this->drupalLogout(); + + // Check access to node and associated comment for access user. + $this->assertTrue(entity_access('view', 'node', $this->node, $this->accessUser), 'Access to view node was granted for access user'); + $this->assertTrue(entity_access('view', 'comment', $comment_loaded, $this->accessUser), 'Access to view comment was granted for access user'); + $this->assertTrue(entity_access('update', 'comment', $comment_loaded, $this->accessUser), 'Access to update comment was granted for access user'); + $this->assertFalse(entity_access('delete', 'comment', $comment_loaded, $this->accessUser), 'Access to delete comment was denied for access user'); + + // Check access to node and associated comment for no access user. + $this->assertFalse(entity_access('view', 'node', $this->node, $this->noAccessUser), 'Access to view node was denied for no access user'); + $this->assertFalse(entity_access('view', 'comment', $comment_loaded, $this->noAccessUser), 'Access to view comment was denied for no access user'); + $this->assertFalse(entity_access('update', 'comment', $comment_loaded, $this->noAccessUser), 'Access to update comment was denied for no access user'); + $this->assertFalse(entity_access('delete', 'comment', $comment_loaded, $this->noAccessUser), 'Access to delete comment was denied for no access user'); + } +} + +/** + * Test the i18n integration. + */ +class EntityAPIi18nItegrationTestCase extends EntityWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Entity CRUD i18n integration', + 'description' => 'Tests the i18n integration provided by the Entity CRUD API.', + 'group' => 'Entity API', + 'dependencies' => array('i18n_string'), + ); + } + + function setUp() { + parent::setUp('entity_test_i18n'); + $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages')); + $this->drupalLogin($this->admin_user); + $this->addLanguage('de'); + } + + /** + * Copied from i18n module (class Drupali18nTestCase). + * + * We cannot extend from Drupali18nTestCase as else the test-bot would die. + */ + public function addLanguage($language_code) { + // Check to make sure that language has not already been installed. + $this->drupalGet('admin/config/regional/language'); + + if (strpos($this->drupalGetContent(), 'enabled[' . $language_code . ']') === FALSE) { + // Doesn't have language installed so add it. + $edit = array(); + $edit['langcode'] = $language_code; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Make sure we are not using a stale list. + drupal_static_reset('language_list'); + $languages = language_list('language'); + $this->assertTrue(array_key_exists($language_code, $languages), t('Language was installed successfully.')); + + if (array_key_exists($language_code, $languages)) { + $this->assertRaw(t('The language %language has been created and can now be used. More information is available on the help screen.', array('%language' => $languages[$language_code]->name, '@locale-help' => url('admin/help/locale'))), t('Language has been created.')); + } + } + elseif ($this->xpath('//input[@type="checkbox" and @name=:name and @checked="checked"]', array(':name' => 'enabled[' . $language_code . ']'))) { + // It's installed and enabled. No need to do anything. + $this->assertTrue(true, 'Language [' . $language_code . '] already installed and enabled.'); + } + else { + // It's installed but not enabled. Enable it. + $this->assertTrue(true, 'Language [' . $language_code . '] already installed.'); + $this->drupalPost(NULL, array('enabled[' . $language_code . ']' => TRUE), t('Save configuration')); + $this->assertRaw(t('Configuration saved.'), t('Language successfully enabled.')); + } + } + + /** + * Tests the provided default controller. + */ + function testDefaultController() { + // Create test entities for the user1 and unrelated to a user. + $entity = entity_create('entity_test_type', array( + 'name' => 'test', + 'uid' => $GLOBALS['user']->uid, + 'label' => 'label-en', + )); + $entity->save(); + + // Add a translation. + i18n_string_textgroup('entity_test')->update_translation("entity_test_type:{$entity->name}:label", 'de', 'label-de'); + + $default = entity_i18n_string("entity_test:entity_test_type:{$entity->name}:label", 'label-en'); + $translation = entity_i18n_string("entity_test:entity_test_type:{$entity->name}:label", 'label-en', 'de'); + + $this->assertEqual($translation, 'label-de', 'Label has been translated.'); + $this->assertEqual($default, 'label-en', 'Default label retrieved.'); + + // Test the helper method. + $translation = $entity->getTranslation('label', 'de'); + $default = $entity->getTranslation('label'); + $this->assertEqual($translation, 'label-de', 'Label has been translated via the helper method.'); + $this->assertEqual($default, 'label-en', 'Default label retrieved via the helper method.'); + + // Test updating and make sure the translation stays. + $entity->name = 'test2'; + $entity->save(); + $translation = $entity->getTranslation('label', 'de'); + $this->assertEqual($translation, 'label-de', 'Translation survives a name change.'); + + // Test using the wrapper to retrieve a translation. + $wrapper = entity_metadata_wrapper('entity_test_type', $entity); + $translation = $wrapper->language('de')->label->value(); + $this->assertEqual($translation, 'label-de', 'Translation retrieved via the wrapper.'); + + // Test deleting. + $entity->delete(); + $translation = entity_i18n_string("entity_test:entity_test_type:{$entity->name}:label", 'label-en', 'de'); + $this->assertEqual($translation, 'label-en', 'Translation has been deleted.'); + } +} + +/** + * Tests metadata wrappers. + */ +class EntityMetadataTestCase extends EntityWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Metadata Wrapper', + 'description' => 'Makes sure metadata wrapper are working right.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp('entity', 'entity_test', 'locale'); + // Create a field having 4 values for testing multiple value support. + $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); + $this->field = array('field_name' => $this->field_name, 'type' => 'text', 'cardinality' => 4); + $this->field = field_create_field($this->field); + $this->field_id = $this->field['id']; + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'page', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'text_processing' => FALSE, + ), + 'widget' => array( + 'type' => 'text_textfield', + 'label' => 'Test Field', + 'settings' => array( + 'size' => 64, + ) + ) + ); + field_create_instance($this->instance); + + // Make the body field and the node type 'page' translatable. + $field = field_info_field('body'); + $field['translatable'] = TRUE; + field_update_field($field); + variable_set('language_content_type_page', 1); + } + + /** + * Creates a user and a node, then tests getting the properties. + */ + function testEntityMetadataWrapper() { + $account = $this->drupalCreateUser(); + // For testing sanitizing give the user a malicious user name + $account = user_save($account, array('name' => 'BadName')); + $title = 'Is it bold?'; + $body[LANGUAGE_NONE][0] = array('value' => 'The body & nothing.', 'summary' => 'The body.'); + $node = $this->drupalCreateNode(array('uid' => $account->uid, 'name' => $account->name, 'body' => $body, 'title' => $title, 'summary' => '', 'type' => 'page')); + + // First test without sanitizing. + $wrapper = entity_metadata_wrapper('node', $node); + + $this->assertEqual('Is it bold?', $wrapper->title->value(), 'Getting a field value.'); + $this->assertEqual($node->title, $wrapper->title->raw(), 'Getting a raw property value.'); + + // Test chaining. + $this->assertEqual($account->mail, $wrapper->author->mail->value(), 'Testing chained usage.'); + $this->assertEqual($account->name, $wrapper->author->name->value(), 'Testing chained usage with callback and sanitizing.'); + + // Test sanitized output. + $options = array('sanitize' => TRUE); + $this->assertEqual(check_plain('Is it bold?'), $wrapper->title->value($options), 'Getting sanitized field.'); + $this->assertEqual(filter_xss($node->name), $wrapper->author->name->value($options), 'Getting sanitized property with getter callback.'); + + // Test getting an not existing property. + try { + echo $wrapper->dummy; + $this->fail('Getting an not existing property.'); + } + catch (EntityMetadataWrapperException $e) { + $this->pass('Getting an not existing property.'); + } + + // Test setting. + $wrapper->author = 0; + $this->assertEqual(0, $wrapper->author->uid->value(), 'Setting a property.'); + try { + $wrapper->url = 'dummy'; + $this->fail('Setting an unsupported property.'); + } + catch (EntityMetadataWrapperException $e) { + $this->pass('Setting an unsupported property.'); + } + + // Test value validation. + $this->assertFalse($wrapper->author->name->validate(array(3)), 'Validation correctly checks for valid data types.'); + try { + $wrapper->author->mail = 'foo'; + $this->fail('An invalid mail address has been set.'); + } + catch (EntityMetadataWrapperException $e) { + $this->pass('Setting an invalid mail address throws exception.'); + } + // Test unsetting a required property. + try { + $wrapper->author = NULL; + $this->fail('The required node author has been unset.'); + } + catch (EntityMetadataWrapperException $e) { + $this->pass('Unsetting the required node author throws an exception.'); + } + + // Test setting a referenced entity by id. + $wrapper->author->set($GLOBALS['user']->uid); + $this->assertEqual($wrapper->author->getIdentifier(), $GLOBALS['user']->uid, 'Get the identifier of a referenced entity.'); + $this->assertEqual($wrapper->author->uid->value(), $GLOBALS['user']->uid, 'Successfully set referenced entity using the identifier.'); + // Set by object. + $wrapper->author->set($GLOBALS['user']); + $this->assertEqual($wrapper->author->uid->value(), $GLOBALS['user']->uid, 'Successfully set referenced entity using the entity.'); + + + // Test getting by the field API processed values like the node body. + $body_value = $wrapper->body->value; + $this->assertEqual("

      The body & nothing.

      \n", $body_value->value(), "Getting processed value."); + $this->assertEqual("The body & nothing.\n", $body_value->value(array('decode' => TRUE)), "Decoded value."); + $this->assertEqual("The body & nothing.", $body_value->raw(), "Raw body returned."); + + // Test getting the summary. + $this->assertEqual("

      The body.

      \n", $wrapper->body->summary->value(), "Getting body summary."); + + $wrapper->body->set(array('value' => "The second body.")); + $this->assertEqual("

      The second body.

      \n", $wrapper->body->value->value(), "Setting a processed field value and reading it again."); + $this->assertEqual($node->body[LANGUAGE_NONE][0]['value'], "The second body.", 'Update appears in the wrapped entity.'); + $this->assert(isset($node->body[LANGUAGE_NONE][0]['safe_value']), 'Formatted text has been processed.'); + + // Test translating the body on an English node. + locale_add_language('de'); + $body['en'][0] = array('value' => 'English body.', 'summary' => 'The body.'); + $node = $this->drupalCreateNode(array('body' => $body, 'language' => 'en', 'type' => 'page')); + $wrapper = entity_metadata_wrapper('node', $node); + + $wrapper->language('de'); + + $languages = language_list(); + $this->assertEqual($wrapper->getPropertyLanguage(), $languages['de'], 'Wrapper language has been set to German'); + $this->assertEqual($wrapper->body->value->value(), "

      English body.

      \n", 'Language fallback on default language.'); + + // Set a German text using the wrapper. + $wrapper->body->set(array('value' => "Der zweite Text.")); + $this->assertEqual($wrapper->body->value->value(), "

      Der zweite Text.

      \n", 'German body set and retrieved.'); + + $wrapper->language(LANGUAGE_NONE); + $this->assertEqual($wrapper->body->value->value(), "

      English body.

      \n", 'Default language text is still there.'); + + // Test iterator. + $type_info = entity_get_property_info('node'); + $this->assertFalse(array_diff_key($type_info['properties'], iterator_to_array($wrapper->getIterator())), 'Iterator is working.'); + foreach ($wrapper as $property) { + $this->assertTrue($property instanceof EntityMetadataWrapper, 'Iterate over wrapper properties.'); + } + + // Test setting a new node. + $node->title = 'foo'; + $wrapper->set($node); + $this->assertEqual($wrapper->title->value(), 'foo', 'Changed the wrapped node.'); + + // Test getting options lists. + $this->assertEqual($wrapper->type->optionsList(), node_type_get_names(), 'Options list returned.'); + + // Test making use of a generic 'entity' reference property the + // 'entity_test' module provides. The property defaults to the node author. + $this->assertEqual($wrapper->reference->uid->value(), $wrapper->author->getIdentifier(), 'Used generic entity reference property.'); + // Test updating a property of the generic entity reference. + $wrapper->reference->name->set('foo'); + $this->assertEqual($wrapper->reference->name->value(), 'foo', 'Updated property of generic entity reference'); + // For testing, just point the reference to the node itself now. + $wrapper->reference->set($wrapper); + $this->assertEqual($wrapper->reference->nid->value(), $wrapper->getIdentifier(), 'Correctly updated the generic entity referenced property.'); + + // Test saving and deleting. + $wrapper->save(); + $wrapper->delete(); + $return = node_load($wrapper->getIdentifier()); + $this->assertFalse($return, "Node has been successfully deleted."); + + // Ensure changing the bundle changes available wrapper properties. + $wrapper->type->set('article'); + $this->assertTrue(isset($wrapper->field_tags), 'Changing bundle changes available wrapper properties.'); + + // Test labels. + $user = $this->drupalCreateUser(); + user_save($user, array('roles' => array())); + $wrapper->author = $user->uid; + $this->assertEqual($wrapper->label(), $node->title, 'Entity label returned.'); + $this->assertEqual($wrapper->author->roles[0]->label(), t('authenticated user'), 'Label from options list returned'); + $this->assertEqual($wrapper->author->roles->label(), t('authenticated user'), 'Label for a list from options list returned'); + } + + /** + * Test supporting multi-valued fields. + */ + function testListMetadataWrappers() { + $property = $this->field_name; + $values = array(); + $values[LANGUAGE_NONE][0] = array('value' => '2009-09-05'); + $values[LANGUAGE_NONE][1] = array('value' => '2009-09-05'); + $values[LANGUAGE_NONE][2] = array('value' => '2009-08-05'); + + $node = $this->drupalCreateNode(array('type' => 'page', $property => $values)); + $wrapper = entity_metadata_wrapper('node', $node); + + $this->assertEqual('2009-09-05', $wrapper->{$property}[0]->value(), 'Getting array entry.'); + $this->assertEqual('2009-09-05', $wrapper->{$property}->get(1)->value(), 'Getting array entry.'); + $this->assertEqual(3, count($wrapper->{$property}->value()), 'Getting the whole array.'); + + // Test sanitizing + $this->assertEqual(check_plain('2009-09-05'), $wrapper->{$property}[0]->value(array('sanitize' => TRUE)), 'Getting array entry.'); + + // Test iterator + $this->assertEqual(array_keys(iterator_to_array($wrapper->$property->getIterator())), array_keys($wrapper->$property->value()), 'Iterator is working.'); + foreach ($wrapper->$property as $p) { + $this->assertTrue($p instanceof EntityMetadataWrapper, 'Iterate over list wrapper properties.'); + } + + // Make sure changing the array changes the actual entity property. + $wrapper->{$property}[0] = '2009-10-05'; + unset($wrapper->{$property}[1], $wrapper->{$property}[2]); + $this->assertEqual($wrapper->{$property}->value(), array('2009-10-05'), 'Setting multiple property values.'); + + // Test setting an arbitrary list item. + $list = array(0 => REQUEST_TIME); + $wrapper = entity_metadata_wrapper('list', $list); + $wrapper[1] = strtotime('2009-09-05'); + $this->assertEqual($wrapper->value(), array(REQUEST_TIME, strtotime('2009-09-05')), 'Setting a list item.'); + $this->assertEqual($wrapper->count(), 2, 'List count is correct.'); + + // Test using a list wrapper without data. + $wrapper = entity_metadata_wrapper('list'); + $info = array(); + foreach ($wrapper as $item) { + $info[] = $item->info(); + } + $this->assertTrue($info[0]['type'] == 'date', 'Iterated over empty list wrapper.'); + + // Test using a list of entities with a list of term objects. + $list = array(); + $list[] = entity_property_values_create_entity('taxonomy_term', array( + 'name' => 'term 1', + 'vocabulary' => 1, + ))->save()->value(); + $list[] = entity_property_values_create_entity('taxonomy_term', array( + 'name' => 'term 2', + 'vocabulary' => 1, + ))->save()->value(); + $wrapper = entity_metadata_wrapper('list', $list); + $this->assertTrue($wrapper[0]->name->value() == 'term 1', 'Used a list of entities.'); + // Test getting a list of identifiers. + $ids = $wrapper->value(array('identifier' => TRUE)); + $this->assertTrue(!is_object($ids[0]), 'Get a list of entity ids.'); + + $wrapper = entity_metadata_wrapper('list', $ids); + $this->assertTrue($wrapper[0]->name->value() == 'term 1', 'Created a list of entities with ids.'); + + // Test with a list of generic entities. The list is expected to be a list + // of entity wrappers, otherwise the entity type is unknown. + $node = $this->drupalCreateNode(array('title' => 'node 1')); + $list = array(); + $list[] = entity_metadata_wrapper('node', $node); + $wrapper = entity_metadata_wrapper('list', $list); + $this->assertEqual($wrapper[0]->title->value(), 'node 1', 'Wrapped node was found in generic list of entities.'); + } + + /** + * Tests using the wrapper without any data. + */ + function testWithoutData() { + $wrapper = entity_metadata_wrapper('node', NULL, array('bundle' => 'page')); + $this->assertTrue(isset($wrapper->title), 'Bundle properties have been added.'); + $info = $wrapper->author->mail->info(); + $this->assertTrue(!empty($info) && is_array($info) && isset($info['label']), 'Property info returned.'); + } + + /** + * Test using access() method. + */ + function testAccess() { + // Test without data. + $account = $this->drupalCreateUser(array('bypass node access')); + $this->assertTrue(entity_access('view', 'node', NULL, $account), 'Access without data checked.'); + + // Test with actual data. + $values[LANGUAGE_NONE][0] = array('value' => '2009-09-05'); + $values[LANGUAGE_NONE][1] = array('value' => '2009-09-05'); + $node = $this->drupalCreateNode(array('type' => 'page', $this->field_name => $values)); + $this->assertTrue(entity_access('delete', 'node', $node, $account), 'Access with data checked.'); + + // Test per property access without data. + $account2 = $this->drupalCreateUser(array('bypass node access', 'administer nodes')); + $wrapper = entity_metadata_wrapper('node', NULL, array('bundle' => 'page')); + $this->assertTrue($wrapper->access('edit', $account), 'Access to node granted.'); + $this->assertFalse($wrapper->status->access('edit', $account), 'Access for admin property denied.'); + $this->assertTrue($wrapper->status->access('edit', $account2), 'Access for admin property allowed for the admin.'); + + // Test per property access with data. + $wrapper = entity_metadata_wrapper('node', $node, array('bundle' => 'page')); + $this->assertFalse($wrapper->status->access('edit', $account), 'Access for admin property denied.'); + $this->assertTrue($wrapper->status->access('edit', $account2), 'Access for admin property allowed for the admin.'); + + // Test field level access. + $this->assertTrue($wrapper->{$this->field_name}->access('view'), 'Field access granted.'); + } + + /** + * Tests using a data structure with passed in metadata. + */ + function testDataStructureWrapper() { + $log_entry = array( + 'type' => 'entity', + 'message' => $this->randomName(8), + 'variables' => array(), + 'severity' => WATCHDOG_NOTICE, + 'link' => '', + 'user' => $GLOBALS['user'], + ); + $info['property info'] = array( + 'type' => array('type' => 'text', 'label' => 'The category to which this message belongs.'), + 'message' => array('type' => 'text', 'label' => 'The log message.'), + 'user' => array('type' => 'user', 'label' => 'The user causing the log entry.'), + ); + $wrapper = entity_metadata_wrapper('log_entry', $log_entry, $info); + $this->assertEqual($wrapper->user->name->value(), $GLOBALS['user']->name, 'Wrapped custom entity.'); + } + + /** + * Tests using entity_property_query(). + */ + function testEntityQuery() { + // Creat a test node. + $title = 'Is it bold?'; + $values[LANGUAGE_NONE][0] = array('value' => 'foo'); + $node = $this->drupalCreateNode(array($this->field_name => $values, 'title' => $title, 'uid' => $GLOBALS['user']->uid)); + + $results = entity_property_query('node', 'title', $title); + $this->assertEqual($results, array($node->nid), 'Queried nodes with a given title.'); + + $results = entity_property_query('node', $this->field_name, 'foo'); + $this->assertEqual($results, array($node->nid), 'Queried nodes with a given field value.'); + + $results = entity_property_query('node', $this->field_name, array('foo', 'bar')); + $this->assertEqual($results, array($node->nid), 'Queried nodes with a list of possible values.'); + + $results = entity_property_query('node', 'author', $GLOBALS['user']); + $this->assertEqual($results, array($node->nid), 'Queried nodes with a given auhtor.'); + + // Create another test node and try querying for tags. + $tag = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->save(); + $field_tag_value[LANGUAGE_NONE][0]['tid'] = $tag->getIdentifier(); + $node = $this->drupalCreateNode(array('type' => 'article', 'field_tags' => $field_tag_value)); + + // Try query-ing with a single value. + $results = entity_property_query('node', 'field_tags', $tag->getIdentifier()); + $this->assertEqual($results, array($node->nid), 'Queried nodes with a given term id.'); + + $results = entity_property_query('node', 'field_tags', $tag->value()); + $this->assertEqual($results, array($node->nid), 'Queried nodes with a given term object.'); + + // Try query-ing with a list of possible values. + $results = entity_property_query('node', 'field_tags', array($tag->getIdentifier())); + $this->assertEqual($results, array($node->nid), 'Queried nodes with a list of term ids.'); + } + + /** + * Tests serializing data wrappers, in particular for EntityDrupalWrapper. + */ + function testWrapperSerialization() { + $node = $this->drupalCreateNode(); + $wrapper = entity_metadata_wrapper('node', $node); + $this->assertTrue($wrapper->value() == $node, 'Data correctly wrapped.'); + + // Test serializing and make sure only the node id is stored. + $this->assertTrue(strpos(serialize($wrapper), $node->title) === FALSE, 'Node has been correctly serialized.'); + $this->assertEqual(unserialize(serialize($wrapper))->title->value(), $node->title, 'Serializing works right.'); + + $wrapper2 = unserialize(serialize($wrapper)); + // Test serializing the unloaded wrapper. + $this->assertEqual(unserialize(serialize($wrapper2))->title->value(), $node->title, 'Serializing works right.'); + + // Test loading a not more existing node. + $s = serialize($wrapper2); + node_delete($node->nid); + $this->assertFalse(node_load($node->nid), 'Node deleted.'); + + $value = unserialize($s)->value(); + $this->assertNull($value, 'Tried to load not existing node.'); + } +} + +/** + * Tests provided entity property info of the core modules. + */ +class EntityTokenTestCase extends EntityWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Entity tokens', + 'description' => 'Tests provided tokens for entity properties.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp('entity_token'); + } + + /** + * Tests whether token support is basically working. + */ + function testTokenSupport() { + // Test basic tokens. + $node = $this->drupalCreateNode(array('sticky' => TRUE, 'promote' => FALSE)); + $text = "Sticky: [node:sticky] Promote: [node:promote] User: [site:current-user:name]"; + $true = t('true'); + $false = t('false'); + $user_name = $GLOBALS['user']->name; + $target = "Sticky: $true Promote: $false User: $user_name"; + $replace = token_replace($text, array('node' => $node)); + $this->assertEqual($replace, $target, 'Provided tokens basically work.'); + + // Test multiple-value tokens using the tags field of articles. + for ($i = 0; $i < 4; $i++) { + $tags[$i] = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => 1, + ))->save(); + $field_value[LANGUAGE_NONE][$i]['tid'] = $tags[$i]->getIdentifier(); + $labels[$i] = $tags[$i]->label(); + } + $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', 'field_tags' => $field_value)); + + $text = "Tags: [node:field-tags] First: [node:field-tags:0] 2nd name: [node:field-tags:1:name] 1st vocab [node:field-tags:0:vocabulary]"; + $tag_labels = implode(', ', $labels); + $target = "Tags: $tag_labels First: $labels[0] 2nd name: $labels[1] 1st vocab {$tags[0]->vocabulary->label()}"; + $replace = token_replace($text, array('node' => $node)); + $this->assertEqual($replace, $target, 'Multiple-value token replacements have been replaced.'); + + // Make sure not existing values are not handled. + $replace = token_replace("[node:field-tags:43]", array('node' => $node)); + $this->assertEqual($replace, "[node:field-tags:43]", 'Not existing values are not replaced.'); + + // Test data-structure tokens like [site:current-page:url]. + $replace = token_replace("[site:current-page:url]", array()); + $this->assertEqual($replace, $GLOBALS['base_root'] . request_uri(), 'Token replacements of data structure properties replaced.'); + + // Test chaining of data-structure tokens using an image-field. + $file = $this->createFile('image'); + $node = $this->drupalCreateNode(array('type' => 'article')); + $wrapper = entity_metadata_wrapper('node', $node); + + $wrapper->field_image = array('fid' => $file->fid); + $replace = token_replace("[node:field-image:file:name]", array('node' => $node)); + $this->assertEqual($replace, $wrapper->field_image->file->name->value(), 'Token replacements of an image field have been replaced.'); + } +} + +/** + * Tests provided entity property info of the core modules. + */ +class EntityMetadataIntegrationTestCase extends EntityWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Property info core integration', + 'description' => 'Tests using metadata wrapper for drupal core.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp('entity', 'book', 'statistics', 'locale'); + } + + protected function assertException($wrapper, $name, $text = NULL) { + $this->assertTrue(isset($wrapper->$name), 'Property wrapper ' . check_plain($name) . ' exists.'); + $text = isset($text) ? $text : 'Getting the not existing property ' . $name . ' throws exception.'; + try { + $wrapper->$name->value(); + $this->fail($text); + } + catch (EntityMetadataWrapperException $e) { + $this->pass($text); + } + } + + protected function assertEmpty($wrapper, $name) { + $this->assertTrue(isset($wrapper->$name), 'Property ' . check_plain($name) . ' exists.'); + $this->assertTrue($wrapper->$name->value() === NULL, 'Property ' . check_plain($name) . ' is empty.'); + } + + protected function assertValue($wrapper, $key) { + $this->assertTrue($wrapper->$key->value() !== NULL, check_plain($key) . ' property returned.'); + $info = $wrapper->$key->info(); + if (!empty($info['raw getter callback'])) { + // Also test getting the raw value + $this->assertTrue($wrapper->$key->raw() !== NULL, check_plain($key) . ' raw value returned.'); + } + } + + /** + * Test book module integration. + */ + function testBookModule() { + $title = 'Book 1'; + $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'book')); + $node2 = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => $node->nid))); + $node3 = $this->drupalCreateNode(array('type' => 'page')); + + // Test whether the properties work. + $wrapper = entity_metadata_wrapper('node', $node2); + $this->assertEqual("Book 1", $wrapper->book->title->value(), "Book title returned."); + $this->assertEqual($node->nid, $wrapper->book->nid->value(), "Book id returned."); + + // Try using book properties for no book nodes. + $wrapper = entity_metadata_wrapper('node', $node3); + $this->assertException($wrapper, 'book'); + } + + /** + * Test properties of a comment. + */ + function testComments() { + $title = 'Node 1'; + $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'page')); + $account = $this->drupalCreateUser(); + $comment = (object)array( + 'subject' => 'topic', + 'nid' => $node->nid, + 'uid' => $account->uid, + 'cid' => FALSE, + 'pid' => 0, + 'homepage' => '', + 'language' => LANGUAGE_NONE, + 'hostname' => ip_address(), + ); + $comment->comment_body[LANGUAGE_NONE][0] = array('value' => 'text', 'format' => 0); + comment_save($comment); + $wrapper = entity_metadata_wrapper('comment', $comment); + foreach ($wrapper as $key => $value) { + if ($key != 'parent') { + $this->assertValue($wrapper, $key); + } + } + $this->assertEmpty($wrapper, 'parent'); + } + + /** + * Test all properties of a node. + */ + function testNodeProperties() { + $title = 'Book 1'; + $node = $this->drupalCreateNode(array('title' => $title, 'type' => 'page')); + $wrapper = entity_metadata_wrapper('node', $node); + foreach ($wrapper as $key => $value) { + if ($key != 'book' && $key != 'source' && $key != 'last_view') { + $this->assertValue($wrapper, $key); + } + } + $this->assertException($wrapper, 'book'); + $this->assertEmpty($wrapper, 'source'); + $this->assertException($wrapper->source, 'title'); + $this->assertEmpty($wrapper, 'last_view'); + } + + /** + * Tests properties provided by the taxonomy module. + */ + function testTaxonomyProperties() { + $vocab = $this->createVocabulary(); + $term_parent = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => $vocab, + ))->save()->value(); + $term_parent2 = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => $vocab, + ))->save()->value(); + $term = entity_property_values_create_entity('taxonomy_term', array( + 'name' => $this->randomName(), + 'vocabulary' => $vocab, + 'description' => $this->randomString(), + 'weight' => mt_rand(0, 10), + 'parent' => array($term_parent->tid), + ))->save()->value(); + + $wrapper = entity_metadata_wrapper('taxonomy_term', $term); + foreach ($wrapper as $key => $value) { + $this->assertValue($wrapper, $key); + } + // Test setting another parent using the full object. + $wrapper->parent[] = $term_parent2; + $this->assertEqual($wrapper->parent[1]->getIdentifier(), $term_parent2->tid, 'Term parent added.'); + + $parents = $wrapper->parent->value(); + $tids = $term_parent->tid . ':' . $term_parent2->tid; + $this->assertEqual($parents[0]->tid . ':' . $parents[1]->tid, $tids, 'Parents returned.'); + $this->assertEqual(implode(':', $wrapper->parent->value(array('identifier' => TRUE))), $tids, 'Parent ids returned.'); + + // Test vocabulary. + foreach ($wrapper->vocabulary as $key => $value) { + $this->assertValue($wrapper->vocabulary, $key); + } + // Test field integration. + $tags[LANGUAGE_NONE][0]['tid'] = $term->tid; + $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', 'field_tags' => $tags)); + $wrapper = entity_metadata_wrapper('node', $node); + $this->assertEqual($wrapper->field_tags[0]->name->value(), $term->name, 'Get an associated tag of a node with the wrapper.'); + + $wrapper->field_tags[1] = $term_parent; + $tags = $wrapper->field_tags->value(); + $this->assertEqual($tags[1]->tid, $term_parent->tid, 'Associated a new tag with a node.'); + $this->assertEqual($tags[0]->tid, $term->tid, 'Previsous set association kept.'); + + // Test getting a list of identifiers. + $tags = $wrapper->field_tags->value(array('identifier' => TRUE)); + $this->assertEqual($tags, array($term->tid, $term_parent->tid), 'List of referenced term identifiers returned.'); + + // Test setting tags by using ids. + $wrapper->field_tags->set(array(2)); + $this->assertEqual($wrapper->field_tags[0]->tid->value(), 2, 'Specified tags by a list of term ids.'); + + // Test unsetting all tags. + $wrapper->field_tags = NULL; + $this->assertFalse($wrapper->field_tags->value(), 'Unset all tags from a node.'); + + // Test setting entity references to NULL. + // Create a taxonomy term field for that purpose. + $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = array('field_name' => $field_name, 'type' => 'taxonomy_term_reference', 'cardinality' => 1); + $field = field_create_field($field); + $field_id = $field['id']; + $field_instance = array( + 'field_name' => $field_name, + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'widget' => array( + 'type' => 'options_select', + 'label' => 'Test term field', + ) + ); + field_create_instance($field_instance); + $term_field[LANGUAGE_NONE][0]['tid'] = $term->tid; + $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', $field_name => $term_field)); + $wrapper = entity_metadata_wrapper('node', $node); + $wrapper->$field_name->set(NULL); + $termref = $wrapper->$field_name->value(); + $this->assertNull($termref, 'Unset of a term reference successful.'); + } + + /** + * Test all properties of a user. + */ + function testUserProperties() { + $account = $this->drupalCreateUser(); + $account->login = REQUEST_TIME; + $account->access = REQUEST_TIME; + $wrapper = entity_metadata_wrapper('user', $account); + foreach ($wrapper as $key => $value) { + $this->assertValue($wrapper, $key); + } + } + + /** + * Test properties provided by system module. + */ + function testSystemProperties() { + $wrapper = entity_metadata_site_wrapper(); + foreach ($wrapper as $key => $value) { + $this->assertValue($wrapper, $key); + } + // Test page request related properties. + foreach ($wrapper->current_page as $key => $value) { + $this->assertValue($wrapper->current_page, $key); + } + + // Test files. + $file = $this->createFile(); + + $wrapper = entity_metadata_wrapper('file', $file); + foreach ($wrapper as $key => $value) { + $this->assertValue($wrapper, $key); + } + } + + /** + * Runs some generic tests on each entity. + */ + function testCRUDfunctions() { + $info = entity_get_info(); + foreach ($info as $entity_type => $entity_info) { + // Test using access callback. + entity_access('view', $entity_type); + entity_access('update', $entity_type); + entity_access('create', $entity_type); + entity_access('delete', $entity_type); + + // Test creating the entity. + if (!isset($entity_info['creation callback'])) { + continue; + } + + // Populate $values with all values that are setable. They will be set + // with an metadata wrapper, so we also test setting that way. + $values = array(); + foreach (entity_metadata_wrapper($entity_type) as $name => $wrapper) { + $info = $wrapper->info(); + if (!empty($info['setter callback'])) { + $values[$name] = $this->createValue($wrapper); + } + } + $entity = entity_property_values_create_entity($entity_type, $values)->value(); + $this->assertTrue($entity, "Created $entity_type and set all setable values."); + + // Save the new entity. + $return = entity_save($entity_type, $entity); + if ($return === FALSE) { + continue; // No support for saving. + } + $id = entity_metadata_wrapper($entity_type, $entity)->getIdentifier(); + $this->assertTrue($id, "$entity_type has been successfully saved."); + + // And delete it. + $return = entity_delete($entity_type, $id); + if ($return === FALSE) { + continue; // No support for deleting. + } + $return = entity_load_single($entity_type, $id); + $this->assertFalse($return, "$entity_type has been successfully deleted."); + } + } + + /** + * Test making use of a text fields. + */ + function testTextFields() { + // Create a simple text field without text processing. + $field = array( + 'field_name' => 'field_text', + 'type' => 'text', + 'cardinality' => 2, + ); + field_create_field($field); + $instance = array( + 'field_name' => 'field_text', + 'entity_type' => 'node', + 'label' => 'test', + 'bundle' => 'article', + 'widget' => array( + 'type' => 'text_textfield', + 'weight' => -1, + ), + ); + field_create_instance($instance); + + $node = $this->drupalCreateNode(array('type' => 'article')); + $wrapper = entity_metadata_wrapper('node', $node); + + $wrapper->field_text[0] = 'the text'; + + // Try saving the node and make sure the information is still there after + // loading the node again, thus the correct data structure has been written. + node_save($node); + $node = node_load($node->nid, NULL, TRUE); + $wrapper = entity_metadata_wrapper('node', $node); + + $this->assertEqual('the text', $wrapper->field_text[0]->value(), 'Text has been specified.'); + + // Now activate text processing. + $instance['settings']['text_processing'] = 1; + field_update_instance($instance); + + $node = $this->drupalCreateNode(array('type' => 'article')); + $wrapper = entity_metadata_wrapper('node', $node); + + $wrapper->field_text[0]->set(array('value' => "The second body.")); + $this->assertEqual("

      The second body.

      \n", $wrapper->field_text[0]->value->value(), "Setting a processed text field value and reading it again."); + + // Assert the summary property is correctly removed. + $this->assertFalse(isset($wrapper->field_text[0]->summary), 'Processed text has no summary.'); + + // Create a text field with summary but without text processing. + $field = array( + 'field_name' => 'field_text2', + 'type' => 'text_with_summary', + 'cardinality' => 1, + ); + field_create_field($field); + $instance = array( + 'field_name' => 'field_text2', + 'entity_type' => 'node', + 'label' => 'test', + 'bundle' => 'article', + 'settings' => array('text_processing' => 0), + 'widget' => array( + 'type' => 'text_textarea_with_summary', + 'weight' => -1, + ), + ); + field_create_instance($instance); + + $node = $this->drupalCreateNode(array('type' => 'article')); + $wrapper = entity_metadata_wrapper('node', $node); + + $wrapper->field_text2->summary = 'the summary'; + $wrapper->field_text2->value = 'the text'; + + // Try saving the node and make sure the information is still there after + // loading the node again, thus the correct data structure has been written. + node_save($node); + $node = node_load($node->nid, NULL, TRUE); + $wrapper = entity_metadata_wrapper('node', $node); + + $this->assertEqual('the text', $wrapper->field_text2->value->value(), 'Text has been specified.'); + $this->assertEqual('the summary', $wrapper->field_text2->summary->value(), 'Summary has been specified.'); + } + + /** + * Test making use of a file field. + */ + function testFileFields() { + $file = $this->createFile(); + + // Create a file field. + $field = array( + 'field_name' => 'field_file', + 'type' => 'file', + 'cardinality' => 2, + 'settings' => array('display_field' => TRUE), + ); + field_create_field($field); + $instance = array( + 'field_name' => 'field_file', + 'entity_type' => 'node', + 'label' => 'File', + 'bundle' => 'article', + 'settings' => array('description_field' => TRUE), + 'required' => FALSE, + 'widget' => array( + 'type' => 'file_generic', + 'weight' => -1, + ), + ); + field_create_instance($instance); + + $node = $this->drupalCreateNode(array('type' => 'article')); + $wrapper = entity_metadata_wrapper('node', $node); + + $wrapper->field_file[0] = array('fid' => $file->fid, 'display' => FALSE); + $this->assertEqual($file->filename, $wrapper->field_file[0]->file->name->value(), 'File has been specified.'); + + $wrapper->field_file[0]->description = 'foo'; + $wrapper->field_file[0]->display = TRUE; + + $this->assertEqual($wrapper->field_file[0]->description->value(), 'foo', 'File description has been correctly set.'); + + // Try saving the node and make sure the information is still there after + // loading the node again, thus the correct data structure has been written. + node_save($node); + $node = node_load($node->nid, NULL, TRUE); + $wrapper = entity_metadata_wrapper('node', $node); + + $this->assertEqual($wrapper->field_file[0]->description->value(), 'foo', 'File description has been correctly set.'); + $this->assertEqual($wrapper->field_file[0]->display->value(), TRUE, 'File display value has been correctly set.'); + + // Test adding a new file, the display-property has to be created + // automatically. + $wrapper->field_file[1]->file = $file; + node_save($node); + $node = node_load($node->nid, NULL, TRUE); + $this->assertEqual($file->fid, $wrapper->field_file[1]->file->getIdentifier(), 'New file has been added.'); + + // Test adding an invalid file-field item, i.e. without any file. + try { + $wrapper->field_file[] = array('description' => 'test'); + $this->fail('Exception not thrown.'); + } + catch (EntityMetadataWrapperException $e) { + $this->pass('Not valid file-field item has thrown an exception.'); + } + + // Test remove all file-field items. + $wrapper->field_file = NULL; + $this->assertFalse($wrapper->field_file->value(), 'Removed multiple file-field items.'); + } + + /** + * Test making use of an image field. + */ + function testImageFields() { + $file = $this->createFile('image'); + + // Just use the image field on the article node. + $node = $this->drupalCreateNode(array('type' => 'article')); + $wrapper = entity_metadata_wrapper('node', $node); + + $wrapper->field_image = array('fid' => $file->fid); + $this->assertEqual($file->filename, $wrapper->field_image->file->name->value(), 'File has been specified.'); + + $wrapper->field_image->alt = 'foo'; + $this->assertEqual($wrapper->field_image->alt->value(), 'foo', 'Image alt attribute has been correctly set.'); + + // Try saving the node and make sure the information is still there after + // loading the node again, thus the correct data structure has been written. + node_save($node); + $node = node_load($node->nid, NULL, TRUE); + $wrapper = entity_metadata_wrapper('node', $node); + + $this->assertEqual($wrapper->field_image->alt->value(), 'foo', 'File description has been correctly set.'); + + // Test adding a new image. + $wrapper->field_image->file = $file; + node_save($node); + $node = node_load($node->nid, NULL, TRUE); + $this->assertEqual($file->fid, $wrapper->field_image->file->getIdentifier(), 'New file has been added.'); + + // Test adding an invalid image-field item, i.e. without any file. + try { + $wrapper->field_image = array(); + $this->fail('Exception not thrown.'); + } + catch (EntityMetadataWrapperException $e) { + $this->pass('Not valid image-field item has thrown an exception.'); + } + } + + /** + * Creates a value for the given property. + */ + protected function createValue($wrapper) { + if (!isset($this->node)) { + $this->node = $this->drupalCreateNode(array('type' => 'page')); + $this->user = $this->drupalCreateUser(); + $this->taxonomy_vocabulary = $this->createVocabulary(); + } + + if ($options = $wrapper->optionsList()) { + $options = entity_property_options_flatten($options); + return $wrapper instanceof EntityListWrapper ? array(key($options)) : key($options); + } + + // For mail addresses properly pass an mail address. + $info = $wrapper->info(); + if ($info['name'] == 'mail') { + return 'webmaster@example.com'; + } + + switch ($wrapper->type()) { + case 'decimal': + case 'integer': + case 'duration': + return 1; + case 'date': + return REQUEST_TIME; + case 'boolean': + return TRUE; + case 'token': + return drupal_strtolower($this->randomName(8)); + case 'text': + return $this->randomName(32); + case 'text_formatted': + return array('value' => $this->randomName(16)); + case 'list': + return array(); + + default: + return $this->{$wrapper->type()}; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity_token.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity_token.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Entity tokens +description = Provides token replacements for all properties that have no tokens and are known to the entity API. +core = 7.x +files[] = entity_token.tokens.inc +files[] = entity_token.module +dependencies[] = entity + +; Information added by drupal.org packaging script on 2013-08-14 +version = "7.x-1.2" +core = "7.x" +project = "entity" +datestamp = "1376493705" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/entity_token.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/entity_token.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,6 @@ + $info) { + if ($token_type = isset($info['token type']) ? $info['token type'] : $entity_type) { + $types[$token_type] = $entity_type; + } + } + // Add 'date' and 'site' tokens. + $types['date'] = 'date'; + $types['site'] = 'site'; + // Add a 'struct' type. + $types['struct'] = 'struct'; + } + + if (isset($type)) { + return isset($types[$type]) || entity_property_list_extract_type($type); + } + return $types; +} + +/** + * Gets the right token type for a given property info array. + */ +function _entity_token_map_to_token_type($property_info) { + $lookup = &drupal_static(__FUNCTION__); + + if (!$lookup) { + // Initialize a lookup array mapping property types to token types. + $lookup = array_flip(entity_token_types()); + } + + $type = isset($property_info['type']) ? $property_info['type'] : 'text'; + // Just use the type 'struct' for all structures. + if (!empty($property_info['property info'])) { + $type = 'struct'; + } + + if ($item_type = entity_property_list_extract_type($type)) { + return isset($lookup[$item_type]) ? "list<$lookup[$item_type]>" : FALSE; + } + return isset($lookup[$type]) ? $lookup[$type] : FALSE; +} + +/** + * Implements hook_token_info_alter(). + */ +function entity_token_token_info_alter(&$info) { + $entity_info = entity_get_info(); + $token_types = entity_token_types_chained(); + + // Loop over all chain-able token types, as those may contain further tokens, + // e.g. entity types or 'site'. + foreach ($token_types as $token_type => $type) { + // Just add all properties regardless whether it's in a bundle, but only if + // there is no token of the property yet. + foreach (entity_get_all_property_info($type) as $name => $property) { + $name = str_replace('_', '-', $name); + $property += array('type' => 'text', 'description' => $property['label']); + $property_token_type = _entity_token_map_to_token_type($property); + + if (!isset($info['tokens'][$token_type][$name]) && $property_token_type) { + + $info['tokens'][$token_type][$name] = array( + 'name' => $property['label'], + 'description' => $property['description'], + 'type' => $property_token_type, + // Mark the token so we know we have to provide the value afterwards. + 'entity-token' => TRUE, + ); + } + if ($property_token_type == 'struct' && !empty($property['property info'])) { + $info['tokens'][$token_type][$name]['dynamic'] = TRUE; + $help = array(); + foreach ($property['property info'] as $key => $property_info) { + $help[] = $key . ' (' . $property_info['label'] . ')'; + } + $info['tokens'][$token_type][$name]['description'] .= ' ' . t('The following properties may be appended to the token: @keys', + array('@keys' => implode(', ', $help)) + ); + } + } + } + + // Make sure all chain-able token types we support are registered. + foreach ($token_types as $token_type => $type) { + + if (!empty($info['tokens'][$token_type]) && !isset($info['types'][$token_type])) { + if (isset($entity_info[$type])) { + $info['types'][$token_type] = array( + 'name' => $entity_info[$type]['label'], + 'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])), + 'needs-data' => $token_type, + ); + } + else { + $info['types'][$token_type] = array( + 'name' => drupal_strtoupper($token_type), + 'description' => t('@name tokens.', array('@name' => drupal_strtoupper($token_type))), + 'needs-data' => $token_type, + ); + } + } + if (!empty($info['tokens'][$token_type]) && !isset($info['types']["list<$token_type>"]) && $token_type != 'site') { + if (isset($entity_info[$type])) { + $info['types']["list<$token_type>"] = array( + 'name' => t('List of @entities', array('@entities' => isset($entity_info[$type]['plural label']) ? $entity_info[$type]['plural label'] : $entity_info[$type]['label'] . 's')), + 'description' => t('Tokens related to the "@name" entities.', array('@name' => $entity_info[$type]['label'])), + 'needs-data' => "list<$token_type>", + ); + } + else { + $info['types']["list<$token_type>"] = array( + 'name' => t('List of @type values', array('@type' => $token_type)), + 'description' => t('Tokens for lists of @type values.', array('@type' => $token_type)), + 'needs-data' => "list<$token_type>", + ); + } + // Also add some basic token replacements for lists... + for ($i = 0; $i < 4; $i++) { + $info['tokens']["list<$token_type>"][$i] = array( + 'name' => t('@type with delta @delta', array('@delta' => $i, '@type' => $info['types'][$token_type]['name'])), + 'description' => t('The list item with delta @delta. Delta values start from 0 and are incremented by one per list item.', array('@delta' => $i)), + 'type' => $token_type, + ); + } + } + } +} + +/** + * Implements hook_tokens(). + */ +function entity_token_tokens($type, $tokens, array $data = array(), array $options = array()) { + $token_types = entity_token_types_chained(); + $replacements = array(); + + if (isset($token_types[$type]) && (!empty($data[$type]) || $type == 'site')) { + $data += array($type => FALSE); + + // Make use of token module's token cache if available. + $info = module_exists('token') ? token_get_info() : token_info(); + foreach ($tokens as $name => $original) { + // Provide the token for all properties marked to stem from us. + if (!empty($info['tokens'][$type][$name]['entity-token']) || $type == 'struct') { + $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper; + $property_name = str_replace('-', '_', $name); + try { + $replacement = _entity_token_get_token($wrapper->$property_name, $options); + if (isset($replacement)) { + $replacements[$original] = $replacement; + } + } + catch (EntityMetadataWrapperException $e) { + // If tokens for not existing values are requested, just do nothing. + } + } + } + + // Properly chain everything of a type marked as needs chaining. + $info['tokens'] += array($type => array()); + foreach ($info['tokens'][$type] as $name => $token_info) { + if (!empty($token_info['entity-token']) && isset($token_info['type']) && entity_token_types_chained($token_info['type'])) { + + if ($chained_tokens = token_find_with_prefix($tokens, $name)) { + $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, $token_types[$type], $data[$type], $options) : $wrapper; + $property_name = str_replace('-', '_', $name); + + try { + // Pass on 'struct' properties wrapped, else un-wrap the data. + $value = ($token_info['type'] == 'struct') ? $wrapper->$property_name : $wrapper->$property_name->value(); + $replacements += token_generate($token_info['type'], $chained_tokens, array($token_info['type'] => $value), $options); + } + catch (EntityMetadataWrapperException $e) { + // If tokens for not existing values are requested, just do nothing. + } + } + } + } + } + // Add support for evaluating tokens for "list types. + elseif ($item_token_type = entity_property_list_extract_type($type)) { + foreach ($tokens as $name => $original) { + // Care about getting entries of a list. + if (is_numeric($name)) { + $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper; + try { + $replacement = _entity_token_get_token($wrapper->get($name), $options); + if (isset($replacement)) { + $replacements[$original] = $replacement; + } + } + catch (EntityMetadataWrapperException $e) { + // If tokens for not existing values are requested, just do nothing. + } + } + // Care about generating chained tokens for list-items. + else { + $parts = explode(':', $name, 2); + $delta = $parts[0]; + + if (is_numeric($delta) && $chained_tokens = token_find_with_prefix($tokens, $delta)) { + $wrapper = !isset($wrapper) ? _entity_token_wrap_data($type, "list<$token_types[$item_token_type]>", $data[$type], $options) : $wrapper; + try { + $replacements += token_generate($item_token_type, $chained_tokens, array($item_token_type => $wrapper->get($delta)->value()), $options); + } + catch (EntityMetadataWrapperException $e) { + // If tokens for not existing values are requested, just do nothing. + } + } + } + } + } + + // Add support for chaining struct data. As struct data has no registered + // tokens, we have to chain based upon wrapper property info. + if ($type == 'struct') { + $wrapper = $data[$type]; + foreach ($wrapper as $name => $property) { + $token_type = _entity_token_map_to_token_type($property->info()); + + if (entity_token_types_chained($token_type) && $chained_tokens = token_find_with_prefix($tokens, $name)) { + try { + // Pass on 'struct' properties wrapped, else un-wrap the data. + $value = ($token_type == 'struct') ? $property : $property->value(); + $replacements += token_generate($token_type, $chained_tokens, array($token_type => $value), $options); + } + catch (EntityMetadataWrapperException $e) { + // If tokens for not existing values are requested, just do nothing. + } + } + } + } + + return $replacements; +} + +/** + * Wraps the given data by correctly obeying the options. + */ +function _entity_token_wrap_data($token_type, $type, $data, $options) { + if ($type == 'site') { + $wrapper = entity_metadata_site_wrapper(); + } + elseif ($type == 'struct') { + // 'struct' data items are passed on wrapped. + $wrapper = $data; + } + else { + $wrapper = entity_metadata_wrapper($type, $data); + } + if (isset($options['language']) && $wrapper instanceof EntityStructureWrapper) { + $wrapper->language($options['language']->language); + } + return $wrapper; +} + +/** + * Gets the token replacement by correctly obeying the options. + */ +function _entity_token_get_token($wrapper, $options) { + + if ($wrapper->value() === NULL) { + // Do not provide a replacement if there is no value. + return NULL; + } + + if (empty($options['sanitize'])) { + // When we don't need sanitized tokens decode already sanitizied texts. + $options['decode'] = TRUE; + } + $langcode = isset($options['language']) ? $options['language']->language : NULL; + + // If there is a label for a property, e.g. defined by an options list or an + // entity label, make use of it. + if ($label = $wrapper->label()) { + return empty($options['sanitize']) ? $label : check_plain($label); + } + + switch ($wrapper->type()) { + case 'integer': + return $wrapper->value(); + case 'decimal': + return number_format($wrapper->value(), 2); + case 'date': + return format_date($wrapper->value(), 'medium', '', NULL, $langcode); + case 'duration': + return format_interval($wrapper->value(), 2, $langcode); + case 'boolean': + return $wrapper->value() ? t('true') : t('false'); + case 'uri': + case 'text': + return $wrapper->value($options); + } + + // Care for outputing list values. + if ($wrapper instanceof EntityListWrapper) { + $output = array(); + foreach ($wrapper as $item) { + $output[] = _entity_token_get_token($item, $options); + } + return implode(', ', $output); + } + // Else we do not have a good string to output, e.g. for struct values. Just + // output the string representation of the wrapper. + return (string) $wrapper; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/includes/entity.controller.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/includes/entity.controller.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,976 @@ +entityInfo['bundle of'])) { + $info = entity_get_info($this->entityInfo['bundle of']); + $this->bundleKey = $info['bundle keys']['bundle']; + } + $this->defaultRevisionKey = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision'; + } + + /** + * Overrides DrupalDefaultEntityController::buildQuery(). + */ + protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { + $query = parent::buildQuery($ids, $conditions, $revision_id); + if ($this->revisionKey) { + // Compare revision id of the base and revision table, if equal then this + // is the default revision. + $query->addExpression('base.' . $this->revisionKey . ' = revision.' . $this->revisionKey, $this->defaultRevisionKey); + } + return $query; + } + + /** + * Builds and executes the query for loading. + * + * @return The results in a Traversable object. + */ + public function query($ids, $conditions, $revision_id = FALSE) { + // Build the query. + $query = $this->buildQuery($ids, $conditions, $revision_id); + $result = $query->execute(); + if (!empty($this->entityInfo['entity class'])) { + $result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType)); + } + return $result; + } + + /** + * Overridden. + * @see DrupalDefaultEntityController#load($ids, $conditions) + * + * In contrast to the parent implementation we factor out query execution, so + * fetching can be further customized easily. + */ + public function load($ids = array(), $conditions = array()) { + $entities = array(); + + // Revisions are not statically cached, and require a different query to + // other conditions, so separate the revision id into its own variable. + if ($this->revisionKey && isset($conditions[$this->revisionKey])) { + $revision_id = $conditions[$this->revisionKey]; + unset($conditions[$this->revisionKey]); + } + else { + $revision_id = FALSE; + } + + // Create a new variable which is either a prepared version of the $ids + // array for later comparison with the entity cache, or FALSE if no $ids + // were passed. The $ids array is reduced as items are loaded from cache, + // and we need to know if it's empty for this reason to avoid querying the + // database when all requested entities are loaded from cache. + $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; + + // Try to load entities from the static cache. + if ($this->cache && !$revision_id) { + $entities = $this->cacheGet($ids, $conditions); + // If any entities were loaded, remove them from the ids still to load. + if ($passed_ids) { + $ids = array_keys(array_diff_key($passed_ids, $entities)); + } + } + + // Support the entitycache module if activated. + if (!empty($this->entityInfo['entity cache']) && !$revision_id && $ids && !$conditions) { + $cached_entities = EntityCacheControllerHelper::entityCacheGet($this, $ids, $conditions); + // If any entities were loaded, remove them from the ids still to load. + $ids = array_diff($ids, array_keys($cached_entities)); + $entities += $cached_entities; + + // Add loaded entities to the static cache if we are not loading a + // revision. + if ($this->cache && !empty($cached_entities) && !$revision_id) { + $this->cacheSet($cached_entities); + } + } + + // Load any remaining entities from the database. This is the case if $ids + // is set to FALSE (so we load all entities), if there are any ids left to + // load or if loading a revision. + if (!($this->cacheComplete && $ids === FALSE && !$conditions) && ($ids === FALSE || $ids || $revision_id)) { + $queried_entities = array(); + foreach ($this->query($ids, $conditions, $revision_id) as $record) { + // Skip entities already retrieved from cache. + if (isset($entities[$record->{$this->idKey}])) { + continue; + } + + // For DB-based entities take care of serialized columns. + if (!empty($this->entityInfo['base table'])) { + $schema = drupal_get_schema($this->entityInfo['base table']); + + foreach ($schema['fields'] as $field => $info) { + if (!empty($info['serialize']) && isset($record->$field)) { + $record->$field = unserialize($record->$field); + // Support automatic merging of 'data' fields into the entity. + if (!empty($info['merge']) && is_array($record->$field)) { + foreach ($record->$field as $key => $value) { + $record->$key = $value; + } + unset($record->$field); + } + } + } + } + + $queried_entities[$record->{$this->idKey}] = $record; + } + } + + // Pass all entities loaded from the database through $this->attachLoad(), + // which attaches fields (if supported by the entity type) and calls the + // entity type specific load callback, for example hook_node_load(). + if (!empty($queried_entities)) { + $this->attachLoad($queried_entities, $revision_id); + $entities += $queried_entities; + } + + // Entitycache module support: Add entities to the entity cache if we are + // not loading a revision. + if (!empty($this->entityInfo['entity cache']) && !empty($queried_entities) && !$revision_id) { + EntityCacheControllerHelper::entityCacheSet($this, $queried_entities); + } + + if ($this->cache) { + // Add entities to the cache if we are not loading a revision. + if (!empty($queried_entities) && !$revision_id) { + $this->cacheSet($queried_entities); + + // Remember if we have cached all entities now. + if (!$conditions && $ids === FALSE) { + $this->cacheComplete = TRUE; + } + } + } + // Ensure that the returned array is ordered the same as the original + // $ids array if this was passed in and remove any invalid ids. + if ($passed_ids && $passed_ids = array_intersect_key($passed_ids, $entities)) { + foreach ($passed_ids as $id => $value) { + $passed_ids[$id] = $entities[$id]; + } + $entities = $passed_ids; + } + return $entities; + } + + /** + * Overrides DrupalDefaultEntityController::resetCache(). + */ + public function resetCache(array $ids = NULL) { + $this->cacheComplete = FALSE; + parent::resetCache($ids); + // Support the entitycache module. + if (!empty($this->entityInfo['entity cache'])) { + EntityCacheControllerHelper::resetEntityCache($this, $ids); + } + } + + /** + * Implements EntityAPIControllerInterface. + */ + public function invoke($hook, $entity) { + // entity_revision_delete() invokes hook_entity_revision_delete() and + // hook_field_attach_delete_revision() just as node module does. So we need + // to adjust the name of our revision deletion field attach hook in order to + // stick to this pattern. + $field_attach_hook = ($hook == 'revision_delete' ? 'delete_revision' : $hook); + if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $field_attach_hook)) { + $function($this->entityType, $entity); + } + + if (!empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of'])) { + $type = $this->entityInfo['bundle of']; + // Call field API bundle attachers for the entity we are a bundle of. + if ($hook == 'insert') { + field_attach_create_bundle($type, $entity->{$this->bundleKey}); + } + elseif ($hook == 'delete') { + field_attach_delete_bundle($type, $entity->{$this->bundleKey}); + } + elseif ($hook == 'update' && $entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) { + field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey}); + } + } + // Invoke the hook. + module_invoke_all($this->entityType . '_' . $hook, $entity); + // Invoke the respective entity level hook. + if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') { + module_invoke_all('entity_' . $hook, $entity, $this->entityType); + } + // Invoke rules. + if (module_exists('rules')) { + rules_invoke_event($this->entityType . '_' . $hook, $entity); + } + } + + /** + * Implements EntityAPIControllerInterface. + * + * @param $transaction + * Optionally a DatabaseTransaction object to use. Allows overrides to pass + * in their transaction object. + */ + public function delete($ids, DatabaseTransaction $transaction = NULL) { + $entities = $ids ? $this->load($ids) : FALSE; + if (!$entities) { + // Do nothing, in case invalid or no ids have been passed. + return; + } + // This transaction causes troubles on MySQL, see + // http://drupal.org/node/1007830. So we deactivate this by default until + // is shipped in a point release. + // $transaction = isset($transaction) ? $transaction : db_transaction(); + + try { + $ids = array_keys($entities); + + db_delete($this->entityInfo['base table']) + ->condition($this->idKey, $ids, 'IN') + ->execute(); + + if (isset($this->revisionTable)) { + db_delete($this->revisionTable) + ->condition($this->idKey, $ids, 'IN') + ->execute(); + } + // Reset the cache as soon as the changes have been applied. + $this->resetCache($ids); + + foreach ($entities as $id => $entity) { + $this->invoke('delete', $entity); + } + // Ignore slave server temporarily. + db_ignore_slave(); + } + catch (Exception $e) { + if (isset($transaction)) { + $transaction->rollback(); + } + watchdog_exception($this->entityType, $e); + throw $e; + } + } + + /** + * Implements EntityAPIControllerRevisionableInterface::deleteRevision(). + */ + public function deleteRevision($revision_id) { + if ($entity_revision = entity_revision_load($this->entityType, $revision_id)) { + // Prevent deleting the default revision. + if (entity_revision_is_default($this->entityType, $entity_revision)) { + return FALSE; + } + + db_delete($this->revisionTable) + ->condition($this->revisionKey, $revision_id) + ->execute(); + + $this->invoke('revision_delete', $entity_revision); + return TRUE; + } + return FALSE; + } + + /** + * Implements EntityAPIControllerInterface. + * + * @param $transaction + * Optionally a DatabaseTransaction object to use. Allows overrides to pass + * in their transaction object. + */ + public function save($entity, DatabaseTransaction $transaction = NULL) { + $transaction = isset($transaction) ? $transaction : db_transaction(); + try { + // Load the stored entity, if any. + if (!empty($entity->{$this->idKey}) && !isset($entity->original)) { + // In order to properly work in case of name changes, load the original + // entity using the id key if it is available. + $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->idKey}); + } + $entity->is_new = !empty($entity->is_new) || empty($entity->{$this->idKey}); + $this->invoke('presave', $entity); + + if ($entity->is_new) { + $return = drupal_write_record($this->entityInfo['base table'], $entity); + if ($this->revisionKey) { + $this->saveRevision($entity); + } + $this->invoke('insert', $entity); + } + else { + // Update the base table if the entity doesn't have revisions or + // we are updating the default revision. + if (!$this->revisionKey || !empty($entity->{$this->defaultRevisionKey})) { + $return = drupal_write_record($this->entityInfo['base table'], $entity, $this->idKey); + } + if ($this->revisionKey) { + $return = $this->saveRevision($entity); + } + $this->resetCache(array($entity->{$this->idKey})); + $this->invoke('update', $entity); + + // Field API always saves as default revision, so if the revision saved + // is not default we have to restore the field values of the default + // revision now by invoking field_attach_update() once again. + if ($this->revisionKey && !$entity->{$this->defaultRevisionKey} && !empty($this->entityInfo['fieldable'])) { + field_attach_update($this->entityType, $entity->original); + } + } + + // Ignore slave server temporarily. + db_ignore_slave(); + unset($entity->is_new); + unset($entity->is_new_revision); + unset($entity->original); + + return $return; + } + catch (Exception $e) { + $transaction->rollback(); + watchdog_exception($this->entityType, $e); + throw $e; + } + } + + /** + * Saves an entity revision. + * + * @param Entity $entity + * Entity revision to save. + */ + protected function saveRevision($entity) { + // Convert the entity into an array as it might not have the same properties + // as the entity, it is just a raw structure. + $record = (array) $entity; + // File fields assumes we are using $entity->revision instead of + // $entity->is_new_revision, so we also support it and make sure it's set to + // the same value. + $entity->is_new_revision = !empty($entity->is_new_revision) || !empty($entity->revision) || $entity->is_new; + $entity->revision = &$entity->is_new_revision; + $entity->{$this->defaultRevisionKey} = !empty($entity->{$this->defaultRevisionKey}) || $entity->is_new; + + + + // When saving a new revision, set any existing revision ID to NULL so as to + // ensure that a new revision will actually be created. + if ($entity->is_new_revision && isset($record[$this->revisionKey])) { + $record[$this->revisionKey] = NULL; + } + + if ($entity->is_new_revision) { + drupal_write_record($this->revisionTable, $record); + $update_default_revision = $entity->{$this->defaultRevisionKey}; + } + else { + drupal_write_record($this->revisionTable, $record, $this->revisionKey); + // @todo: Fix original entity to be of the same revision and check whether + // the default revision key has been set. + $update_default_revision = $entity->{$this->defaultRevisionKey} && $entity->{$this->revisionKey} != $entity->original->{$this->revisionKey}; + } + // Make sure to update the new revision key for the entity. + $entity->{$this->revisionKey} = $record[$this->revisionKey]; + + // Mark this revision as the default one. + if ($update_default_revision) { + db_update($this->entityInfo['base table']) + ->fields(array($this->revisionKey => $record[$this->revisionKey])) + ->condition($this->idKey, $entity->{$this->idKey}) + ->execute(); + } + return $entity->is_new_revision ? SAVED_NEW : SAVED_UPDATED; + } + + /** + * Implements EntityAPIControllerInterface. + */ + public function create(array $values = array()) { + // Add is_new property if it is not set. + $values += array('is_new' => TRUE); + if (isset($this->entityInfo['entity class']) && $class = $this->entityInfo['entity class']) { + return new $class($values, $this->entityType); + } + return (object) $values; + } + + /** + * Implements EntityAPIControllerInterface. + * + * @return + * A serialized string in JSON format suitable for the import() method. + */ + public function export($entity, $prefix = '') { + $vars = get_object_vars($entity); + unset($vars['is_new']); + return entity_var_json_export($vars, $prefix); + } + + /** + * Implements EntityAPIControllerInterface. + * + * @param $export + * A serialized string in JSON format as produced by the export() method. + */ + public function import($export) { + $vars = drupal_json_decode($export); + if (is_array($vars)) { + return $this->create($vars); + } + return FALSE; + } + + /** + * Implements EntityAPIControllerInterface. + * + * @param $content + * Optionally. Allows pre-populating the built content to ease overridding + * this method. + */ + public function buildContent($entity, $view_mode = 'full', $langcode = NULL, $content = array()) { + // Remove previously built content, if exists. + $entity->content = $content; + $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language; + + // By default add in properties for all defined extra fields. + if ($extra_field_controller = entity_get_extra_fields_controller($this->entityType)) { + $wrapper = entity_metadata_wrapper($this->entityType, $entity); + $extra = $extra_field_controller->fieldExtraFields(); + $type_extra = &$extra[$this->entityType][$this->entityType]['display']; + $bundle_extra = &$extra[$this->entityType][$wrapper->getBundle()]['display']; + + foreach ($wrapper as $name => $property) { + if (isset($type_extra[$name]) || isset($bundle_extra[$name])) { + $this->renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, $entity->content); + } + } + } + + // Add in fields. + if (!empty($this->entityInfo['fieldable'])) { + // Perform the preparation tasks if they have not been performed yet. + // An internal flag prevents the operation from running twice. + $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL; + field_attach_prepare_view($this->entityType, array($key => $entity), $view_mode); + $entity->content += field_attach_view($this->entityType, $entity, $view_mode, $langcode); + } + // Invoke hook_ENTITY_view() to allow modules to add their additions. + if (module_exists('rules')) { + rules_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode); + } + else { + module_invoke_all($this->entityType . '_view', $entity, $view_mode, $langcode); + } + module_invoke_all('entity_view', $entity, $this->entityType, $view_mode, $langcode); + $build = $entity->content; + unset($entity->content); + return $build; + } + + /** + * Renders a single entity property. + */ + protected function renderEntityProperty($wrapper, $name, $property, $view_mode, $langcode, &$content) { + $info = $property->info(); + + $content[$name] = array( + '#label_hidden' => FALSE, + '#label' => $info['label'], + '#entity_wrapped' => $wrapper, + '#theme' => 'entity_property', + '#property_name' => $name, + '#access' => $property->access('view'), + '#entity_type' => $this->entityType, + ); + $content['#attached']['css']['entity.theme'] = drupal_get_path('module', 'entity') . '/theme/entity.theme.css'; + } + + /** + * Implements EntityAPIControllerInterface. + */ + public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) { + // For Field API and entity_prepare_view, the entities have to be keyed by + // (numeric) id. + $entities = entity_key_array_by_property($entities, $this->idKey); + if (!empty($this->entityInfo['fieldable'])) { + field_attach_prepare_view($this->entityType, $entities, $view_mode); + } + entity_prepare_view($this->entityType, $entities); + $langcode = isset($langcode) ? $langcode : $GLOBALS['language_content']->language; + + $view = array(); + foreach ($entities as $entity) { + $build = entity_build_content($this->entityType, $entity, $view_mode, $langcode); + $build += array( + // If the entity type provides an implementation, use this instead the + // generic one. + // @see template_preprocess_entity() + '#theme' => 'entity', + '#entity_type' => $this->entityType, + '#entity' => $entity, + '#view_mode' => $view_mode, + '#language' => $langcode, + '#page' => $page, + ); + // Allow modules to modify the structured entity. + drupal_alter(array($this->entityType . '_view', 'entity_view'), $build, $this->entityType); + $key = isset($entity->{$this->idKey}) ? $entity->{$this->idKey} : NULL; + $view[$this->entityType][$key] = $build; + } + return $view; + } +} + +/** + * A controller implementing exportables stored in the database. + */ +class EntityAPIControllerExportable extends EntityAPIController { + + protected $entityCacheByName = array(); + protected $nameKey, $statusKey, $moduleKey; + + /** + * Overridden. + * + * Allows specifying a name key serving as uniform identifier for this entity + * type while still internally we are using numeric identifieres. + */ + public function __construct($entityType) { + parent::__construct($entityType); + // Use the name key as primary identifier. + $this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey; + if (!empty($this->entityInfo['exportable'])) { + $this->statusKey = isset($this->entityInfo['entity keys']['status']) ? $this->entityInfo['entity keys']['status'] : 'status'; + $this->moduleKey = isset($this->entityInfo['entity keys']['module']) ? $this->entityInfo['entity keys']['module'] : 'module'; + } + } + + /** + * Support loading by name key. + */ + protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { + // Add the id condition ourself, as we might have a separate name key. + $query = parent::buildQuery(array(), $conditions, $revision_id); + if ($ids) { + // Support loading by numeric ids as well as by machine names. + $key = is_numeric(reset($ids)) ? $this->idKey : $this->nameKey; + $query->condition("base.$key", $ids, 'IN'); + } + return $query; + } + + /** + * Overridden to support passing numeric ids as well as names as $ids. + */ + public function load($ids = array(), $conditions = array()) { + $entities = array(); + + // Only do something if loaded by names. + if (!$ids || $this->nameKey == $this->idKey || is_numeric(reset($ids))) { + return parent::load($ids, $conditions); + } + + // Revisions are not statically cached, and require a different query to + // other conditions, so separate the revision id into its own variable. + if ($this->revisionKey && isset($conditions[$this->revisionKey])) { + $revision_id = $conditions[$this->revisionKey]; + unset($conditions[$this->revisionKey]); + } + else { + $revision_id = FALSE; + } + $passed_ids = !empty($ids) ? array_flip($ids) : FALSE; + + // Care about the static cache. + if ($this->cache && !$revision_id) { + $entities = $this->cacheGetByName($ids, $conditions); + } + // If any entities were loaded, remove them from the ids still to load. + if ($entities) { + $ids = array_keys(array_diff_key($passed_ids, $entities)); + } + + $entities_by_id = parent::load($ids, $conditions); + $entities += entity_key_array_by_property($entities_by_id, $this->nameKey); + + // Ensure that the returned array is keyed by numeric id and ordered the + // same as the original $ids array and remove any invalid ids. + $return = array(); + foreach ($passed_ids as $name => $value) { + if (isset($entities[$name])) { + $return[$entities[$name]->{$this->idKey}] = $entities[$name]; + } + } + return $return; + } + + /** + * Overridden. + * @see DrupalDefaultEntityController::cacheGet() + */ + protected function cacheGet($ids, $conditions = array()) { + if (!empty($this->entityCache) && $ids !== array()) { + $entities = $ids ? array_intersect_key($this->entityCache, array_flip($ids)) : $this->entityCache; + return $this->applyConditions($entities, $conditions); + } + return array(); + } + + /** + * Like cacheGet() but keyed by name. + */ + protected function cacheGetByName($names, $conditions = array()) { + if (!empty($this->entityCacheByName) && $names !== array() && $names) { + // First get the entities by ids, then apply the conditions. + // Generally, we make use of $this->entityCache, but if we are loading by + // name, we have to use $this->entityCacheByName. + $entities = array_intersect_key($this->entityCacheByName, array_flip($names)); + return $this->applyConditions($entities, $conditions); + } + return array(); + } + + protected function applyConditions($entities, $conditions = array()) { + if ($conditions) { + foreach ($entities as $key => $entity) { + $entity_values = (array) $entity; + // We cannot use array_diff_assoc() here because condition values can + // also be arrays, e.g. '$conditions = array('status' => array(1, 2))' + foreach ($conditions as $condition_key => $condition_value) { + if (is_array($condition_value)) { + if (!isset($entity_values[$condition_key]) || !in_array($entity_values[$condition_key], $condition_value)) { + unset($entities[$key]); + } + } + elseif (!isset($entity_values[$condition_key]) || $entity_values[$condition_key] != $condition_value) { + unset($entities[$key]); + } + } + } + } + return $entities; + } + + /** + * Overridden. + * @see DrupalDefaultEntityController::cacheSet() + */ + protected function cacheSet($entities) { + $this->entityCache += $entities; + // If we have a name key, also support static caching when loading by name. + if ($this->nameKey != $this->idKey) { + $this->entityCacheByName += entity_key_array_by_property($entities, $this->nameKey); + } + } + + /** + * Overridden. + * @see DrupalDefaultEntityController::attachLoad() + * + * Changed to call type-specific hook with the entities keyed by name if they + * have one. + */ + protected function attachLoad(&$queried_entities, $revision_id = FALSE) { + // Attach fields. + if ($this->entityInfo['fieldable']) { + if ($revision_id) { + field_attach_load_revision($this->entityType, $queried_entities); + } + else { + field_attach_load($this->entityType, $queried_entities); + } + } + + // Call hook_entity_load(). + foreach (module_implements('entity_load') as $module) { + $function = $module . '_entity_load'; + $function($queried_entities, $this->entityType); + } + // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are + // always the queried entities, followed by additional arguments set in + // $this->hookLoadArguments. + // For entities with a name key, pass the entities keyed by name to the + // specific load hook. + if ($this->nameKey != $this->idKey) { + $entities_by_name = entity_key_array_by_property($queried_entities, $this->nameKey); + } + else { + $entities_by_name = $queried_entities; + } + $args = array_merge(array($entities_by_name), $this->hookLoadArguments); + foreach (module_implements($this->entityInfo['load hook']) as $module) { + call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args); + } + } + + public function resetCache(array $ids = NULL) { + $this->cacheComplete = FALSE; + if (isset($ids)) { + foreach (array_intersect_key($this->entityCache, array_flip($ids)) as $id => $entity) { + unset($this->entityCacheByName[$this->entityCache[$id]->{$this->nameKey}]); + unset($this->entityCache[$id]); + } + } + else { + $this->entityCache = array(); + $this->entityCacheByName = array(); + } + } + + /** + * Overridden to care about reverted entities. + */ + public function delete($ids, DatabaseTransaction $transaction = NULL) { + $entities = $ids ? $this->load($ids) : FALSE; + if ($entities) { + parent::delete($ids, $transaction); + + foreach ($entities as $id => $entity) { + if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) { + entity_defaults_rebuild(array($this->entityType)); + break; + } + } + } + } + + /** + * Overridden to care about reverted bundle entities and to skip Rules. + */ + public function invoke($hook, $entity) { + if ($hook == 'delete') { + // To ease figuring out whether this is a revert, make sure that the + // entity status is updated in case the providing module has been + // disabled. + if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && !module_exists($entity->{$this->moduleKey})) { + $entity->{$this->statusKey} = ENTITY_CUSTOM; + } + $is_revert = entity_has_status($this->entityType, $entity, ENTITY_IN_CODE); + } + + if (!empty($this->entityInfo['fieldable']) && function_exists($function = 'field_attach_' . $hook)) { + $function($this->entityType, $entity); + } + + if (isset($this->entityInfo['bundle of']) && $type = $this->entityInfo['bundle of']) { + // Call field API bundle attachers for the entity we are a bundle of. + if ($hook == 'insert') { + field_attach_create_bundle($type, $entity->{$this->bundleKey}); + } + elseif ($hook == 'delete' && !$is_revert) { + field_attach_delete_bundle($type, $entity->{$this->bundleKey}); + } + elseif ($hook == 'update' && $id = $entity->{$this->nameKey}) { + if ($entity->original->{$this->bundleKey} != $entity->{$this->bundleKey}) { + field_attach_rename_bundle($type, $entity->original->{$this->bundleKey}, $entity->{$this->bundleKey}); + } + } + } + // Invoke the hook. + module_invoke_all($this->entityType . '_' . $hook, $entity); + // Invoke the respective entity level hook. + if ($hook == 'presave' || $hook == 'insert' || $hook == 'update' || $hook == 'delete') { + module_invoke_all('entity_' . $hook, $entity, $this->entityType); + } + } + + /** + * Overridden to care exportables that are overridden. + */ + public function save($entity, DatabaseTransaction $transaction = NULL) { + // Preload $entity->original by name key if necessary. + if (!empty($entity->{$this->nameKey}) && empty($entity->{$this->idKey}) && !isset($entity->original)) { + $entity->original = entity_load_unchanged($this->entityType, $entity->{$this->nameKey}); + } + // Update the status for entities getting overridden. + if (entity_has_status($this->entityType, $entity, ENTITY_IN_CODE) && empty($entity->is_rebuild)) { + $entity->{$this->statusKey} |= ENTITY_CUSTOM; + } + return parent::save($entity, $transaction); + } + + /** + * Overridden. + */ + public function export($entity, $prefix = '') { + $vars = get_object_vars($entity); + unset($vars[$this->statusKey], $vars[$this->moduleKey], $vars['is_new']); + if ($this->nameKey != $this->idKey) { + unset($vars[$this->idKey]); + } + return entity_var_json_export($vars, $prefix); + } + + /** + * Implements EntityAPIControllerInterface. + */ + public function view($entities, $view_mode = 'full', $langcode = NULL, $page = NULL) { + $view = parent::view($entities, $view_mode, $langcode, $page); + + if ($this->nameKey != $this->idKey) { + // Re-key the view array to be keyed by name. + $return = array(); + foreach ($view[$this->entityType] as $id => $content) { + $key = isset($content['#entity']->{$this->nameKey}) ? $content['#entity']->{$this->nameKey} : NULL; + $return[$this->entityType][$key] = $content; + } + $view = $return; + } + return $view; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/includes/entity.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/includes/entity.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,320 @@ +label() and + * $entity->uri() reflect this changes as well. + * + * Defaults for entity properties can be easily defined by adding class + * properties, e.g.: + * @code + * public $name = ''; + * public $count = 0; + * @endcode + */ +class Entity { + + protected $entityType; + protected $entityInfo; + protected $idKey, $nameKey, $statusKey; + protected $defaultLabel = FALSE; + + /** + * Creates a new entity. + * + * @see entity_create() + */ + public function __construct(array $values = array(), $entityType = NULL) { + if (empty($entityType)) { + throw new Exception('Cannot create an instance of Entity without a specified entity type.'); + } + $this->entityType = $entityType; + $this->setUp(); + // Set initial values. + foreach ($values as $key => $value) { + $this->$key = $value; + } + } + + /** + * Set up the object instance on construction or unserializiation. + */ + protected function setUp() { + $this->entityInfo = entity_get_info($this->entityType); + $this->idKey = $this->entityInfo['entity keys']['id']; + $this->nameKey = isset($this->entityInfo['entity keys']['name']) ? $this->entityInfo['entity keys']['name'] : $this->idKey; + $this->statusKey = empty($info['entity keys']['status']) ? 'status' : $info['entity keys']['status']; + } + + /** + * Returns the internal, numeric identifier. + * + * Returns the numeric identifier, even if the entity type has specified a + * name key. In the latter case, the numeric identifier is supposed to be used + * when dealing generically with entities or internally to refer to an entity, + * i.e. in a relational database. If unsure, use Entity:identifier(). + */ + public function internalIdentifier() { + return isset($this->{$this->idKey}) ? $this->{$this->idKey} : NULL; + } + + /** + * Returns the entity identifier, i.e. the entities name or numeric id. + * + * @return + * The identifier of the entity. If the entity type makes use of a name key, + * the name is returned, else the numeric id. + * + * @see entity_id() + */ + public function identifier() { + return isset($this->{$this->nameKey}) ? $this->{$this->nameKey} : NULL; + } + + /** + * Returns the info of the type of the entity. + * + * @see entity_get_info() + */ + public function entityInfo() { + return $this->entityInfo; + } + + /** + * Returns the type of the entity. + */ + public function entityType() { + return $this->entityType; + } + + /** + * Returns the bundle of the entity. + * + * @return + * The bundle of the entity. Defaults to the entity type if the entity type + * does not make use of different bundles. + */ + public function bundle() { + return !empty($this->entityInfo['entity keys']['bundle']) ? $this->{$this->entityInfo['entity keys']['bundle']} : $this->entityType; + } + + /** + * Returns the label of the entity. + * + * Modules may alter the label by specifying another 'label callback' using + * hook_entity_info_alter(). + * + * @see entity_label() + */ + public function label() { + // If the default label flag is enabled, this is being invoked recursively. + // In this case we need to use our default label callback directly. This may + // happen if a module provides a label callback implementation different + // from ours, but then invokes Entity::label() or entity_class_label() from + // there. + if ($this->defaultLabel || (isset($this->entityInfo['label callback']) && $this->entityInfo['label callback'] == 'entity_class_label')) { + return $this->defaultLabel(); + } + $this->defaultLabel = TRUE; + $label = entity_label($this->entityType, $this); + $this->defaultLabel = FALSE; + return $label; + } + + /** + * Defines the entity label if the 'entity_class_label' callback is used. + * + * Specify 'entity_class_label' as 'label callback' in hook_entity_info() to + * let the entity label point to this method. Override this in order to + * implement a custom default label. + */ + protected function defaultLabel() { + // Add in the translated specified label property. + return $this->getTranslation($this->entityInfo['entity keys']['label']); + } + + /** + * Returns the uri of the entity just as entity_uri(). + * + * Modules may alter the uri by specifying another 'uri callback' using + * hook_entity_info_alter(). + * + * @see entity_uri() + */ + public function uri() { + if (isset($this->entityInfo['uri callback']) && $this->entityInfo['uri callback'] == 'entity_class_uri') { + return $this->defaultUri(); + } + return entity_uri($this->entityType, $this); + } + + /** + * Override this in order to implement a custom default URI and specify + * 'entity_class_uri' as 'uri callback' hook_entity_info(). + */ + protected function defaultUri() { + return array('path' => 'default/' . $this->identifier()); + } + + /** + * Checks if the entity has a certain exportable status. + * + * @param $status + * A status constant, i.e. one of ENTITY_CUSTOM, ENTITY_IN_CODE, + * ENTITY_OVERRIDDEN or ENTITY_FIXED. + * + * @return + * For exportable entities TRUE if the entity has the status, else FALSE. + * In case the entity is not exportable, NULL is returned. + * + * @see entity_has_status() + */ + public function hasStatus($status) { + if (!empty($this->entityInfo['exportable'])) { + return isset($this->{$this->statusKey}) && ($this->{$this->statusKey} & $status) == $status; + } + } + + /** + * Permanently saves the entity. + * + * @see entity_save() + */ + public function save() { + return entity_get_controller($this->entityType)->save($this); + } + + /** + * Permanently deletes the entity. + * + * @see entity_delete() + */ + public function delete() { + $id = $this->identifier(); + if (isset($id)) { + entity_get_controller($this->entityType)->delete(array($id)); + } + } + + /** + * Exports the entity. + * + * @see entity_export() + */ + public function export($prefix = '') { + return entity_get_controller($this->entityType)->export($this, $prefix); + } + + /** + * Generate an array for rendering the entity. + * + * @see entity_view() + */ + public function view($view_mode = 'full', $langcode = NULL, $page = NULL) { + return entity_get_controller($this->entityType)->view(array($this), $view_mode, $langcode, $page); + } + + /** + * Builds a structured array representing the entity's content. + * + * @see entity_build_content() + */ + public function buildContent($view_mode = 'full', $langcode = NULL) { + return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode); + } + + /** + * Gets the raw, translated value of a property or field. + * + * Supports retrieving field translations as well as i18n string translations. + * + * Note that this returns raw data values, which might not reflect what + * has been declared for hook_entity_property_info() as no 'getter callbacks' + * are invoked or no referenced entities are loaded. For retrieving values + * reflecting the property info make use of entity metadata wrappers, see + * entity_metadata_wrapper(). + * + * @param $property_name + * The name of the property to return; e.g., 'title'. + * @param $langcode + * (optional) The language code of the language to which the value should + * be translated. If set to NULL, the default display language is being + * used. + * + * @return + * The raw, translated property value; or the raw, un-translated value if no + * translation is available. + * + * @todo Implement an analogous setTranslation() method for updating. + */ + public function getTranslation($property, $langcode = NULL) { + $all_info = entity_get_all_property_info($this->entityType); + // Assign by reference to avoid triggering notices if metadata is missing. + $property_info = &$all_info[$property]; + + if (!empty($property_info['translatable'])) { + if (!empty($property_info['field'])) { + return field_get_items($this->entityType, $this, $property, $langcode); + } + elseif (!empty($property_info['i18n string'])) { + $name = $this->entityInfo['module'] . ':' . $this->entityType . ':' . $this->identifier() . ':' . $property; + return entity_i18n_string($name, $this->$property, $langcode); + } + } + return $this->$property; + } + + /** + * Checks whether the entity is the default revision. + * + * @return Boolean + * + * @see entity_revision_is_default() + */ + public function isDefaultRevision() { + if (!empty($this->entityInfo['entity keys']['revision'])) { + $key = !empty($this->entityInfo['entity keys']['default revision']) ? $this->entityInfo['entity keys']['default revision'] : 'default_revision'; + return !empty($this->$key); + } + return TRUE; + } + + /** + * Magic method to only serialize what's necessary. + */ + public function __sleep() { + $vars = get_object_vars($this); + unset($vars['entityInfo'], $vars['idKey'], $vars['nameKey'], $vars['statusKey']); + // Also key the returned array with the variable names so the method may + // be easily overridden and customized. + return drupal_map_assoc(array_keys($vars)); + } + + /** + * Magic method to invoke setUp() on unserialization. + */ + public function __wakeup() { + $this->setUp(); + } +} + +/** + * These classes are deprecated by "Entity" and are only here for backward + * compatibility reasons. + */ +class EntityDB extends Entity {} +class EntityExtendable extends Entity {} +class EntityDBExtendable extends Entity {} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/includes/entity.property.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/includes/entity.property.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,650 @@ +language; + + if (empty($info)) { + if ($cache = cache_get("entity_property_info:$langcode")) { + $info = $cache->data; + } + else { + $info = module_invoke_all('entity_property_info'); + // Let other modules alter the entity info. + drupal_alter('entity_property_info', $info); + cache_set("entity_property_info:$langcode", $info); + } + } + return empty($entity_type) ? $info : (isset($info[$entity_type]) ? $info[$entity_type] : array()); +} + +/** + * Returns the default information for an entity property. + * + * @return + * An array of optional property information keys mapped to their defaults. + * + * @see hook_entity_property_info() + */ +function entity_property_info_defaults() { + return array( + 'type' => 'text', + 'getter callback' => 'entity_property_verbatim_get', + ); +} + +/** + * Gets an array of info about all properties of a given entity type. + * + * In contrast to entity_get_property_info(), this function returns info about + * all properties the entity might have, thus it adds an all properties assigned + * to entity bundles. + * + * @param $entity_type + * (optiona) The entity type to return properties for. + * + * @return + * An array of info about properties. If the type is ommitted, all known + * properties are returned. + */ +function entity_get_all_property_info($entity_type = NULL) { + if (!isset($entity_type)) { + // Retrieve all known properties. + $properties = array(); + foreach (entity_get_info() as $entity_type => $info) { + $properties += entity_get_all_property_info($entity_type); + } + return $properties; + } + // Else retrieve the properties of the given entity type only. + $info = entity_get_property_info($entity_type); + $info += array('properties' => array(), 'bundles' => array()); + // Add all bundle properties. + foreach ($info['bundles'] as $bundle => $bundle_info) { + $bundle_info += array('properties' => array()); + $info['properties'] += $bundle_info['properties']; + } + return $info['properties']; +} + +/** + * Queries for entities having the given property value. + * + * @param $entity_type + * The type of the entity. + * @param $property + * The name of the property to query for. + * @param $value + * A single property value or an array of possible values to query for. + * @param $limit + * Limit the numer of results. Defaults to 30. + * + * @return + * An array of entity ids or NULL if there is no information how to query for + * the given property. + */ +function entity_property_query($entity_type, $property, $value, $limit = 30) { + $properties = entity_get_all_property_info($entity_type); + $info = $properties[$property] + array('type' => 'text', 'queryable' => !empty($properties[$property]['schema field'])); + + // We still support the deprecated query callback, so just add in EFQ-based + // callbacks in case 'queryable' is set to TRUE and make use of the callback. + if ($info['queryable'] && empty($info['query callback'])) { + $info['query callback'] = !empty($info['field']) ? 'entity_metadata_field_query' : 'entity_metadata_table_query'; + } + + $type = $info['type']; + // Make sure an entity or a list of entities are passed on as identifiers + // with the help of the wrappers. For that ensure the data type matches the + // passed on value(s). + if (is_array($value) && !entity_property_list_extract_type($type)) { + $type = 'list<' . $type . '>'; + } + elseif (!is_array($value) && entity_property_list_extract_type($type)) { + $type = entity_property_list_extract_type($type); + } + + $wrapper = entity_metadata_wrapper($type, $value); + $value = $wrapper->value(array('identifier' => TRUE)); + + if (!empty($info['query callback'])) { + return $info['query callback']($entity_type, $property, $value, $limit); + } +} + +/** + * Resets the cached information of hook_entity_property_info(). + */ +function entity_property_info_cache_clear() { + drupal_static_reset('entity_get_property_info'); + // Clear all languages. + cache_clear_all('entity_property_info:', 'cache', TRUE); +} + +/** + * Implements hook_hook_info(). + */ +function entity_hook_info() { + $hook_info['entity_property_info'] = array( + 'group' => 'info', + ); + $hook_info['entity_property_info_alter'] = array( + 'group' => 'info', + ); + return $hook_info; +} + +/** + * Implements hook_field_info_alter(). + * Defines default property types for core field types. + */ +function entity_field_info_alter(&$field_info) { + if (module_exists('number')) { + $field_info['number_integer']['property_type'] = 'integer'; + $field_info['number_decimal']['property_type'] = 'decimal'; + $field_info['number_float']['property_type'] = 'decimal'; + } + if (module_exists('text')) { + $field_info['text']['property_type'] = 'text'; + $field_info['text']['property_callbacks'][] = 'entity_metadata_field_text_property_callback'; + $field_info['text_long']['property_type'] = 'text'; + $field_info['text_long']['property_callbacks'][] = 'entity_metadata_field_text_property_callback'; + $field_info['text_with_summary']['property_type'] = 'field_item_textsummary'; + $field_info['text_with_summary']['property_callbacks'][] = 'entity_metadata_field_text_property_callback'; + } + if (module_exists('list')) { + $field_info['list_integer']['property_type'] = 'integer'; + $field_info['list_boolean']['property_type'] = 'boolean'; + $field_info['list_float']['property_type'] = 'decimal'; + $field_info['list_text']['property_type'] = 'text'; + } + if (module_exists('taxonomy')) { + $field_info['taxonomy_term_reference']['property_type'] = 'taxonomy_term'; + $field_info['taxonomy_term_reference']['property_callbacks'][] = 'entity_metadata_field_term_reference_callback'; + } + if (module_exists('file')) { + // The callback specifies a custom data structure matching the file field + // items. We introduce a custom type name for this data structure. + $field_info['file']['property_type'] = 'field_item_file'; + $field_info['file']['property_callbacks'][] = 'entity_metadata_field_file_callback'; + } + if (module_exists('image')) { + // The callback specifies a custom data structure matching the image field + // items. We introduce a custom type name for this data structure. + $field_info['image']['property_type'] = 'field_item_image'; + $field_info['image']['property_callbacks'][] = 'entity_metadata_field_file_callback'; + $field_info['image']['property_callbacks'][] = 'entity_metadata_field_image_callback'; + } +} + +/** + * Implements hook_field_create_instance(). + * Clear the cache when a field instance changed. + */ +function entity_field_create_instance() { + entity_property_info_cache_clear(); +} + +/** + * Implements hook_field_delete_instance(). + * Clear the cache when a field instance changed. + */ +function entity_field_delete_instance() { + entity_property_info_cache_clear(); +} + +/** + * Implements hook_field_update_instance(). + * Clear the cache when a field instance changed. + */ +function entity_field_update_instance() { + entity_property_info_cache_clear(); +} + +/** + * Verifies that the given data can be safely used as the given type regardless + * of the PHP variable type of $data. Example: the string "15" is a valid + * integer, but "15nodes" is not. + * + * @return + * Whether the data is valid for the given type. + */ +function entity_property_verify_data_type($data, $type) { + // As this may be called very often statically cache the entity info using + // the fast pattern. + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + // Make use of the same static as entity info. + entity_get_info(); + $drupal_static_fast['entity_info'] = &drupal_static('entity_get_info'); + } + $info = &$drupal_static_fast['entity_info']; + + // First off check for entities, which may be represented by their ids too. + if (isset($info[$type])) { + if (is_object($data)) { + return TRUE; + } + elseif (isset($info[$type]['entity keys']['name'])) { + // Read the data type of the name key from the metadata if available. + $key = $info[$type]['entity keys']['name']; + $property_info = entity_get_property_info($type); + $property_type = isset($property_info['properties'][$key]['type']) ? $property_info['properties'][$key]['type'] : 'token'; + return entity_property_verify_data_type($data, $property_type); + } + return entity_property_verify_data_type($data, empty($info[$type]['fieldable']) ? 'text' : 'integer'); + } + + switch ($type) { + case 'site': + case 'unknown': + return TRUE; + case 'date': + case 'duration': + case 'integer': + return is_numeric($data) && strpos($data, '.') === FALSE; + case 'decimal': + return is_numeric($data); + case 'text': + return is_scalar($data); + case 'token': + return is_scalar($data) && preg_match('!^[a-z][a-z0-9_]*$!', $data); + case 'boolean': + return is_scalar($data) && (is_bool($data) || $data == 0 || $data == 1); + case 'uri': + return valid_url($data, TRUE); + case 'list': + return (is_array($data) && array_values($data) == $data) || (is_object($data) && $data instanceof EntityMetadataArrayObject); + case 'entity': + return is_object($data) && $data instanceof EntityDrupalWrapper; + default: + case 'struct': + return is_object($data) || is_array($data); + } +} + +/** + * Creates the entity object for an array of given property values. + * + * @param $entity_type + * The entity type to create an entity for. + * @param $values + * An array of values as described by the entity's property info. All entity + * properties of the given entity type that are marked as required, must be + * present. + * If the passed values have no matching property, their value will be + * assigned to the entity directly, without the use of the metadata-wrapper + * property. + * + * @return EntityDrupalWrapper + * An EntityDrupalWrapper wrapping the newly created entity or FALSE, if + * there were no information how to create the entity. + */ +function entity_property_values_create_entity($entity_type, $values = array()) { + if (entity_type_supports($entity_type, 'create')) { + $info = entity_get_info($entity_type); + // Create the initial entity by passing the values for all 'entity keys' + // to entity_create(). + $entity_keys = array_filter($info['entity keys']); + $creation_values = array_intersect_key($values, array_flip($entity_keys)); + + // In case the bundle key does not match the property that sets it, ensure + // the bundle key is initialized somehow, so entity_extract_ids() + // does not bail out during wrapper creation. + if (!empty($info['entity keys']['bundle'])) { + $creation_values += array($info['entity keys']['bundle'] => FALSE); + } + $entity = entity_create($entity_type, $creation_values); + + // Now set the remaining values using the wrapper. + $wrapper = entity_metadata_wrapper($entity_type, $entity); + foreach ($values as $key => $value) { + if (!in_array($key, $info['entity keys'])) { + if (isset($wrapper->$key)) { + $wrapper->$key->set($value); + } + else { + $entity->$key = $value; + } + } + } + // @todo: Once we require Drupal 7.7 or later, verify the entity has + // now a valid bundle and throw the EntityMalformedException if not. + return $wrapper; + } + return FALSE; +} + + +/** + * Extracts the contained type for a list type string like list. + * + * @return + * The contained type or FALSE, if the given type string is no list. + */ +function entity_property_list_extract_type($type) { + if (strpos($type, 'list<') === 0 && $type[strlen($type)-1] == '>') { + return substr($type, 5, -1); + } + return FALSE; +} + +/** + * Extracts the innermost type for a type string like list>. + * + * @param $type + * The type to examine. + * + * @return + * For list types, the innermost type. The type itself otherwise. + */ +function entity_property_extract_innermost_type($type) { + while (strpos($type, 'list<') === 0 && $type[strlen($type)-1] == '>') { + $type = substr($type, 5, -1); + } + return $type; +} + +/** + * Gets the property just as it is set in the data. + */ +function entity_property_verbatim_get($data, array $options, $name, $type, $info) { + $name = isset($info['schema field']) ? $info['schema field'] : $name; + if ((is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) && isset($data[$name])) { + return $data[$name]; + } + elseif (is_object($data) && isset($data->$name)) { + // Incorporate i18n_string translations. We may rely on the entity class + // here as its usage is required by the i18n integration. + if (isset($options['language']) && !empty($info['i18n string'])) { + return $data->getTranslation($name, $options['language']->language); + } + else { + return $data->$name; + } + } + return NULL; +} + +/** + * Date values are converted from ISO strings to timestamp if needed. + */ +function entity_property_verbatim_date_get($data, array $options, $name, $type, $info) { + $name = isset($info['schema field']) ? $info['schema field'] : $name; + return is_numeric($data[$name]) ? $data[$name] : strtotime($data[$name], REQUEST_TIME); +} + +/** + * Sets the property to the given value. May be used as 'setter callback'. + */ +function entity_property_verbatim_set(&$data, $name, $value, $langcode, $type, $info) { + $name = isset($info['schema field']) ? $info['schema field'] : $name; + if (is_array($data) || (is_object($data) && $data instanceof ArrayAccess)) { + $data[$name] = $value; + } + elseif (is_object($data)) { + $data->$name = $value; + } +} + +/** + * Gets the property using the getter method (named just like the property). + */ +function entity_property_getter_method($object, array $options, $name) { + // Remove any underscores as classes are expected to use CamelCase. + $method = strtr($name, array('_' => '')); + return $object->$method(); +} + +/** + * Sets the property to the given value using the setter method. May be used as + * 'setter callback'. + */ +function entity_property_setter_method($object, $name, $value) { + // Remove any underscores as classes are expected to use CamelCase. + $method = 'set' . strtr($name, array('_' => '')); + // Invoke the setProperty() method where 'Property' is the property name. + $object->$method($value); +} + +/** + * Getter callback for getting an array. Makes sure it's numerically indexed. + */ +function entity_property_get_list($data, array $options, $name) { + return isset($data->$name) ? array_values($data->$name) : array(); +} + +/** + * A validation callback ensuring the passed integer is positive. + */ +function entity_property_validate_integer_positive($value) { + return $value > 0; +} + +/** + * A validation callback ensuring the passed integer is non-negative. + */ +function entity_property_validate_integer_non_negative($value) { + return $value >= 0; +} + +/** + * A simple auto-creation callback for array based data structures. + */ +function entity_property_create_array($property_name, $context) { + return array(); +} + +/** + * Flattens the given options in single dimensional array. + * We don't depend on options module, so we cannot use options_array_flatten(). + * + * @see options_array_flatten() + */ +function entity_property_options_flatten($options) { + $result = array(); + foreach ($options as $key => $value) { + if (is_array($value)) { + $result += $value; + } + else { + $result[$key] = $value; + } + } + return $result; +} + +/** + * Defines info for the properties of the text_formatted data structure. + */ +function entity_property_text_formatted_info() { + return array( + 'value' => array( + 'type' => 'text', + 'label' => t('Text'), + 'sanitized' => TRUE, + 'getter callback' => 'entity_metadata_field_text_get', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'raw getter callback' => 'entity_property_verbatim_get', + ), + 'summary' => array( + 'type' => 'text', + 'label' => t('Summary'), + 'sanitized' => TRUE, + 'getter callback' => 'entity_metadata_field_text_get', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'raw getter callback' => 'entity_property_verbatim_get', + ), + 'format' => array( + 'type' => 'token', + 'label' => t('Text format'), + 'options list' => 'entity_metadata_field_text_formats', + 'getter callback' => 'entity_property_verbatim_get', + ), + ); +} + +/** + * Defines info for the properties of the field_item_textsummary data structure. + */ +function entity_property_field_item_textsummary_info() { + return array( + 'value' => array( + 'type' => 'text', + 'label' => t('Text'), + 'setter callback' => 'entity_property_verbatim_set', + ), + 'summary' => array( + 'type' => 'text', + 'label' => t('Summary'), + 'setter callback' => 'entity_property_verbatim_set', + ), + ); +} + +/** + * Defines info for the properties of the file-field item data structure. + */ +function entity_property_field_item_file_info() { + $properties['file'] = array( + 'type' => 'file', + 'label' => t('The file.'), + 'getter callback' => 'entity_metadata_field_file_get', + 'setter callback' => 'entity_metadata_field_file_set', + 'required' => TRUE, + ); + $properties['description'] = array( + 'type' => 'text', + 'label' => t('The file description'), + 'setter callback' => 'entity_property_verbatim_set', + ); + $properties['display'] = array( + 'type' => 'boolean', + 'label' => t('Whether the file is being displayed.'), + 'setter callback' => 'entity_property_verbatim_set', + ); + return $properties; +} + +/** + * Defines info for the properties of the image-field item data structure. + */ +function entity_property_field_item_image_info() { + $properties['file'] = array( + 'type' => 'file', + 'label' => t('The image file.'), + 'getter callback' => 'entity_metadata_field_file_get', + 'setter callback' => 'entity_metadata_field_file_set', + 'required' => TRUE, + ); + $properties['alt'] = array( + 'type' => 'text', + 'label' => t('The "Alt" attribute text'), + 'setter callback' => 'entity_property_verbatim_set', + ); + $properties['title'] = array( + 'type' => 'text', + 'label' => t('The "Title" attribute text'), + 'setter callback' => 'entity_property_verbatim_set', + ); + return $properties; +} + + +/** + * Previously, hook_entity_property_info() has been provided by the removed + * entity metadata module. To provide backward compatibility for provided + * helpers that may be specified in hook_entity_property_info(), the following + * (deprecated) functions are provided. + */ + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_verbatim_get($data, array $options, $name) { + return entity_property_verbatim_get($data, $options, $name); +} + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_verbatim_set($data, $name, $value) { + return entity_property_verbatim_set($data, $name, $value); +} + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_getter_method($object, array $options, $name) { + return entity_property_getter_method($object, $options, $name); +} + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_setter_method($object, $name, $value) { + entity_property_setter_method($object, $name, $value); +} + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_get_list($data, array $options, $name) { + return entity_property_get_list($data, $options, $name); +} + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_validate_integer_positive($value) { + return entity_property_validate_integer_positive($value); +} + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_validate_integer_non_negative($value) { + return entity_property_validate_integer_non_negative($value); +} + +/** + * Deprecated. + * Do not make use of this function, instead use the new one. + */ +function entity_metadata_text_formatted_properties() { + return entity_property_text_formatted_info(); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/includes/entity.ui.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/includes/entity.ui.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,834 @@ +entityType = $entity_type; + $this->entityInfo = $entity_info; + $this->path = $this->entityInfo['admin ui']['path']; + $this->statusKey = empty($this->entityInfo['entity keys']['status']) ? 'status' : $this->entityInfo['entity keys']['status']; + } + + /** + * Provides definitions for implementing hook_menu(). + */ + public function hook_menu() { + $items = array(); + // Set this on the object so classes that extend hook_menu() can use it. + $this->id_count = count(explode('/', $this->path)); + $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object'; + $plural_label = isset($this->entityInfo['plural label']) ? $this->entityInfo['plural label'] : $this->entityInfo['label'] . 's'; + + $items[$this->path] = array( + 'title' => $plural_label, + 'page callback' => 'drupal_get_form', + 'page arguments' => array($this->entityType . '_overview_form', $this->entityType), + 'description' => 'Manage ' . $plural_label . '.', + 'access callback' => 'entity_access', + 'access arguments' => array('view', $this->entityType), + 'file' => 'includes/entity.ui.inc', + ); + $items[$this->path . '/list'] = array( + 'title' => 'List', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items[$this->path . '/add'] = array( + 'title callback' => 'entity_ui_get_action_title', + 'title arguments' => array('add', $this->entityType), + 'page callback' => 'entity_ui_get_form', + 'page arguments' => array($this->entityType, NULL, 'add'), + 'access callback' => 'entity_access', + 'access arguments' => array('create', $this->entityType), + 'type' => MENU_LOCAL_ACTION, + ); + $items[$this->path . '/manage/' . $wildcard] = array( + 'title' => 'Edit', + 'title callback' => 'entity_label', + 'title arguments' => array($this->entityType, $this->id_count + 1), + 'page callback' => 'entity_ui_get_form', + 'page arguments' => array($this->entityType, $this->id_count + 1), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('update', $this->entityType, $this->id_count + 1), + ); + $items[$this->path . '/manage/' . $wildcard . '/edit'] = array( + 'title' => 'Edit', + 'load arguments' => array($this->entityType), + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + + // Clone form, a special case for the edit form. + $items[$this->path . '/manage/' . $wildcard . '/clone'] = array( + 'title' => 'Clone', + 'page callback' => 'entity_ui_get_form', + 'page arguments' => array($this->entityType, $this->id_count + 1, 'clone'), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('create', $this->entityType), + ); + // Menu item for operations like revert and delete. + $items[$this->path . '/manage/' . $wildcard . '/%'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count + 1, $this->id_count + 2), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('delete', $this->entityType, $this->id_count + 1), + 'file' => 'includes/entity.ui.inc', + ); + + if (!empty($this->entityInfo['exportable'])) { + // Menu item for importing an entity. + $items[$this->path . '/import'] = array( + 'title callback' => 'entity_ui_get_action_title', + 'title arguments' => array('import', $this->entityType), + 'page callback' => 'drupal_get_form', + 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, NULL, 'import'), + 'access callback' => 'entity_access', + 'access arguments' => array('create', $this->entityType), + 'file' => 'includes/entity.ui.inc', + 'type' => MENU_LOCAL_ACTION, + ); + } + + if (!empty($this->entityInfo['admin ui']['file'])) { + // Add in the include file for the entity form. + foreach (array("/manage/$wildcard", "/manage/$wildcard/clone", '/add') as $path_end) { + $items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file']; + $items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']); + } + } + return $items; + } + + /** + * Provides definitions for implementing hook_forms(). + * + * Use per bundle form ids if possible, such that easy per bundle alterations + * are supported too. + * + * Note that for performance reasons, this method is only invoked for forms, + * which receive the entity_type as first argument. Thus any forms added, must + * follow that pattern. + * + * @see entity_forms() + */ + public function hook_forms() { + // The overview and the operation form are implemented by the controller, + // the callback and validation + submit handlers just invoke the controller. + $forms[$this->entityType . '_overview_form'] = array( + 'callback' => 'entity_ui_overview_form', + 'wrapper_callback' => 'entity_ui_form_defaults', + ); + $forms[$this->entityType . '_operation_form'] = array( + 'callback' => 'entity_ui_operation_form', + 'wrapper_callback' => 'entity_ui_form_defaults', + ); + + // The entity form (ENTITY_TYPE_form) handles editing, adding and cloning. + // For that form, the wrapper callback entity_ui_main_form_defaults() gets + // directly invoked via entity_ui_get_form(). + // If there are bundles though, we use form ids that include the bundle name + // (ENTITY_TYPE_edit_BUNDLE_NAME_form) to enable per bundle alterations + // as well as alterations based upon the base form id (ENTITY_TYPE_form). + if (!(count($this->entityInfo['bundles']) == 1 && isset($this->entityInfo['bundles'][$this->entityType]))) { + foreach ($this->entityInfo['bundles'] as $bundle => $bundle_info) { + $forms[$this->entityType . '_edit_' . $bundle . '_form']['callback'] = $this->entityType . '_form'; + // Again the wrapper callback is invoked by entity_ui_get_form() anyway. + } + } + return $forms; + } + + /** + * Builds the entity overview form. + */ + public function overviewForm($form, &$form_state) { + // By default just show a simple overview for all entities. + $form['table'] = $this->overviewTable(); + $form['pager'] = array('#theme' => 'pager'); + return $form; + } + + /** + * Overview form validation callback. + * + * @param $form + * The form array of the overview form. + * @param $form_state + * The overview form state which will be used for validating. + */ + public function overviewFormValidate($form, &$form_state) {} + + /** + * Overview form submit callback. + * + * @param $form + * The form array of the overview form. + * @param $form_state + * The overview form state which will be used for submitting. + */ + public function overviewFormSubmit($form, &$form_state) {} + + + /** + * Generates the render array for a overview table for arbitrary entities + * matching the given conditions. + * + * @param $conditions + * An array of conditions as needed by entity_load(). + + * @return Array + * A renderable array. + */ + public function overviewTable($conditions = array()) { + + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $this->entityType); + + // Add all conditions to query. + foreach ($conditions as $key => $value) { + $query->propertyCondition($key, $value); + } + + if ($this->overviewPagerLimit) { + $query->pager($this->overviewPagerLimit); + } + + $results = $query->execute(); + + $ids = isset($results[$this->entityType]) ? array_keys($results[$this->entityType]) : array(); + $entities = $ids ? entity_load($this->entityType, $ids) : array(); + ksort($entities); + + $rows = array(); + foreach ($entities as $entity) { + $rows[] = $this->overviewTableRow($conditions, entity_id($this->entityType, $entity), $entity); + } + + $render = array( + '#theme' => 'table', + '#header' => $this->overviewTableHeaders($conditions, $rows), + '#rows' => $rows, + '#empty' => t('None.'), + ); + return $render; + } + + /** + * Generates the table headers for the overview table. + */ + protected function overviewTableHeaders($conditions, $rows, $additional_header = array()) { + $header = $additional_header; + array_unshift($header, t('Label')); + if (!empty($this->entityInfo['exportable'])) { + $header[] = t('Status'); + } + // Add operations with the right colspan. + $header[] = array('data' => t('Operations'), 'colspan' => $this->operationCount()); + return $header; + } + + /** + * Returns the operation count for calculating colspans. + */ + protected function operationCount() { + $count = 3; + $count += !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui') ? 2 : 0; + $count += !empty($this->entityInfo['exportable']) ? 1 : 0; + $count += !empty($this->entityInfo['i18n controller class']) ? 1 : 0; + return $count; + } + + /** + * Generates the row for the passed entity and may be overridden in order to + * customize the rows. + * + * @param $additional_cols + * Additional columns to be added after the entity label column. + */ + protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) { + $entity_uri = entity_uri($this->entityType, $entity); + + $row[] = array('data' => array( + '#theme' => 'entity_ui_overview_item', + '#label' => entity_label($this->entityType, $entity), + '#name' => !empty($this->entityInfo['exportable']) ? entity_id($this->entityType, $entity) : FALSE, + '#url' => $entity_uri ? $entity_uri : FALSE, + '#entity_type' => $this->entityType), + ); + + // Add in any passed additional cols. + foreach ($additional_cols as $col) { + $row[] = $col; + } + + // Add a row for the exportable status. + if (!empty($this->entityInfo['exportable'])) { + $row[] = array('data' => array( + '#theme' => 'entity_status', + '#status' => $entity->{$this->statusKey}, + )); + } + // In case this is a bundle, we add links to the field ui tabs. + $field_ui = !empty($this->entityInfo['bundle of']) && entity_type_is_fieldable($this->entityInfo['bundle of']) && module_exists('field_ui'); + // For exportable entities we add an export link. + $exportable = !empty($this->entityInfo['exportable']); + // If i18n integration is enabled, add a link to the translate tab. + $i18n = !empty($this->entityInfo['i18n controller class']); + + // Add operations depending on the status. + if (entity_has_status($this->entityType, $entity, ENTITY_FIXED)) { + $row[] = array('data' => l(t('clone'), $this->path . '/manage/' . $id . '/clone'), 'colspan' => $this->operationCount()); + } + else { + $row[] = l(t('edit'), $this->path . '/manage/' . $id); + + if ($field_ui) { + $row[] = l(t('manage fields'), $this->path . '/manage/' . $id . '/fields'); + $row[] = l(t('manage display'), $this->path . '/manage/' . $id . '/display'); + } + if ($i18n) { + $row[] = l(t('translate'), $this->path . '/manage/' . $id . '/translate'); + } + if ($exportable) { + $row[] = l(t('clone'), $this->path . '/manage/' . $id . '/clone'); + } + + if (empty($this->entityInfo['exportable']) || !entity_has_status($this->entityType, $entity, ENTITY_IN_CODE)) { + $row[] = l(t('delete'), $this->path . '/manage/' . $id . '/delete', array('query' => drupal_get_destination())); + } + elseif (entity_has_status($this->entityType, $entity, ENTITY_OVERRIDDEN)) { + $row[] = l(t('revert'), $this->path . '/manage/' . $id . '/revert', array('query' => drupal_get_destination())); + } + else { + $row[] = ''; + } + } + if ($exportable) { + $row[] = l(t('export'), $this->path . '/manage/' . $id . '/export'); + } + return $row; + } + + + /** + * Builds the operation form. + * + * For the export operation a serialized string of the entity is directly + * shown in the form (no submit function needed). + */ + public function operationForm($form, &$form_state, $entity, $op) { + switch ($op) { + case 'revert': + $label = entity_label($this->entityType, $entity); + $confirm_question = t('Are you sure you want to revert the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label)); + return confirm_form($form, $confirm_question, $this->path); + + case 'delete': + $label = entity_label($this->entityType, $entity); + $confirm_question = t('Are you sure you want to delete the %entity %label?', array('%entity' => $this->entityInfo['label'], '%label' => $label)); + return confirm_form($form, $confirm_question, $this->path); + + case 'export': + if (!empty($this->entityInfo['exportable'])) { + $export = entity_export($this->entityType, $entity); + $form['export'] = array( + '#type' => 'textarea', + '#title' => t('Export'), + '#description' => t('For importing copy the content of the text area and paste it into the import page.'), + '#rows' => 25, + '#default_value' => $export, + ); + return $form; + } + + case 'import': + $form['import'] = array( + '#type' => 'textarea', + '#title' => t('Import'), + '#description' => t('Paste an exported %entity_type here.', array('%entity_type' => $this->entityInfo['label'])), + '#rows' => 20, + ); + $form['overwrite'] = array( + '#title' => t('Overwrite'), + '#type' => 'checkbox', + '#description' => t('If checked, any existing %entity with the same identifier will be replaced by the import.', array('%entity' => $this->entityInfo['label'])), + '#default_value' => FALSE, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Import'), + ); + return $form; + } + drupal_not_found(); + exit; + } + + /** + * Operation form validation callback. + */ + public function operationFormValidate($form, &$form_state) { + if ($form_state['op'] == 'import') { + if ($entity = entity_import($this->entityType, $form_state['values']['import'])) { + // Store the successfully imported entity in $form_state. + $form_state[$this->entityType] = $entity; + if (!$form_state['values']['overwrite']) { + // Check for existing entities with the same identifier. + $id = entity_id($this->entityType, $entity); + $entities = entity_load($this->entityType, array($id)); + if (!empty($entities)) { + $label = entity_label($this->entityType, $entity); + $vars = array('%entity' => $this->entityInfo['label'], '%label' => $label); + form_set_error('import', t('Import of %entity %label failed, a %entity with the same machine name already exists. Check the overwrite option to replace it.', $vars)); + } + } + } + else { + form_set_error('import', t('Import failed.')); + } + } + } + + /** + * Operation form submit callback. + */ + public function operationFormSubmit($form, &$form_state) { + $msg = $this->applyOperation($form_state['op'], $form_state[$this->entityType]); + drupal_set_message($msg); + $form_state['redirect'] = $this->path; + } + + /** + * Applies an operation to the given entity. + * + * Note: the export operation is directly carried out by the operationForm() + * method. + * + * @param string $op + * The operation (revert, delete or import). + * @param $entity + * The entity to manipulate. + * + * @return + * The status message of what has been applied. + */ + public function applyOperation($op, $entity) { + $label = entity_label($this->entityType, $entity); + $vars = array('%entity' => $this->entityInfo['label'], '%label' => $label); + $id = entity_id($this->entityType, $entity); + $edit_link = l(t('edit'), $this->path . '/manage/' . $id . '/edit'); + + switch ($op) { + case 'revert': + entity_delete($this->entityType, $id); + watchdog($this->entityType, 'Reverted %entity %label to the defaults.', $vars, WATCHDOG_NOTICE, $edit_link); + return t('Reverted %entity %label to the defaults.', $vars); + + case 'delete': + entity_delete($this->entityType, $id); + watchdog($this->entityType, 'Deleted %entity %label.', $vars); + return t('Deleted %entity %label.', $vars); + + case 'import': + // First check if there is any existing entity with the same ID. + $id = entity_id($this->entityType, $entity); + $entities = entity_load($this->entityType, array($id)); + if ($existing_entity = reset($entities)) { + // Copy DB id and remove the new indicator to overwrite the DB record. + $idkey = $this->entityInfo['entity keys']['id']; + $entity->{$idkey} = $existing_entity->{$idkey}; + unset($entity->is_new); + } + entity_save($this->entityType, $entity); + watchdog($this->entityType, 'Imported %entity %label.', $vars); + return t('Imported %entity %label.', $vars); + + default: + return FALSE; + } + } + + /** + * Entity submit builder invoked via entity_ui_form_submit_build_entity(). + * + * Extracts the form values and updates the entity. + * + * The provided implementation makes use of the helper function + * entity_form_submit_build_entity() provided by core, which already invokes + * the field API attacher for fieldable entities. + * + * @return + * The updated entity. + * + * @see entity_ui_form_submit_build_entity() + */ + public function entityFormSubmitBuildEntity($form, &$form_state) { + // Add the bundle property to the entity if the entity type supports bundles + // and the form provides a value for the bundle key. Especially new entities + // need to have their bundle property pre-populated before we invoke + // entity_form_submit_build_entity(). + if (!empty($this->entityInfo['entity keys']['bundle']) && isset($form_state['values'][$this->entityInfo['entity keys']['bundle']])) { + $form_state[$this->entityType]->{$this->entityInfo['entity keys']['bundle']} = $form_state['values'][$this->entityInfo['entity keys']['bundle']]; + } + entity_form_submit_build_entity($this->entityType, $form_state[$this->entityType], $form, $form_state); + return $form_state[$this->entityType]; + } +} + +/** + * UI controller providing UI for content entities. + * + * For a controller providing UI for bundleable content entities, see + * EntityBundleableUIController. + * For a controller providing admin UI for configuration entities, see + * EntityDefaultUIController. + */ +class EntityContentUIController extends EntityDefaultUIController { + + /** + * Provides definitions for implementing hook_menu(). + */ + public function hook_menu() { + $items = parent::hook_menu(); + $wildcard = isset($this->entityInfo['admin ui']['menu wildcard']) ? $this->entityInfo['admin ui']['menu wildcard'] : '%entity_object'; + + // Unset the manage entity path, as the provided UI is for admin entities. + unset($items[$this->path]); + + $defaults = array( + 'file' => $this->entityInfo['admin ui']['file'], + 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']), + ); + + // Add view, edit and delete menu items for content entities. + $items[$this->path . '/' . $wildcard] = array( + 'title callback' => 'entity_ui_get_page_title', + 'title arguments' => array('view', $this->entityType, $this->id_count), + 'page callback' => 'entity_ui_entity_page_view', + 'page arguments' => array($this->id_count), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('view', $this->entityType, $this->id_count), + ) + $defaults; + $items[$this->path . '/' . $wildcard . '/view'] = array( + 'title' => 'View', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'load arguments' => array($this->entityType), + 'weight' => -10, + ) + $defaults; + $items[$this->path . '/' . $wildcard . '/edit'] = array( + 'page callback' => 'entity_ui_get_form', + 'page arguments' => array($this->entityType, $this->id_count), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('edit', $this->entityType, $this->id_count), + 'title' => 'Edit', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + ) + $defaults; + $items[$this->path . '/' . $wildcard . '/delete'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array($this->entityType . '_operation_form', $this->entityType, $this->id_count, 'delete'), + 'load arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('delete', $this->entityType, $this->id_count), + 'title' => 'Delete', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'file' => $this->entityInfo['admin ui']['file'], + 'file path' => isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']), + ) + $defaults; + + return $items; + } + + /** + * Operation form submit callback. + */ + public function operationFormSubmit($form, &$form_state) { + parent::operationFormSubmit($form, $form_state); + // The manage entity path is unset for the content entity UI. + $form_state['redirect'] = ''; + } +} + +/** + * UI controller providing UI for bundleable content entities. + * + * Adds a bundle selection page to the entity/add path, analogously to the + * node/add path. + */ +class EntityBundleableUIController extends EntityContentUIController { + + /** + * Provides definitions for implementing hook_menu(). + */ + public function hook_menu() { + $items = parent::hook_menu(); + + // Extend the 'add' path. + $items[$this->path . '/add'] = array( + 'title callback' => 'entity_ui_get_action_title', + 'title arguments' => array('add', $this->entityType), + 'page callback' => 'entity_ui_bundle_add_page', + 'page arguments' => array($this->entityType), + 'access callback' => 'entity_access', + 'access arguments' => array('create', $this->entityType), + 'type' => MENU_LOCAL_ACTION, + ); + $items[$this->path . '/add/%'] = array( + 'title callback' => 'entity_ui_get_action_title', + 'title arguments' => array('add', $this->entityType, $this->id_count + 1), + 'page callback' => 'entity_ui_get_bundle_add_form', + 'page arguments' => array($this->entityType, $this->id_count + 1), + 'access callback' => 'entity_access', + 'access arguments' => array('create', $this->entityType), + ); + + if (!empty($this->entityInfo['admin ui']['file'])) { + // Add in the include file for the entity form. + foreach (array('/add', '/add/%') as $path_end) { + $items[$this->path . $path_end]['file'] = $this->entityInfo['admin ui']['file']; + $items[$this->path . $path_end]['file path'] = isset($this->entityInfo['admin ui']['file path']) ? $this->entityInfo['admin ui']['file path'] : drupal_get_path('module', $this->entityInfo['module']); + } + } + + return $items; + } +} + +/** + * Form builder function for the overview form. + * + * @see EntityDefaultUIController::overviewForm() + */ +function entity_ui_overview_form($form, &$form_state, $entity_type) { + return entity_ui_controller($entity_type)->overviewForm($form, $form_state); +} + +/** + * Form builder for the entity operation form. + * + * @see EntityDefaultUIController::operationForm() + */ +function entity_ui_operation_form($form, &$form_state, $entity_type, $entity, $op) { + $form_state['op'] = $op; + return entity_ui_controller($entity_type)->operationForm($form, $form_state, $entity, $op); +} + +/** + * Form wrapper the main entity form. + * + * @see entity_ui_form_defaults() + */ +function entity_ui_main_form_defaults($form, &$form_state, $entity = NULL, $op = NULL) { + // Now equals entity_ui_form_defaults() but is still here to keep backward + // compatability. + return entity_ui_form_defaults($form, $form_state, $form_state['entity_type'], $entity, $op); +} + +/** + * Clones the entity object and makes sure it will get saved as new entity. + * + * @return + * The cloned entity object. + */ +function entity_ui_clone_entity($entity_type, $entity) { + // Clone the entity and make sure it will get saved as a new entity. + $entity = clone $entity; + + $entity_info = entity_get_info($entity_type); + $entity->{$entity_info['entity keys']['id']} = FALSE; + if (!empty($entity_info['entity keys']['name'])) { + $entity->{$entity_info['entity keys']['name']} = FALSE; + } + $entity->is_new = TRUE; + + // Make sure the status of a cloned exportable is custom. + if (!empty($entity_info['exportable'])) { + $status_key = isset($entity_info['entity keys']['status']) ? $entity_info['entity keys']['status'] : 'status'; + $entity->$status_key = ENTITY_CUSTOM; + } + return $entity; +} + +/** + * Form wrapper callback for all entity ui forms. + * + * This callback makes sure the form state is properly initialized and sets + * some useful default titles. + * + * @see EntityDefaultUIController::hook_forms() + */ +function entity_ui_form_defaults($form, &$form_state, $entity_type, $entity = NULL, $op = NULL) { + $defaults = array( + 'entity_type' => $entity_type, + ); + if (isset($entity)) { + $defaults[$entity_type] = $entity; + } + if (isset($op)) { + $defaults['op'] = $op; + } + $form_state += $defaults; + if (isset($op)) { + drupal_set_title(entity_ui_get_page_title($op, $entity_type, $entity), PASS_THROUGH); + } + // Add in handlers pointing to the controller for the forms implemented by it. + if (isset($form_state['build_info']['base_form_id']) && $form_state['build_info']['base_form_id'] != $entity_type . '_form') { + $form['#validate'][] = 'entity_ui_controller_form_validate'; + $form['#submit'][] = 'entity_ui_controller_form_submit'; + } + return $form; +} + +/** + * Validation callback for forms implemented by the UI controller. + */ +function entity_ui_controller_form_validate($form, &$form_state) { + // Remove 'entity_ui_' prefix and the '_form' suffix. + $base = substr($form_state['build_info']['base_form_id'], 10, -5); + $method = $base . 'FormValidate'; + entity_ui_controller($form_state['entity_type'])->$method($form, $form_state); +} + +/** + * Submit callback for forms implemented by the UI controller. + */ +function entity_ui_controller_form_submit($form, &$form_state) { + // Remove 'entity_ui_' prefix and the '_form' suffix. + $base = substr($form_state['build_info']['base_form_id'], 10, -5); + $method = $base . 'FormSubmit'; + entity_ui_controller($form_state['entity_type'])->$method($form, $form_state); +} + +/** + * Gets the page title for the passed operation. + */ +function entity_ui_get_page_title($op, $entity_type, $entity = NULL) { + $label = entity_label($entity_type, $entity); + switch ($op) { + case 'view': + return $label; + case 'edit': + return t('Edit @label', array('@label' => $label)); + case 'clone': + return t('Clone @label', array('@label' => $label)); + case 'revert': + return t('Revert @label', array('@label' => $label)); + case 'delete': + return t('Delete @label', array('@label' => $label)); + case 'export': + return t('Export @label', array('@label' => $label)); + } + if (isset($entity)) { + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + } + return entity_ui_get_action_title($op, $entity_type, $bundle); +} + +/** + * Gets the page/menu title for local action operations. + * + * @param $op + * The current operation. One of 'add' or 'import'. + * @param $entity_type + * The entity type. + * @param $bundle_name + * (Optional) The name of the bundle. May be NULL if the bundle name is not + * relevant to the current page. If the entity type has only one bundle, or no + * bundles, this will be the same as the entity type. + */ +function entity_ui_get_action_title($op, $entity_type, $bundle_name = NULL) { + $info = entity_get_info($entity_type); + switch ($op) { + case 'add': + if (isset($bundle_name) && $bundle_name != $entity_type) { + return t('Add @bundle_name @entity_type', array( + '@bundle_name' => drupal_strtolower($info['bundles'][$bundle_name]['label']), + '@entity_type' => drupal_strtolower($info['label']), + )); + } + else { + return t('Add @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); + } + case 'import': + return t('Import @entity_type', array('@entity_type' => drupal_strtolower($info['label']))); + } +} + +/** + * Submit builder for the main entity form, which extracts the form values and updates the entity. + * + * This is a helper function for entities making use of the entity UI + * controller. + * + * @return + * The updated entity. + * + * @see EntityDefaultUIController::hook_forms() + * @see EntityDefaultUIController::entityFormSubmitBuildEntity() + */ +function entity_ui_form_submit_build_entity($form, &$form_state) { + return entity_ui_controller($form_state['entity_type'])->entityFormSubmitBuildEntity($form, $form_state); +} + +/** + * Validation callback for machine names of exportables. + * + * We don't allow numeric machine names, as entity_load() treats them as the + * numeric identifier and they are easily confused with ids in general. + */ +function entity_ui_validate_machine_name($element, &$form_state) { + if (is_numeric($element['#value'])) { + form_error($element, t('Machine-readable names must not consist of numbers only.')); + } +} + +/** + * Returns HTML for an entity on the entity overview listing. + * + * @ingroup themeable + */ +function theme_entity_ui_overview_item($variables) { + $output = $variables['url'] ? l($variables['label'], $variables['url']['path'], $variables['url']['options']) : check_plain($variables['label']); + if ($variables['name']) { + $output .= ' (' . t('Machine name') . ': ' . check_plain($variables['name']) . ')'; + } + return $output; +} + +/** + * Page callback for viewing an entity. + * + * @param Entity $entity + * The entity to be rendered. + * + * @return array + * A renderable array of the entity in full view mode. + */ +function entity_ui_entity_page_view($entity) { + return $entity->view('full', NULL, TRUE); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/includes/entity.wrapper.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/includes/entity.wrapper.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1194 @@ +type = $type; + $this->info = $info + array( + 'langcode' => NULL, + ); + $this->info['type'] = $type; + if (isset($data)) { + $this->set($data); + } + } + + /** + * Gets info about the wrapped data. + * + * @return Array + * Keys set are all keys as specified for a property in hook_entity_info() + * as well as possible the following keys: + * - name: If this wraps a property, the name of the property. + * - parent: The parent wrapper, if any. + * - langcode: The language code, if this data is language specific. + */ + public function info() { + return $this->info; + } + + /** + * Gets the (entity)type of the wrapped data. + */ + public function type() { + return $this->type; + } + + /** + * Returns the wrapped data. If no options are given the data is returned as + * described in the info. + * + * @param $options + * (optional) A keyed array of options: + * - sanitize: A boolean flag indicating that textual properties should be + * sanitized for display to a web browser. Defaults to FALSE. + * - decode: If set to TRUE and some textual data is already sanitized, it + * strips HTML tags and decodes HTML entities. Defaults to FALSE. + * + * @return + * The value of the wrapped data. If the data property is not set, NULL + * is returned. + * + * @throws EntityMetadataWrapperException + * In case there are no data values available to the wrapper, an exception + * is thrown. E.g. if the value for an entity property is to be retrieved + * and there is no entity available, the exception is thrown. However, if + * an entity is available but the property is not set, NULL is returned. + */ + public function value(array $options = array()) { + if (!$this->dataAvailable() && isset($this->info['parent'])) { + throw new EntityMetadataWrapperException('Missing data values.'); + } + if (!isset($this->data) && isset($this->info['name'])) { + $this->data = $this->info['parent']->getPropertyValue($this->info['name'], $this->info); + } + return $this->data; + } + + /** + * Returns the raw, unprocessed data. Most times this is the same as returned + * by value(), however for already processed and sanitized textual data, this + * will return the unprocessed data in contrast to value(). + */ + public function raw() { + if (!$this->dataAvailable()) { + throw new EntityMetadataWrapperException('Missing data values.'); + } + if (isset($this->info['name']) && isset($this->info['parent'])) { + return $this->info['parent']->getPropertyRaw($this->info['name'], $this->info); + } + // Else return the usual value, which should be raw in this case. + return $this->value(); + } + + /** + * Returns whether data is available to work with. + * + * @return + * If we operate without any data FALSE, else TRUE. + */ + protected function dataAvailable() { + return isset($this->data) || (isset($this->info['parent']) && $this->info['parent']->dataAvailable()); + } + + /** + * Set a new data value. + */ + public function set($value) { + if (!$this->validate($value)) { + throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.'); + } + $this->clear(); + $this->data = $value; + $this->updateParent($value); + return $this; + } + + /** + * Updates the parent data structure of a data property with the latest data value. + */ + protected function updateParent($value) { + if (isset($this->info['parent'])) { + $this->info['parent']->setProperty($this->info['name'], $value); + } + } + + /** + * Returns whether $value is a valid value to set. + */ + public function validate($value) { + if (isset($value) && !entity_property_verify_data_type($value, $this->type)) { + return FALSE; + } + // Only proceed with further checks if this is not a list item. If this is + // a list item, the checks are performed on the list property level. + if (isset($this->info['parent']) && $this->info['parent'] instanceof EntityListWrapper) { + return TRUE; + } + if (!isset($value) && !empty($this->info['required'])) { + // Do not allow NULL values if the property is required. + return FALSE; + } + return !isset($this->info['validation callback']) || call_user_func($this->info['validation callback'], $value, $this->info); + } + + public function __toString() { + return isset($this->info) ? 'Property ' . $this->info['name'] : $this->type; + } + + /** + * Clears the data value and the wrapper cache. + */ + protected function clear() { + $this->data = NULL; + foreach ($this->cache as $wrapper) { + $wrapper->clear(); + } + } + + /** + * Returns the options list specifying possible values for the property, if + * defined. + * + * @param $op + * (optional) One of 'edit' or 'view'. In case the list of possible values + * a user could set for a property differs from the list of values a + * property could have, $op determines which options should be returned. + * Defaults to 'edit'. + * E.g. all possible roles a user could have include the anonymous and the + * authenticated user roles, while those roles cannot be added to a user + * account. So their options would be included for 'view', but for 'edit' + * not. + * + * @return + * An array as used by hook_options_list() or FALSE. + */ + public function optionsList($op = 'edit') { + if (isset($this->info['options list']) && is_callable($this->info['options list'])) { + $name = isset($this->info['name']) ? $this->info['name'] : NULL; + return call_user_func($this->info['options list'], $name, $this->info, $op); + } + return FALSE; + } + + /** + * Returns the label for the currently set property value if there is one + * available, i.e. if an options list has been specified. + */ + public function label() { + if ($options = $this->optionsList('view')) { + $options = entity_property_options_flatten($options); + $value = $this->value(); + if (is_scalar($value) && isset($options[$value])) { + return $options[$value]; + } + } + } + + /** + * Determines whether the given user has access to view or edit this property. + * Apart from relying on access metadata of properties, this takes into + * account information about entity level access, if available: + * - Referenced entities can only be viewed, when the user also has + * permission to view the entity. + * - A property may be only edited, if the user has permission to update the + * entity containing the property. + * + * @param $op + * The operation being performed. One of 'view' or 'edit. + * @param $account + * The user to check for. Leave it to NULL to check for the global user. + * @return boolean + * Whether access to entity property is allowed for the given operation. + * However if we wrap no data, it returns whether access is allowed to the + * property of all entities of this type. + * If there is no access information for this property, TRUE is returned. + */ + public function access($op, $account = NULL) { + if (empty($this->info['parent']) && $this instanceof EntityDrupalWrapper) { + // If there is no parent just incorporate entity based access. + return $this->entityAccess($op == 'edit' ? 'update' : 'view', $account); + } + return !empty($this->info['parent']) ? $this->info['parent']->propertyAccess($this->info['name'], $op, $account) : TRUE; + } + + /** + * Prepare for serializiation. + */ + public function __sleep() { + $vars = get_object_vars($this); + unset($vars['cache']); + return drupal_map_assoc(array_keys($vars)); + } +} + +/** + * Wraps a single value. + */ +class EntityValueWrapper extends EntityMetadataWrapper { + + /** + * Overrides EntityMetadataWrapper#value(). + * Sanitizes or decode textual data if necessary. + */ + public function value(array $options = array()) { + $data = parent::value(); + if ($this->type == 'text' && isset($data)) { + $info = $this->info + array('sanitized' => FALSE, 'sanitize' => 'check_plain'); + $options += array('sanitize' => FALSE, 'decode' => FALSE); + if ($options['sanitize'] && !$info['sanitized']) { + return call_user_func($info['sanitize'], $data); + } + elseif ($options['decode'] && $info['sanitized']) { + return decode_entities(strip_tags($data)); + } + } + return $data; + } +} + +/** + * Provides a general wrapper for any data structure. For this to work the + * metadata has to be passed during construction. + */ +class EntityStructureWrapper extends EntityMetadataWrapper implements IteratorAggregate { + + protected $propertyInfo = array(), $propertyInfoAltered = FALSE; + protected $langcode = LANGUAGE_NONE; + + protected $propertyInfoDefaults = array( + 'type' => 'text', + 'getter callback' => 'entity_property_verbatim_get', + 'clear' => array(), + ); + + /** + * Construct a new EntityStructureWrapper object. + * + * @param $type + * The type of the passed data. + * @param $data + * Optional. The data to wrap. + * @param $info + * Used to for specifying metadata about the data and internally to pass + * info about properties down the tree. For specifying metadata known keys + * are: + * - property info: An array of info about the properties of the wrapped + * data structure. It has to contain an array of property info in the same + * structure as used by hook_entity_property_info(). + */ + public function __construct($type, $data = NULL, $info = array()) { + parent::__construct($type, $data, $info); + $this->info += array('property defaults' => array()); + $info += array('property info' => array()); + $this->propertyInfo['properties'] = $info['property info']; + } + + /** + * May be used to lazy-load additional info about the data, depending on the + * concrete passed data. + */ + protected function spotInfo() { + // Apply the callback if set, such that the caller may alter the info. + if (!empty($this->info['property info alter']) && !$this->propertyInfoAltered) { + $this->propertyInfo = call_user_func($this->info['property info alter'], $this, $this->propertyInfo); + $this->propertyInfoAltered = TRUE; + } + } + + /** + * Gets the info about the given property. + * + * @param $name + * The name of the property. If not given, info about all properties will + * be returned. + * @throws EntityMetadataWrapperException + * If there is no such property. + * @return + * An array of info about the property. + */ + public function getPropertyInfo($name = NULL) { + $this->spotInfo(); + if (!isset($name)) { + return $this->propertyInfo['properties']; + } + if (!isset($this->propertyInfo['properties'][$name])) { + throw new EntityMetadataWrapperException('Unknown data property ' . check_plain($name) . '.'); + } + return $this->propertyInfo['properties'][$name] + $this->info['property defaults'] + $this->propertyInfoDefaults; + } + + /** + * Returns a reference on the property info. + * + * If possible, use the property info alter callback for spotting metadata. + * The reference may be used to alter the property info for any remaining + * cases, e.g. if additional metadata has been asserted. + */ + public function &refPropertyInfo() { + return $this->propertyInfo; + } + + /** + * Sets a new language to use for retrieving properties. + * + * @param $langcode + * The language code of the language to set. + * @return EntityWrapper + */ + public function language($langcode = LANGUAGE_NONE) { + if ($langcode != $this->langcode) { + $this->langcode = $langcode; + $this->cache = array(); + } + return $this; + } + + /** + * Gets the language used for retrieving properties. + * + * @return String + * The language object of the language or NULL for the default language. + * + * @see EntityStructureWrapper::language() + */ + public function getPropertyLanguage() { + if ($this->langcode != LANGUAGE_NONE && $list = language_list()) { + if (isset($list[$this->langcode])) { + return $list[$this->langcode]; + } + } + return NULL; + } + + /** + * Get the wrapper for a property. + * + * @return + * An instance of EntityMetadataWrapper. + */ + public function get($name) { + // Look it up in the cache if possible. + if (!array_key_exists($name, $this->cache)) { + if ($info = $this->getPropertyInfo($name)) { + $info += array('parent' => $this, 'name' => $name, 'langcode' => $this->langcode, 'property defaults' => array()); + $info['property defaults'] += $this->info['property defaults']; + $this->cache[$name] = entity_metadata_wrapper($info['type'], NULL, $info); + } + else { + throw new EntityMetadataWrapperException('There is no property ' . check_plain($name) . " for this entity."); + } + } + return $this->cache[$name]; + } + + /** + * Magic method: Get a wrapper for a property. + */ + public function __get($name) { + if (strpos($name, 'krumo') === 0) { + // #914934 Ugly workaround to allow krumo to write its recursion property. + // This is necessary to make dpm() work without throwing exceptions. + return NULL; + } + $get = $this->get($name); + return $get; + } + + /** + * Magic method: Set a property. + */ + public function __set($name, $value) { + if (strpos($name, 'krumo') === 0) { + // #914934 Ugly workaround to allow krumo to write its recursion property. + // This is necessary to make dpm() work without throwing exceptions. + $this->$name = $value; + } + else { + $this->get($name)->set($value); + } + } + + /** + * Gets the value of a property. + */ + protected function getPropertyValue($name, &$info) { + $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE); + $data = $this->value(); + if (!isset($data)) { + throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.'); + } + return $info['getter callback']($data, $options, $name, $this->type, $info); + } + + /** + * Gets the raw value of a property. + */ + protected function getPropertyRaw($name, &$info) { + if (!empty($info['raw getter callback'])) { + $options = array('language' => $this->getPropertyLanguage(), 'absolute' => TRUE); + $data = $this->value(); + if (!isset($data)) { + throw new EntityMetadataWrapperException('Unable to get the data property ' . check_plain($name) . ' as the parent data structure is not set.'); + } + return $info['raw getter callback']($data, $options, $name, $this->type, $info); + } + return $this->getPropertyValue($name, $info); + } + + /** + * Sets a property. + */ + protected function setProperty($name, $value) { + $info = $this->getPropertyInfo($name); + if (!empty($info['setter callback'])) { + $data = $this->value(); + + // In case the data structure is not set, support simple auto-creation + // for arrays. Else an exception is thrown. + if (!isset($data)) { + if (!empty($this->info['auto creation']) && !($this instanceof EntityDrupalWrapper)) { + $data = $this->info['auto creation']($name, $this->info); + } + else { + throw new EntityMetadataWrapperException('Unable to set the data property ' . check_plain($name) . ' as the parent data structure is not set.'); + } + } + + // Invoke the setter callback for updating our data. + $info['setter callback']($data, $name, $value, $this->langcode, $this->type, $info); + + // If the setter has not thrown any exceptions, proceed and apply the + // update to the current and any parent wrappers as necessary. + $data = $this->info['type'] == 'entity' ? $this : $data; + $this->set($data); + + // Clear the cache of properties dependent on this value. + foreach ($info['clear'] as $name) { + if (isset($this->cache[$name])) { + $this->cache[$name]->clear(); + } + } + } + else { + throw new EntityMetadataWrapperException('Entity property ' . check_plain($name) . " doesn't support writing."); + } + } + + protected function propertyAccess($name, $op, $account = NULL) { + $info = $this->getPropertyInfo($name); + // If the property should be accessed and it's an entity, make sure the user + // is allowed to view that entity. + if ($op == 'view' && $this->$name instanceof EntityDrupalWrapper && !$this->$name->entityAccess($op, $account)) { + return FALSE; + } + // If a property should be edited and this is an entity, make sure the user + // has update access for this entity. + if ($op == 'edit') { + $entity = $this; + while (!($entity instanceof EntityDrupalWrapper) && isset($entity->info['parent'])) { + $entity = $entity->info['parent']; + } + if ($entity instanceof EntityDrupalWrapper && !$entity->entityAccess('update', $account)) { + return FALSE; + } + } + if (!empty($info['access callback'])) { + $data = $this->dataAvailable() ? $this->value() : NULL; + return call_user_func($info['access callback'], $op, $name, $data, $account, $this->type); + } + elseif ($op == 'edit' && isset($info['setter permission'])) { + return user_access($info['setter permission'], $account); + } + return TRUE; + } + + /** + * Magic method: Can be used to check if a property is known. + */ + public function __isset($name) { + $this->spotInfo(); + return isset($this->propertyInfo['properties'][$name]); + } + + public function getIterator() { + $this->spotInfo(); + return new EntityMetadataWrapperIterator($this, array_keys($this->propertyInfo['properties'])); + } + + /** + * Returns the identifier of the data structure. If there is none, NULL is + * returned. + */ + public function getIdentifier() { + return isset($this->id) && $this->dataAvailable() ? $this->id->value() : NULL; + } + + /** + * Prepare for serializiation. + */ + public function __sleep() { + $vars = parent::__sleep(); + unset($vars['propertyInfoDefaults']); + return $vars; + } + + public function clear() { + $this->propertyInfoAltered = FALSE; + parent::clear(); + } +} + +/** + * Provides a wrapper for entities registrered in hook_entity_info(). + * + * The wrapper eases applying getter and setter callbacks of entity properties + * specified in hook_entity_property_info(). + */ +class EntityDrupalWrapper extends EntityStructureWrapper { + + /** + * Contains the entity id. + */ + protected $id = FALSE; + protected $bundle; + protected $entityInfo; + + /** + * Construct a new EntityDrupalWrapper object. + * + * @param $type + * The type of the passed data. + * @param $data + * Optional. The entity to wrap or its identifier. + * @param $info + * Optional. Used internally to pass info about properties down the tree. + */ + public function __construct($type, $data = NULL, $info = array()) { + parent::__construct($type, $data, $info); + $this->setUp(); + } + + protected function setUp() { + $this->propertyInfo = entity_get_property_info($this->type) + array('properties' => array()); + $info = $this->info + array('property info' => array(), 'bundle' => NULL); + $this->propertyInfo['properties'] += $info['property info']; + $this->bundle = $info['bundle']; + $this->entityInfo = entity_get_info($this->type); + if (isset($this->bundle)) { + $this->spotBundleInfo(FALSE); + } + } + + /** + * Sets the entity internally accepting both the entity id and object. + */ + protected function setEntity($data) { + // For entities we allow getter callbacks to return FALSE, which we + // interpret like NULL values as unset properties. + if (isset($data) && $data !== FALSE && !is_object($data)) { + $this->id = $data; + $this->data = FALSE; + } + elseif (is_object($data) && $data instanceof EntityDrupalWrapper) { + // We got a wrapped entity passed, so take over its values. + $this->id = $data->id; + $this->data = $data->data; + // For generic entity references, also update the entity type accordingly. + if ($this->info['type'] == 'entity') { + $this->type = $data->type; + } + } + elseif (is_object($data)) { + // We got the entity object passed. + $this->data = $data; + $id = entity_id($this->type, $data); + $this->id = isset($id) ? $id : FALSE; + } + else { + $this->id = FALSE; + $this->data = NULL; + } + } + + /** + * Used to lazy-load bundle info. So the wrapper can be loaded e.g. just + * for setting without the data being loaded. + */ + protected function spotInfo() { + if (!$this->propertyInfoAltered) { + if ($this->info['type'] == 'entity' && $this->dataAvailable() && $this->value()) { + // Add in entity-type specific details. + $this->setUp(); + } + $this->spotBundleInfo(TRUE); + parent::spotInfo(); + $this->propertyInfoAltered = TRUE; + } + } + + /** + * Tries to determine the bundle and adds in the according property info. + * + * @param $load + * Whether the entity should be loaded to spot the info if necessary. + */ + protected function spotBundleInfo($load = TRUE) { + // Like entity_extract_ids() assume the entity type if no key is given. + if (empty($this->entityInfo['entity keys']['bundle']) && $this->type != 'entity') { + $this->bundle = $this->type; + } + // Detect the bundle if not set yet and add in properties from the bundle. + elseif (!$this->bundle && $load && $this->dataAvailable()) { + try { + if ($entity = $this->value()) { + list($id, $vid, $bundle) = entity_extract_ids($this->type, $entity); + $this->bundle = $bundle; + } + } + catch (EntityMetadataWrapperException $e) { + // Loading data failed, so we cannot derive the used bundle. + } + } + + if ($this->bundle && isset($this->propertyInfo['bundles'][$this->bundle])) { + $bundle_info = (array) $this->propertyInfo['bundles'][$this->bundle] + array('properties' => array()); + // Allow bundles to re-define existing properties, such that the bundle + // can add in more bundle-specific details like the bundle of a referenced + // entity. + $this->propertyInfo['properties'] = $bundle_info['properties'] + $this->propertyInfo['properties']; + } + } + + /** + * Returns the identifier of the wrapped entity. + * + * @see entity_id() + */ + public function getIdentifier() { + return $this->dataAvailable() ? $this->value(array('identifier' => TRUE)) : NULL; + } + + /** + * Returns the bundle of an entity, or FALSE if it has no bundles. + */ + public function getBundle() { + if ($this->dataAvailable()) { + $this->spotInfo(); + return $this->bundle; + } + } + + /** + * Overridden. + * + * @param $options + * An array of options. Known keys: + * - identifier: If set to TRUE, the entity identifier is returned. + */ + public function value(array $options = array()) { + // Try loading the data via the getter callback if there is none yet. + if (!isset($this->data)) { + $this->setEntity(parent::value()); + } + if (!empty($options['identifier'])) { + return $this->id; + } + elseif (!$this->data && !empty($this->id)) { + // Lazy load the entity if necessary. + $return = entity_load($this->type, array($this->id)); + // In case the entity cannot be loaded, we return NULL just as for empty + // properties. + $this->data = $return ? reset($return) : NULL; + } + return $this->data; + } + + /** + * Returns the entity prepared for rendering. + * + * @see entity_view() + */ + public function view($view_mode = 'full', $langcode = NULL, $page = NULL) { + return entity_view($this->type(), array($this->value()), $view_mode, $langcode, $page); + } + + /** + * Overridden to support setting the entity by either the object or the id. + */ + public function set($value) { + if (!$this->validate($value)) { + throw new EntityMetadataWrapperException('Invalid data value given. Be sure it matches the required data type and format.'); + } + if ($this->info['type'] == 'entity' && $value === $this) { + // Nothing to do. + return $this; + } + $previous_id = $this->id; + $previous_type = $this->type; + // Set value, so we get the identifier and pass it to the normal setter. + $this->clear(); + $this->setEntity($value); + // Generally, we have to update the parent only if the entity reference + // has changed. In case of a generic entity reference, we pass the entity + // wrapped. Else we just pass the id of the entity to the setter callback. + if ($this->info['type'] == 'entity' && ($previous_id != $this->id || $previous_type != $this->type)) { + // We need to clone the wrapper we pass through as value, so it does not + // get cleared when the current wrapper instance gets cleared. + $this->updateParent(clone $this); + } + // In case the entity has been unset, we cannot properly detect changes as + // the previous id defaults to FALSE for unloaded entities too. So in that + // case we just always update the parent. + elseif ($this->id === FALSE && !$this->data) { + $this->updateParent(NULL); + } + elseif ($previous_id != $this->id) { + $this->updateParent($this->id); + } + return $this; + } + + /** + * Overridden. + */ + public function clear() { + $this->id = NULL; + $this->bundle = isset($this->info['bundle']) ? $this->info['bundle'] : NULL; + if ($this->type != $this->info['type']) { + // Reset entity info / property info based upon the info provided during + // the creation of the wrapper. + $this->type = $this->info['type']; + $this->setUp(); + } + parent::clear(); + } + + /** + * Overridden. + */ + public function type() { + // In case of a generic entity wrapper, load the data first to determine + // the type of the concrete entity. + if ($this->dataAvailable() && $this->info['type'] == 'entity') { + try { + $this->value(array('identifier' => TRUE)); + } + catch (EntityMetadataWrapperException $e) { + // If loading data fails, we cannot determine the concrete entity type. + } + } + return $this->type; + } + + /** + * Checks whether the operation $op is allowed on the entity. + * + * @see entity_access() + */ + public function entityAccess($op, $account = NULL) { + $entity = $this->dataAvailable() ? $this->value() : NULL; + return entity_access($op, $this->type, $entity, $account); + } + + /** + * Permanently save the wrapped entity. + * + * @throws EntityMetadataWrapperException + * If the entity type does not support saving. + * + * @return EntityDrupalWrapper + */ + public function save() { + if ($this->data) { + if (!entity_type_supports($this->type, 'save')) { + throw new EntityMetadataWrapperException("There is no information about how to save entities of type " . check_plain($this->type) . '.'); + } + entity_save($this->type, $this->data); + // On insert, update the identifier afterwards. + if (!$this->id) { + list($this->id, , ) = entity_extract_ids($this->type, $this->data); + } + } + // If the entity hasn't been loaded yet, don't bother saving it. + return $this; + } + + /** + * Permanently delete the wrapped entity. + * + * @return EntityDrupalWrapper + */ + public function delete() { + if ($this->dataAvailable() && $this->value()) { + $return = entity_delete($this->type, $this->id); + if ($return === FALSE) { + throw new EntityMetadataWrapperException("There is no information about how to delete entities of type " . check_plain($this->type) . '.'); + } + } + return $this; + } + + /** + * Gets the info about the wrapped entity. + */ + public function entityInfo() { + return $this->entityInfo; + } + + /** + * Returns the name of the key used by the entity for given entity key. + * + * @param $name + * One of 'id', 'name', 'bundle' or 'revision'. + * @return + * The name of the key used by the entity. + */ + public function entityKey($name) { + return isset($this->entityInfo['entity keys'][$name]) ? $this->entityInfo['entity keys'][$name] : FALSE; + } + + /** + * Returns the entity label. + * + * @see entity_label() + */ + public function label() { + if ($entity = $this->value()) { + return entity_label($this->type, $entity); + } + } + + /** + * Prepare for serializiation. + */ + public function __sleep() { + $vars = parent::__sleep(); + // Don't serialize the loaded entity and its property info. + unset($vars['data'], $vars['propertyInfo'], $vars['propertyInfoAltered'], $vars['entityInfo']); + // In case the entity is not saved yet, serialize the unsaved data. + if ($this->dataAvailable() && $this->id === FALSE) { + $vars['data'] = 'data'; + } + return $vars; + } + + public function __wakeup() { + $this->setUp(); + if ($this->id !== FALSE) { + // Make sure data is set, so the entity will be loaded when needed. + $this->data = FALSE; + } + } +} + +/** + * Wraps a list of values. + * + * If the wrapped data is a list of data, its numerical indexes may be used to + * retrieve wrappers for the list items. For that this wrapper implements + * ArrayAccess so it may be used like a usual numerically indexed array. + */ +class EntityListWrapper extends EntityMetadataWrapper implements IteratorAggregate, ArrayAccess, Countable { + + /** + * The type of contained items. + */ + protected $itemType; + + /** + * Whether this is a list of entities with a known entity type, i.e. for + * generic list of entities (list) this is FALSE. + */ + protected $isEntityList; + + + public function __construct($type, $data = NULL, $info = array()) { + parent::__construct($type, NULL, $info); + + $this->itemType = entity_property_list_extract_type($this->type); + if (!$this->itemType) { + $this->itemType = 'unknown'; + } + $this->isEntityList = (bool) entity_get_info($this->itemType); + + if (isset($data)) { + $this->set($data); + } + } + + /** + * Get the wrapper for a single item. + * + * @return + * An instance of EntityMetadataWrapper. + */ + public function get($delta) { + // Look it up in the cache if possible. + if (!array_key_exists($delta, $this->cache)) { + if (!isset($delta)) { + // The [] operator has been used so point at a new entry. + $values = parent::value(); + $delta = $values ? max(array_keys($values)) + 1 : 0; + } + if (is_numeric($delta)) { + $info = array('parent' => $this, 'name' => $delta) + $this->info; + $this->cache[$delta] = entity_metadata_wrapper($this->itemType, NULL, $info); + } + else { + throw new EntityMetadataWrapperException('There can be only numerical keyed items in a list.'); + } + } + return $this->cache[$delta]; + } + + protected function getPropertyValue($delta) { + // Make use parent::value() to easily by-pass any entity-loading. + $data = parent::value(); + if (isset($data[$delta])) { + return $data[$delta]; + } + } + + protected function getPropertyRaw($delta) { + return $this->getPropertyValue($delta); + } + + protected function setProperty($delta, $value) { + $data = parent::value(); + if (is_numeric($delta)) { + $data[$delta] = $value; + $this->set($data); + } + } + + protected function propertyAccess($delta, $op, $account = NULL) { + return $this->access($op, $account); + } + + /** + * Returns the list as numerically indexed array. + * + * Note that a list of entities might contain stale entity references. In + * that case the wrapper and the identifier of a stale reference would be + * still accessible, however the entity object value would be NULL. That way, + * there may be NULL values in lists of entity objects due to stale entity + * references. + * + * @param $options + * An array of options. Known keys: + * - identifier: If set to TRUE for a list of entities, it won't be returned + * as list of fully loaded entity objects, but as a list of entity ids. + * Note that this list may contain ids of stale entity references. + */ + public function value(array $options = array()) { + // For lists of entities fetch full entity objects before returning. + // Generic entity-wrappers need to be handled separately though. + if ($this->isEntityList && empty($options['identifier']) && $this->dataAvailable()) { + $list = parent::value(); + $entities = $list ? entity_load($this->get(0)->type, $list) : array(); + // Make sure to keep the array keys as present in the list. + foreach ($list as $key => $id) { + // In case the entity cannot be loaded, we return NULL just as for empty + // properties. + $list[$key] = isset($entities[$id]) ? $entities[$id] : NULL; + } + return $list; + } + return parent::value(); + } + + public function set($values) { + // Support setting lists of fully loaded entities. + if ($this->isEntityList && $values && is_object(reset($values))) { + foreach ($values as $key => $value) { + list($id, $vid, $bundle) = entity_extract_ids($this->itemType, $value); + $values[$key] = $id; + } + } + return parent::set($values); + } + + /** + * If we wrap a list, we return an iterator over the data list. + */ + public function getIterator() { + // In case there is no data available, just iterate over the first item. + return new EntityMetadataWrapperIterator($this, $this->dataAvailable() ? array_keys(parent::value()) : array(0)); + } + + /** + * Implements the ArrayAccess interface. + */ + public function offsetGet($delta) { + return $this->get($delta); + } + + public function offsetExists($delta) { + return $this->dataAvailable() && ($data = $this->value()) && array_key_exists($delta, $data); + } + + public function offsetSet($delta, $value) { + $this->get($delta)->set($value); + } + + public function offsetUnset($delta) { + if ($this->offsetExists($delta)) { + unset($this->data[$delta]); + $this->set($this->data); + } + } + + public function count() { + return $this->dataAvailable() ? count($this->value()) : 0; + } + + /** + * Overridden. + */ + public function validate($value) { + // Required lists may not be empty or unset. + if (!empty($this->info['required']) && empty($value)) { + return FALSE; + } + return parent::validate($value); + } + + /** + * Returns the label for the list of set values if available. + */ + public function label() { + if ($options = $this->optionsList('view')) { + $options = entity_property_options_flatten($options); + $labels = array_intersect_key($options, array_flip((array) parent::value())); + } + else { + // Get each label on its own, e.g. to support getting labels of a list + // of entities. + $labels = array(); + foreach ($this as $key => $property) { + $label = $property->label(); + if (!$label) { + return NULL; + } + $labels[] = $label; + } + } + return isset($labels) ? implode(', ', $labels) : NULL; + } +} + +/** + * Provide a separate Exception so it can be caught separately. + */ +class EntityMetadataWrapperException extends Exception { } + + +/** + * Allows to easily iterate over existing child wrappers. + */ +class EntityMetadataWrapperIterator implements RecursiveIterator { + + protected $position = 0; + protected $wrapper, $keys; + + public function __construct(EntityMetadataWrapper $wrapper, array $keys) { + $this->wrapper = $wrapper; + $this->keys = $keys; + } + + function rewind() { + $this->position = 0; + } + + function current() { + return $this->wrapper->get($this->keys[$this->position]); + } + + function key() { + return $this->keys[$this->position]; + } + + function next() { + $this->position++; + } + + function valid() { + return isset($this->keys[$this->position]); + } + + public function hasChildren() { + return $this->current() instanceof IteratorAggregate; + } + + public function getChildren() { + return $this->current()->getIterator(); + } +} + +/** + * An array object implementation keeping the reference on the given array so + * changes to the object are reflected in the passed array. + */ +class EntityMetadataArrayObject implements ArrayAccess, Countable, IteratorAggregate { + + protected $data; + + public function __construct(&$array) { + $this->data =& $array; + } + + public function &getArray() { + return $this->data; + } + + /** + * Implements the ArrayAccess interface. + */ + public function offsetGet($delta) { + return $this->data[$delta]; + } + + public function offsetExists($delta) { + return array_key_exists($delta, $this->data); + } + + public function offsetSet($delta, $value) { + $this->data[$delta] = $value; + } + + public function offsetUnset($delta) { + unset($this->data[$delta]); + } + + public function count() { + return count($this->data); + } + + public function getIterator() { + return new ArrayIterator($this->data); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/book.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/book.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,23 @@ + t("Book"), + 'type' => 'node', + 'description' => t("If part of a book, the book to which this book page belongs."), + 'getter callback' => 'entity_metadata_book_get_properties', + ); +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/callbacks.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/callbacks.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,974 @@ +book['bid'])) { + throw new EntityMetadataWrapperException('This node is no book page.'); + } + return $node->book['bid']; +} + +/** + * Callback for getting comment properties. + * @see entity_metadata_comment_entity_info_alter() + */ +function entity_metadata_comment_get_properties($comment, array $options, $name) { + switch ($name) { + case 'name': + return $comment->name; + + case 'mail': + if ($comment->uid != 0) { + $account = user_load($comment->uid); + return $account->mail; + } + return $comment->mail; + + case 'edit_url': + return url('comment/edit/' . $comment->cid, $options); + + case 'parent': + if (!empty($comment->pid)) { + return $comment->pid; + } + // There is no parent comment. + return NULL; + } +} + +/** + * Callback for setting comment properties. + * @see entity_metadata_comment_entity_info_alter() + */ +function entity_metadata_comment_setter($comment, $name, $value) { + switch ($name) { + case 'node': + $comment->nid = $value; + // Also set the bundle name. + $node = node_load($value); + $comment->node_type = 'comment_node_' . $node->type; + break; + } +} + +/** + * Callback for getting comment related node properties. + * @see entity_metadata_comment_entity_info_alter() + */ +function entity_metadata_comment_get_node_properties($node, array $options, $name, $entity_type) { + switch ($name) { + case 'comment_count': + return isset($node->comment_count) ? $node->comment_count : 0; + + case 'comment_count_new': + return comment_num_new($node->nid); + } +} + +/** + * Getter callback for getting global languages. + */ +function entity_metadata_locale_get_languages($data, array $options, $name) { + return isset($GLOBALS[$name]) ? $GLOBALS[$name]->language : NULL; +} + +/** + * Getter callback for getting the preferred user language. + */ +function entity_metadata_locale_get_user_language($account, array $options, $name) { + return user_preferred_language($account)->language; +} + +/** + * Return the options lists for the node and comment status property. + */ +function entity_metadata_status_options_list() { + return array( + NODE_PUBLISHED => t('Published'), + NODE_NOT_PUBLISHED => t('Unpublished'), + ); +} + +/** + * Callback for getting node properties. + * + * @see entity_metadata_node_entity_info_alter() + */ +function entity_metadata_node_get_properties($node, array $options, $name, $entity_type) { + switch ($name) { + case 'is_new': + return empty($node->nid) || !empty($node->is_new); + + case 'source': + if (!empty($node->tnid) && $source = node_load($node->tnid)) { + return $source; + } + return NULL; + + case 'edit_url': + return url('node/' . $node->nid . '/edit', $options); + } +} + +/** + * Callback for determing access for node revision related properties. + */ +function entity_metadata_node_revision_access($op, $name, $entity = NULL, $account = NULL) { + return $op == 'view' ? user_access('view revisions', $account) : user_access('administer nodes', $account); +} + +/** + * Callback for getting poll properties. + * @see entity_metadata_poll_entity_info_alter() + */ +function entity_metadata_poll_node_get_properties($node, array $options, $name) { + $total_votes = $highest_votes = 0; + foreach ($node->choice as $choice) { + if ($choice['chvotes'] > $highest_votes) { + $winner = $choice; + $highest_votes = $choice['chvotes']; + } + $total_votes = $total_votes + $choice['chvotes']; + } + + if ($name == 'poll_duration') { + return $node->runtime; + } + elseif ($name == 'poll_votes') { + return $total_votes; + } + elseif (!isset($winner)) { + // There is no poll winner yet. + return NULL; + } + switch ($name) { + case 'poll_winner_votes': + return $winner['chvotes']; + + case 'poll_winner': + return $winner['chtext']; + + case 'poll_winner_percent': + return ($winner['chvotes'] / $total_votes) * 100; + } +} + +/** + * Callback for getting statistics properties. + * @see entity_metadata_statistics_entity_info_alter() + */ +function entity_metadata_statistics_node_get_properties($node, array $options, $name) { + $statistics = (array) statistics_get($node->nid); + $statistics += array('totalcount' => 0, 'daycount' => 0, 'timestamp' => NULL); + + switch ($name) { + case 'views': + return $statistics['totalcount']; + + case 'day_views': + return $statistics['daycount']; + + case 'last_view': + return $statistics['timestamp']; + } +} + +/** + * Callback for getting site-wide properties. + * @see entity_metadata_system_entity_info_alter() + */ +function entity_metadata_system_get_properties($data = FALSE, array $options, $name) { + switch ($name) { + case 'name': + return variable_get('site_name', 'Drupal'); + + case 'url': + return url('', $options); + + case 'login_url': + return url('user', $options); + + case 'current_user': + return $GLOBALS['user']->uid ? $GLOBALS['user']->uid : drupal_anonymous_user(); + + case 'current_date': + return REQUEST_TIME; + + case 'current_page': + // Subsequent getters of the struct retrieve the actual values. + return array(); + + default: + return variable_get('site_' . $name, ''); + } +} + +/** + * Callback for getting properties for the current page request. + * @see entity_metadata_system_entity_info_alter() + */ +function entity_metadata_system_get_page_properties($data = array(), array $options, $name) { + switch ($name) { + case 'url': + return $GLOBALS['base_root'] . request_uri(); + } +} + +/** + * Callback for getting file properties. + * @see entity_metadata_system_entity_info_alter() + */ +function entity_metadata_system_get_file_properties($file, array $options, $name) { + switch ($name) { + case 'name': + return $file->filename; + + case 'mime': + return $file->filemime; + + case 'size': + return $file->filesize; + + case 'url': + return url(file_create_url($file->uri), $options); + + case 'owner': + return $file->uid; + } +} + +/** + * Callback for getting term properties. + * + * @see entity_metadata_taxonomy_entity_info_alter() + */ +function entity_metadata_taxonomy_term_get_properties($term, array $options, $name) { + switch ($name) { + case 'node_count': + return count(taxonomy_select_nodes($term->tid)); + + case 'description': + return check_markup($term->description, isset($term->format) ? $term->format : NULL, '', TRUE); + + case 'parent': + if (isset($term->parent[0]) && !is_array(isset($term->parent[0]))) { + return $term->parent; + } + return array_keys(taxonomy_get_parents($term->tid)); + + case 'parents_all': + // We have to return an array of ids. + $tids = array(); + foreach (taxonomy_get_parents_all($term->tid) as $parent) { + $tids[] = $parent->tid; + } + return $tids; + } +} + +/** + * Callback for setting term properties. + * + * @see entity_metadata_taxonomy_entity_info_alter() + */ +function entity_metadata_taxonomy_term_setter($term, $name, $value) { + switch ($name) { + case 'vocabulary': + // Make sure to update the taxonomy bundle key, so load the vocabulary. + // Support both, loading by name or ID. + $vocabulary = is_numeric($value) ? taxonomy_vocabulary_load($value) : taxonomy_vocabulary_machine_name_load($value); + $term->vocabulary_machine_name = $vocabulary->machine_name; + return $term->vid = $vocabulary->vid; + case 'parent': + return $term->parent = $value; + } +} + +/** + * Callback for getting vocabulary properties. + * @see entity_metadata_taxonomy_entity_info_alter() + */ +function entity_metadata_taxonomy_vocabulary_get_properties($vocabulary, array $options, $name) { + switch ($name) { + case 'term_count': + $sql = "SELECT COUNT (1) FROM {taxonomy_term_data} td WHERE td.vid = :vid"; + return db_query($sql, array(':vid' => $vocabulary->vid))->fetchField(); + } +} + +/** + * Callback for getting user properties. + * @see entity_metadata_user_entity_info_alter() + */ +function entity_metadata_user_get_properties($account, array $options, $name, $entity_type) { + switch ($name) { + case 'last_access': + // In case there was no access the value is 0, but we have to return NULL. + return empty($account->access) ? NULL : $account->access; + + case 'last_login': + return empty($account->login) ? NULL : $account->login; + + case 'name': + return empty($account->uid) ? variable_get('anonymous', t('Anonymous')) : $account->name; + + case 'url': + if (empty($account->uid)) { + return NULL; + } + $return = entity_uri('user', $account); + return $return ? url($return['path'], $return['options'] + $options) : ''; + + case 'edit_url': + return empty($account->uid) ? NULL : url("user/$account->uid/edit", $options); + + case 'roles': + return isset($account->roles) ? array_keys($account->roles) : array(); + + case 'theme': + return empty($account->theme) ? variable_get('theme_default', 'bartik') : $account->theme; + } +} + +/** + * Callback for setting user properties. + * @see entity_metadata_user_entity_info_alter() + */ +function entity_metadata_user_set_properties($account, $name, $value) { + switch ($name) { + case 'roles': + $account->roles = array_intersect_key(user_roles(), array_flip($value)); + break; + } +} + +/** + * Options list callback returning all user roles. + */ +function entity_metadata_user_roles($property_name = 'roles', $info = array(), $op = 'edit') { + $roles = user_roles(); + if ($op == 'edit') { + unset($roles[DRUPAL_AUTHENTICATED_RID], $roles[DRUPAL_ANONYMOUS_RID]); + } + return $roles; +} + +/** + * Return the options lists for user status property. + */ +function entity_metadata_user_status_options_list() { + return array( + 0 => t('Blocked'), + 1 => t('Active'), + ); +} + +/** + * Callback defining an options list for language properties. + */ +function entity_metadata_language_list() { + $list = array(); + $list[LANGUAGE_NONE] = t('Language neutral'); + foreach (language_list() as $language) { + $list[$language->language] = $language->name; + } + return $list; +} + +/** + * Callback for getting field property values. + */ +function entity_metadata_field_property_get($entity, array $options, $name, $entity_type, $info) { + $field = field_info_field($name); + $columns = array_keys($field['columns']); + $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE; + $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode, TRUE); + $values = array(); + if (isset($entity->{$name}[$langcode])) { + foreach ($entity->{$name}[$langcode] as $delta => $data) { + $values[$delta] = $data[$columns[0]]; + if ($info['type'] == 'boolean' || $info['type'] == 'list') { + // Ensure that we have a clean boolean data type. + $values[$delta] = (boolean) $values[$delta]; + } + } + } + // For an empty single-valued field, we have to return NULL. + return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values; +} + +/** + * Callback for setting field property values. + */ +function entity_metadata_field_property_set($entity, $name, $value, $langcode, $entity_type, $info) { + $field = field_info_field($name); + $columns = array_keys($field['columns']); + $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode); + $values = $field['cardinality'] == 1 ? array($value) : (array) $value; + + $items = array(); + foreach ($values as $delta => $value) { + if (isset($value)) { + $items[$delta][$columns[0]] = $value; + if ($info['type'] == 'boolean' || $info['type'] == 'list') { + // Convert boolean values back to an integer for writing. + $items[$delta][$columns[0]] = (integer) $items[$delta][$columns[0]] = $value; + } + } + } + $entity->{$name}[$langcode] = $items; + // Empty the static field language cache, so the field system picks up any + // possible new languages. + drupal_static_reset('field_language'); +} + +/** + * Callback returning the options list of a field. + */ +function entity_metadata_field_options_list($name, $info) { + $field_property_info = $info; + if (is_numeric($name) && isset($info['parent'])) { + // The options list is to be returned for a single item of a multiple field. + $field_property_info = $info['parent']->info(); + $name = $field_property_info['name']; + } + if (($field = field_info_field($name)) && isset($field_property_info['parent'])) { + // Retrieve the wrapped entity holding the field. + $wrapper = $field_property_info['parent']; + try { + $entity = $wrapper->value(); + } + catch (EntityMetadataWrapperException $e) { + // No data available. + $entity = NULL; + } + $instance = $wrapper->getBundle() ? field_info_instance($wrapper->type(), $name, $wrapper->getBundle()) : NULL; + return (array) module_invoke($field['module'], 'options_list', $field, $instance, $wrapper->type(), $entity); + } +} + +/** + * Callback to verbatim get the data structure of a field. Useful for fields + * that add metadata for their own data structure. + */ +function entity_metadata_field_verbatim_get($entity, array $options, $name, $entity_type, &$context) { + // Set contextual info useful for getters of any child properties. + $context['instance'] = field_info_instance($context['parent']->type(), $name, $context['parent']->getBundle()); + $context['field'] = field_info_field($name); + $langcode = isset($options['language']) ? $options['language']->language : LANGUAGE_NONE; + $langcode = entity_metadata_field_get_language($entity_type, $entity, $context['field'], $langcode, TRUE); + + if ($context['field']['cardinality'] == 1) { + return isset($entity->{$name}[$langcode][0]) ? $entity->{$name}[$langcode][0] : NULL; + } + return isset($entity->{$name}[$langcode]) ? $entity->{$name}[$langcode] : array(); +} + +/** + * Writes the passed field items in the object. Useful as field level setter + * to set the whole data structure at once. + */ +function entity_metadata_field_verbatim_set($entity, $name, $items, $langcode, $entity_type) { + $field = field_info_field($name); + $langcode = entity_metadata_field_get_language($entity_type, $entity, $field, $langcode); + $value = $field['cardinality'] == 1 ? array($items) : (array) $items; + // Filter out any items set to NULL. + $entity->{$name}[$langcode] = array_filter($value); + + // Empty the static field language cache, so the field system picks up any + // possible new languages. + drupal_static_reset('field_language'); +} + +/** + * Helper for determining the field language to be used. + * + * Note that we cannot use field_language() as we are not about to display + * values, but generally read/write values. + * + * @param $fallback + * (optional) Whether to fall back to the entity default language, if no + * value is available for the given language code yet. + * + * @return + * The language code to use. + */ +function entity_metadata_field_get_language($entity_type, $entity, $field, $langcode = LANGUAGE_NONE, $fallback = FALSE) { + // Try to figure out the default language used by the entity. + // With Drupal >= 7.15 we can use entity_language(). + if (function_exists('entity_language')) { + $default_langcode = entity_language($entity_type, $entity); + } + else { + $default_langcode = !empty($entity->language) ? $entity->language : LANGUAGE_NONE; + } + + // Determine the right language to use. + if ($default_langcode != LANGUAGE_NONE && field_is_translatable($entity_type, $field)) { + $langcode = ($langcode != LANGUAGE_NONE) ? field_valid_language($langcode, $default_langcode) : $default_langcode; + if (!isset($entity->{$field['field_name']}[$langcode]) && $fallback) { + $langcode = $default_langcode; + } + return $langcode; + } + else { + return LANGUAGE_NONE; + } +} + +/** + * Callback for getting the sanitized text of 'text_formatted' properties. + * This callback is used for both the 'value' and the 'summary'. + */ +function entity_metadata_field_text_get($item, array $options, $name, $type, $context) { + // $name is either 'value' or 'summary'. + if (!isset($item['safe_' . $name])) { + // Apply input formats. + $langcode = isset($options['language']) ? $options['language']->language : ''; + $format = isset($item['format']) ? $item['format'] : filter_default_format(); + $item['safe_' . $name] = check_markup($item[$name], $format, $langcode); + // To speed up subsequent calls, update $item with the 'safe_value'. + $context['parent']->set($item); + } + return $item['safe_' . $name]; +} + +/** + * Defines the list of all available text formats. + */ +function entity_metadata_field_text_formats() { + foreach (filter_formats() as $key => $format) { + $formats[$key] = $format->name; + } + return $formats; +} + +/** + * Callback for getting the file entity of file fields. + */ +function entity_metadata_field_file_get($item) { + return $item['fid']; +} + +/** + * Callback for setting the file entity of file fields. + */ +function entity_metadata_field_file_set(&$item, $property_name, $value) { + $item['fid'] = $value; +} + +/** + * Callback for auto-creating file field $items. + */ +function entity_metadata_field_file_create_item($property_name, $context) { + // 'fid' is required, so 'file' has to be set as initial property. + return array('display' => isset($context['field']['settings']['display_default']) ? $context['field']['settings']['display_default'] : 0); +} + +/** + * Callback for validating file field $items. + */ +function entity_metadata_field_file_validate_item($items, $context) { + // Allow NULL values. + if (!isset($items)) { + return TRUE; + } + + // Stream-line $items for multiple vs non-multiple fields. + $items = !entity_property_list_extract_type($context['type']) ? array($items) : (array) $items; + + foreach ($items as $item) { + // File-field items require a valid file. + if (!isset($item['fid']) || !file_load($item['fid'])) { + return FALSE; + } + if (isset($context['property info']['display']) && !isset($item['display'])) { + return FALSE; + } + } + return TRUE; +} + +/** + * Access callback for the node entity. + * + * This function does not implement hook_node_access(), thus it may not be + * called entity_metadata_node_access(). + */ +function entity_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) { + if (isset($node)) { + // If a non-default revision is given, incorporate revision access. + $default_revision = node_load($node->nid); + if ($node->vid != $default_revision->vid) { + return _node_revision_access($node, $op); + } + else { + return node_access($op, $node, $account); + } + } + // Is access to all nodes allowed? + if (!user_access('access content', $account)) { + return FALSE; + } + if (user_access('bypass node access', $account) || (!isset($account) && $op == 'view' && node_access_view_all_nodes())) { + return TRUE; + } + return FALSE; +} + +/** + * Access callback for the user entity. + */ +function entity_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type) { + $account = isset($account) ? $account : $GLOBALS['user']; + // Grant access to the users own user account and to the anonymous one. + if (isset($entity) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) { + return TRUE; + } + if (user_access('administer users', $account) || user_access('access user profiles', $account) && $op == 'view' && $entity->status) { + return TRUE; + } + return FALSE; +} + +/** + * Access callback for restricted user properties. + */ +function entity_metadata_user_properties_access($op, $property, $entity = NULL, $account = NULL) { + if (user_access('administer users', $account)) { + return TRUE; + } + $account = isset($account) ? $account : $GLOBALS['user']; + // Flag to indicate if this user entity is the own user account. + $is_own_account = isset($entity) && $account->uid == $entity->uid; + switch ($property) { + case 'name': + // Allow view access to anyone with access to the entity. + if ($op == 'view') { + return TRUE; + } + // Allow edit access for own user name if the permission is satisfied. + return $is_own_account && user_access('change own username', $account); + case 'mail': + // Allow access to own mail address. + return $is_own_account; + case 'roles': + // Allow view access for own roles. + return ($op == 'view' && $is_own_account); + } + return FALSE; +} + +/** + * Access callback for the comment entity. + */ +function entity_metadata_comment_access($op, $entity = NULL, $account = NULL) { + // When determining access to a comment, 'comment_access' does not take any + // access restrictions to the comment's associated node into account. If a + // comment has an associated node, the user must be able to view it in order + // to access the comment. + if (isset($entity->nid)) { + if (!entity_access('view', 'node', node_load($entity->nid), $account)) { + return FALSE; + } + } + if (isset($entity) && $op == 'update') { + // Because 'comment_access' only checks the current user, we need to do our + // own access checking if an account was specified. + if (!isset($account)) { + return comment_access('edit', $entity); + } + else { + return ($account->uid && $account->uid == $entity->uid && $entity->status == COMMENT_PUBLISHED && user_access('edit own comments', $account)) || user_access('administer comments', $account); + } + } + if (user_access('administer comments', $account) || user_access('access comments', $account) && $op == 'view') { + return TRUE; + } + return FALSE; +} + +/** + * Access callback for the taxonomy entities. + */ +function entity_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type) { + if ($entity_type == 'taxonomy_vocabulary') { + return user_access('administer taxonomy', $account); + } + if (isset($entity) && $op == 'update' && !isset($account) && taxonomy_term_edit_access($entity)) { + return TRUE; + } + if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') { + return TRUE; + } + return FALSE; +} + +/** + * Access callback for file entities. + */ +function entity_metadata_file_access($op, $file = NULL, $account = NULL, $entity_type) { + // We can only check access for the current user, so return FALSE on other accounts. + global $user; + if ($op == 'view' && isset($file) && (!isset($account) || $user->uid == $account->uid)) { + // Invoke hook_file_download() to obtain access information. + foreach (module_implements('file_download') as $module) { + $result = module_invoke($module, 'file_download', $file->uri); + if ($result == -1) { + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + + +/** + * Callback to determine access for properties which are fields. + */ +function entity_metadata_field_access_callback($op, $name, $entity = NULL, $account = NULL, $entity_type) { + $field = field_info_field($name); + return field_access($op, $field, $entity_type, $entity, $account); +} + +/** + * Callback to create entity objects. + */ +function entity_metadata_create_object($values = array(), $entity_type) { + $info = entity_get_info($entity_type); + // Make sure at least the bundle and label properties are set. + if (isset($info['entity keys']['bundle']) && $key = $info['entity keys']['bundle']) { + $values += array($key => NULL); + } + if (isset($info['entity keys']['label']) && $key = $info['entity keys']['label']) { + $values += array($key => NULL); + } + $entity = (object) $values; + $entity->is_new = TRUE; + return $entity; +} + +/** + * Callback to create a new comment. + */ +function entity_metadata_create_comment($values = array()) { + $comment = (object) ($values + array( + 'status' => COMMENT_PUBLISHED, + 'pid' => 0, + 'subject' => '', + 'uid' => 0, + 'language' => LANGUAGE_NONE, + 'node_type' => NULL, + 'is_new' => TRUE, + )); + $comment->cid = FALSE; + return $comment; +} + +/** + * Callback to create a new node. + */ +function entity_metadata_create_node($values = array()) { + $node = (object) array( + 'type' => $values['type'], + 'language' => LANGUAGE_NONE, + 'is_new' => TRUE, + ); + // Set some defaults. + $node_options = variable_get('node_options_' . $node->type, array('status', 'promote')); + foreach (array('status', 'promote', 'sticky') as $key) { + $node->$key = (int) in_array($key, $node_options); + } + if (module_exists('comment') && !isset($node->comment)) { + $node->comment = variable_get("comment_$node->type", COMMENT_NODE_OPEN); + } + // Apply the given values. + foreach ($values as $key => $value) { + $node->$key = $value; + } + return $node; +} + +/** + * Callback to save a user account. + */ +function entity_metadata_user_save($account) { + $edit = (array) $account; + // Don't save the hashed password as password. + unset($edit['pass']); + user_save($account, $edit); +} + +/** + * Callback to delete a file. + * Watch out to not accidentilly implement hook_file_delete(). + */ +function entity_metadata_delete_file($fid) { + file_delete(file_load($fid), TRUE); +} + +/** + * Callback to view nodes. + */ +function entity_metadata_view_node($entities, $view_mode = 'full', $langcode = NULL) { + $result = node_view_multiple($entities, $view_mode, 0, $langcode); + // Make sure to key the result with 'node' instead of 'nodes'. + return array('node' => reset($result)); +} + +/** + * Callback to view comments. + */ +function entity_metadata_view_comment($entities, $view_mode = 'full', $langcode = NULL) { + $build = array(); + $nodes = array(); + // The comments, indexed by nid and then by cid. + $nid_comments = array(); + foreach ($entities as $cid => $comment) { + $nid = $comment->nid; + $nodes[$nid] = $nid; + $nid_comments[$nid][$cid] = $comment; + } + $nodes = node_load_multiple(array_keys($nodes)); + foreach ($nid_comments as $nid => $comments) { + $node = isset($nodes[$nid]) ? $nodes[$nid] : NULL; + $build += comment_view_multiple($comments, $node, $view_mode, 0, $langcode); + } + return array('comment' => $build); +} + +/** + * Callback to view an entity, for which just ENTITYTYPE_view() is available. + */ +function entity_metadata_view_single($entities, $view_mode = 'full', $langcode = NULL, $entity_type) { + $function = $entity_type . '_view'; + $build = array(); + foreach ($entities as $key => $entity) { + $build[$entity_type][$key] = $function($entity, $view_mode, $langcode); + } + return $build; +} + +/** + * Callback to get the form of a node. + */ +function entity_metadata_form_node($node) { + // Pre-populate the form-state with the right form include. + $form_state['build_info']['args'] = array($node); + form_load_include($form_state, 'inc', 'node', 'node.pages'); + return drupal_build_form($node->type . '_node_form', $form_state); +} + +/** + * Callback to get the form of a comment. + */ +function entity_metadata_form_comment($comment) { + if (!isset($comment->node_type)) { + $node = node_load($comment->nid); + $comment->node_type = 'comment_node_' . $node->type; + } + return drupal_get_form($comment->node_type . '_form', $comment); +} + +/** + * Callback to get the form of a user account. + */ +function entity_metadata_form_user($account) { + // Pre-populate the form-state with the right form include. + $form_state['build_info']['args'] = array($account); + form_load_include($form_state, 'inc', 'user', 'user.pages'); + return drupal_build_form('user_profile_form', $form_state); +} + +/** + * Callback to get the form of a term. + */ +function entity_metadata_form_taxonomy_term($term) { + // Pre-populate the form-state with the right form include. + $form_state['build_info']['args'] = array($term); + form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin'); + return drupal_build_form('taxonomy_form_term', $form_state); +} + +/** + * Callback to get the form of a vocabulary. + */ +function entity_metadata_form_taxonomy_vocabulary($term) { + // Pre-populate the form-state with the right form include. + $form_state['build_info']['args'] = array($term); + form_load_include($form_state, 'inc', 'taxonomy', 'taxonomy.admin'); + return drupal_build_form('taxonomy_form_term', $form_state); +} + +/** + * Callback to get the form for entities using the entity API admin ui. + */ +function entity_metadata_form_entity_ui($entity, $entity_type) { + $info = entity_get_info($entity_type); + $form_state = form_state_defaults(); + // Add in the include file as the form API does else with the include file + // specified for the active menu item. + if (!empty($info['admin ui']['file'])) { + $path = isset($info['admin ui']['file path']) ? $info['admin ui']['file path'] : drupal_get_path('module', $info['module']); + $form_state['build_info']['files']['entity_ui'] = $path . '/' . $info['admin ui']['file']; + // Also load the include file. + if (file_exists($form_state['build_info']['files']['entity_ui'])) { + require_once DRUPAL_ROOT . '/' . $form_state['build_info']['files']['entity_ui']; + } + } + return entity_ui_get_form($entity_type, $entity, $op = 'edit', $form_state); +} + +/** + * Callback for querying entity properties having their values stored in the + * entities main db table. + */ +function entity_metadata_table_query($entity_type, $property, $value, $limit) { + $properties = entity_get_all_property_info($entity_type); + $info = $properties[$property] + array('schema field' => $property); + + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $entity_type, '=') + ->propertyCondition($info['schema field'], $value, is_array($value) ? 'IN' : '=') + ->range(0, $limit); + + $result = $query->execute(); + return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array(); +} + +/** + * Callback for querying entities by field values. This function just queries + * for the value of the first specified column. Also it is only suitable for + * fields that don't process the data, so it's stored the same way as returned. + */ +function entity_metadata_field_query($entity_type, $property, $value, $limit) { + $query = new EntityFieldQuery(); + $field = field_info_field($property); + $columns = array_keys($field['columns']); + + $query->entityCondition('entity_type', $entity_type, '=') + ->fieldCondition($field, $columns[0], $value, is_array($value) ? 'IN' : '=') + ->range(0, $limit); + + $result = $query->execute(); + return !empty($result[$entity_type]) ? array_keys($result[$entity_type]) : array(); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/comment.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/comment.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,164 @@ + t("Comment ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the comment."), + 'schema field' => 'cid', + ); + $properties['hostname'] = array( + 'label' => t("IP Address"), + 'description' => t("The IP address of the computer the comment was posted from."), + 'schema field' => 'hostname', + ); + $properties['name'] = array( + 'label' => t("Name"), + 'description' => t("The name left by the comment author."), + 'getter callback' => 'entity_metadata_comment_get_properties', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer comments', + 'sanitize' => 'filter_xss', + 'schema field' => 'name', + ); + $properties['mail'] = array( + 'label' => t("Email address"), + 'description' => t("The email address left by the comment author."), + 'getter callback' => 'entity_metadata_comment_get_properties', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer comments', + 'validation callback' => 'valid_email_address', + 'schema field' => 'mail', + ); + $properties['homepage'] = array( + 'label' => t("Home page"), + 'description' => t("The home page URL left by the comment author."), + 'sanitize' => 'filter_xss_bad_protocol', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer comments', + 'schema field' => 'homepage', + ); + $properties['subject'] = array( + 'label' => t("Subject"), + 'description' => t("The subject of the comment."), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer comments', + 'sanitize' => 'filter_xss', + 'required' => TRUE, + 'schema field' => 'subject', + ); + $properties['url'] = array( + 'label' => t("URL"), + 'description' => t("The URL of the comment."), + 'getter callback' => 'entity_metadata_entity_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + ); + $properties['edit_url'] = array( + 'label' => t("Edit URL"), + 'description' => t("The URL of the comment's edit page."), + 'getter callback' => 'entity_metadata_comment_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + ); + $properties['created'] = array( + 'label' => t("Date created"), + 'description' => t("The date the comment was posted."), + 'type' => 'date', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer comments', + 'schema field' => 'created', + ); + $properties['parent'] = array( + 'label' => t("Parent"), + 'description' => t("The comment's parent, if comment threading is active."), + 'type' => 'comment', + 'getter callback' => 'entity_metadata_comment_get_properties', + 'schema field' => 'pid', + ); + $properties['node'] = array( + 'label' => t("Node"), + 'description' => t("The node the comment was posted to."), + 'type' => 'node', + 'setter callback' => 'entity_metadata_comment_setter', + 'setter permission' => 'administer comments', + 'required' => TRUE, + 'schema field' => 'nid', + ); + $properties['author'] = array( + 'label' => t("Author"), + 'description' => t("The author of the comment."), + 'type' => 'user', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer comments', + 'required' => TRUE, + 'schema field' => 'uid', + ); + $properties['status'] = array( + 'label' => t("Status"), + 'description' => t("Whether the comment is published or unpublished."), + 'setter callback' => 'entity_property_verbatim_set', + // Although the status is expected to be boolean, its schema suggests + // it is an integer, so we follow the schema definition. + 'type' => 'integer', + 'options list' => 'entity_metadata_status_options_list', + 'setter permission' => 'administer comments', + 'schema field' => 'status', + ); + return $info; +} + +/** + * Implements hook_entity_property_info_alter() on top of comment module. + * @see entity_entity_property_info_alter() + */ +function entity_metadata_comment_entity_property_info_alter(&$info) { + // Add info about comment module related properties to the node entity. + $properties = &$info['node']['properties']; + $properties['comment'] = array( + 'label' => t("Comments allowed"), + 'description' => t("Whether comments are allowed on this node: 0 = no, 1 = closed (read only), 2 = open (read/write)."), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer comments', + 'type' => 'integer', + ); + $properties['comment_count'] = array( + 'label' => t("Comment count"), + 'description' => t("The number of comments posted on a node."), + 'getter callback' => 'entity_metadata_comment_get_node_properties', + 'type' => 'integer', + ); + $properties['comment_count_new'] = array( + 'label' => t("New comment count"), + 'description' => t("The number of comments posted on a node since the reader last viewed it."), + 'getter callback' => 'entity_metadata_comment_get_node_properties', + 'type' => 'integer', + ); + + // The comment body field is usually available for all bundles, so add it + // directly to the comment entity. + $info['comment']['properties']['comment_body'] = array( + 'type' => 'text_formatted', + 'label' => t('The main body text'), + 'getter callback' => 'entity_metadata_field_verbatim_get', + 'setter callback' => 'entity_metadata_field_verbatim_set', + 'property info' => entity_property_text_formatted_info(), + 'field' => TRUE, + 'required' => TRUE, + ); + unset($info['comment']['properties']['comment_body']['property info']['summary']); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/field.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/field.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,170 @@ + $field) { + $field += array('bundles' => array()); + if ($field_type = field_info_field_types($field['type'])) { + // Add in our default callback as the first one. + $field_type += array('property_callbacks' => array()); + array_unshift($field_type['property_callbacks'], 'entity_metadata_field_default_property_callback'); + + foreach ($field['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance = field_info_instance($entity_type, $field_name, $bundle); + + if ($instance && empty($instance['deleted'])) { + foreach ($field_type['property_callbacks'] as $callback) { + $callback($info, $entity_type, $field, $instance, $field_type); + } + } + } + } + } + } + return $info; +} + +/** + * Callback to add in property info defaults per field instance. + * @see entity_metadata_field_entity_property_info(). + */ +function entity_metadata_field_default_property_callback(&$info, $entity_type, $field, $instance, $field_type) { + if (!empty($field_type['property_type'])) { + if ($field['cardinality'] != 1) { + $field_type['property_type'] = 'list<' . $field_type['property_type'] . '>'; + } + // Add in instance specific property info, if given and apply defaults. + $name = $field['field_name']; + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name]; + $instance += array('property info' => array()); + $property = $instance['property info'] + array( + 'label' => $instance['label'], + 'type' => $field_type['property_type'], + 'description' => t('Field "@name".', array('@name' => $name)), + 'getter callback' => 'entity_metadata_field_property_get', + 'setter callback' => 'entity_metadata_field_property_set', + 'access callback' => 'entity_metadata_field_access_callback', + 'query callback' => 'entity_metadata_field_query', + 'translatable' => !empty($field['translatable']), + // Specify that this property stems from a field. + 'field' => TRUE, + 'required' => !empty($instance['required']), + ); + // For field types of the list module add in the options list callback. + if (strpos($field['type'], 'list') === 0) { + $property['options list'] = 'entity_metadata_field_options_list'; + } + } +} + +/** + * Additional callback to adapt the property info for text fields. If a text + * field is processed we make use of a separate data structure so that format + * filters are available too. For the text value the sanitized, thus processed + * value is returned by default. + * + * @see entity_metadata_field_entity_property_info() + * @see entity_field_info_alter() + * @see entity_property_text_formatted_info() + */ +function entity_metadata_field_text_property_callback(&$info, $entity_type, $field, $instance, $field_type) { + if (!empty($instance['settings']['text_processing']) || $field['type'] == 'text_with_summary') { + // Define a data structure for dealing with text that is formatted or has + // a summary. + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + + $property['getter callback'] = 'entity_metadata_field_verbatim_get'; + $property['setter callback'] = 'entity_metadata_field_verbatim_set'; + unset($property['query callback']); + + if (empty($instance['settings']['text_processing'])) { + $property['property info'] = entity_property_field_item_textsummary_info(); + } + else { + // For formatted text we use the type name 'text_formatted'. + $property['type'] = ($field['cardinality'] != 1) ? 'list' : 'text_formatted'; + $property['property info'] = entity_property_text_formatted_info(); + } + // Enable auto-creation of the item, so that it is possible to just set + // the textual or summary value. + $property['auto creation'] = 'entity_property_create_array'; + + if ($field['type'] != 'text_with_summary') { + unset($property['property info']['summary']); + } + } +} + +/** + * Additional callback to adapt the property info for term reference fields. + * @see entity_metadata_field_entity_property_info(). + */ +function entity_metadata_field_term_reference_callback(&$info, $entity_type, $field, $instance, $field_type) { + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + if (count($field['settings']['allowed_values']) == 1) { + $settings = reset($field['settings']['allowed_values']); + $property['bundle'] = $settings['vocabulary']; + } + // Only add the options list callback for controlled vocabularies, thus + // vocabularies not using the autocomplete widget. + if ($instance['widget']['type'] != 'taxonomy_autocomplete') { + $property['options list'] = 'entity_metadata_field_options_list'; + } +} + +/** + * Additional callback to adapt the property info for file fields. + * @see entity_metadata_field_entity_property_info(). + */ +function entity_metadata_field_file_callback(&$info, $entity_type, $field, $instance, $field_type) { + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + // Define a data structure so it's possible to deal with files and their + // descriptions. + $property['getter callback'] = 'entity_metadata_field_verbatim_get'; + $property['setter callback'] = 'entity_metadata_field_verbatim_set'; + + // Auto-create the field $items as soon as a property is set. + $property['auto creation'] = 'entity_metadata_field_file_create_item'; + $property['validation callback'] = 'entity_metadata_field_file_validate_item'; + + $property['property info'] = entity_property_field_item_file_info(); + + if (empty($instance['settings']['description_field'])) { + unset($property['property info']['description']); + } + if (empty($field['settings']['display_field'])) { + unset($property['property info']['display']); + } + unset($property['query callback']); +} + +/** + * Additional callback to adapt the property info for image fields. + * This callback gets invoked after entity_metadata_field_file_callback(). + * @see entity_metadata_field_entity_property_info(). + */ +function entity_metadata_field_image_callback(&$info, $entity_type, $field, $instance, $field_type) { + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + // Update the property info with the info for image fields. + $property['property info'] = entity_property_field_item_image_info(); + + if (empty($instance['settings']['alt_field'])) { + unset($property['property info']['alt']); + } + if (empty($field['settings']['title_field'])) { + unset($property['property info']['title']); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/locale.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/locale.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,40 @@ + t("Language"), + 'description' => t("This account's default language for e-mails, and preferred language for site presentation."), + 'type' => 'token', + 'getter callback' => 'entity_metadata_locale_get_user_language', + 'setter callback' => 'entity_property_verbatim_set', + 'options list' => 'entity_metadata_language_list', + 'schema field' => 'language', + 'setter permission' => 'administer users', + ); + + $info['site']['properties']['current_page']['property info']['language'] = array( + 'label' => t("Interface language"), + 'description' => t("The language code of the current user interface language."), + 'type' => 'token', + 'getter callback' => 'entity_metadata_locale_get_languages', + 'options list' => 'entity_metadata_language_list', + ); + $info['site']['properties']['current_page']['property info']['language_content'] = array( + 'label' => t("Content language"), + 'description' => t("The language code of the current content language."), + 'type' => 'token', + 'getter callback' => 'entity_metadata_locale_get_languages', + 'options list' => 'entity_metadata_language_list', + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/node.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/node.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,165 @@ + t("Node ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the node."), + 'schema field' => 'nid', + ); + $properties['vid'] = array( + 'label' => t("Revision ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the node's revision."), + 'schema field' => 'vid', + ); + $properties['is_new'] = array( + 'label' => t("Is new"), + 'type' => 'boolean', + 'description' => t("Whether the node is new and not saved to the database yet."), + 'getter callback' => 'entity_metadata_node_get_properties', + ); + $properties['type'] = array( + 'label' => t("Content type"), + 'type' => 'token', + 'description' => t("The type of the node."), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'options list' => 'node_type_get_names', + 'required' => TRUE, + 'schema field' => 'type', + ); + $properties['title'] = array( + 'label' => t("Title"), + 'description' => t("The title of the node."), + 'setter callback' => 'entity_property_verbatim_set', + 'schema field' => 'title', + 'required' => TRUE, + ); + $properties['language'] = array( + 'label' => t("Language"), + 'type' => 'token', + 'description' => t("The language the node is written in."), + 'setter callback' => 'entity_property_verbatim_set', + 'options list' => 'entity_metadata_language_list', + 'schema field' => 'language', + 'setter permission' => 'administer nodes', + ); + $properties['url'] = array( + 'label' => t("URL"), + 'description' => t("The URL of the node."), + 'getter callback' => 'entity_metadata_entity_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + ); + $properties['edit_url'] = array( + 'label' => t("Edit URL"), + 'description' => t("The URL of the node's edit page."), + 'getter callback' => 'entity_metadata_node_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + ); + $properties['status'] = array( + 'label' => t("Status"), + 'description' => t("Whether the node is published or unpublished."), + // Although the status is expected to be boolean, its schema suggests + // it is an integer, so we follow the schema definition. + 'type' => 'integer', + 'options list' => 'entity_metadata_status_options_list', + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'schema field' => 'status', + ); + $properties['promote'] = array( + 'label' => t("Promoted to frontpage"), + 'description' => t("Whether the node is promoted to the frontpage."), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'schema field' => 'promote', + 'type' => 'boolean', + ); + $properties['sticky'] = array( + 'label' => t("Sticky in lists"), + 'description' => t("Whether the node is displayed at the top of lists in which it appears."), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'schema field' => 'sticky', + 'type' => 'boolean', + ); + $properties['created'] = array( + 'label' => t("Date created"), + 'type' => 'date', + 'description' => t("The date the node was posted."), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'schema field' => 'created', + ); + $properties['changed'] = array( + 'label' => t("Date changed"), + 'type' => 'date', + 'schema field' => 'changed', + 'description' => t("The date the node was most recently updated."), + ); + $properties['author'] = array( + 'label' => t("Author"), + 'type' => 'user', + 'description' => t("The author of the node."), + 'setter callback' => 'entity_property_verbatim_set', + 'setter permission' => 'administer nodes', + 'required' => TRUE, + 'schema field' => 'uid', + ); + $properties['source'] = array( + 'label' => t("Translation source node"), + 'type' => 'node', + 'description' => t("The original-language version of this node, if one exists."), + 'getter callback' => 'entity_metadata_node_get_properties', + ); + $properties['log'] = array( + 'label' => t("Revision log message"), + 'type' => 'text', + 'description' => t("In case a new revision is to be saved, the log entry explaining the changes for this version."), + 'setter callback' => 'entity_property_verbatim_set', + 'access callback' => 'entity_metadata_node_revision_access', + ); + $properties['revision'] = array( + 'label' => t("Creates revision"), + 'type' => 'boolean', + 'description' => t("Whether saving this node creates a new revision."), + 'setter callback' => 'entity_property_verbatim_set', + 'access callback' => 'entity_metadata_node_revision_access', + ); + return $info; +} + +/** + * Implements hook_entity_property_info_alter() on top of node module. + * @see entity_metadata_entity_property_info_alter() + */ +function entity_metadata_node_entity_property_info_alter(&$info) { + // Move the body property to the node by default, as its usually there this + // makes dealing with it more convenient. + $info['node']['properties']['body'] = array( + 'type' => 'text_formatted', + 'label' => t('The main body text'), + 'getter callback' => 'entity_metadata_field_verbatim_get', + 'setter callback' => 'entity_metadata_field_verbatim_set', + 'property info' => entity_property_text_formatted_info(), + 'auto creation' => 'entity_property_create_array', + 'field' => TRUE, + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/poll.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/poll.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,50 @@ + t("Poll votes"), + 'description' => t("The number of votes that have been cast on a poll node."), + 'type' => 'integer', + 'getter callback' => 'entity_metadata_poll_node_get_properties', + 'computed' => TRUE, + ); + $properties['poll_winner'] = array( + 'label' => t("Poll winner"), + 'description' => t("The winning poll answer."), + 'getter callback' => 'entity_metadata_poll_node_get_properties', + 'sanitize' => 'filter_xss', + 'computed' => TRUE, + ); + $properties['poll_winner_votes'] = array( + 'label' => t("Poll winner votes"), + 'description' => t("The number of votes received by the winning poll answer."), + 'type' => 'integer', + 'getter callback' => 'entity_metadata_poll_node_get_properties', + 'computed' => TRUE, + ); + $properties['poll_winner_percent'] = array( + 'label' => t("Poll winner percent"), + 'description' => t("The percentage of votes received by the winning poll answer."), + 'getter callback' => 'entity_metadata_poll_node_get_properties', + 'type' => 'decimal', + 'computed' => TRUE, + ); + $properties['poll_duration'] = array( + 'label' => t("Poll duration"), + 'description' => t("The length of time the poll node is set to run."), + 'getter callback' => 'entity_metadata_poll_node_get_properties', + 'type' => 'duration', + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/statistics.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/statistics.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,37 @@ + t("Number of views"), + 'description' => t("The number of visitors who have read the node."), + 'type' => 'integer', + 'getter callback' => 'entity_metadata_statistics_node_get_properties', + 'computed' => TRUE, + ); + $properties['day_views'] = array( + 'label' => t("Views today"), + 'description' => t("The number of visitors who have read the node today."), + 'type' => 'integer', + 'getter callback' => 'entity_metadata_statistics_node_get_properties', + 'computed' => TRUE, + ); + $properties['last_view'] = array( + 'label' => t("Last view"), + 'description' => t("The date on which a visitor last read the node."), + 'type' => 'date', + 'getter callback' => 'entity_metadata_statistics_node_get_properties', + 'computed' => TRUE, + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/system.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/system.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,132 @@ + t("Name"), + 'description' => t("The name of the site."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'sanitize' => 'check_plain', + ); + $properties['slogan'] = array( + 'label' => t("Slogan"), + 'description' => t("The slogan of the site."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'sanitize' => 'check_plain', + ); + $properties['mail'] = array( + 'label' => t("Email"), + 'description' => t("The administrative email address for the site."), + 'getter callback' => 'entity_metadata_system_get_properties', + ); + $properties['url'] = array( + 'label' => t("URL"), + 'description' => t("The URL of the site's front page."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'type' => 'uri', + ); + $properties['login_url'] = array( + 'label' => t("Login page"), + 'description' => t("The URL of the site's login page."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'type' => 'uri', + ); + $properties['current_user'] = array( + 'label' => t("Logged in user"), + 'description' => t("The currently logged in user."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'type' => 'user', + ); + $properties['current_date'] = array( + 'label' => t("Current date"), + 'description' => t("The current date and time."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'type' => 'date', + ); + $properties['current_page'] = array( + 'label' => t("Current page"), + 'description' => t("Information related to the current page request."), + 'getter callback' => 'entity_metadata_system_get_properties', + 'type' => 'struct', + 'property info' => array( + 'path' => array( + 'label' => t("Path"), + 'description' => t("The internal Drupal path of the current page request."), + 'getter callback' => 'current_path', + 'type' => 'text', + ), + 'url' => array( + 'label' => t("URL"), + 'description' => t("The full URL of the current page request."), + 'getter callback' => 'entity_metadata_system_get_page_properties', + 'type' => 'uri', + ), + ), + ); + + // Files. + $properties = &$info['file']['properties']; + $properties['fid'] = array( + 'label' => t("File ID"), + 'description' => t("The unique ID of the uploaded file."), + 'type' => 'integer', + 'validation callback' => 'entity_metadata_validate_integer_positive', + 'schema field' => 'fid', + ); + $properties['name'] = array( + 'label' => t("File name"), + 'description' => t("The name of the file on disk."), + 'getter callback' => 'entity_metadata_system_get_file_properties', + 'schema field' => 'filename', + ); + $properties['mime'] = array( + 'label' => t("MIME type"), + 'description' => t("The MIME type of the file."), + 'getter callback' => 'entity_metadata_system_get_file_properties', + 'sanitize' => 'filter_xss', + 'schema field' => 'filemime', + ); + $properties['size'] = array( + 'label' => t("File size"), + 'description' => t("The size of the file, in kilobytes."), + 'getter callback' => 'entity_metadata_system_get_file_properties', + 'type' => 'integer', + 'schema field' => 'filesize', + ); + $properties['url'] = array( + 'label' => t("URL"), + 'description' => t("The web-accessible URL for the file."), + 'getter callback' => 'entity_metadata_system_get_file_properties', + ); + $properties['timestamp'] = array( + 'label' => t("Timestamp"), + 'description' => t("The date the file was most recently changed."), + 'type' => 'date', + 'schema field' => 'timestamp', + ); + $properties['owner'] = array( + 'label' => t("Owner"), + 'description' => t("The user who originally uploaded the file."), + 'type' => 'user', + 'getter callback' => 'entity_metadata_system_get_file_properties', + 'schema field' => 'uid', + ); + return $info; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/taxonomy.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/taxonomy.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,124 @@ + t("Term ID"), + 'description' => t("The unique ID of the taxonomy term."), + 'type' => 'integer', + 'schema field' => 'tid', + ); + $properties['name'] = array( + 'label' => t("Name"), + 'description' => t("The name of the taxonomy term."), + 'setter callback' => 'entity_property_verbatim_set', + 'required' => TRUE, + 'schema field' => 'name', + ); + $properties['description'] = array( + 'label' => t("Description"), + 'description' => t("The optional description of the taxonomy term."), + 'sanitized' => TRUE, + 'raw getter callback' => 'entity_property_verbatim_get', + 'getter callback' => 'entity_metadata_taxonomy_term_get_properties', + 'setter callback' => 'entity_property_verbatim_set', + 'schema field' => 'description', + ); + $properties['weight'] = array( + 'label' => t("Weight"), + 'type' => 'integer', + 'description' => t('The weight of the term, which is used for ordering terms during display.'), + 'setter callback' => 'entity_property_verbatim_set', + 'schema field' => 'weight', + ); + $properties['node_count'] = array( + 'label' => t("Node count"), + 'type' => 'integer', + 'description' => t("The number of nodes tagged with the taxonomy term."), + 'getter callback' => 'entity_metadata_taxonomy_term_get_properties', + 'computed' => TRUE, + ); + $properties['url'] = array( + 'label' => t("URL"), + 'description' => t("The URL of the taxonomy term."), + 'getter callback' => 'entity_metadata_entity_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + ); + $properties['vocabulary'] = array( + 'label' => t("Vocabulary"), + 'description' => t("The vocabulary the taxonomy term belongs to."), + 'setter callback' => 'entity_metadata_taxonomy_term_setter', + 'type' => 'taxonomy_vocabulary', + 'required' => TRUE, + 'schema field' => 'vid', + ); + $properties['parent'] = array( + 'label' => t("Parent terms"), + 'description' => t("The parent terms of the taxonomy term."), + 'getter callback' => 'entity_metadata_taxonomy_term_get_properties', + 'setter callback' => 'entity_metadata_taxonomy_term_setter', + 'type' => 'list', + ); + $properties['parents_all'] = array( + 'label' => t("All parent terms"), + 'description' => t("Ancestors of the term, i.e. parent of all above hierarchy levels."), + 'getter callback' => 'entity_metadata_taxonomy_term_get_properties', + 'type' => 'list', + 'computed' => TRUE, + ); + + // Add meta-data about the basic vocabulary properties. + $properties = &$info['taxonomy_vocabulary']['properties']; + + // Taxonomy vocabulary related variables. + $properties['vid'] = array( + 'label' => t("Vocabulary ID"), + 'description' => t("The unique ID of the taxonomy vocabulary."), + 'type' => 'integer', + 'schema field' => 'vid', + ); + $properties['name'] = array( + 'label' => t("Name"), + 'description' => t("The name of the taxonomy vocabulary."), + 'setter callback' => 'entity_property_verbatim_set', + 'required' => TRUE, + 'schema field' => 'name', + ); + $properties['machine_name'] = array( + 'label' => t("Machine name"), + 'type' => 'token', + 'description' => t("The machine name of the taxonomy vocabulary."), + 'setter callback' => 'entity_property_verbatim_set', + 'required' => TRUE, + 'schema field' => 'machine_name', + ); + $properties['description'] = array( + 'label' => t("Description"), + 'description' => t("The optional description of the taxonomy vocabulary."), + 'setter callback' => 'entity_property_verbatim_set', + 'sanitize' => 'filter_xss', + 'schema field' => 'description', + ); + $properties['term_count'] = array( + 'label' => t("Term count"), + 'type' => 'integer', + 'description' => t("The number of terms belonging to the taxonomy vocabulary."), + 'getter callback' => 'entity_metadata_taxonomy_vocabulary_get_properties', + 'computed' => TRUE, + ); + return $info; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/modules/user.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/modules/user.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,108 @@ + t("User ID"), + 'type' => 'integer', + 'description' => t("The unique ID of the user account."), + 'schema field' => 'uid', + ); + $properties['name'] = array( + 'label' => t("Name"), + 'description' => t("The login name of the user account."), + 'getter callback' => 'entity_metadata_user_get_properties', + 'setter callback' => 'entity_property_verbatim_set', + 'sanitize' => 'filter_xss', + 'required' => TRUE, + 'access callback' => 'entity_metadata_user_properties_access', + 'schema field' => 'name', + ); + $properties['mail'] = array( + 'label' => t("Email"), + 'description' => t("The email address of the user account."), + 'setter callback' => 'entity_property_verbatim_set', + 'validation callback' => 'valid_email_address', + 'required' => TRUE, + 'access callback' => 'entity_metadata_user_properties_access', + 'schema field' => 'mail', + ); + $properties['url'] = array( + 'label' => t("URL"), + 'description' => t("The URL of the account profile page."), + 'getter callback' => 'entity_metadata_user_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + ); + $properties['edit_url'] = array( + 'label' => t("Edit URL"), + 'description' => t("The url of the account edit page."), + 'getter callback' => 'entity_metadata_user_get_properties', + 'type' => 'uri', + 'computed' => TRUE, + ); + $properties['last_access'] = array( + 'label' => t("Last access"), + 'description' => t("The date the user last accessed the site."), + 'getter callback' => 'entity_metadata_user_get_properties', + 'type' => 'date', + 'schema field' => 'access', + ); + $properties['last_login'] = array( + 'label' => t("Last login"), + 'description' => t("The date the user last logged in to the site."), + 'getter callback' => 'entity_metadata_user_get_properties', + 'type' => 'date', + 'schema field' => 'login', + ); + $properties['created'] = array( + 'label' => t("Created"), + 'description' => t("The date the user account was created."), + 'type' => 'date', + 'schema field' => 'created', + ); + $properties['roles'] = array( + 'label' => t("User roles"), + 'description' => t("The roles of the user."), + 'type' => 'list', + 'getter callback' => 'entity_metadata_user_get_properties', + 'setter callback' => 'entity_metadata_user_set_properties', + 'setter permission' => 'administer users', + 'options list' => 'entity_metadata_user_roles', + 'access callback' => 'entity_metadata_user_properties_access', + ); + $properties['status'] = array( + 'label' => t("Status"), + 'description' => t("Whether the user is active or blocked."), + 'setter callback' => 'entity_property_verbatim_set', + // Although the status is expected to be boolean, its schema suggests + // it is an integer, so we follow the schema definition. + 'type' => 'integer', + 'options list' => 'entity_metadata_user_status_options_list', + 'setter permission' => 'administer users', + 'schema field' => 'status', + ); + $properties['theme'] = array( + 'label' => t("Default theme"), + 'description' => t("The user's default theme."), + 'getter callback' => 'entity_metadata_user_get_properties', + 'setter callback' => 'entity_property_verbatim_set', + 'access callback' => 'entity_metadata_user_properties_access', + 'schema field' => 'theme', + ); + return $info; +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/tests/entity_feature.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/tests/entity_feature.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = Entity feature module +description = Provides some entities in code. +version = VERSION +core = 7.x +files[] = entity_feature.module +dependencies[] = entity_test +hidden = TRUE + +; Information added by drupal.org packaging script on 2013-08-14 +version = "7.x-1.2" +core = "7.x" +project = "entity" +datestamp = "1376493705" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/tests/entity_feature.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/tests/entity_feature.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,32 @@ + 'main', + 'label' => t('Main test type'), + 'weight' => 0, + 'locked' => TRUE, + )); + + // Types used during CRUD testing. + $types['test'] = entity_create('entity_test_type', array( + 'name' => 'test', + 'label' => 'label', + 'weight' => 0, + )); + $types['test2'] = entity_create('entity_test_type', array( + 'name' => 'test2', + 'label' => 'label2', + 'weight' => 2, + )); + + return $types; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/tests/entity_test.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/tests/entity_test.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,15 @@ +name = Entity CRUD test module +description = Provides entity types based upon the CRUD API. +version = VERSION +core = 7.x +files[] = entity_test.module +files[] = entity_test.install +dependencies[] = entity +hidden = TRUE + +; Information added by drupal.org packaging script on 2013-08-14 +version = "7.x-1.2" +core = "7.x" +project = "entity" +datestamp = "1376493705" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/tests/entity_test.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/tests/entity_test.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,164 @@ +fields('et') + ->execute() + ->fetchAllAssoc('name'); + + foreach ($types as $name => $type) { + field_attach_delete_bundle('entity_test', $name); + } +} + +/** + * Implements hook_schema(). + */ +function entity_test_schema() { + $schema['entity_test'] = array( + 'description' => 'Stores entity_test items.', + 'fields' => array( + 'pid' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique entity_test item ID.', + ), + 'name' => array( + 'description' => 'The name of the entity_test.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'default' => NULL, + 'description' => "The {users}.uid of the associated user.", + ), + ), + 'indexes' => array( + 'uid' => array('uid'), + ), + 'foreign keys' => array( + 'uid' => array('users' => 'uid'), + 'name' => array('entity_test_types' => 'name'), + ), + 'primary key' => array('pid'), + ); + + $schema['entity_test_type'] = array( + 'description' => 'Stores information about all defined entity_test types.', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique entity_test type ID.', + ), + 'name' => array( + 'description' => 'The machine-readable name of this entity_test type.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'label' => array( + 'description' => 'The human-readable name of this entity_test type.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + 'description' => 'The weight of this entity_test type in relation to others.', + ), + 'locked' => array( + 'description' => 'A boolean indicating whether the administrator may delete this type.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'data' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + 'serialize' => TRUE, + 'description' => 'A serialized array of additional data related to this entity_test type.', + 'merge' => TRUE, + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + // Set the default to ENTITY_CUSTOM without using the constant as it is + // not safe to use it at this point. + 'default' => 0x01, + 'size' => 'tiny', + 'description' => 'The exportable status of the entity.', + ), + 'module' => array( + 'description' => 'The name of the providing module if the entity has been defined in code.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'name' => array('name'), + ), + ); + + // Add schema for the revision-test-entity. + $schema['entity_test2'] = $schema['entity_test']; + $schema['entity_test2']['fields']['revision_id'] = array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'default' => NULL, + 'description' => 'The ID of the entity\'s default revision.', + ); + $schema['entity_test2']['fields']['title'] = array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ); + + $schema['entity_test2_revision'] = $schema['entity_test']; + $schema['entity_test2_revision']['fields']['revision_id'] = array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique revision ID.', + ); + $schema['entity_test2_revision']['fields']['pid'] = array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => FALSE, + 'default' => NULL, + 'description' => 'The ID of the attached entity.', + ); + $schema['entity_test2_revision']['fields']['title'] = array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ); + $schema['entity_test2_revision']['primary key'] = array('revision_id'); + + return $schema; +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/tests/entity_test.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/tests/entity_test.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,280 @@ + array( + 'label' => t('Test Entity'), + 'plural label' => t('Test Entities'), + 'description' => t('An entity type used by the entity API tests.'), + 'entity class' => 'EntityClass', + 'controller class' => 'EntityAPIController', + 'base table' => 'entity_test', + 'fieldable' => TRUE, + 'entity keys' => array( + 'id' => 'pid', + 'bundle' => 'name', + ), + // Make use the class' label() and uri() implementation by default. + 'label callback' => 'entity_class_label', + 'uri callback' => 'entity_class_uri', + 'bundles' => array(), + 'bundle keys' => array( + 'bundle' => 'name', + ), + 'module' => 'entity_test', + ), + 'entity_test_type' => array( + 'label' => t('Test entity type'), + 'entity class' => 'Entity', + 'controller class' => 'EntityAPIControllerExportable', + 'base table' => 'entity_test_type', + 'fieldable' => FALSE, + 'bundle of' => 'entity_test', + 'exportable' => TRUE, + 'entity keys' => array( + 'id' => 'id', + 'name' => 'name', + ), + 'module' => 'entity_test', + ), + + 'entity_test2' => array( + 'label' => t('Test Entity (revision support)'), + 'entity class' => 'EntityClassRevision', + 'controller class' => 'EntityAPIController', + 'base table' => 'entity_test2', + 'revision table' => 'entity_test2_revision', + 'fieldable' => TRUE, + 'entity keys' => array( + 'id' => 'pid', + 'revision' => 'revision_id', + ), + // Make use of the class label() and uri() implementation by default. + 'label callback' => 'entity_class_label', + 'uri callback' => 'entity_class_uri', + 'bundles' => array(), + 'bundle keys' => array( + 'bundle' => 'name', + ), + ), + ); + + // Add bundle info but bypass entity_load() as we cannot use it here. + $types = db_select('entity_test_type', 'et') + ->fields('et') + ->execute() + ->fetchAllAssoc('name'); + + foreach ($types as $name => $type) { + $return['entity_test']['bundles'][$name] = array( + 'label' => $type->label, + ); + } + + // Support entity cache module. + if (module_exists('entitycache')) { + $return['entity_test']['field cache'] = FALSE; + $return['entity_test']['entity cache'] = TRUE; + } + + return $return; +} + +/** + * Gets an array of all test entity types, keyed by the name. + * + * @param $name + * If set, the type with the given name is returned. + */ +function entity_test_get_types($name = NULL) { + $types = entity_load_multiple_by_name('entity_test_type', isset($name) ? array($name) : FALSE); + return isset($name) ? reset($types) : $types; +} + +/** + * Load multiple test entities based on certain conditions. + * + * @param $pids + * An array of entity IDs. + * @param $conditions + * An array of conditions to match against the {entity} table. + * @param $reset + * A boolean indicating that the internal cache should be reset. + * @return + * An array of test entity objects, indexed by pid. + */ +function entity_test_load_multiple($pids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('entity_test', $pids, $conditions, $reset); +} + +/** + * Delete multiple test entities. + * + * @param $pids + * An array of test entity IDs. + */ +function entity_test_delete_multiple(array $pids) { + entity_get_controller('entity_test')->delete($pids); +} + + +/** + * Main class for test entities. + */ +class EntityClass extends Entity { + + public function __construct(array $values = array(), $entityType = NULL) { + parent::__construct($values, 'entity_test'); + } + + /** + * Override buildContent() to add the username to the output. + */ + public function buildContent($view_mode = 'full', $langcode = NULL) { + $content['user'] = array( + '#markup' => "User: ". format_username(user_load($this->uid)), + ); + return entity_get_controller($this->entityType)->buildContent($this, $view_mode, $langcode, $content); + } + + /** + * Specifies the default label, which is picked up by label() by default. + */ + protected function defaultLabel() { + $type = entity_test_get_types($this->name); + return $type->label; + } + + /** + * Specifies the default uri, which is picked up by uri() by default. + */ + protected function defaultURI() { + return array('path' => 'custom/' . $this->identifier()); + } +} + +/** + * Main class for test entities (with revision support). + */ +class EntityClassRevision extends EntityClass { + + public function __construct(array $values = array(), $entityType = NULL) { + Entity::__construct($values, 'entity_test2'); + } + +} + +/** + * + * + * Some hook implementations used by the tests. + * + * + */ + + +/** + * Implements hook_entity_insert(). + */ +function entity_test_entity_insert($entity, $entity_type) { + if ($entity_type == 'entity_test_type') { + $_SESSION['entity_hook_test']['entity_insert'][] = entity_id($entity_type, $entity); + } +} + +/** + * Implements hook_entity_update(). + */ +function entity_test_entity_update($entity, $entity_type) { + $_SESSION['entity_hook_test']['entity_update'][] = entity_id($entity_type, $entity); +} + +/** + * Implements hook_entity_delete(). + */ +function entity_test_entity_delete($entity, $entity_type) { + if ($entity_type == 'entity_test_type') { + $_SESSION['entity_hook_test']['entity_delete'][] = entity_id($entity_type, $entity); + } +} + +/** + * Implements hook_entity_test_type_insert(). + */ +function entity_test_entity_test_type_insert($entity) { + $_SESSION['entity_hook_test']['entity_test_type_insert'][] = $entity->identifier(); +} + +/** + * Implements hook_entity_test_type_update(). + */ +function entity_test_entity_test_type_update($entity) { + $_SESSION['entity_hook_test']['entity_test_type_update'][] = $entity->identifier(); + + // Determine changes on update. + if (!empty($entity->original) && $entity->original->label == 'test_changes') { + if ($entity->original->label != $entity->label) { + $entity->label .= '_update'; + } + } +} + +/** + * Implements hook_entity_test_type_delete(). + */ +function entity_test_entity_test_type_delete($entity) { + $_SESSION['entity_hook_test']['entity_test_type_delete'][] = $entity->identifier(); +} + +/** + * Implements hook_entity_test_type_presave(). + */ +function entity_test_entity_test_type_presave($entity) { + // Determine changes. + if (!empty($entity->original) && $entity->original->label == 'test_changes') { + if ($entity->original->label != $entity->label) { + $entity->label .= '_presave'; + } + } +} + +/** + * Implements hook_entity_property_info_alter() for testing an property of type + * 'entity'. + */ +function entity_test_entity_property_info_alter(&$info) { + $info['node']['properties']['reference'] = array( + 'label' => t('Test reference'), + 'description' => t('A generic entity reference.'), + 'getter callback' => 'entity_test_entity_getter', + 'setter callback' => 'entity_test_entity_setter', + 'type' => 'entity', + ); +} + +/** + * Getter callback for the 'reference' property. + */ +function entity_test_entity_getter($node) { + if (empty($node->entity)) { + $node->entity = array('type' => 'user', 'id' => $node->uid); + } + // We have to return the entity wrapped. + return entity_metadata_wrapper($node->entity['type'], $node->entity['id']); +} + +/** + * Setter callback for the 'reference' property. + */ +function entity_test_entity_setter($node, $property_name, $wrapper) { + // The entity has to be passed wrapped. + $node->entity = array('type' => $wrapper->type(), 'id' => $wrapper->getIdentifier()); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/tests/entity_test_i18n.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/tests/entity_test_i18n.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Entity-test type translation +description = Allows translating entity-test types. +dependencies[] = entity_test +dependencies[] = i18n_string +package = Multilingual - Internationalization +core = 7.x +hidden = TRUE +; Information added by drupal.org packaging script on 2013-08-14 +version = "7.x-1.2" +core = "7.x" +project = "entity" +datestamp = "1376493705" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/tests/entity_test_i18n.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/tests/entity_test_i18n.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,53 @@ +original->name != $test_type->name) { + i18n_string_update_context("entity_test:entity_test_type:{$test_type->original->name}:*", "entity_test:entity_test_type:{$test_type->name}:*"); + } + i18n_string_object_update('entity_test_type', $test_type); +} + +/** + * Implements hook_{entity_test_type}_delete(). + */ +function entity_test_i18n_entity_test_type_delete($test_type) { + i18n_string_object_remove('entity_test_type', $test_type); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/theme/entity.theme.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/theme/entity.theme.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,4 @@ + +.entity-property-label { + font-weight: bold; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/theme/entity.theme.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/theme/entity.theme.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,212 @@ +' . $variables['label'] . ': 

    '; + } + + // Render the content. + $content_suffix = ''; + if (!$variables['label_hidden'] || $variables['content_attributes']) { + $output .= ''; + $content_suffix = ''; + } + $output .= $variables['content'] . $content_suffix; + + // Render the top-level DIV. + return '' . $output . ''; +} + +/** + * Theme preprocess function for theme_entity_property(). + * + * @see theme_entity_property() + */ +function template_preprocess_entity_property(&$variables, $hook) { + $element = $variables['elements']; + + $variables += array( + 'theme_hook_suggestions' => array(), + 'attributes_array' => array(), + ); + // Generate variables from element properties. + foreach (array('label_hidden', 'label', 'property_name') as $name) { + $variables[$name] = check_plain($element['#' . $name]); + } + $variables['title_attributes_array']['class'][] = 'entity-property-label'; + $variables['attributes_array'] = array_merge($variables['attributes_array'], isset($element['#attributes']) ? $element['#attributes'] : array()); + + $variables['property_name_css'] = strtr($element['#property_name'], '_', '-'); + $variables['attributes_array']['class'][] = 'entity-property'; + $variables['attributes_array']['class'][] = 'entity-property-' . $variables['property_name_css']; + + // Add specific suggestions that can override the default implementation. + $variables['theme_hook_suggestions'] += array( + 'entity_property__' . $element['#property_name'], + 'entity_property__' . $element['#entity_type'] . '__' . $element['#property_name'], + ); + + // Populate the content with sensible defaults. + if (!isset($variables['content'])) { + $variables['content'] = entity_property_default_render_value_by_type($element['#entity_wrapped']->{$element['#property_name']}); + } +} + +/** + * Renders a property using simple defaults based upon the property type. + * + * @return string + */ +function entity_property_default_render_value_by_type(EntityMetadataWrapper $property) { + // If there is an options list or entity label, render that by default. + if ($label = $property->label()) { + if ($property instanceof EntityDrupalWrapper && $uri = entity_uri($property->type(), $property->value())) { + return l($label, $uri['path'], $uri['options']); + } + else { + return check_plain($label); + } + } + switch ($property->type()) { + case 'boolean': + return $property->value() ? t('yes') : t('no'); + default: + return check_plain($property->value()); + } +} + +/** + * Theme process function for theme_entity_property(). + * + * Taken over from template_process_field() + * + * @see theme_entity_property() + */ +function template_process_entity_property(&$variables, $hook) { + $element = $variables['elements']; + // The default theme implementation is a function, so template_process() does + // not automatically run, so we need to flatten the classes and attributes + // here. For best performance, only call drupal_attributes() when needed, and + // note that template_preprocess_field() does not initialize the + // *_attributes_array variables. + $variables['attributes'] = empty($variables['attributes_array']) ? '' : drupal_attributes($variables['attributes_array']); + $variables['title_attributes'] = empty($variables['title_attributes_array']) ? '' : drupal_attributes($variables['title_attributes_array']); + $variables['content_attributes'] = empty($variables['content_attributes_array']) ? '' : drupal_attributes($variables['content_attributes_array']); +} + +/** + * Themes the exportable status of an entity. + */ +function theme_entity_status($variables) { + $status = $variables['status']; + $html = $variables['html']; + if (($status & ENTITY_FIXED) == ENTITY_FIXED) { + $label = t('Fixed'); + $help = t('The configuration is fixed and cannot be changed.'); + return $html ? "" . $label . "" : $label; + } + elseif (($status & ENTITY_OVERRIDDEN) == ENTITY_OVERRIDDEN) { + $label = t('Overridden'); + $help = t('This configuration is provided by a module, but has been changed.'); + return $html ? "" . $label . "" : $label; + } + elseif ($status & ENTITY_IN_CODE) { + $label = t('Default'); + $help = t('A module provides this configuration.'); + return $html ? "" . $label . "" : $label; + } + elseif ($status & ENTITY_CUSTOM) { + $label = t('Custom'); + $help = t('A custom configuration by a user.'); + return $html ? "" . $label . "" : $label; + } +} + +/** + * Process variables for entity.tpl.php. + */ +function template_preprocess_entity(&$variables) { + $variables['view_mode'] = $variables['elements']['#view_mode']; + $entity_type = $variables['elements']['#entity_type']; + $variables['entity_type'] = $entity_type; + $entity = $variables['elements']['#entity']; + $variables[$variables['elements']['#entity_type']] = $entity; + $info = entity_get_info($entity_type); + + $variables['title'] = check_plain(entity_label($entity_type, $entity)); + + $uri = entity_uri($entity_type, $entity); + $variables['url'] = $uri ? url($uri['path'], $uri['options']) : FALSE; + + if (isset($variables['elements']['#page'])) { + // If set by the caller, respect the page property. + $variables['page'] = $variables['elements']['#page']; + } + else { + // Else, try to automatically detect it. + $variables['page'] = $uri && $uri['path'] == $_GET['q']; + } + + // Helpful $content variable for templates. + $variables['content'] = array(); + foreach (element_children($variables['elements']) as $key) { + $variables['content'][$key] = $variables['elements'][$key]; + } + + if (!empty($info['fieldable'])) { + // Make the field variables available with the appropriate language. + field_attach_preprocess($entity_type, $entity, $variables['content'], $variables); + } + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + + // Gather css classes. + $variables['classes_array'][] = drupal_html_class('entity-' . $entity_type); + $variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle); + + // Add RDF type and about URI. + if (module_exists('rdf')) { + $variables['attributes_array']['about'] = empty($uri['path']) ? NULL: url($uri['path']); + $variables['attributes_array']['typeof'] = empty($entity->rdf_mapping['rdftype']) ? NULL : $entity->rdf_mapping['rdftype']; + } + + // Add suggestions. + $variables['theme_hook_suggestions'][] = $entity_type; + $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle; + $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode']; + if ($id = entity_id($entity_type, $entity)) { + $variables['theme_hook_suggestions'][] = $entity_type . '__' . $id; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/theme/entity.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/theme/entity.tpl.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,48 @@ + +
    > + + + > + + + + + + + + +
    > + +
    +
    diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/entity.views.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/entity.views.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,636 @@ + $info) { + // Provide default integration with the basic controller class if we know + // the module providing the entity and it does not provide views integration. + if (!isset($info['views controller class'])) { + $info['views controller class'] = isset($info['module']) && !module_hook($info['module'], 'views_data') ? 'EntityDefaultViewsController' : FALSE; + } + if ($info['views controller class']) { + $controller = new $info['views controller class']($type); + // Relationship data may return views data for already existing tables, + // so merge results on the second level. + foreach ($controller->views_data() as $table => $table_data) { + $data += array($table => array()); + $data[$table] = array_merge($data[$table], $table_data); + } + } + } + + // Add tables based upon data selection "queries" for all entity types. + foreach (entity_get_info() as $type => $info) { + $table = entity_views_table_definition($type); + if ($table) { + $data['entity_' . $type] = $table; + } + // Generally expose properties marked as 'entity views field'. + $data['views_entity_' . $type] = array(); + foreach (entity_get_all_property_info($type) as $key => $property) { + if (!empty($property['entity views field'])) { + entity_views_field_definition($key, $property, $data['views_entity_' . $type]); + } + } + } + + // Expose generally usable entity-related fields. + foreach (entity_get_info() as $entity_type => $info) { + if (entity_type_supports($entity_type, 'view')) { + // Expose a field allowing to display the rendered entity. + $data['views_entity_' . $entity_type]['rendered_entity'] = array( + 'title' => t('Rendered @entity-type', array('@entity-type' => $info['label'])), + 'help' => t('The @entity-type of the current relationship rendered using a view mode.', array('@entity-type' => $info['label'])), + 'field' => array( + 'handler' => 'entity_views_handler_field_entity', + 'type' => $entity_type, + // The EntityFieldHandlerHelper treats the 'entity object' data + // selector as special case for loading the base entity. + 'real field' => 'entity object', + ), + ); + } + } + + $data['entity__global']['table']['group'] = t('Entity'); + $data['entity__global']['table']['join'] = array( + // #global let's it appear all the time. + '#global' => array(), + ); + $data['entity__global']['entity'] = array( + 'title' => t('Rendered entity'), + 'help' => t('Displays a single chosen entity.'), + 'area' => array( + 'handler' => 'entity_views_handler_area_entity', + ), + ); + + return $data; +} + +/** + * Helper function for getting data selection based entity Views table definitions. + * + * This creates extra tables for each entity type that are not associated with a + * query plugin (and thus are not base tables) and just rely on the entities to + * retrieve the displayed data. To obtain the entities corresponding to a + * certain result set, the field handlers defined on the table use a generic + * interface defined for query plugins that are based on entity handling, and + * which is described in the entity_views_example_query class. + * + * These tables are called "data selection tables". + * + * Other modules providing Views integration with new query plugins that are + * based on entities can then use these tables as a base for their own tables + * (by directly using this method and modifying the returned table) and/or by + * specifying relationships to them. The tables returned here already specify + * relationships to each other wherever an entity contains a reference to + * another (e.g., the node author constructs a relationship from nodes to + * users). + * + * As filtering and other query manipulation is potentially more plugin-specific + * than the display, only field handlers and relationships are provided with + * these tables. By providing a add_selector_orderby() method, the query plugin + * can, however, support click-sorting for the field handlers in these tables. + * + * For a detailed discussion see http://drupal.org/node/1266036 + * + * For example use see the Search API views module in the Search API project: + * http://drupal.org/project/search_api + * + * @param $type + * The entity type whose table definition should be returned. + * @param $exclude + * Whether properties already exposed as 'entity views field' should be + * excluded. Defaults to TRUE, as they are available for all views tables for + * the entity type anyways. + * + * @return + * An array containing the data selection Views table definition for the + * entity type. + * + * @see entity_views_field_definition() + */ +function entity_views_table_definition($type, $exclude = TRUE) { + // As other modules might want to copy these tables as a base for their own + // Views integration, we statically cache the tables to save some time. + $tables = &drupal_static(__FUNCTION__, array()); + + if (!isset($tables[$type])) { + // Work-a-round to fix updating, see http://drupal.org/node/1330874. + // Views data might be rebuilt on update.php before the registry is rebuilt, + // thus the class cannot be auto-loaded. + if (!class_exists('EntityFieldHandlerHelper')) { + module_load_include('inc', 'entity', 'views/handlers/entity_views_field_handler_helper'); + } + + $info = entity_get_info($type); + $tables[$type]['table'] = array( + 'group' => $info['label'], + 'entity type' => $type, + ); + foreach (entity_get_all_property_info($type) as $key => $property) { + if (!$exclude || empty($property['entity views field'])) { + entity_views_field_definition($key, $property, $tables[$type]); + } + } + } + + return $tables[$type]; +} + +/** + * Helper function for adding a Views field definition to data selection based Views tables. + * + * @param $field + * The data selector of the field to add. E.g. "title" would derive the node + * title property, "body:summary" the node body's summary. + * @param array $property_info + * The property information for which to create a field definition. + * @param array $table + * The table into which the definition should be inserted. + * @param $title_prefix + * Internal use only. + * + * @see entity_views_table_definition() + */ +function entity_views_field_definition($field, array $property_info, array &$table, $title_prefix = '') { + $additional = array(); + $additional_field = array(); + + // Create a valid Views field identifier (no colons, etc.). Keep the original + // data selector as real field though. + $key = _entity_views_field_identifier($field, $table); + if ($key != $field) { + $additional['real field'] = $field; + } + $field_name = EntityFieldHandlerHelper::get_selector_field_name($field); + + $field_handlers = entity_views_get_field_handlers(); + + $property_info += entity_property_info_defaults(); + $type = entity_property_extract_innermost_type($property_info['type']); + $title = $title_prefix . $property_info['label']; + if ($info = entity_get_info($type)) { + $additional['relationship'] = array( + 'handler' => $field_handlers['relationship'], + 'base' => 'entity_' . $type, + 'base field' => $info['entity keys']['id'], + 'relationship field' => $field, + 'label' => $title, + ); + if ($property_info['type'] != $type) { + // This is a list of entities, so we should mark the relationship as such. + $additional['relationship']['multiple'] = TRUE; + } + // Implementers of the field handlers alter hook could add handlers for + // specific entity types. + if (!isset($field_handlers[$type])) { + $type = 'entity'; + } + } + elseif (!empty($property_info['field'])) { + $type = 'field'; + // Views' Field API field handler needs some extra definitions to work. + $additional_field['field_name'] = $field_name; + $additional_field['entity_tables'] = array(); + $additional_field['entity type'] = $table['table']['entity type']; + $additional_field['is revision'] = FALSE; + } + // Copied from EntityMetadataWrapper::optionsList() + elseif (isset($property_info['options list']) && is_callable($property_info['options list'])) { + // If this is a nested property, we need to get rid of all prefixes first. + $type = 'options'; + $additional_field['options callback'] = array( + 'function' => $property_info['options list'], + 'info' => $property_info, + ); + } + elseif ($type == 'decimal') { + $additional_field['float'] = TRUE; + } + + if (isset($field_handlers[$type])) { + $table += array($key => array()); + $table[$key] += array( + 'title' => $title, + 'help' => empty($property_info['description']) ? t('(No information available)') : $property_info['description'], + 'field' => array(), + ); + $table[$key]['field'] += array( + 'handler' => $field_handlers[$type], + 'type' => $property_info['type'], + ); + $table[$key] += $additional; + $table[$key]['field'] += $additional_field; + } + if (!empty($property_info['property info'])) { + foreach ($property_info['property info'] as $nested_key => $nested_property) { + entity_views_field_definition($field . ':' . $nested_key, $nested_property, $table, $title . ' » '); + } + } +} + +/** + * @return array + * The handlers to use for the data selection based Views tables. + * + * @see hook_entity_views_field_handlers_alter() + */ +function entity_views_get_field_handlers() { + $field_handlers = drupal_static(__FUNCTION__); + if (!isset($field_handlers)) { + // Field handlers for the entity tables, by type. + $field_handlers = array( + 'text' => 'entity_views_handler_field_text', + 'token' => 'entity_views_handler_field_text', + 'integer' => 'entity_views_handler_field_numeric', + 'decimal' => 'entity_views_handler_field_numeric', + 'date' => 'entity_views_handler_field_date', + 'duration' => 'entity_views_handler_field_duration', + 'boolean' => 'entity_views_handler_field_boolean', + 'uri' => 'entity_views_handler_field_uri', + 'options' => 'entity_views_handler_field_options', + 'field' => 'entity_views_handler_field_field', + 'entity' => 'entity_views_handler_field_entity', + 'relationship' => 'entity_views_handler_relationship', + ); + drupal_alter('entity_views_field_handlers', $field_handlers); + } + return $field_handlers; +} + +/** + * Helper function for creating valid Views field identifiers out of data selectors. + * + * Uses $table to test whether the identifier is already used, and also + * recognizes if a definition for the same field is already present and returns + * that definition's identifier. + * + * @return string + * A valid Views field identifier that is not yet used as a key in $table. + */ +function _entity_views_field_identifier($field, array $table) { + $key = $base = preg_replace('/[^a-zA-Z0-9]+/S', '_', $field); + $i = 0; + // The condition checks whether this sanitized field identifier is already + // used for another field in this table (and whether the identifier is + // "table", which can never be used). + // If $table[$key] is set, the identifier is already used, but this might be + // already for the same field. To test that, we need the original field name, + // which is either $table[$key]['real field'], if set, or $key. If this + // original field name is equal to $field, we can use that key. Otherwise, we + // append numeric suffixes until we reach an unused key. + while ($key == 'table' || (isset($table[$key]) && (isset($table[$key]['real field']) ? $table[$key]['real field'] : $key) != $field)) { + $key = $base . '_' . ++$i; + } + return $key; +} + +/** + * Implements hook_views_plugins(). + */ +function entity_views_plugins() { + // Have views cache the table list for us so it gets + // cleared at the appropriate times. + $data = views_cache_get('entity_base_tables', TRUE); + if (!empty($data->data)) { + $base_tables = $data->data; + } + else { + $base_tables = array(); + foreach (views_fetch_data() as $table => $data) { + if (!empty($data['table']['entity type']) && !empty($data['table']['base'])) { + $base_tables[] = $table; + } + } + views_cache_set('entity_base_tables', $base_tables, TRUE); + } + if (!empty($base_tables)) { + return array( + 'module' => 'entity', + 'row' => array( + 'entity' => array( + 'title' => t('Rendered entity'), + 'help' => t('Renders a single entity in a specific view mode (e.g. teaser).'), + 'handler' => 'entity_views_plugin_row_entity_view', + 'uses fields' => FALSE, + 'uses options' => TRUE, + 'type' => 'normal', + 'base' => $base_tables, + ), + ), + ); + } +} + +/** + * Default controller for generating basic views integration. + * + * The controller tries to generate suiting views integration for the entity + * based upon the schema information of its base table and the provided entity + * property information. + * For that it is possible to map a property name to its schema/views field + * name by adding a 'schema field' key with the name of the field as value to + * the property info. + */ +class EntityDefaultViewsController { + + protected $type, $info, $relationships; + + public function __construct($type) { + $this->type = $type; + $this->info = entity_get_info($type); + } + + /** + * Defines the result for hook_views_data(). + */ + public function views_data() { + $data = array(); + $this->relationships = array(); + + if (!empty($this->info['base table'])) { + $table = $this->info['base table']; + // Define the base group of this table. Fields that don't + // have a group defined will go into this field by default. + $data[$table]['table']['group'] = drupal_ucfirst($this->info['label']); + $data[$table]['table']['entity type'] = $this->type; + + // If the plural label isn't available, use the regular label. + $label = isset($this->info['plural label']) ? $this->info['plural label'] : $this->info['label']; + $data[$table]['table']['base'] = array( + 'field' => $this->info['entity keys']['id'], + 'access query tag' => $this->type . '_access', + 'title' => drupal_ucfirst($label), + 'help' => isset($this->info['description']) ? $this->info['description'] : '', + ); + $data[$table]['table']['entity type'] = $this->type; + $data[$table] += $this->schema_fields(); + + // Add in any reverse-relationships which have been determined. + $data += $this->relationships; + } + return $data; + } + + /** + * Try to come up with some views fields with the help of the schema and + * the entity property information. + */ + protected function schema_fields() { + $schema = drupal_get_schema($this->info['base table']); + $properties = entity_get_property_info($this->type) + array('properties' => array()); + $data = array(); + + foreach ($properties['properties'] as $name => $property_info) { + if (isset($property_info['schema field']) && isset($schema['fields'][$property_info['schema field']])) { + if ($views_info = $this->map_from_schema_info($name, $schema['fields'][$property_info['schema field']], $property_info)) { + $data[$name] = $views_info; + } + } + } + return $data; + } + + /** + * Comes up with views information based on the given schema and property + * info. + */ + protected function map_from_schema_info($property_name, $schema_field_info, $property_info) { + $type = isset($property_info['type']) ? $property_info['type'] : 'text'; + $views_field_name = $property_info['schema field']; + + $return = array(); + + if (!empty($schema_field_info['serialize'])) { + return FALSE; + } + + $description = array( + 'title' => $property_info['label'], + 'help' => isset($property_info['description']) ? $property_info['description'] : NULL, + ); + + // Add in relationships to related entities. + if (($info = entity_get_info($type)) && !empty($info['base table'])) { + + // Prepare reversed relationship data. + $label_lowercase = drupal_strtolower($this->info['label'][0]) . drupal_substr($this->info['label'], 1); + $property_label_lowercase = drupal_strtolower($property_info['label'][0]) . drupal_substr($property_info['label'], 1); + + // We name the field of the first reverse-relationship just with the + // base table to be backward compatible, for subsequents relationships we + // append the views field name in order to get a unique name. + $name = !isset($this->relationships[$info['base table']][$this->info['base table']]) ? $this->info['base table'] : $this->info['base table'] . '_' . $views_field_name; + $this->relationships[$info['base table']][$name] = array( + 'title' => $this->info['label'], + 'help' => t("Associated @label via the @label's @property.", array('@label' => $label_lowercase, '@property' => $property_label_lowercase)), + 'relationship' => array( + 'label' => $this->info['label'], + 'handler' => $this->getRelationshipHandlerClass($this->type, $type), + 'base' => $this->info['base table'], + 'base field' => $views_field_name, + 'relationship field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'], + ), + ); + + $return['relationship'] = array( + 'label' => drupal_ucfirst($info['label']), + 'handler' => $this->getRelationshipHandlerClass($type, $this->type), + 'base' => $info['base table'], + 'base field' => isset($info['entity keys']['name']) ? $info['entity keys']['name'] : $info['entity keys']['id'], + 'relationship field' => $views_field_name, + ); + + // Add in direct field/filters/sorts for the id itself too. + $type = isset($info['entity keys']['name']) ? 'token' : 'integer'; + // Append the views-field-name to the title if it is different to the + // property name. + if ($property_name != $views_field_name) { + $description['title'] .= ' ' . $views_field_name; + } + } + + switch ($type) { + case 'token': + case 'text': + $return += $description + array( + 'field' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_field', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_argument_string', + ), + ); + break; + + case 'decimal': + case 'integer': + $return += $description + array( + 'field' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_field_numeric', + 'click sortable' => TRUE, + 'float' => ($type == 'decimal'), + ), + 'sort' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_filter_numeric', + ), + 'argument' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_argument_numeric', + ), + ); + break; + + case 'date': + $return += $description + array( + 'field' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_field_date', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_sort_date', + ), + 'filter' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_filter_date', + ), + 'argument' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_argument_date', + ), + ); + break; + + case 'uri': + $return += $description + array( + 'field' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_field_url', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_filter_string', + ), + 'argument' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_argument_string', + ), + ); + break; + + case 'boolean': + $return += $description + array( + 'field' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_field_boolean', + 'click sortable' => TRUE, + ), + 'sort' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_sort', + ), + 'filter' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_filter_boolean_operator', + ), + 'argument' => array( + 'real field' => $views_field_name, + 'handler' => 'views_handler_argument_string', + ), + ); + break; + } + + // If there is an options list callback, add to the filter and field. + if (isset($return['filter']) && !empty($property_info['options list'])) { + $return['filter']['handler'] = 'views_handler_filter_in_operator'; + $return['filter']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback'); + $return['filter']['options arguments'] = array($this->type, $property_name, 'view'); + } + // @todo: This class_exists is needed until views 3.2. + if (isset($return['field']) && !empty($property_info['options list']) && class_exists('views_handler_field_machine_name')) { + $return['field']['handler'] = 'views_handler_field_machine_name'; + $return['field']['options callback'] = array('EntityDefaultViewsController', 'optionsListCallback'); + $return['field']['options arguments'] = array($this->type, $property_name, 'view'); + } + return $return; + } + + /** + * Determines the handler to use for a relationship to an entity type. + * + * @param $entity_type + * The entity type to join to. + * @param $left_type + * The data type from which to join. + */ + function getRelationshipHandlerClass($entity_type, $left_type) { + // Look for an entity type which is used as bundle for the given entity + // type. If there is one, allow filtering the relation by bundle by using + // our own handler. + foreach (entity_get_info() as $type => $info) { + // In case we already join from the bundle entity we do not need to filter + // by bundle entity any more, so we stay with the general handler. + if (!empty($info['bundle of']) && $info['bundle of'] == $entity_type && $type != $left_type) { + return 'entity_views_handler_relationship_by_bundle'; + } + } + return 'views_handler_relationship'; + } + + /** + * A callback returning property options, suitable to be used as views options callback. + */ + public static function optionsListCallback($type, $selector, $op = 'view') { + $wrapper = entity_metadata_wrapper($type, NULL); + $parts = explode(':', $selector); + foreach ($parts as $part) { + $wrapper = $wrapper->get($part); + } + return $wrapper->optionsList($op); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/entity_views_example_query.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/entity_views_example_query.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,88 @@ +definition['type'])) { + $options['list']['contains']['mode'] = array('default' => 'collapse'); + $options['list']['contains']['separator'] = array('default' => ', '); + $options['list']['contains']['type'] = array('default' => 'ul'); + } + $options['link_to_entity'] = array('default' => FALSE); + + return $options; + } + + /** + * Provide an appropriate default option form for a handler. + */ + public static function options_form($handler, &$form, &$form_state) { + if (entity_property_list_extract_type($handler->definition['type'])) { + $form['list']['mode'] = array( + '#type' => 'select', + '#title' => t('List handling'), + '#options' => array( + 'collapse' => t('Concatenate values using a seperator'), + 'list' => t('Output values as list'), + 'first' => t('Show first (if present)'), + 'count' => t('Show item count'), + ), + '#default_value' => $handler->options['list']['mode'], + ); + $form['list']['separator'] = array( + '#type' => 'textfield', + '#title' => t('List seperator'), + '#default_value' => $handler->options['list']['separator'], + '#dependency' => array('edit-options-list-mode' => array('collapse')), + ); + $form['list']['type'] = array( + '#type' => 'select', + '#title' => t('List type'), + '#options' => array( + 'ul' => t('Unordered'), + 'ol' => t('Ordered'), + ), + '#default_value' => $handler->options['list']['type'], + '#dependency' => array('edit-options-list-mode' => array('list')), + ); + } + $form['link_to_entity'] = array( + '#type' => 'checkbox', + '#title' => t('Link this field to its entity'), + '#description' => t("When using this, you should not set any other link on the field."), + '#default_value' => $handler->options['link_to_entity'], + ); + } + + /** + * Add the field for the entity ID (if necessary). + */ + public static function query($handler) { + // Copied over from views_handler_field_entity::query(). + // Sets table_alias (entity table), base_field (entity id field) and + // field_alias (the field's alias). + $handler->table_alias = $base_table = $handler->view->base_table; + $handler->base_field = $handler->view->base_field; + + if (!empty($handler->relationship)) { + foreach ($handler->view->relationship as $relationship) { + if ($relationship->alias == $handler->relationship) { + $base_table = $relationship->definition['base']; + $handler->table_alias = $relationship->alias; + + $table_data = views_fetch_data($base_table); + $handler->base_field = empty($relationship->definition['base field']) ? $table_data['table']['base']['field'] : $relationship->definition['base field']; + } + } + } + + // Add the field if the query back-end implements an add_field() method, + // just like the default back-end. + if (method_exists($handler->query, 'add_field')) { + $handler->field_alias = $handler->query->add_field($handler->table_alias, $handler->base_field, ''); + } + else { + // To ensure there is an alias just set the field alias to the real field. + $handler->field_alias = $handler->real_field; + } + } + + /** + * Extracts the innermost field name from a data selector. + * + * @param $selector + * The data selector. + * + * @return + * The last component of the data selector. + */ + public static function get_selector_field_name($selector) { + return ltrim(substr($selector, strrpos($selector, ':')), ':'); + } + + /** + * Adds a click-sort to the query. + * + * @param $order + * Either 'ASC' or 'DESC'. + */ + public static function click_sort($handler, $order) { + // The normal orderby() method for this usually won't work here. So we need + // query plugins to provide their own method for this. + if (method_exists($handler->query, 'add_selector_orderby')) { + $selector = self::construct_property_selector($handler, TRUE); + $handler->query->add_selector_orderby($selector, $order); + } + } + + /** + * Load the entities for all rows that are about to be displayed. + * + * Automatically takes care of relationships, including data selection + * relationships. Results are written into @code $handler->wrappers @endcode + * and @code $handler->entity_type @endcode is set. + */ + public static function pre_render($handler, &$values, $load_always = FALSE) { + if (empty($values)) { + return; + } + if (!$load_always && empty($handler->options['link_to_entity'])) { + // Check whether we even need to load the entities. + $selector = self::construct_property_selector($handler, TRUE); + $load = FALSE; + foreach ($values as $row) { + if (empty($row->_entity_properties) || !array_key_exists($selector, $row->_entity_properties)) { + $load = TRUE; + break; + } + } + if (!$load) { + return; + } + } + + if (method_exists($handler->query, 'get_result_wrappers')) { + list($handler->entity_type, $handler->wrappers) = $handler->query->get_result_wrappers($values, $handler->relationship, $handler->real_field); + } + else { + list($handler->entity_type, $entities) = $handler->query->get_result_entities($values, $handler->relationship, $handler->real_field); + $handler->wrappers = array(); + foreach ($entities as $id => $entity) { + $handler->wrappers[$id] = entity_metadata_wrapper($handler->entity_type, $entity); + } + } + } + + /** + * Return an Entity API data selector for the given handler's relationship. + * + * A data selector is a concatenation of properties which should be followed + * to arrive at a desired property that may be nested in related entities or + * structures. The separate properties are herein concatenated with colons. + * + * For instance, a data selector of "author:roles" would mean to first + * access the "author" property of the given wrapper, and then for this new + * wrapper to access and return the "roles" property. + * + * Lists of entities are handled automatically by always returning only the + * first entity. + * + * @param $handler + * The handler for which to construct the selector. + * @param $complete + * If TRUE, the complete selector for the field is returned, not just the + * one for its parent. Defaults to FALSE. + * + * @return + * An Entity API data selector for the given handler's relationship. + */ + public static function construct_property_selector($handler, $complete = FALSE) { + $return = ''; + if ($handler->relationship) { + $current_handler = $handler; + $view = $current_handler->view; + while (!empty($current_handler->relationship) && !empty($view->relationship[$current_handler->relationship])) { + $current_handler = $view->relationship[$current_handler->relationship]; + $return = $current_handler->real_field . ($return ? ":$return" : ''); + } + } + + if ($complete) { + $return .= ($return ? ':' : '') . $handler->real_field; + } + elseif ($pos = strrpos($handler->real_field, ':')) { + // If we have a selector as the real_field, append this to the returned + // relationship selector. + $return .= ($return ? ':' : '') . substr($handler->real_field, 0, $pos); + } + + return $return; + } + + /** + * Extracts data from several metadata wrappers based on a data selector. + * + * All metadata wrappers passed to this function have to be based on the exact + * same property information. The data will be returned wrapped by one or more + * metadata wrappers. + * + * Can be used in query plugins for the get_result_entities() and + * get_result_wrappers() methods. + * + * @param array $wrappers + * The EntityMetadataWrapper objects from which to extract data. + * @param $selector + * The selector specifying the data to extract. + * + * @return array + * An array with numeric indices, containing the type of the extracted + * wrappers in the first element. The second element of the array contains + * the extracted property value(s) for each wrapper, keyed to the same key + * that was used for the respecive wrapper in $wrappers. All extracted + * properties are returned as metadata wrappers. + */ + public static function extract_property_multiple(array $wrappers, $selector) { + $parts = explode(':', $selector, 2); + $name = $parts[0]; + + $results = array(); + $entities = array(); + $type = ''; + foreach ($wrappers as $i => $wrapper) { + try { + $property = $wrapper->$name; + $type = $property->type(); + if ($property instanceof EntityDrupalWrapper) { + // Remember the entity IDs to later load all at once (so as to + // properly utilize multiple load functionality). + $id = $property->getIdentifier(); + // Only accept valid ids. $id can be FALSE for entity values that are + // NULL. + if ($id) { + $entities[$type][$i] = $id; + } + } + elseif ($property instanceof EntityStructureWrapper) { + $results[$i] = $property; + } + elseif ($property instanceof EntityListWrapper) { + foreach ($property as $item) { + $results[$i] = $item; + $type = $item->type(); + break; + } + } + // Do nothing in case it cannot be applied. + } + catch (EntityMetadataWrapperException $e) { + // Skip single empty properties. + } + } + + if ($entities) { + // Map back the loaded entities back to the results array. + foreach ($entities as $type => $id_map) { + $loaded = entity_load($type, $id_map); + foreach ($id_map as $i => $id) { + if (isset($loaded[$id])) { + $results[$i] = entity_metadata_wrapper($type, $loaded[$id]); + } + } + } + } + + // If there are no further parts in the selector, we are done now. + if (empty($parts[1])) { + return array($type, $results); + } + return self::extract_property_multiple($results, $parts[1]); + } + + /** + * Get the value of a certain data selector. + * + * Uses $values->_entity_properties to look for already extracted properties. + * + * @param $handler + * The field handler for which to return a value. + * @param $values + * The values for the current row retrieved from the Views query, as an + * object. + * @param $field + * The field to extract. If no value is given, the field of the given + * handler is used instead. The special "entity object" value can be used to + * get the base entity instead of a special field. + * @param $default + * The value to return if the entity or field are not present. + */ + public static function get_value($handler, $values, $field = NULL, $default = NULL) { + // There is a value cache on each handler so parent handlers rendering a + // single field value from a list will get the single value, not the whole + // list. + if (!isset($field) && isset($handler->current_value)) { + return $handler->current_value; + } + $field = isset($field) ? $field : self::get_selector_field_name($handler->real_field); + $selector = self::construct_property_selector($handler); + $selector = $selector ? "$selector:$field" : $field; + if (!isset($values->_entity_properties)) { + $values->_entity_properties = array(); + } + if (!array_key_exists($selector, $values->_entity_properties)) { + if (!isset($handler->wrappers[$handler->view->row_index])) { + $values->_entity_properties[$selector] = $default; + } + elseif (is_array($handler->wrappers[$handler->view->row_index])) { + $values->_entity_properties[$selector] = self::extract_list_wrapper_values($handler->wrappers[$handler->view->row_index], $field); + } + else { + $wrapper = $handler->wrappers[$handler->view->row_index]; + try { + if ($field === 'entity object') { + $values->_entity_properties[$selector] = $wrapper->value(); + } + else { + $values->_entity_properties[$selector] = isset($wrapper->$field) ? $wrapper->$field->value(array('identifier' => TRUE)) : $default; + } + } + catch (EntityMetadataWrapperException $e) { + $values->_entity_properties[$selector] = $default; + } + } + } + return $values->_entity_properties[$selector]; + } + + /** + * Helper method for extracting the values from an array of wrappers. + * + * Nested arrays of wrappers are also handled, the values are returned in a + * flat (not nested) array. + */ + public static function extract_list_wrapper_values(array $wrappers, $field) { + $return = array(); + foreach ($wrappers as $wrapper) { + if (is_array($wrapper)) { + $values = self::extract_list_wrapper_values($wrapper, $field); + if ($values) { + $return = array_merge($return, $values); + } + } + else { + try { + if ($field == 'entity object') { + $return[] = $wrapper->value(); + } + elseif (isset($wrapper->$field)) { + $return[] = $wrapper->$field->value(array('identifier' => TRUE)); + } + } + catch (EntityMetadataWrapperException $e) { + // An exception probably signifies a non-present property, so we just + // ignore it. + } + } + } + return $return; + } + + /** + * Render the field. + * + * Implements the entity link functionality and list handling. Basic handling + * of the single values is delegated back to the field handler. + * + * @param $handler + * The field handler whose field should be rendered. + * @param $values + * The values for the current row retrieved from the Views query, as an + * object. + * + * @return + * The rendered value for the field. + */ + public static function render($handler, $values) { + $value = $handler->get_value($values); + if (is_array($value)) { + return self::render_list($handler, $value, $values); + } + return self::render_entity_link($handler, $value, $values); + } + + /** + * Render a list of values. + * + * @param $handler + * The field handler whose field is rendered. + * @param $list + * The list of values to render. + * @param $values + * The values for the current row retrieved from the Views query, as an + * object. + * + * @return + * The rendered value for the given list. + */ + public static function render_list($handler, $list, $values) { + // Allow easy overriding of this behaviour in the specific field handler. + if (method_exists($handler, 'render_list')) { + return $handler->render_list($list, $values); + } + $mode = isset($handler->options['list']['mode']) ? $handler->options['list']['mode'] : NULL; + switch ($mode) { + case 'first': + $list = count($list) ? array_shift($list) : NULL; + if (is_array($list)) { + return self::render_list($handler, $list, $values); + } + elseif (isset($list)) { + return self::render_entity_link($handler, $list, $values); + } + return NULL; + + case 'count': + return count($list); + + // Handles both collapse and list output. Fallback is to collapse. + default: + $inner_values = array(); + foreach ($list as $value) { + $value = is_array($value) ? self::render_list($handler, $value, $values) : self::render_entity_link($handler, $value, $values); + if ($value) { + $inner_values[] = $value; + } + } + + // Format output as list. + if ($mode == 'list') { + $type = isset($handler->options['list']['type']) ? $handler->options['list']['type'] : 'ul'; + return theme('item_list', array( + 'items' => $inner_values, + 'type' => $type, + )); + } + + $separator = isset($handler->options['list']['separator']) ? $handler->options['list']['separator'] : ', '; + return implode($separator, $inner_values); + } + } + + /** + * Render a single value as a link to the entity if applicable. + * + * @param $handler + * The field handler whose field is rendered. + * @param $value + * The single value to render. + * @param $values + * The values for the current row retrieved from the Views query, as an + * object. + * + * @return + * The rendered value. + */ + public static function render_entity_link($handler, $value, $values) { + // Allow easy overriding of this behaviour in the specific field handler. + if (method_exists($handler, 'render_entity_link')) { + return $handler->render_entity_link($value, $values); + } + $render = self::render_single_value($handler, $value, $values); + if (!$handler->options['link_to_entity']) { + return $render; + } + $entity = $handler->get_value($values, 'entity object'); + if (is_object($entity) && ($url = entity_uri($handler->entity_type, $entity))) { + return l($render, $url['path'], array('html' => TRUE) + $url['options']); + } + return $render; + } + + /** + * Render a single value. + * + * @param $handler + * The field handler whose field is rendered. + * @param $value + * The single value to render. + * @param $values + * The values for the current row retrieved from the Views query, as an + * object. + * + * @return + * The rendered value. + */ + public static function render_single_value($handler, $value, $values) { + // Try to use the method in the specific field handler. + if (method_exists($handler, 'render_single_value')) { + $handler->current_value = $value; + $return = $handler->render_single_value($value, $values); + unset($handler->current_value); + return $return; + } + // Default fallback in case the field handler doesn't provide the method. + return is_scalar($value) ? check_plain($value) : nl2br(check_plain(print_r($value, TRUE))); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_area_entity.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_area_entity.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,120 @@ + 'node'); + $options['entity_id'] = array('default' => ''); + $options['view_mode'] = array('default' => 'full'); + $options['bypass_access'] = array('default' => FALSE); + return $options; + } + + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $entity_type_options = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + $entity_type_options[$entity_type] = $entity_info['label']; + } + + $entity_type = $this->options['entity_type']; + + $form['entity_type'] = array( + '#type' => 'select', + '#title' => t('Entity type'), + '#options' => $entity_type_options, + '#description' => t('Choose the entity type you want to display in the area.'), + '#default_value' => $entity_type, + '#ajax' => array( + 'path' => views_ui_build_form_url($form_state), + ), + '#submit' => array('views_ui_config_item_form_submit_temporary'), + '#executes_submit_callback' => TRUE, + ); + + $form['entity_id'] = array( + '#type' => 'textfield', + '#title' => t('Entity id'), + '#description' => t('Choose the entity you want to display in the area.'), + '#default_value' => $this->options['entity_id'], + ); + + if ($entity_type) { + $entity_info = entity_get_info($entity_type); + $options = array(); + if (!empty($entity_info['view modes'])) { + foreach ($entity_info['view modes'] as $mode => $settings) { + $options[$mode] = $settings['label']; + } + } + + if (count($options) > 1) { + $form['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('View mode'), + '#default_value' => $this->options['view_mode'], + ); + } + else { + $form['view_mode_info'] = array( + '#type' => 'item', + '#title' => t('View mode'), + '#description' => t('Only one view mode is available for this entity type.'), + '#markup' => $options ? current($options) : t('Default'), + ); + $form['view_mode'] = array( + '#type' => 'value', + '#value' => $options ? key($options) : 'default', + ); + } + } + $form['bypass_access'] = array( + '#type' => 'checkbox', + '#title' => t('Bypass access checks'), + '#description' => t('If enabled, access permissions for rendering the entity are not checked.'), + '#default_value' => !empty($this->options['bypass_access']), + ); + return $form; + } + + public function admin_summary() { + $label = parent::admin_summary(); + if (!empty($this->options['entity_id'])) { + return t('@label @entity_type:@entity_id', array( + '@label' => $label, + '@entity_type' => $this->options['entity_type'], + '@entity_id' => $this->options['entity_id'], + )); + } + } + + public function render($empty = FALSE) { + if (!$empty || !empty($this->options['empty'])) { + return $this->render_entity($this->options['entity_type'], $this->options['entity_id'], $this->options['view_mode']); + } + return ''; + } + + /** + * Render an entity using the view mode. + */ + public function render_entity($entity_type, $entity_id, $view_mode) { + if (!empty($entity_type) && !empty($entity_id) && !empty($view_mode)) { + $entity = entity_load_single($entity_type, $entity_id); + if (!empty($this->options['bypass_access']) || entity_access('view', $entity_type, $entity)) { + $render = entity_view($entity_type, array($entity), $view_mode); + $render_entity = reset($render); + return drupal_render($render_entity); + } + } + else { + return ''; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_field_boolean.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_boolean.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,99 @@ + TRUE); + $options['granularity'] = array('default' => 2); + $options['prefix'] = array('default' => '', 'translatable' => TRUE); + $options['suffix'] = array('default' => '', 'translatable' => TRUE); + + return $options; + } + + public function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + EntityFieldHandlerHelper::options_form($this, $form, $form_state); + + $form['format_interval'] = array( + '#type' => 'checkbox', + '#title' => t('Format interval'), + '#description' => t('If checked, the value will be formatted as a time interval. Otherwise, just the number of seconds will be displayed.'), + '#default_value' => $this->options['format_interval'], + ); + $form['granularity'] = array( + '#type' => 'textfield', + '#title' => t('Granularity'), + '#default_value' => $this->options['granularity'], + '#description' => t('Specify how many different units to display.'), + '#dependency' => array('edit-options-format-interval' => array(TRUE)), + '#size' => 2, + ); + $form['prefix'] = array( + '#type' => 'textfield', + '#title' => t('Prefix'), + '#default_value' => $this->options['prefix'], + '#description' => t('Text to put before the duration text.'), + ); + $form['suffix'] = array( + '#type' => 'textfield', + '#title' => t('Suffix'), + '#default_value' => $this->options['suffix'], + '#description' => t('Text to put after the duration text.'), + ); + } + + /** + * Render the field. + * + * @param $values + * The values retrieved from the database. + */ + public function render($values) { + return EntityFieldHandlerHelper::render($this, $values); + } + + /** + * Render a single field value. + */ + public function render_single_value($value, $values) { + if ($this->options['format_interval']) { + $value = format_interval($value, (int) $this->options['granularity']); + } + return $this->sanitize_value($this->options['prefix'], 'xss') . + $this->sanitize_value($value) . + $this->sanitize_value($this->options['suffix'], 'xss'); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_field_entity.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_entity.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,207 @@ +field_entity_type = entity_property_extract_innermost_type($this->definition['type']); + } + + /** + * Overridden to add the field for the entity ID (if necessary). + */ + public function query() { + EntityFieldHandlerHelper::query($this); + } + + /** + * Adds a click-sort to the query. + */ + public function click_sort($order) { + EntityFieldHandlerHelper::click_sort($this, $order); + } + + /** + * Load the entities for all rows that are about to be displayed. + */ + public function pre_render(&$values) { + EntityFieldHandlerHelper::pre_render($this, $values); + } + + /** + * Overridden to use a metadata wrapper. + */ + public function get_value($values, $field = NULL) { + return EntityFieldHandlerHelper::get_value($this, $values, $field); + } + + public function option_definition() { + $options = parent::option_definition(); + $options += EntityFieldHandlerHelper::option_definition($this); + + $options['display'] = array('default' => 'label'); + $options['link_to_entity']['default'] = TRUE; + $options['view_mode'] = array('default' => 'default'); + $options['bypass_access'] = array('default' => FALSE); + + return $options; + } + + public function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + EntityFieldHandlerHelper::options_form($this, $form, $form_state); + // We want a different form field at a different place. + unset($form['link_to_entity']); + + $options = array( + 'label' => t('Show entity label'), + 'id' => t('Show entity ID'), + 'view' => t('Show complete entity'), + ); + $form['display'] = array( + '#type' => 'select', + '#title' => t('Display'), + '#description' => t('Decide how this field will be displayed.'), + '#options' => $options, + '#default_value' => $this->options['display'], + ); + $form['link_to_entity'] = array( + '#type' => 'checkbox', + '#title' => t('Link to entity'), + '#description' => t('Link this field to the entity.'), + '#default_value' => $this->options['link_to_entity'], + '#dependency' => array('edit-options-display' => array('label', 'id')), + ); + + // Stolen from entity_views_plugin_row_entity_view. + $entity_info = entity_get_info($this->field_entity_type); + $options = array(); + if (!empty($entity_info['view modes'])) { + foreach ($entity_info['view modes'] as $mode => $settings) { + $options[$mode] = $settings['label']; + } + } + + if (count($options) > 1) { + $form['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('View mode'), + '#default_value' => $this->options['view_mode'], + '#dependency' => array('edit-options-display' => array('view')), + ); + } + else { + $form['view_mode'] = array( + '#type' => 'value', + '#value' => $options ? key($options) : 'default', + ); + } + $form['bypass_access'] = array( + '#type' => 'checkbox', + '#title' => t('Bypass access checks'), + '#description' => t('If enabled, access permissions for rendering the entity are not checked.'), + '#default_value' => !empty($this->options['bypass_access']), + ); + } + + public function render($values) { + return EntityFieldHandlerHelper::render($this, $values); + } + + /** + * Render a value as a link to the entity if applicable. + * + * @param $value + * The value to render. + * @param $values + * The values for the current row retrieved from the Views query, as an + * object. + */ + public function render_entity_link($entity, $values) { + $type = $this->field_entity_type; + if (!is_object($entity) && isset($entity) && $entity !== FALSE) { + $entity = entity_load_single($type, $entity); + } + if (!$entity) { + return ''; + } + $render = $this->render_single_value($entity, $values); + if (!$this->options['link_to_entity'] || $this->options['display'] == 'view') { + return $render; + } + if (is_object($entity) && ($url = entity_uri($type, $entity))) { + return l($render, $url['path'], array('html' => TRUE) + $url['options']); + } + return $render; + } + + /** + * Render a single field value. + */ + public function render_single_value($entity, $values) { + $type = $this->field_entity_type; + if (!is_object($entity) && isset($entity) && $entity !== FALSE) { + $entity = entity_load_single($type, $entity); + } + // Make sure the entity exists and access is either given or bypassed. + if (!$entity || !(!empty($this->options['bypass_access']) || entity_access('view', $type, $entity))) { + return ''; + } + + if ($this->options['display'] === 'view') { + $entity_view = entity_view($type, array($entity), $this->options['view_mode']); + return render($entity_view); + } + + if ($this->options['display'] == 'label') { + $value = entity_label($type, $entity); + } + // Either $options[display] == 'id', or we have no label. + if (empty($value)) { + $value = entity_id($type, $entity); + } + $value = $this->sanitize_value($value); + + return $value; + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_field_field.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_field.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,105 @@ +field_info, $this->definition['entity type']); + } + + /** + * Overridden to add the field for the entity ID (if necessary). + */ + public function query($use_groupby = FALSE) { + EntityFieldHandlerHelper::query($this); + } + + /** + * Adds a click-sort to the query. + */ + public function click_sort($order) { + EntityFieldHandlerHelper::click_sort($this, $order); + } + + /** + * Override so it doesn't do any harm (or, anything at all). + */ + public function post_execute(&$values) { } + + /** + * Load the entities for all rows that are about to be displayed. + */ + public function pre_render(&$values) { + parent::pre_render($values); + EntityFieldHandlerHelper::pre_render($this, $values, TRUE); + } + + /** + * Overridden to get the items our way. + */ + public function get_items($values) { + $items = array(); + // Set the entity type for the parent handler. + $values->_field_data[$this->field_alias]['entity_type'] = $this->entity_type; + // We need special handling for lists of entities as the base. + $entities = EntityFieldHandlerHelper::get_value($this, $values, 'entity object'); + if (!is_array($entities)) { + $entities = $entities ? array($entities) : array(); + } + foreach ($entities as $entity) { + // Only try to render the field if it is even present on this bundle. + // Otherwise, field_view_field() will trigger a fatal. + list (, , $bundle) = entity_extract_ids($this->entity_type, $entity); + if (field_info_instance($this->entity_type, $this->definition['field_name'], $bundle)) { + // Set the currently rendered entity. + $values->_field_data[$this->field_alias]['entity'] = $entity; + $items = array_merge($items, $this->set_items($values, $this->view->row_index)); + } + } + return $items; + } + + /** + * Overridden to force displaying multiple values in a single row. + */ + function multiple_options_form(&$form, &$form_state) { + parent::multiple_options_form($form, $form_state); + $form['group_rows']['#default_value'] = TRUE; + $form['group_rows']['#disabled'] = TRUE; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_field_numeric.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_numeric.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,99 @@ + TRUE); + return $options; + } + + /** + * Returns an option form for setting this handler's options. + */ + public function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + EntityFieldHandlerHelper::options_form($this, $form, $form_state); + + $form['format_name'] = array( + '#title' => t('Use human-readable name'), + '#type' => 'checkbox', + '#description' => t("If this is checked, the values' names will be displayed instead of their internal identifiers."), + '#default_value' => $this->options['format_name'], + '#weight' => -5, + ); + } + + public function render($values) { + return EntityFieldHandlerHelper::render($this, $values); + } + + /** + * Render a single field value. + */ + public function render_single_value($value, $values) { + if (!isset($this->option_list)) { + $this->option_list = array(); + $callback = $this->definition['options callback']; + if (is_callable($callback['function'])) { + // If a selector is used, get the name of the selected field. + $field_name = EntityFieldHandlerHelper::get_selector_field_name($this->real_field); + $this->option_list = call_user_func($callback['function'], $field_name, $callback['info'], 'view'); + } + } + if ($this->options['format_name'] && isset($this->option_list[$value])) { + $value = $this->option_list[$value]; + } + + return $this->sanitize_value($value); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_field_text.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_text.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,99 @@ +sanitize_value($value, 'xss'); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_field_uri.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_field_uri.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,99 @@ +definition['multiple'])) { + $form['multiple_note'] = array( + '#markup' => t('Note: This is a multi-valued relationship, which is currently not supported. ' . + 'Only the first related entity will be shown.'), + '#weight' => -5, + ); + } + } + + /** + * Called to implement a relationship in a query. + * + * As we don't add any data to the query itself, we don't have to do anything + * here. Views just don't thinks we have been called unless we set our + * $alias property. Otherwise, this override is just here to keep PHP from + * blowing up by calling inexistent methods on the query plugin. + */ + public function query() { + $this->alias = $this->options['id']; + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/handlers/entity_views_handler_relationship_by_bundle.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,117 @@ + array()); + + return $options; + } + + /** + * Add an entity type option. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + // Get the entity type and info from the table data for the base on the + // right hand side of the relationship join. + $table_data = views_fetch_data($this->definition['base']); + $entity_type = $table_data['table']['entity type']; + $entity_info = entity_get_info($entity_type); + + // Get the info of the bundle entity. + foreach (entity_get_info() as $type => $info) { + if (isset($info['bundle of']) && $info['bundle of'] == $entity_type) { + $entity_bundle_info = $info; + break; + } + } + + $plural_label = isset($entity_bundle_info['plural label']) ? $entity_bundle_info['plural label'] : $entity_bundle_info['label'] . 's'; + $bundle_options = array(); + foreach ($entity_info['bundles'] as $name => $info) { + $bundle_options[$name] = $info['label']; + } + + $form['bundle_types'] = array( + '#title' => $plural_label, + '#type' => 'checkboxes', + '#description' => t('Restrict this relationship to one or more @bundles.', array('@bundles' => strtolower($entity_bundle_info['plural label']))), + '#options' => $bundle_options, + '#default_value' => $this->options['bundle_types'], + ); + } + + /** + * Make sure only checked bundle types are left. + */ + function options_submit(&$form, &$form_state) { + $form_state['values']['options']['bundle_types'] = array_filter($form_state['values']['options']['bundle_types']); + parent::options_submit($form, $form_state); + } + + /** + * Called to implement a relationship in a query. + * + * Mostly the same as the parent method, except we add an extra clause to + * the join. + */ + function query() { + $table_data = views_fetch_data($this->definition['base']); + $base_field = empty($this->definition['base field']) ? $table_data['table']['base']['field'] : $this->definition['base field']; + $this->ensure_my_table(); + + $def = $this->definition; + $def['table'] = $this->definition['base']; + $def['field'] = $base_field; + $def['left_table'] = $this->table_alias; + $def['left_field'] = $this->field; + if (!empty($this->options['required'])) { + $def['type'] = 'INNER'; + } + + // Add an extra clause to the join if there are bundle types selected. + if ($this->options['bundle_types']) { + $entity_info = entity_get_info($table_data['table']['entity type']); + $def['extra'] = array( + array( + // The table and the IN operator are implicit. + 'field' => $entity_info['bundle keys']['bundle'], + 'value' => $this->options['bundle_types'], + ), + ); + } + + if (!empty($def['join_handler']) && class_exists($def['join_handler'])) { + $join = new $def['join_handler']; + } + else { + $join = new views_join(); + } + + $join->definition = $def; + $join->construct(); + $join->adjusted = TRUE; + + // Use a short alias for this. + $alias = $def['table'] . '_' . $this->table; + $this->alias = $this->query->add_relationship($alias, $join, $this->definition['base'], $this->relationship); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entity/views/plugins/entity_views_plugin_row_entity_view.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entity/views/plugins/entity_views_plugin_row_entity_view.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,95 @@ +view->base_table); + $this->entity_type = $table_data['table']['entity type']; + // Set base table and field information as used by views_plugin_row to + // select the entity id if used with default query class. + $info = entity_get_info($this->entity_type); + if (!empty($info['base table']) && $info['base table'] == $this->view->base_table) { + $this->base_table = $info['base table']; + $this->base_field = $info['entity keys']['id']; + } + } + + public function option_definition() { + $options = parent::option_definition(); + $options['view_mode'] = array('default' => 'full'); + return $options; + } + + public function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $entity_info = entity_get_info($this->entity_type); + $options = array(); + if (!empty($entity_info['view modes'])) { + foreach ($entity_info['view modes'] as $mode => $settings) { + $options[$mode] = $settings['label']; + } + } + + if (count($options) > 1) { + $form['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('View mode'), + '#default_value' => $this->options['view_mode'], + ); + } + else { + $form['view_mode_info'] = array( + '#type' => 'item', + '#title' => t('View mode'), + '#description' => t('Only one view mode is available for this entity type.'), + '#markup' => $options ? current($options) : t('Default'), + ); + $form['view_mode'] = array( + '#type' => 'value', + '#value' => $options ? key($options) : 'default', + ); + } + return $form; + } + + public function pre_render($values) { + if (!empty($values)) { + list($this->entity_type, $this->entities) = $this->view->query->get_result_entities($values, !empty($this->relationship) ? $this->relationship : NULL, isset($this->field_alias) ? $this->field_alias : NULL); + } + // Render the entities. + if ($this->entities) { + $render = entity_view($this->entity_type, $this->entities, $this->options['view_mode']); + // Remove the first level of the render array. + $this->rendered_content = reset($render); + } + } + + /** + * Overridden to return the entity object. + */ + function get_value($values, $field = NULL) { + return isset($this->entities[$this->view->row_index]) ? $this->entities[$this->view->row_index] : FALSE; + } + + public function render($values) { + if ($entity = $this->get_value($values)) { + $render = $this->rendered_content[entity_id($this->entity_type, $entity)]; + return drupal_render($render); + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,16 @@ +DESCRIPTION +=========== +Provides a field type that can reference arbitrary entities. + +SITE BUILDERS +============= +Note that when using a select widget, Entity reference loads all the +entities in that list in order to get the entity's label. If there are +too many loaded entities that site might reach its memory limit and crash +(also known as WSOD). In such a case you are advised to change the widget +to "autocomplete". If you get a WSOD when trying to edit the field +settings, you can reach the widget settings directly by navigation to + + admin/structure/types/manage/[ENTITY-TYPE]/fields/[FIELD-NAME]/widget-type + +Replace ENTITY-TYPE and FIELD_NAME with the correct values. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.admin.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.admin.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,4 @@ + +.entityreference-settings { + margin-left: 1.5em; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.devel_generate.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.devel_generate.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,26 @@ +getReferencableEntities(); + if (is_array($referencable_entity) && !empty($referencable_entity)) { + // Get a random key. + $object_field['target_id'] = array_rand($referencable_entity); + } + return $object_field; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.diff.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.diff.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,90 @@ + $info) { + $old_items[$delta]['entity'] = isset($entities[$info['target_id']]) ? $entities[$info['target_id']] : NULL; + } + foreach ($new_items as $delta => $info) { + $new_items[$delta]['entity'] = isset($entities[$info['target_id']]) ? $entities[$info['target_id']] : NULL; + } +} + +/** + * Diff field callback for parsing entity field comparative values. + */ +function entityreference_field_diff_view($items, $context) { + $field = $context['field']; + $instance = $context['instance']; + $settings = $context['settings']; + $entity_type = $field['settings']['target_type']; + + $diff_items = array(); + + // We populate as much as possible to allow the best flexability in any + // string overrides. + $t_args = array(); + $t_args['!entity_type'] = $entity_type; + + $entity_info = entity_get_info($entity_type); + $t_args['!entity_type_label'] = $entity_info['label']; + + foreach ($items as $delta => $item) { + if (isset($item['entity'])) { + $output = array(); + + list($id,, $bundle) = entity_extract_ids($entity_type, $item['entity']); + $t_args['!id'] = $id; + $t_args['!bundle'] = $bundle; + $t_args['!diff_entity_label'] = entity_label($entity_type, $item['entity']); + + $output['entity'] = t('!diff_entity_label', $t_args); + if ($settings['show_id']) { + $output['id'] = t('ID: !id', $t_args); + } + $diff_items[$delta] = implode('; ', $output); + } + } + + return $diff_items; +} + +/** + * Provide default field comparison options. + */ +function entityreference_field_diff_default_options($field_type) { + return array( + 'show_id' => 0, + ); +} + +/** + * Provide a form for setting the field comparison options. + */ +function entityreference_field_diff_options_form($field_type, $settings) { + $options_form = array(); + $options_form['show_id'] = array( + '#type' => 'checkbox', + '#title' => t('Show ID'), + '#default_value' => $settings['show_id'], + ); + return $options_form; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.feeds.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.feeds.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,121 @@ + $instance) { + $info = field_info_field($name); + if ($info['type'] == 'entityreference') { + $targets[$name] = array( + 'name' => check_plain($instance['label']), + 'callback' => 'entityreference_feeds_set_target', + 'description' => t('The field instance @label of @id', array( + '@label' => $instance['label'], + '@id' => $name, + )), + ); + } + } +} + +/** + * Entity reference callback for mapping. + * + * When the callback is invoked, $target contains the name of the field the + * user has decided to map to and $value contains the value of the feed item + * element the user has picked as a source. + * + * @param $source + * A FeedsSource object. + * @param $entity + * The entity to map to. + * @param $target + * The target key on $entity to map to. + * @param $value + * The value to map. MUST be an array. + * @param $mapping + * Array of mapping settings for current value. + * @param $input_format + * TRUE if an input format should be applied. + */ +function entityreference_feeds_set_target($source, $entity, $target, $value, $mapping, $input_format = FALSE) { + + // Don't do anything if we weren't given any data. + if (empty($value)) { + return; + } + + // Assume that the passed in value could really be any number of values. + if (is_array($value)) { + $values = $value; + } + else { + $values = array($value); + } + + // Get some useful field information. + $info = field_info_field($target); + + // Set the language of the field depending on the mapping. + $language = isset($mapping['language']) ? $mapping['language'] : LANGUAGE_NONE; + + // Iterate over all values. + $iterator = 0; + $field = isset($entity->$target) ? $entity->$target : array(); + foreach ($values as $value) { + + // Only process if this value was set for this instance. + if ($value) { + + // Fetch the entity ID resulting from the mapping table look-up. + $entity_id = db_query( + 'SELECT entity_id FROM {feeds_item} WHERE guid = :guid', + array(':guid' => $value) + )->fetchField(); + + /* + * Only add a reference to an existing entity ID if there exists a + * mapping between it and the provided GUID. In cases where no such + * mapping exists (yet), don't do anything here. There may be a mapping + * defined later in the CSV file. If so, and the user re-runs the import + * (as a second pass), we can add this reference then. (The "Update + * existing nodes" option must be selected during the second pass.) + */ + if ($entity_id) { + + // Assign the target ID. + $field[$language][$iterator]['target_id'] = $entity_id; + } + else /* there is no $entity_id, no mapping */ { + + /* + * Feeds stores a hash of every line imported from CSVs in order to + * make the import process more efficient by ignoring lines it's + * already seen. We need to short-circuit this process in this case + * because users may want to re-import the same line as an update later + * when (and if) a map to a reference exists. So in order to provide + * this opportunity later, we need to destroy the hash. + */ + unset($entity->feeds_item->hash); + } + } + + // Break out of the loop if this field is single-valued. + if ($info['cardinality'] == 1) { + break; + } + $iterator++; + } + + // Add the field to the entity definition. + $entity->{$target} = $field; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,30 @@ +name = Entity Reference +description = Provides a field that can reference other entities. +core = 7.x +package = Fields +dependencies[] = entity +dependencies[] = ctools + +; Migrate handler. +files[] = entityreference.migrate.inc + +; Our plugins interfaces and abstract implementations. +files[] = plugins/selection/abstract.inc +files[] = plugins/selection/views.inc +files[] = plugins/behavior/abstract.inc + +files[] = views/entityreference_plugin_display.inc +files[] = views/entityreference_plugin_style.inc +files[] = views/entityreference_plugin_row_fields.inc + +; Tests. +files[] = tests/entityreference.handlers.test +files[] = tests/entityreference.taxonomy.test +files[] = tests/entityreference.admin.test + +; Information added by drupal.org packaging script on 2012-11-18 +version = "7.x-1.0" +core = "7.x" +project = "entityreference" +datestamp = "1353230808" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,164 @@ + array( + 'target_id' => array( + 'description' => 'The id of the target entity.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + ), + 'indexes' => array( + 'target_id' => array('target_id'), + ), + 'foreign keys' => array(), + ); + + // Create a foreign key to the target entity type base type, if available. + $entity_type = $field['settings']['target_type']; + if (isset($base_tables[$entity_type])) { + list($base_table, $id_column) = $base_tables[$entity_type]; + $schema['foreign keys'][$base_table] = array( + 'table' => $base_table, + 'columns' => array('target_id' => $id_column), + ); + } + + // Invoke the behaviors to allow them to change the schema. + foreach (entityreference_get_behavior_handlers($field) as $handler) { + $handler->schema_alter($schema, $field); + } + + return $schema; + } +} + +/** + * Update the field configuration to the new plugin structure. + */ +function entityreference_update_7000() { + // Enable ctools. + if (!module_enable(array('ctools'))) { + throw new DrupalUpdateException('This version of Entity Reference requires ctools, but it could not be enabled.'); + } + + // Get the list of fields of type 'entityreference'. + $fields = array(); + foreach (field_info_fields() as $field_name => $field) { + // Update the field configuration. + if ($field['type'] == 'entityreference') { + $settings = &$field['settings']; + if (!isset($settings['handler'])) { + $settings['handler'] = 'base'; + $settings['handler_settings']['target_bundles'] = $settings['target_bundles']; + unset($settings['target_bundles']); + field_update_field($field); + } + } + + // Update the instance configurations. + foreach ($field['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $instance = field_info_instance($entity_type, $field_name, $bundle); + $save = FALSE; + if ($instance['widget']['type'] == 'entityreference_autocomplete') { + $instance['widget']['type'] = 'entityreference_autocomplete_tags'; + $save = TRUE; + } + // When the autocomplete path is the default value, remove it from + // the configuration. + if (isset($instance['widget']['settings']['path']) && $instance['widget']['settings']['path'] == 'entityreference/autocomplete') { + unset($instance['widget']['settings']['path']); + $save = TRUE; + } + if ($save) { + field_update_instance($instance); + } + } + } + } +} + +/** + * Drop "target_type" from the field schema. + */ +function entityreference_update_7001() { + if (!module_exists('field_sql_storage')) { + return; + } + foreach (field_info_fields() as $field_name => $field) { + if ($field['type'] != 'entityreference') { + // Not an entity reference field. + continue; + } + + // Update the field settings. + $field = field_info_field($field_name); + unset($field['indexes']['target_entity']); + $field['indexes']['target_id'] = array('target_id'); + field_update_field($field); + + if ($field['storage']['type'] !== 'field_sql_storage') { + // Field doesn't use SQL storage, we cannot modify the schema. + continue; + } + $table_name = _field_sql_storage_tablename($field); + $revision_name = _field_sql_storage_revision_tablename($field); + + db_drop_index($table_name, $field_name . '_target_entity'); + db_drop_index($table_name, $field_name . '_target_id'); + db_drop_field($table_name, $field_name . '_target_type'); + db_add_index($table_name, $field_name . '_target_id', array($field_name . '_target_id')); + + db_drop_index($revision_name, $field_name . '_target_entity'); + db_drop_index($revision_name, $field_name . '_target_id'); + db_drop_field($revision_name, $field_name . '_target_type'); + db_add_index($revision_name, $field_name . '_target_id', array($field_name . '_target_id')); + } +} + +/** + * Make the target_id column NOT NULL. + */ +function entityreference_update_7002() { + if (!module_exists('field_sql_storage')) { + return; + } + foreach (field_info_fields() as $field_name => $field) { + if ($field['type'] != 'entityreference') { + // Not an entity reference field. + continue; + } + + if ($field['storage']['type'] !== 'field_sql_storage') { + // Field doesn't use SQL storage, we cannot modify the schema. + continue; + } + + $table_name = _field_sql_storage_tablename($field); + $revision_name = _field_sql_storage_revision_tablename($field); + + db_change_field($table_name, $field_name . '_target_id', $field_name . '_target_id', array( + 'description' => 'The id of the target entity.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + )); + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.migrate.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.migrate.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,27 @@ + 2, + 'field_handlers' => array('MigrateEntityReferenceFieldHandler'), + ); +} + +class MigrateEntityReferenceFieldHandler extends MigrateSimpleFieldHandler { + public function __construct() { + parent::__construct(array( + 'value_key' => 'target_id', + 'skip_empty' => TRUE, + )); + + $this->registerTypes(array('entityreference')); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/entityreference.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/entityreference.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1270 @@ + array('class'), + ); + $plugins['behavior'] = array( + 'classes' => array('class'), + 'process' => 'entityreference_behavior_plugin_process', + ); + return $plugins; +} + +/** + * CTools callback; Process the behavoir plugins. + */ +function entityreference_behavior_plugin_process(&$plugin, $info) { + $plugin += array( + 'description' => '', + 'behavior type' => 'field', + 'access callback' => FALSE, + 'force enabled' => FALSE, + ); +} + +/** + * Implements hook_field_info(). + */ +function entityreference_field_info() { + $field_info['entityreference'] = array( + 'label' => t('Entity Reference'), + 'description' => t('This field reference another entity.'), + 'settings' => array( + // Default to the core target entity type node. + 'target_type' => 'node', + // The handler for this field. + 'handler' => 'base', + // The handler settings. + 'handler_settings' => array(), + ), + 'instance_settings' => array(), + 'default_widget' => 'entityreference_autocomplete', + 'default_formatter' => 'entityreference_label', + 'property_callbacks' => array('entityreference_field_property_callback'), + ); + return $field_info; +} + +/** + * Implements hook_flush_caches(). + */ +function entityreference_flush_caches() { + // Because of the intricacies of the info hooks, we are forced to keep a + // separate list of the base tables of each entities, so that we can use + // it in entityreference_field_schema() without calling entity_get_info(). + // See http://drupal.org/node/1416558 for details. + $base_tables = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + if (!empty($entity_info['base table']) && !empty($entity_info['entity keys']['id'])) { + $base_tables[$entity_type] = array($entity_info['base table'], $entity_info['entity keys']['id']); + } + } + // We are using a variable because cache is going to be cleared right after + // hook_flush_caches() is finished. + variable_set('entityreference:base-tables', $base_tables); +} + +/** + * Implements hook_menu(). + */ +function entityreference_menu() { + $items = array(); + + $items['entityreference/autocomplete/single/%/%/%'] = array( + 'title' => 'Entity Reference Autocomplete', + 'page callback' => 'entityreference_autocomplete_callback', + 'page arguments' => array(2, 3, 4, 5), + 'access callback' => 'entityreference_autocomplete_access_callback', + 'access arguments' => array(2, 3, 4, 5), + 'type' => MENU_CALLBACK, + ); + $items['entityreference/autocomplete/tags/%/%/%'] = array( + 'title' => 'Entity Reference Autocomplete', + 'page callback' => 'entityreference_autocomplete_callback', + 'page arguments' => array(2, 3, 4, 5), + 'access callback' => 'entityreference_autocomplete_access_callback', + 'access arguments' => array(2, 3, 4, 5), + 'type' => MENU_CALLBACK, + ); + + return $items; +} + +/** + * Implements hook_field_is_empty(). + */ +function entityreference_field_is_empty($item, $field) { + $empty = !isset($item['target_id']) || !is_numeric($item['target_id']); + + // Invoke the behaviors to allow them to override the empty status. + foreach (entityreference_get_behavior_handlers($field) as $handler) { + $handler->is_empty_alter($empty, $item, $field); + } + return $empty; +} + +/** + * Get the behavior handlers for a given entityreference field. + */ +function entityreference_get_behavior_handlers($field, $instance = NULL) { + $object_cache = drupal_static(__FUNCTION__); + $identifier = $field['field_name']; + if (!empty($instance)) { + $identifier .= ':' . $instance['entity_type'] . ':' . $instance['bundle']; + } + + if (!isset($object_cache[$identifier])) { + $object_cache[$identifier] = array(); + + // Merge in defaults. + $field['settings'] += array('behaviors' => array()); + + $object_cache[$field['field_name']] = array(); + $behaviors = !empty($field['settings']['handler_settings']['behaviors']) ? $field['settings']['handler_settings']['behaviors'] : array(); + if (!empty($instance['settings']['behaviors'])) { + $behaviors = array_merge($behaviors, $instance['settings']['behaviors']); + } + foreach ($behaviors as $behavior => $settings) { + if (empty($settings['status'])) { + // Behavior is not enabled. + continue; + } + + $object_cache[$identifier][] = _entityreference_get_behavior_handler($behavior); + } + } + + return $object_cache[$identifier]; +} + +/** + * Get the behavior handler for a given entityreference field and instance. + * + * @param $handler + * The behavior handler name. + */ +function _entityreference_get_behavior_handler($behavior) { + $object_cache = drupal_static(__FUNCTION__); + + if (!isset($object_cache[$behavior])) { + ctools_include('plugins'); + $class = ctools_plugin_load_class('entityreference', 'behavior', $behavior, 'class'); + + $class = class_exists($class) ? $class : 'EntityReference_BehaviorHandler_Broken'; + $object_cache[$behavior] = new $class($behavior); + } + + return $object_cache[$behavior]; +} + +/** + * Get the selection handler for a given entityreference field. + */ +function entityreference_get_selection_handler($field, $instance = NULL, $entity_type = NULL, $entity = NULL) { + ctools_include('plugins'); + $handler = $field['settings']['handler']; + $class = ctools_plugin_load_class('entityreference', 'selection', $handler, 'class'); + + if (class_exists($class)) { + return call_user_func(array($class, 'getInstance'), $field, $instance, $entity_type, $entity); + } + else { + return EntityReference_SelectionHandler_Broken::getInstance($field, $instance, $entity_type, $entity); + } +} + +/** + * Implements hook_field_load(). + */ +function entityreference_field_load($entity_type, $entities, $field, $instances, $langcode, &$items) { + // Invoke the behaviors. + foreach (entityreference_get_behavior_handlers($field) as $handler) { + $handler->load($entity_type, $entities, $field, $instances, $langcode, $items); + } +} + +/** + * Implements hook_field_validate(). + */ +function entityreference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { + $ids = array(); + foreach ($items as $delta => $item) { + if (!entityreference_field_is_empty($item, $field) && $item['target_id'] !== NULL) { + $ids[$item['target_id']] = $delta; + } + } + + if ($ids) { + $valid_ids = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->validateReferencableEntities(array_keys($ids)); + + $invalid_entities = array_diff_key($ids, array_flip($valid_ids)); + if ($invalid_entities) { + foreach ($invalid_entities as $id => $delta) { + $errors[$field['field_name']][$langcode][$delta][] = array( + 'error' => 'entityreference_invalid_entity', + 'message' => t('The referenced entity (@type: @id) is invalid.', array('@type' => $field['settings']['target_type'], '@id' => $id)), + ); + } + } + } + + // Invoke the behaviors. + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->validate($entity_type, $entity, $field, $instance, $langcode, $items, $errors); + } +} + +/** + * Implements hook_field_presave(). + * + * Adds the target type to the field data structure when saving. + */ +function entityreference_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Invoke the behaviors. + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->presave($entity_type, $entity, $field, $instance, $langcode, $items); + } +} + +/** + * Implements hook_field_insert(). + */ +function entityreference_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Invoke the behaviors. + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->insert($entity_type, $entity, $field, $instance, $langcode, $items); + } +} + +/** + * Implements hook_field_attach_insert(). + * + * Emulates a post-insert hook. + */ +function entityreference_field_attach_insert($entity_type, $entity) { + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) { + $field = field_info_field($field_name); + if ($field['type'] == 'entityreference') { + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->postInsert($entity_type, $entity, $field, $instance); + } + } + } +} + +/** + * Implements hook_field_update(). + */ +function entityreference_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Invoke the behaviors. + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->update($entity_type, $entity, $field, $instance, $langcode, $items); + } +} + +/** + * Implements hook_field_attach_update(). + * + * Emulates a post-update hook. + */ +function entityreference_field_attach_update($entity_type, $entity) { + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) { + $field = field_info_field($field_name); + if ($field['type'] == 'entityreference') { + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->postUpdate($entity_type, $entity, $field, $instance); + } + } + } +} + +/** + * Implements hook_field_delete(). + */ +function entityreference_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Invoke the behaviors. + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->delete($entity_type, $entity, $field, $instance, $langcode, $items); + } +} + +/** + * Implements hook_field_attach_delete(). + * + * Emulates a post-delete hook. + */ +function entityreference_field_attach_delete($entity_type, $entity) { + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) { + $field = field_info_field($field_name); + if ($field['type'] == 'entityreference') { + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->postDelete($entity_type, $entity, $field, $instance); + } + } + } +} + +/** + * Implements hook_entity_insert(). + */ +function entityreference_entity_insert($entity, $entity_type) { + entityreference_entity_crud($entity, $entity_type, 'entityPostInsert'); +} + +/** + * Implements hook_entity_update(). + */ +function entityreference_entity_update($entity, $entity_type) { + entityreference_entity_crud($entity, $entity_type, 'entityPostUpdate'); +} + +/** + * Implements hook_entity_delete(). + */ +function entityreference_entity_delete($entity, $entity_type) { + entityreference_entity_crud($entity, $entity_type, 'entityPostDelete'); +} + +/** + * Invoke a behavior based on entity CRUD. + * + * @param $entity + * The entity object. + * @param $entity_type + * The entity type. + * @param $method_name + * The method to invoke. + */ +function entityreference_entity_crud($entity, $entity_type, $method_name) { + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) { + $field = field_info_field($field_name); + if ($field['type'] == 'entityreference') { + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->{$method_name}($entity_type, $entity, $field, $instance); + } + } + } +} + +/** + * Implements hook_field_settings_form(). + */ +function entityreference_field_settings_form($field, $instance, $has_data) { + // The field settings infrastructure is not AJAX enabled by default, + // because it doesn't pass over the $form_state. + // Build the whole form into a #process in which we actually have access + // to the form state. + $form = array( + '#type' => 'container', + '#attached' => array( + 'css' => array(drupal_get_path('module', 'entityreference') . '/entityreference.admin.css'), + ), + '#process' => array( + '_entityreference_field_settings_process', + '_entityreference_field_settings_ajax_process', + ), + '#element_validate' => array('_entityreference_field_settings_validate'), + '#field' => $field, + '#instance' => $instance, + '#has_data' => $has_data, + ); + return $form; +} + +function _entityreference_field_settings_process($form, $form_state) { + $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field']; + $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance']; + $has_data = $form['#has_data']; + + $settings = $field['settings']; + $settings += array('handler' => 'base'); + + // Select the target entity type. + $entity_type_options = array(); + foreach (entity_get_info() as $entity_type => $entity_info) { + $entity_type_options[$entity_type] = $entity_info['label']; + } + + $form['target_type'] = array( + '#type' => 'select', + '#title' => t('Target type'), + '#options' => $entity_type_options, + '#default_value' => $field['settings']['target_type'], + '#required' => TRUE, + '#description' => t('The entity type that can be referenced through this field.'), + '#disabled' => $has_data, + '#size' => 1, + '#ajax' => TRUE, + '#limit_validation_errors' => array(), + ); + + ctools_include('plugins'); + $handlers = ctools_get_plugins('entityreference', 'selection'); + uasort($handlers, 'ctools_plugin_sort'); + $handlers_options = array(); + foreach ($handlers as $handler => $handler_info) { + $handlers_options[$handler] = check_plain($handler_info['title']); + } + + $form['handler'] = array( + '#type' => 'fieldset', + '#title' => t('Entity selection'), + '#tree' => TRUE, + '#process' => array('_entityreference_form_process_merge_parent'), + ); + + $form['handler']['handler'] = array( + '#type' => 'select', + '#title' => t('Mode'), + '#options' => $handlers_options, + '#default_value' => $settings['handler'], + '#required' => TRUE, + '#ajax' => TRUE, + '#limit_validation_errors' => array(), + ); + $form['handler_submit'] = array( + '#type' => 'submit', + '#value' => t('Change handler'), + '#limit_validation_errors' => array(), + '#attributes' => array( + 'class' => array('js-hide'), + ), + '#submit' => array('entityreference_settings_ajax_submit'), + ); + + $form['handler']['handler_settings'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('entityreference-settings')), + ); + + $handler = entityreference_get_selection_handler($field, $instance); + $form['handler']['handler_settings'] += $handler->settingsForm($field, $instance); + + _entityreference_get_behavior_elements($form, $field, $instance, 'field'); + if (!empty($form['behaviors'])) { + $form['behaviors'] += array( + '#type' => 'fieldset', + '#title' => t('Additional behaviors'), + '#parents' => array_merge($form['#parents'], array('handler_settings', 'behaviors')), + ); + } + + return $form; +} + +function _entityreference_field_settings_ajax_process($form, $form_state) { + _entityreference_field_settings_ajax_process_element($form, $form); + return $form; +} + +function _entityreference_field_settings_ajax_process_element(&$element, $main_form) { + if (isset($element['#ajax']) && $element['#ajax'] === TRUE) { + $element['#ajax'] = array( + 'callback' => 'entityreference_settings_ajax', + 'wrapper' => $main_form['#id'], + 'element' => $main_form['#array_parents'], + ); + } + + foreach (element_children($element) as $key) { + _entityreference_field_settings_ajax_process_element($element[$key], $main_form); + } +} + +function _entityreference_form_process_merge_parent($element) { + $parents = $element['#parents']; + array_pop($parents); + $element['#parents'] = $parents; + return $element; +} + +function _entityreference_element_validate_filter(&$element, &$form_state) { + $element['#value'] = array_filter($element['#value']); + form_set_value($element, $element['#value'], $form_state); +} + +function _entityreference_field_settings_validate($form, &$form_state) { + // Store the new values in the form state. + $field = $form['#field']; + if (isset($form_state['values']['field'])) { + $field['settings'] = $form_state['values']['field']['settings']; + } + $form_state['entityreference']['field'] = $field; + + unset($form_state['values']['field']['settings']['handler_submit']); +} + +/** + * Implements hook_field_instance_settings_form(). + */ +function entityreference_field_instance_settings_form($field, $instance) { + $form['settings'] = array( + '#type' => 'container', + '#attached' => array( + 'css' => array(drupal_get_path('module', 'entityreference') . '/entityreference.admin.css'), + ), + '#weight' => 10, + '#tree' => TRUE, + '#process' => array( + '_entityreference_form_process_merge_parent', + '_entityreference_field_instance_settings_form', + '_entityreference_field_settings_ajax_process', + ), + '#element_validate' => array('_entityreference_field_instance_settings_validate'), + '#field' => $field, + '#instance' => $instance, + ); + + return $form; +} + +function _entityreference_field_instance_settings_form($form, $form_state) { + $field = isset($form_state['entityreference']['field']) ? $form_state['entityreference']['field'] : $form['#field']; + $instance = isset($form_state['entityreference']['instance']) ? $form_state['entityreference']['instance'] : $form['#instance']; + + _entityreference_get_behavior_elements($form, $field, $instance, 'instance'); + if (!empty($form['behaviors'])) { + $form['behaviors'] += array( + '#type' => 'fieldset', + '#title' => t('Additional behaviors'), + '#process' => array( + '_entityreference_field_settings_ajax_process', + ), + ); + } + return $form; +} + +function _entityreference_field_instance_settings_validate($form, &$form_state) { + // Store the new values in the form state. + $instance = $form['#instance']; + if (isset($form_state['values']['instance'])) { + $instance = drupal_array_merge_deep($instance, $form_state['values']['instance']); + } + $form_state['entityreference']['instance'] = $instance; +} + +/** + * Get the field or instance elements for the field configuration. + */ +function _entityreference_get_behavior_elements(&$element, $field, $instance, $level) { + // Add the accessible behavior handlers. + $behavior_plugins = entityreference_get_accessible_behavior_plugins($field, $instance); + + if ($behavior_plugins[$level]) { + $element['behaviors'] = array(); + + foreach ($behavior_plugins[$level] as $name => $plugin) { + if ($level == 'field') { + $settings = !empty($field['settings']['handler_settings']['behaviors'][$name]) ? $field['settings']['handler_settings']['behaviors'][$name] : array(); + } + else { + $settings = !empty($instance['settings']['behaviors'][$name]) ? $instance['settings']['behaviors'][$name] : array(); + } + $settings += array('status' => $plugin['force enabled']); + + // Render the checkbox. + $element['behaviors'][$name] = array( + '#tree' => TRUE, + ); + $element['behaviors'][$name]['status'] = array( + '#type' => 'checkbox', + '#title' => check_plain($plugin['title']), + '#description' => $plugin['description'], + '#default_value' => $settings['status'], + '#disabled' => $plugin['force enabled'], + '#ajax' => TRUE, + ); + + if ($settings['status']) { + $handler = _entityreference_get_behavior_handler($name); + if ($behavior_elements = $handler->settingsForm($field, $instance)) { + foreach ($behavior_elements as $key => &$behavior_element) { + $behavior_element += array( + '#default_value' => !empty($settings[$key]) ? $settings[$key] : NULL, + ); + } + + // Get the behavior settings. + $behavior_elements += array( + '#type' => 'container', + '#process' => array('_entityreference_form_process_merge_parent'), + '#attributes' => array( + 'class' => array('entityreference-settings'), + ), + ); + $element['behaviors'][$name]['settings'] = $behavior_elements; + } + } + } + } +} + +/** + * Get all accessible behavior plugins. + */ +function entityreference_get_accessible_behavior_plugins($field, $instance) { + ctools_include('plugins'); + $plugins = array('field' => array(), 'instance' => array()); + foreach (ctools_get_plugins('entityreference', 'behavior') as $name => $plugin) { + $handler = _entityreference_get_behavior_handler($name); + $level = $plugin['behavior type']; + if ($handler->access($field, $instance)) { + $plugins[$level][$name] = $plugin; + } + } + return $plugins; +} + +/** + * Ajax callback for the handler settings form. + * + * @see entityreference_field_settings_form() + */ +function entityreference_settings_ajax($form, $form_state) { + $trigger = $form_state['triggering_element']; + return drupal_array_get_nested_value($form, $trigger['#ajax']['element']); +} + +/** + * Submit handler for the non-JS case. + * + * @see entityreference_field_settings_form() + */ +function entityreference_settings_ajax_submit($form, &$form_state) { + $form_state['rebuild'] = TRUE; +} + +/** + * Property callback for the Entity Metadata framework. + */ +function entityreference_field_property_callback(&$info, $entity_type, $field, $instance, $field_type) { + // Set the property type based on the targe type. + $field_type['property_type'] = $field['settings']['target_type']; + + // Then apply the default. + entity_metadata_field_default_property_callback($info, $entity_type, $field, $instance, $field_type); + + // Invoke the behaviors to allow them to change the properties. + foreach (entityreference_get_behavior_handlers($field, $instance) as $handler) { + $handler->property_info_alter($info, $entity_type, $field, $instance, $field_type); + } +} + +/** + * Implements hook_field_widget_info(). + */ +function entityreference_field_widget_info() { + $widgets['entityreference_autocomplete'] = array( + 'label' => t('Autocomplete'), + 'description' => t('An autocomplete text field.'), + 'field types' => array('entityreference'), + 'settings' => array( + 'match_operator' => 'CONTAINS', + 'size' => 60, + // We don't have a default here, because it's not the same between + // the two widgets, and the Field API doesn't update default + // settings when the widget changes. + 'path' => '', + ), + ); + + $widgets['entityreference_autocomplete_tags'] = array( + 'label' => t('Autocomplete (Tags style)'), + 'description' => t('An autocomplete text field.'), + 'field types' => array('entityreference'), + 'settings' => array( + 'match_operator' => 'CONTAINS', + 'size' => 60, + // We don't have a default here, because it's not the same between + // the two widgets, and the Field API doesn't update default + // settings when the widget changes. + 'path' => '', + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + ), + ); + + return $widgets; +} + +/** + * Implements hook_field_widget_info_alter(). + */ +function entityreference_field_widget_info_alter(&$info) { + if (module_exists('options')) { + $info['options_select']['field types'][] = 'entityreference'; + $info['options_buttons']['field types'][] = 'entityreference'; + } +} + +/** + * Implements hook_field_widget_settings_form(). + */ +function entityreference_field_widget_settings_form($field, $instance) { + $widget = $instance['widget']; + $settings = $widget['settings'] + field_info_widget_settings($widget['type']); + + $form = array(); + + if ($widget['type'] == 'entityreference_autocomplete' || $widget['type'] == 'entityreference_autocomplete_tags') { + $form['match_operator'] = array( + '#type' => 'select', + '#title' => t('Autocomplete matching'), + '#default_value' => $settings['match_operator'], + '#options' => array( + 'STARTS_WITH' => t('Starts with'), + 'CONTAINS' => t('Contains'), + ), + '#description' => t('Select the method used to collect autocomplete suggestions. Note that Contains can cause performance issues on sites with thousands of nodes.'), + ); + $form['size'] = array( + '#type' => 'textfield', + '#title' => t('Size of textfield'), + '#default_value' => $settings['size'], + '#element_validate' => array('_element_validate_integer_positive'), + '#required' => TRUE, + ); + } + + return $form; +} + +/** + * Implements hook_options_list(). + */ +function entityreference_options_list($field, $instance = NULL, $entity_type = NULL, $entity = NULL) { + if (!$options = entityreference_get_selection_handler($field, $instance, $entity_type, $entity)->getReferencableEntities()) { + return array(); + } + + // Rebuild the array, by changing the bundle key into the bundle label. + $target_type = $field['settings']['target_type']; + $entity_info = entity_get_info($target_type); + + $return = array(); + foreach ($options as $bundle => $entity_ids) { + $bundle_label = check_plain($entity_info['bundles'][$bundle]['label']); + $return[$bundle_label] = $entity_ids; + } + + return count($return) == 1 ? reset($return) : $return; +} + +/** + * Implements hook_query_TAG_alter(). + */ +function entityreference_query_entityreference_alter(QueryAlterableInterface $query) { + $handler = $query->getMetadata('entityreference_selection_handler'); + $handler->entityFieldQueryAlter($query); +} + +/** + * Implements hook_field_widget_form(). + */ +function entityreference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + $entity_type = $instance['entity_type']; + $entity = isset($element['#entity']) ? $element['#entity'] : NULL; + $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); + + if ($instance['widget']['type'] == 'entityreference_autocomplete' || $instance['widget']['type'] == 'entityreference_autocomplete_tags') { + + if ($instance['widget']['type'] == 'entityreference_autocomplete') { + // We let the Field API handles multiple values for us, only take + // care of the one matching our delta. + if (isset($items[$delta])) { + $items = array($items[$delta]); + } + else { + $items = array(); + } + } + + $entity_ids = array(); + $entity_labels = array(); + + // Build an array of entities ID. + foreach ($items as $item) { + $entity_ids[] = $item['target_id']; + } + + // Load those entities and loop through them to extract their labels. + $entities = entity_load($field['settings']['target_type'], $entity_ids); + + foreach ($entities as $entity_id => $entity_item) { + $label = $handler->getLabel($entity_item); + $key = "$label ($entity_id)"; + // Labels containing commas or quotes must be wrapped in quotes. + if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) { + $key = '"' . str_replace('"', '""', $key) . '"'; + } + $entity_labels[] = $key; + } + + // Prepare the autocomplete path. + if (!empty($instance['widget']['settings']['path'])) { + $autocomplete_path = $instance['widget']['settings']['path']; + } + else { + $autocomplete_path = $instance['widget']['type'] == 'entityreference_autocomplete' ? 'entityreference/autocomplete/single' : 'entityreference/autocomplete/tags'; + } + + $autocomplete_path .= '/' . $field['field_name'] . '/' . $instance['entity_type'] . '/' . $instance['bundle'] . '/'; + // Use as a placeholder in the URL when we don't have an entity. + // Most webservers collapse two consecutive slashes. + $id = 'NULL'; + if ($entity) { + list($eid) = entity_extract_ids($entity_type, $entity); + if ($eid) { + $id = $eid; + } + } + $autocomplete_path .= $id; + + if ($instance['widget']['type'] == 'entityreference_autocomplete') { + $element += array( + '#type' => 'textfield', + '#maxlength' => 1024, + '#default_value' => implode(', ', $entity_labels), + '#autocomplete_path' => $autocomplete_path, + '#size' => $instance['widget']['settings']['size'], + '#element_validate' => array('_entityreference_autocomplete_validate'), + ); + return array('target_id' => $element); + } + else { + $element += array( + '#type' => 'textfield', + '#maxlength' => 1024, + '#default_value' => implode(', ', $entity_labels), + '#autocomplete_path' => $autocomplete_path, + '#size' => $instance['widget']['settings']['size'], + '#element_validate' => array('_entityreference_autocomplete_tags_validate'), + ); + return $element; + } + } +} + +function _entityreference_autocomplete_validate($element, &$form_state, $form) { + // If a value was entered into the autocomplete... + $value = ''; + if (!empty($element['#value'])) { + // Take "label (entity id)', match the id from parenthesis. + if (preg_match("/.+\((\d+)\)/", $element['#value'], $matches)) { + $value = $matches[1]; + } + else { + // Try to get a match from the input string when the user didn't use the + // autocomplete but filled in a value manually. + $field = field_info_field($element['#field_name']); + $handler = entityreference_get_selection_handler($field); + $field_name = $element['#field_name']; + $field = field_info_field($field_name); + $instance = field_info_instance($element['#entity_type'], $field_name, $element['#bundle']); + $handler = entityreference_get_selection_handler($field, $instance); + $value = $handler->validateAutocompleteInput($element['#value'], $element, $form_state, $form); + } + } + // Update the value of this element so the field can validate the product IDs. + form_set_value($element, $value, $form_state); +} + +function _entityreference_autocomplete_tags_validate($element, &$form_state, $form) { + $value = array(); + // If a value was entered into the autocomplete... + if (!empty($element['#value'])) { + $entities = drupal_explode_tags($element['#value']); + $value = array(); + foreach ($entities as $entity) { + // Take "label (entity id)', match the id from parenthesis. + if (preg_match("/.+\((\d+)\)/", $entity, $matches)) { + $value[] = array( + 'target_id' => $matches[1], + ); + } + else { + // Try to get a match from the input string when the user didn't use the + // autocomplete but filled in a value manually. + $field = field_info_field($element['#field_name']); + $handler = entityreference_get_selection_handler($field); + $value[] = array( + 'target_id' => $handler->validateAutocompleteInput($entity, $element, $form_state, $form), + ); + } + } + } + // Update the value of this element so the field can validate the product IDs. + form_set_value($element, $value, $form_state); +} + +/** + * Implements hook_field_widget_error(). + */ +function entityreference_field_widget_error($element, $error) { + form_error($element, $error['message']); +} + +/** + * Menu Access callback for the autocomplete widget. + * + * @param $type + * The widget type (i.e. 'single' or 'tags'). + * @param $field_name + * The name of the entity-reference field. + * @param $entity_type + * The entity type. + * @param $bundle_name + * The bundle name. + * @return + * True if user can access this menu item. + */ +function entityreference_autocomplete_access_callback($type, $field_name, $entity_type, $bundle_name) { + $field = field_info_field($field_name); + $instance = field_info_instance($entity_type, $field_name, $bundle_name); + + if (!$field || !$instance || $field['type'] != 'entityreference' || !field_access('edit', $field, $entity_type)) { + return FALSE; + } + return TRUE; +} + +/** + * Menu callback: autocomplete the label of an entity. + * + * @param $type + * The widget type (i.e. 'single' or 'tags'). + * @param $field_name + * The name of the entity-reference field. + * @param $entity_type + * The entity type. + * @param $bundle_name + * The bundle name. + * @param $entity_id + * Optional; The entity ID the entity-reference field is attached to. + * Defaults to ''. + * @param $string + * The label of the entity to query by. + */ +function entityreference_autocomplete_callback($type, $field_name, $entity_type, $bundle_name, $entity_id = '', $string = '') { + $field = field_info_field($field_name); + $instance = field_info_instance($entity_type, $field_name, $bundle_name); + + return entityreference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id, $string); +} + +/** + * Return JSON based on given field, instance and string. + * + * This function can be used by other modules that wish to pass a mocked + * definition of the field on instance. + * + * @param $type + * The widget type (i.e. 'single' or 'tags'). + * @param $field + * The field array defintion. + * @param $instance + * The instance array defintion. + * @param $entity_type + * The entity type. + * @param $entity_id + * Optional; The entity ID the entity-reference field is attached to. + * Defaults to ''. + * @param $string + * The label of the entity to query by. + */ +function entityreference_autocomplete_callback_get_matches($type, $field, $instance, $entity_type, $entity_id = '', $string = '') { + $matches = array(); + + $entity = NULL; + if ($entity_id !== 'NULL') { + $entity = entity_load_single($entity_type, $entity_id); + if (!$entity || !entity_access('view', $entity_type, $entity)) { + return MENU_ACCESS_DENIED; + } + } + + $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); + + if ($type == 'tags') { + // The user enters a comma-separated list of tags. We only autocomplete the last tag. + $tags_typed = drupal_explode_tags($string); + $tag_last = drupal_strtolower(array_pop($tags_typed)); + if (!empty($tag_last)) { + $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : ''; + } + } + else { + // The user enters a single tag. + $prefix = ''; + $tag_last = $string; + } + + if (isset($tag_last)) { + // Get an array of matching entities. + $entity_labels = $handler->getReferencableEntities($tag_last, $instance['widget']['settings']['match_operator'], 10); + + // Loop through the products and convert them into autocomplete output. + foreach ($entity_labels as $values) { + foreach ($values as $entity_id => $label) { + $key = "$label ($entity_id)"; + // Strip things like starting/trailing white spaces, line breaks and tags. + $key = preg_replace('/\s\s+/', ' ', str_replace("\n", '', trim(decode_entities(strip_tags($key))))); + // Names containing commas or quotes must be wrapped in quotes. + if (strpos($key, ',') !== FALSE || strpos($key, '"') !== FALSE) { + $key = '"' . str_replace('"', '""', $key) . '"'; + } + $matches[$prefix . $key] = '
    ' . $label . '
    '; + } + } + } + + drupal_json_output($matches); +} + +/** + * Implements hook_field_formatter_info(). + */ +function entityreference_field_formatter_info() { + return array( + 'entityreference_label' => array( + 'label' => t('Label'), + 'description' => t('Display the label of the referenced entities.'), + 'field types' => array('entityreference'), + 'settings' => array( + 'link' => FALSE, + ), + ), + 'entityreference_entity_id' => array( + 'label' => t('Entity id'), + 'description' => t('Display the id of the referenced entities.'), + 'field types' => array('entityreference'), + ), + 'entityreference_entity_view' => array( + 'label' => t('Rendered entity'), + 'description' => t('Display the referenced entities rendered by entity_view().'), + 'field types' => array('entityreference'), + 'settings' => array( + 'view_mode' => '', + 'links' => TRUE, + ), + ), + ); +} + +/** + * Implements hook_field_formatter_settings_form(). + */ +function entityreference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + + if ($display['type'] == 'entityreference_label') { + $element['link'] = array( + '#title' => t('Link label to the referenced entity'), + '#type' => 'checkbox', + '#default_value' => $settings['link'], + ); + } + + if ($display['type'] == 'entityreference_entity_view') { + $entity_info = entity_get_info($field['settings']['target_type']); + $options = array(); + if (!empty($entity_info['view modes'])) { + foreach ($entity_info['view modes'] as $view_mode => $view_mode_settings) { + $options[$view_mode] = $view_mode_settings['label']; + } + } + + if (count($options) > 1) { + $element['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => t('View mode'), + '#default_value' => $settings['view_mode'], + ); + } + + $element['links'] = array( + '#type' => 'checkbox', + '#title' => t('Show links'), + '#default_value' => $settings['links'], + ); + } + + return $element; +} + +/** + * Implements hook_field_formatter_settings_summary(). + */ +function entityreference_field_formatter_settings_summary($field, $instance, $view_mode) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + + $summary = array(); + + if ($display['type'] == 'entityreference_label') { + $summary[] = $settings['link'] ? t('Link to the referenced entity') : t('No link'); + } + + if ($display['type'] == 'entityreference_entity_view') { + $entity_info = entity_get_info($field['settings']['target_type']); + $summary[] = t('Rendered as @mode', array('@mode' => isset($entity_info['view modes'][$settings['view_mode']]['label']) ? $entity_info['view modes'][$settings['view_mode']]['label'] : $settings['view_mode'])); + $summary[] = !empty($settings['links']) ? t('Display links') : t('Do not display links'); + } + + return implode('
    ', $summary); +} + +/** + * Implements hook_field_formatter_prepare_view(). + */ +function entityreference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) { + $target_ids = array(); + + // Collect every possible entity attached to any of the entities. + foreach ($entities as $id => $entity) { + foreach ($items[$id] as $delta => $item) { + if (isset($item['target_id'])) { + $target_ids[] = $item['target_id']; + } + } + } + + if ($target_ids) { + $target_entities = entity_load($field['settings']['target_type'], $target_ids); + } + else { + $target_entities = array(); + } + + // Iterate through the fieldable entities again to attach the loaded data. + foreach ($entities as $id => $entity) { + $rekey = FALSE; + + foreach ($items[$id] as $delta => $item) { + // Check whether the referenced entity could be loaded. + if (isset($target_entities[$item['target_id']])) { + // Replace the instance value with the term data. + $items[$id][$delta]['entity'] = $target_entities[$item['target_id']]; + // Check whether the user has access to the referenced entity. + $items[$id][$delta]['access'] = entity_access('view', $field['settings']['target_type'], $target_entities[$item['target_id']]); + } + // Otherwise, unset the instance value, since the entity does not exist. + else { + unset($items[$id][$delta]); + $rekey = TRUE; + } + } + + if ($rekey) { + // Rekey the items array. + $items[$id] = array_values($items[$id]); + } + } +} + +/** + * Implements hook_field_formatter_view(). + */ +function entityreference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $result = array(); + $settings = $display['settings']; + + // Rebuild the items list to contain only those with access. + foreach ($items as $key => $item) { + if (empty($item['access'])) { + unset($items[$key]); + } + } + + switch ($display['type']) { + case 'entityreference_label': + $handler = entityreference_get_selection_handler($field, $instance, $entity_type, $entity); + + foreach ($items as $delta => $item) { + $label = $handler->getLabel($item['entity']); + // If the link is to be displayed and the entity has a uri, display a link. + // Note the assignment ($url = ) here is intended to be an assignment. + if ($display['settings']['link'] && ($uri = entity_uri($field['settings']['target_type'], $item['entity']))) { + $result[$delta] = array('#markup' => l($label, $uri['path'], $uri['options'])); + } + else { + $result[$delta] = array('#markup' => check_plain($label)); + } + } + break; + + case 'entityreference_entity_id': + foreach ($items as $delta => $item) { + $result[$delta] = array('#markup' => check_plain($item['target_id'])); + } + break; + + case 'entityreference_entity_view': + foreach ($items as $delta => $item) { + // Protect ourselves from recursive rendering. + static $depth = 0; + $depth++; + if ($depth > 20) { + throw new EntityReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering entity @entity_type(@entity_id). Aborting rendering.', array('@entity_type' => $entity_type, '@entity_id' => $item['target_id']))); + } + + $entity = clone $item['entity']; + unset($entity->content); + $result[$delta] = entity_view($field['settings']['target_type'], array($item['target_id'] => $entity), $settings['view_mode'], $langcode, FALSE); + + if (empty($settings['links']) && isset($result[$delta][$field['settings']['target_type']][$item['target_id']]['links'])) { + $result[$delta][$field['settings']['target_type']][$item['target_id']]['links']['#access'] = FALSE; + } + $depth = 0; + } + break; + } + + return $result; +} + +/** + * Exception thrown when the entity view renderer goes into a potentially infinite loop. + */ +class EntityReferenceRecursiveRenderingException extends Exception {} + +/** + * Implements hook_views_api(). + */ +function entityreference_views_api() { + return array( + 'api' => 3, + 'path' => drupal_get_path('module', 'entityreference') . '/views', + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,12 @@ +name = Entity Reference Behavior Example +description = Provides some example code for implementing Entity Reference behaviors. +core = 7.x +package = Fields +dependencies[] = entityreference + +; Information added by drupal.org packaging script on 2012-11-18 +version = "7.x-1.0" +core = "7.x" +project = "entityreference" +datestamp = "1353230808" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/examples/entityreference_behavior_example/entityreference_behavior_example.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,15 @@ + 'checkbox', + '#title' => t('Field behavior setting'), + ); + return $form; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/examples/entityreference_behavior_example/plugins/behavior/EntityReferenceInstanceBehaviorExample.class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/examples/entityreference_behavior_example/plugins/behavior/EntityReferenceInstanceBehaviorExample.class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,31 @@ + 'checkbox', + '#title' => t('Instance behavior setting'), + ); + return $form; + } +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/examples/entityreference_behavior_example/plugins/behavior/test_field_behavior.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/examples/entityreference_behavior_example/plugins/behavior/test_field_behavior.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,8 @@ + t('Test behavior'), + 'class' => 'EntityReferenceFieldBehaviorExample', + 'weight' => 10, + 'behavior type' => 'field', +); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/examples/entityreference_behavior_example/plugins/behavior/test_instance_behavior.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/examples/entityreference_behavior_example/plugins/behavior/test_instance_behavior.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,8 @@ + t('Test instance behavior'), + 'class' => 'EntityReferenceInstanceBehaviorExample', + 'weight' => 10, + 'behavior type' => 'instance', +); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/behavior/EntityReferenceBehavior_TaxonomyIndex.class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,189 @@ +buildNodeIndex($entity); + } + + /** + * Overrides EntityReference_BehaviorHandler_Abstract::entityPostUpdate(). + * + * Runs after hook_node_update() used by taxonomy module. + */ + public function entityPostUpdate($entity_type, $entity, $field, $instance) { + if ($entity_type != 'node') { + return; + } + + $this->buildNodeIndex($entity); + } + + /** + * Builds and inserts taxonomy index entries for a given node. + * + * The index lists all terms that are related to a given node entity, and is + * therefore maintained at the entity level. + * + * @param $node + * The node object. + * + * @see taxonomy_build_node_index() + */ + protected function buildNodeIndex($node) { + // We maintain a denormalized table of term/node relationships, containing + // only data for current, published nodes. + $status = NULL; + if (variable_get('taxonomy_maintain_index_table', TRUE)) { + // If a node property is not set in the node object when node_save() is + // called, the old value from $node->original is used. + if (!empty($node->original)) { + $status = (int)(!empty($node->status) || (!isset($node->status) && !empty($node->original->status))); + $sticky = (int)(!empty($node->sticky) || (!isset($node->sticky) && !empty($node->original->sticky))); + } + else { + $status = (int)(!empty($node->status)); + $sticky = (int)(!empty($node->sticky)); + } + } + // We only maintain the taxonomy index for published nodes. + if ($status) { + // Collect a unique list of all the term IDs from all node fields. + $tid_all = array(); + foreach (field_info_instances('node', $node->type) as $instance) { + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + if (!empty($field['settings']['target_type']) && $field['settings']['target_type'] == 'taxonomy_term' && $field['storage']['type'] == 'field_sql_storage') { + // If a field value is not set in the node object when node_save() is + // called, the old value from $node->original is used. + if (isset($node->{$field_name})) { + $items = $node->{$field_name}; + } + elseif (isset($node->original->{$field_name})) { + $items = $node->original->{$field_name}; + } + else { + continue; + } + foreach (field_available_languages('node', $field) as $langcode) { + if (!empty($items[$langcode])) { + foreach ($items[$langcode] as $item) { + $tid_all[$item['target_id']] = $item['target_id']; + } + } + } + } + + // Re-calculate the terms added in taxonomy_build_node_index() so + // we can optimize database queries. + $original_tid_all = array(); + if ($field['module'] == 'taxonomy' && $field['storage']['type'] == 'field_sql_storage') { + // If a field value is not set in the node object when node_save() is + // called, the old value from $node->original is used. + if (isset($node->{$field_name})) { + $items = $node->{$field_name}; + } + elseif (isset($node->original->{$field_name})) { + $items = $node->original->{$field_name}; + } + else { + continue; + } + foreach (field_available_languages('node', $field) as $langcode) { + if (!empty($items[$langcode])) { + foreach ($items[$langcode] as $item) { + $original_tid_all[$item['tid']] = $item['tid']; + } + } + } + } + } + + // Insert index entries for all the node's terms, that were not + // already inserted in taxonomy_build_node_index(). + $tid_all = array_diff($tid_all, $original_tid_all); + + // Insert index entries for all the node's terms. + if (!empty($tid_all)) { + $query = db_insert('taxonomy_index')->fields(array('nid', 'tid', 'sticky', 'created')); + foreach ($tid_all as $tid) { + $query->values(array( + 'nid' => $node->nid, + 'tid' => $tid, + 'sticky' => $sticky, + 'created' => $node->created, + )); + } + $query->execute(); + } + } + } + + /** + * Overrides EntityReference_BehaviorHandler_Abstract::settingsForm(). + */ + public function settingsForm($field, $instance) { + $form = array(); + $target = $field['settings']['target_type']; + if ($target != 'taxonomy_term') { + $form['ti-on-terms'] = array( + '#markup' => t('This behavior can only be set when the target type is taxonomy_term, but the target of this field is %target.', array('%target' => $target)), + ); + } + + $entity_type = $instance['entity_type']; + if ($entity_type != 'node') { + $form['ti-on-nodes'] = array( + '#markup' => t('This behavior can only be set when the entity type is node, but the entity type of this instance is %type.', array('%type' => $entity_type)), + ); + } + + if (!variable_get('taxonomy_maintain_index_table', TRUE)) { + $form['ti-disabled'] = array( + '#markup' => t('This core variable "taxonomy_maintain_index_table" is disabled.'), + ); + } + return $form; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/behavior/EntityReferenceBehavior_ViewsFilterSelect.class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/behavior/EntityReferenceBehavior_ViewsFilterSelect.class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,18 @@ + &$table_data) { + if (isset($table_data[$field_name])) { + // Set the entity id filter to use the in_operator handler with our + // own callback to return the values. + $table_data[$field_name]['filter']['handler'] = 'views_handler_filter_in_operator'; + $table_data[$field_name]['filter']['options callback'] = 'entityreference_views_handler_options_list'; + $table_data[$field_name]['filter']['options arguments'] = array($field['field_name']); + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/behavior/abstract.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/behavior/abstract.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,214 @@ +behavior = $behavior; + + ctools_include('plugins'); + $plugin = ctools_get_plugins('entityreference', 'behavior', $behavior); + $this->plugin = $plugin; + } + + public function schema_alter(&$schema, $field) {} + + public function property_info_alter(&$info, $entity_type, $field, $instance, $field_type) {} + + public function views_data_alter(&$data, $field) {} + + public function load($entity_type, $entities, $field, $instances, $langcode, &$items) {} + + public function is_empty_alter(&$empty, $item, $field) {} + + public function validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {} + + public function presave($entity_type, $entity, $field, $instance, $langcode, &$items) {} + + public function insert($entity_type, $entity, $field, $instance, $langcode, &$items) {} + + public function postInsert($entity_type, $entity, $field, $instance) {} + + public function update($entity_type, $entity, $field, $instance, $langcode, &$items) {} + + public function postUpdate($entity_type, $entity, $field, $instance) {} + + public function delete($entity_type, $entity, $field, $instance, $langcode, &$items) {} + + public function postDelete($entity_type, $entity, $field, $instance) {} + + public function entityPostInsert($entity_type, $entity, $field, $instance) {} + + public function entityPostUpdate($entity_type, $entity, $field, $instance) {} + + public function entityPostDelete($entity_type, $entity, $field, $instance) {} + + public function settingsForm($field, $instance) {} + + public function access($field, $instance) { + return TRUE; + } +} + +/** + * A broken implementation of EntityReference_BehaviorHandler. + */ +class EntityReference_BehaviorHandler_Broken extends EntityReference_BehaviorHandler_Abstract { + public function settingsForm($field, $instance) { + $form['behavior_handler'] = array( + '#markup' => t('The selected behavior handler is broken.'), + ); + return $form; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/behavior/taxonomy-index.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/behavior/taxonomy-index.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,16 @@ + t('Taxonomy index'), + 'description' => t('Include the term references created by instances of this field carried by node entities in the core {taxonomy_index} table. This will allow various modules to handle them like core term_reference fields.'), + 'class' => 'EntityReferenceBehavior_TaxonomyIndex', + 'behavior type' => 'instance', + 'force enabled' => TRUE, + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/behavior/views-select-list.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/behavior/views-select-list.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,10 @@ + t('Render Views filters as select list'), + 'description' => t('Provides a select list for Views filters on this field. This should not be used when there are over 100 entities, as it might cause an out of memory error.'), + 'class' => 'EntityReferenceBehavior_ViewsFilterSelect', + 'behavior type' => 'field', + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/selection/EntityReference_SelectionHandler_Generic.class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,553 @@ +field = $field; + $this->instance = $instance; + $this->entity_type = $entity_type; + $this->entity = $entity; + } + + /** + * Implements EntityReferenceHandler::settingsForm(). + */ + public static function settingsForm($field, $instance) { + $entity_info = entity_get_info($field['settings']['target_type']); + + // Merge-in default values. + $field['settings']['handler_settings'] += array( + 'target_bundles' => array(), + 'sort' => array( + 'type' => 'none', + ) + ); + + if (!empty($entity_info['entity keys']['bundle'])) { + $bundles = array(); + foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { + $bundles[$bundle_name] = $bundle_info['label']; + } + + $form['target_bundles'] = array( + '#type' => 'checkboxes', + '#title' => t('Target bundles'), + '#options' => $bundles, + '#default_value' => $field['settings']['handler_settings']['target_bundles'], + '#size' => 6, + '#multiple' => TRUE, + '#description' => t('The bundles of the entity type that can be referenced. Optional, leave empty for all bundles.'), + '#element_validate' => array('_entityreference_element_validate_filter'), + ); + } + else { + $form['target_bundles'] = array( + '#type' => 'value', + '#value' => array(), + ); + } + + $form['sort']['type'] = array( + '#type' => 'select', + '#title' => t('Sort by'), + '#options' => array( + 'none' => t("Don't sort"), + 'property' => t('A property of the base table of the entity'), + 'field' => t('A field attached to this entity'), + ), + '#ajax' => TRUE, + '#limit_validation_errors' => array(), + '#default_value' => $field['settings']['handler_settings']['sort']['type'], + ); + + $form['sort']['settings'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('entityreference-settings')), + '#process' => array('_entityreference_form_process_merge_parent'), + ); + + if ($field['settings']['handler_settings']['sort']['type'] == 'property') { + // Merge-in default values. + $field['settings']['handler_settings']['sort'] += array( + 'property' => NULL, + ); + + $form['sort']['settings']['property'] = array( + '#type' => 'select', + '#title' => t('Sort property'), + '#required' => TRUE, + '#options' => drupal_map_assoc($entity_info['schema_fields_sql']['base table']), + '#default_value' => $field['settings']['handler_settings']['sort']['property'], + ); + } + elseif ($field['settings']['handler_settings']['sort']['type'] == 'field') { + // Merge-in default values. + $field['settings']['handler_settings']['sort'] += array( + 'field' => NULL, + ); + + $fields = array(); + foreach (field_info_instances($field['settings']['target_type']) as $bundle_name => $bundle_instances) { + foreach ($bundle_instances as $instance_name => $instance_info) { + $field_info = field_info_field($instance_name); + foreach ($field_info['columns'] as $column_name => $column_info) { + $fields[$instance_name . ':' . $column_name] = t('@label (column @column)', array('@label' => $instance_info['label'], '@column' => $column_name)); + } + } + } + + $form['sort']['settings']['field'] = array( + '#type' => 'select', + '#title' => t('Sort field'), + '#required' => TRUE, + '#options' => $fields, + '#default_value' => $field['settings']['handler_settings']['sort']['field'], + ); + } + + if ($field['settings']['handler_settings']['sort']['type'] != 'none') { + // Merge-in default values. + $field['settings']['handler_settings']['sort'] += array( + 'direction' => 'ASC', + ); + + $form['sort']['settings']['direction'] = array( + '#type' => 'select', + '#title' => t('Sort direction'), + '#required' => TRUE, + '#options' => array( + 'ASC' => t('Ascending'), + 'DESC' => t('Descending'), + ), + '#default_value' => $field['settings']['handler_settings']['sort']['direction'], + ); + } + + return $form; + } + + /** + * Implements EntityReferenceHandler::getReferencableEntities(). + */ + public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + $options = array(); + $entity_type = $this->field['settings']['target_type']; + + $query = $this->buildEntityFieldQuery($match, $match_operator); + if ($limit > 0) { + $query->range(0, $limit); + } + + $results = $query->execute(); + + if (!empty($results[$entity_type])) { + $entities = entity_load($entity_type, array_keys($results[$entity_type])); + foreach ($entities as $entity_id => $entity) { + list(,, $bundle) = entity_extract_ids($entity_type, $entity); + $options[$bundle][$entity_id] = check_plain($this->getLabel($entity)); + } + } + + return $options; + } + + /** + * Implements EntityReferenceHandler::countReferencableEntities(). + */ + public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') { + $query = $this->buildEntityFieldQuery($match, $match_operator); + return $query + ->count() + ->execute(); + } + + /** + * Implements EntityReferenceHandler::validateReferencableEntities(). + */ + public function validateReferencableEntities(array $ids) { + if ($ids) { + $entity_type = $this->field['settings']['target_type']; + $query = $this->buildEntityFieldQuery(); + $query->entityCondition('entity_id', $ids, 'IN'); + $result = $query->execute(); + if (!empty($result[$entity_type])) { + return array_keys($result[$entity_type]); + } + } + + return array(); + } + + /** + * Implements EntityReferenceHandler::validateAutocompleteInput(). + */ + public function validateAutocompleteInput($input, &$element, &$form_state, $form) { + $entities = $this->getReferencableEntities($input, '=', 6); + if (empty($entities)) { + // Error if there are no entities available for a required field. + form_error($element, t('There are no entities matching "%value"', array('%value' => $input))); + } + elseif (count($entities) > 5) { + // Error if there are more than 5 matching entities. + form_error($element, t('Many entities are called %value. Specify the one you want by appending the id in parentheses, like "@value (@id)"', array( + '%value' => $input, + '@value' => $input, + '@id' => key($entities), + ))); + } + elseif (count($entities) > 1) { + // More helpful error if there are only a few matching entities. + $multiples = array(); + foreach ($entities as $id => $name) { + $multiples[] = $name . ' (' . $id . ')'; + } + form_error($element, t('Multiple entities match this reference; "%multiple"', array('%multiple' => implode('", "', $multiples)))); + } + else { + // Take the one and only matching entity. + return key($entities); + } + } + + /** + * Build an EntityFieldQuery to get referencable entities. + */ + protected function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') { + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', $this->field['settings']['target_type']); + if (!empty($this->field['settings']['handler_settings']['target_bundles'])) { + $query->entityCondition('bundle', $this->field['settings']['handler_settings']['target_bundles'], 'IN'); + } + if (isset($match)) { + $entity_info = entity_get_info($this->field['settings']['target_type']); + if (isset($entity_info['entity keys']['label'])) { + $query->propertyCondition($entity_info['entity keys']['label'], $match, $match_operator); + } + } + + // Add a generic entity access tag to the query. + $query->addTag($this->field['settings']['target_type'] . '_access'); + $query->addTag('entityreference'); + $query->addMetaData('field', $this->field); + $query->addMetaData('entityreference_selection_handler', $this); + + // Add the sort option. + if (!empty($this->field['settings']['handler_settings']['sort'])) { + $sort_settings = $this->field['settings']['handler_settings']['sort']; + if ($sort_settings['type'] == 'property') { + $query->propertyOrderBy($sort_settings['property'], $sort_settings['direction']); + } + elseif ($sort_settings['type'] == 'field') { + list($field, $column) = explode(':', $sort_settings['field'], 2); + $query->fieldOrderBy($field, $column, $sort_settings['direction']); + } + } + + return $query; + } + + /** + * Implements EntityReferenceHandler::entityFieldQueryAlter(). + */ + public function entityFieldQueryAlter(SelectQueryInterface $query) { + + } + + /** + * Helper method: pass a query to the alteration system again. + * + * This allow Entity Reference to add a tag to an existing query, to ask + * access control mechanisms to alter it again. + */ + protected function reAlterQuery(SelectQueryInterface $query, $tag, $base_table) { + // Save the old tags and metadata. + // For some reason, those are public. + $old_tags = $query->alterTags; + $old_metadata = $query->alterMetaData; + + $query->alterTags = array($tag => TRUE); + $query->alterMetaData['base_table'] = $base_table; + drupal_alter(array('query', 'query_' . $tag), $query); + + // Restore the tags and metadata. + $query->alterTags = $old_tags; + $query->alterMetaData = $old_metadata; + } + + /** + * Implements EntityReferenceHandler::getLabel(). + */ + public function getLabel($entity) { + return entity_label($this->field['settings']['target_type'], $entity); + } + + /** + * Ensure a base table exists for the query. + * + * If we have a field-only query, we want to assure we have a base-table + * so we can later alter the query in entityFieldQueryAlter(). + * + * @param $query + * The Select query. + * + * @return + * The alias of the base-table. + */ + public function ensureBaseTable(SelectQueryInterface $query) { + $tables = $query->getTables(); + + // Check the current base table. + foreach ($tables as $table) { + if (empty($table['join'])) { + $alias = $table['alias']; + break; + } + } + + if (strpos($alias, 'field_data_') !== 0) { + // The existing base-table is the correct one. + return $alias; + } + + // Join the known base-table. + $target_type = $this->field['settings']['target_type']; + $entity_info = entity_get_info($target_type); + $id = $entity_info['entity keys']['id']; + // Return the alias of the table. + return $query->innerJoin($target_type, NULL, "$target_type.$id = $alias.entity_id"); + } +} + +/** + * Override for the Node type. + * + * This only exists to workaround core bugs. + */ +class EntityReference_SelectionHandler_Generic_node extends EntityReference_SelectionHandler_Generic { + public function entityFieldQueryAlter(SelectQueryInterface $query) { + // Adding the 'node_access' tag is sadly insufficient for nodes: core + // requires us to also know about the concept of 'published' and + // 'unpublished'. We need to do that as long as there are no access control + // modules in use on the site. As long as one access control module is there, + // it is supposed to handle this check. + if (!user_access('bypass node access') && !count(module_implements('node_grants'))) { + $base_table = $this->ensureBaseTable($query); + $query->condition("$base_table.status", NODE_PUBLISHED); + } + } +} + +/** + * Override for the User type. + * + * This only exists to workaround core bugs. + */ +class EntityReference_SelectionHandler_Generic_user extends EntityReference_SelectionHandler_Generic { + public function buildEntityFieldQuery($match = NULL, $match_operator = 'CONTAINS') { + $query = parent::buildEntityFieldQuery($match, $match_operator); + + // The user entity doesn't have a label column. + if (isset($match)) { + $query->propertyCondition('name', $match, $match_operator); + } + + // Adding the 'user_access' tag is sadly insufficient for users: core + // requires us to also know about the concept of 'blocked' and + // 'active'. + if (!user_access('administer users')) { + $query->propertyCondition('status', 1); + } + return $query; + } + + public function entityFieldQueryAlter(SelectQueryInterface $query) { + if (user_access('administer users')) { + // In addition, if the user is administrator, we need to make sure to + // match the anonymous user, that doesn't actually have a name in the + // database. + $conditions = &$query->conditions(); + foreach ($conditions as $key => $condition) { + if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'users.name') { + // Remove the condition. + unset($conditions[$key]); + + // Re-add the condition and a condition on uid = 0 so that we end up + // with a query in the form: + // WHERE (name LIKE :name) OR (:anonymous_name LIKE :name AND uid = 0) + $or = db_or(); + $or->condition($condition['field'], $condition['value'], $condition['operator']); + // Sadly, the Database layer doesn't allow us to build a condition + // in the form ':placeholder = :placeholder2', because the 'field' + // part of a condition is always escaped. + // As a (cheap) workaround, we separately build a condition with no + // field, and concatenate the field and the condition separately. + $value_part = db_and(); + $value_part->condition('anonymous_name', $condition['value'], $condition['operator']); + $value_part->compile(Database::getConnection(), $query); + $or->condition(db_and() + ->where(str_replace('anonymous_name', ':anonymous_name', (string) $value_part), $value_part->arguments() + array(':anonymous_name' => format_username(user_load(0)))) + ->condition('users.uid', 0) + ); + $query->condition($or); + } + } + } + } +} + +/** + * Override for the Comment type. + * + * This only exists to workaround core bugs. + */ +class EntityReference_SelectionHandler_Generic_comment extends EntityReference_SelectionHandler_Generic { + public function entityFieldQueryAlter(SelectQueryInterface $query) { + // Adding the 'comment_access' tag is sadly insufficient for comments: core + // requires us to also know about the concept of 'published' and + // 'unpublished'. + if (!user_access('administer comments')) { + $base_table = $this->ensureBaseTable($query); + $query->condition("$base_table.status", COMMENT_PUBLISHED); + } + + // The Comment module doesn't implement any proper comment access, + // and as a consequence doesn't make sure that comments cannot be viewed + // when the user doesn't have access to the node. + $tables = $query->getTables(); + $base_table = key($tables); + $node_alias = $query->innerJoin('node', 'n', '%alias.nid = ' . $base_table . '.nid'); + // Pass the query to the node access control. + $this->reAlterQuery($query, 'node_access', $node_alias); + + // Alas, the comment entity exposes a bundle, but doesn't have a bundle column + // in the database. We have to alter the query ourself to go fetch the + // bundle. + $conditions = &$query->conditions(); + foreach ($conditions as $key => &$condition) { + if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'node_type') { + $condition['field'] = $node_alias . '.type'; + foreach ($condition['value'] as &$value) { + if (substr($value, 0, 13) == 'comment_node_') { + $value = substr($value, 13); + } + } + break; + } + } + + // Passing the query to node_query_node_access_alter() is sadly + // insufficient for nodes. + // @see EntityReferenceHandler_node::entityFieldQueryAlter() + if (!user_access('bypass node access') && !count(module_implements('node_grants'))) { + $query->condition($node_alias . '.status', 1); + } + } +} + +/** + * Override for the File type. + * + * This only exists to workaround core bugs. + */ +class EntityReference_SelectionHandler_Generic_file extends EntityReference_SelectionHandler_Generic { + public function entityFieldQueryAlter(SelectQueryInterface $query) { + // Core forces us to know about 'permanent' vs. 'temporary' files. + $tables = $query->getTables(); + $base_table = key($tables); + $query->condition('status', FILE_STATUS_PERMANENT); + + // Access control to files is a very difficult business. For now, we are not + // going to give it a shot. + // @todo: fix this when core access control is less insane. + return $query; + } + + public function getLabel($entity) { + // The file entity doesn't have a label. More over, the filename is + // sometimes empty, so use the basename in that case. + return $entity->filename !== '' ? $entity->filename : basename($entity->uri); + } +} + +/** + * Override for the Taxonomy term type. + * + * This only exists to workaround core bugs. + */ +class EntityReference_SelectionHandler_Generic_taxonomy_term extends EntityReference_SelectionHandler_Generic { + public function entityFieldQueryAlter(SelectQueryInterface $query) { + // The Taxonomy module doesn't implement any proper taxonomy term access, + // and as a consequence doesn't make sure that taxonomy terms cannot be viewed + // when the user doesn't have access to the vocabulary. + $base_table = $this->ensureBaseTable($query); + $vocabulary_alias = $query->innerJoin('taxonomy_vocabulary', 'n', '%alias.vid = ' . $base_table . '.vid'); + $query->addMetadata('base_table', $vocabulary_alias); + // Pass the query to the taxonomy access control. + $this->reAlterQuery($query, 'taxonomy_vocabulary_access', $vocabulary_alias); + + // Also, the taxonomy term entity exposes a bundle, but doesn't have a bundle + // column in the database. We have to alter the query ourself to go fetch + // the bundle. + $conditions = &$query->conditions(); + foreach ($conditions as $key => &$condition) { + if ($key !== '#conjunction' && is_string($condition['field']) && $condition['field'] === 'vocabulary_machine_name') { + $condition['field'] = $vocabulary_alias . '.machine_name'; + break; + } + } + } + + /** + * Implements EntityReferenceHandler::getReferencableEntities(). + */ + public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + if ($match || $limit) { + return parent::getReferencableEntities($match , $match_operator, $limit); + } + + $options = array(); + $entity_type = $this->field['settings']['target_type']; + + // We imitate core by calling taxonomy_get_tree(). + $entity_info = entity_get_info('taxonomy_term'); + $bundles = !empty($this->field['settings']['handler_settings']['target_bundles']) ? $this->field['settings']['handler_settings']['target_bundles'] : array_keys($entity_info['bundles']); + + foreach ($bundles as $bundle) { + if ($vocabulary = taxonomy_vocabulary_machine_name_load($bundle)) { + if ($terms = taxonomy_get_tree($vocabulary->vid, 0)) { + foreach ($terms as $term) { + $options[$vocabulary->machine_name][$term->tid] = str_repeat('-', $term->depth) . check_plain($term->name); + } + } + } + } + + return $options; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/selection/EntityReference_SelectionHandler_Views.class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,193 @@ +field = $field; + $this->instance = $instance; + } + + /** + * Implements EntityReferenceHandler::settingsForm(). + */ + public static function settingsForm($field, $instance) { + $view_settings = empty($field['settings']['handler_settings']['view']) ? '' : $field['settings']['handler_settings']['view']; + $displays = views_get_applicable_views('entityreference display'); + // Filter views that list the entity type we want, and group the separate + // displays by view. + $entity_info = entity_get_info($field['settings']['target_type']); + $options = array(); + foreach ($displays as $data) { + list($view, $display_id) = $data; + if ($view->base_table == $entity_info['base table']) { + $options[$view->name . ':' . $display_id] = $view->name . ' - ' . $view->display[$display_id]->display_title; + } + } + + // The value of the 'view_and_display' select below will need to be split + // into 'view_name' and 'view_display' in the final submitted values, so + // we massage the data at validate time on the wrapping element (not + // ideal). + $form['view']['#element_validate'] = array('entityreference_view_settings_validate'); + + if ($options) { + $default = !empty($view_settings['view_name']) ? $view_settings['view_name'] . ':' . $view_settings['display_name'] : NULL; + $form['view']['view_and_display'] = array( + '#type' => 'select', + '#title' => t('View used to select the entities'), + '#required' => TRUE, + '#options' => $options, + '#default_value' => $default, + '#description' => '

    ' . t('Choose the view and display that select the entities that can be referenced.
    Only views with a display of type "Entity Reference" are eligible.') . '

    ', + ); + + $default = !empty($view_settings['args']) ? implode(', ', $view_settings['args']) : ''; + $form['view']['args'] = array( + '#type' => 'textfield', + '#title' => t('View arguments'), + '#default_value' => $default, + '#required' => FALSE, + '#description' => t('Provide a comma separated list of arguments to pass to the view.'), + ); + } + else { + $form['view']['no_view_help'] = array( + '#markup' => '

    ' . t('No eligible views were found. Create a view with an Entity Reference display, or add such a display to an existing view.', array( + '@create' => url('admin/structure/views/add'), + '@existing' => url('admin/structure/views'), + )) . '

    ', + ); + } + return $form; + } + + protected function initializeView($match = NULL, $match_operator = 'CONTAINS', $limit = 0, $ids = NULL) { + $view_name = $this->field['settings']['handler_settings']['view']['view_name']; + $display_name = $this->field['settings']['handler_settings']['view']['display_name']; + $args = $this->field['settings']['handler_settings']['view']['args']; + $entity_type = $this->field['settings']['target_type']; + + // Check that the view is valid and the display still exists. + $this->view = views_get_view($view_name); + if (!$this->view || !isset($this->view->display[$display_name]) || !$this->view->access($display_name)) { + watchdog('entityreference', 'The view %view_name is no longer eligible for the %field_name field.', array('%view_name' => $view_name, '%field_name' => $this->instance['label']), WATCHDOG_WARNING); + return FALSE; + } + $this->view->set_display($display_name); + + // Make sure the query is not cached. + $this->view->is_cacheable = FALSE; + + // Pass options to the display handler to make them available later. + $entityreference_options = array( + 'match' => $match, + 'match_operator' => $match_operator, + 'limit' => $limit, + 'ids' => $ids, + ); + $this->view->display_handler->set_option('entityreference_options', $entityreference_options); + return TRUE; + } + + /** + * Implements EntityReferenceHandler::getReferencableEntities(). + */ + public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + $display_name = $this->field['settings']['handler_settings']['view']['display_name']; + $args = $this->field['settings']['handler_settings']['view']['args']; + $result = array(); + if ($this->initializeView($match, $match_operator, $limit)) { + // Get the results. + $result = $this->view->execute_display($display_name, $args); + } + + $return = array(); + if ($result) { + $target_type = $this->field['settings']['target_type']; + $entities = entity_load($target_type, array_keys($result)); + foreach($entities as $entity) { + list($id,, $bundle) = entity_extract_ids($target_type, $entity); + $return[$bundle][$id] = $result[$id]; + } + } + return $return; + } + + /** + * Implements EntityReferenceHandler::countReferencableEntities(). + */ + function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') { + $this->getReferencableEntities($match, $match_operator); + return $this->view->total_items; + } + + function validateReferencableEntities(array $ids) { + $display_name = $this->field['settings']['handler_settings']['view']['display_name']; + $args = $this->field['settings']['handler_settings']['view']['args']; + $result = array(); + if ($this->initializeView(NULL, 'CONTAINS', 0, $ids)) { + // Get the results. + $entities = $this->view->execute_display($display_name, $args); + $result = array_keys($entities); + } + return $result; + } + + /** + * Implements EntityReferenceHandler::validateAutocompleteInput(). + */ + public function validateAutocompleteInput($input, &$element, &$form_state, $form) { + return NULL; + } + + /** + * Implements EntityReferenceHandler::getLabel(). + */ + public function getLabel($entity) { + return entity_label($this->field['settings']['target_type'], $entity); + } + + /** + * Implements EntityReferenceHandler::entityFieldQueryAlter(). + */ + public function entityFieldQueryAlter(SelectQueryInterface $query) { + + } + +} + +function entityreference_view_settings_validate($element, &$form_state, $form) { + // Split view name and display name from the 'view_and_display' value. + if (!empty($element['view_and_display']['#value'])) { + list($view, $display) = explode(':', $element['view_and_display']['#value']); + } + else { + form_error($element, t('The views entity selection mode requires a view.')); + return; + } + + // Explode the 'args' string into an actual array. Beware, explode() turns an + // empty string into an array with one empty string. We'll need an empty array + // instead. + $args_string = trim($element['args']['#value']); + if ($args_string === '') { + $args = array(); + } + else { + // array_map is called to trim whitespaces from the arguments. + $args = array_map('trim', explode(',', $args_string)); + } + + $value = array('view_name' => $view, 'display_name' => $display, 'args' => $args); + form_set_value($element, $value, $form_state); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/selection/abstract.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/selection/abstract.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,113 @@ +field = $field; + $this->instance = $instance; + } + + public static function settingsForm($field, $instance) { + $form['selection_handler'] = array( + '#markup' => t('The selected selection handler is broken.'), + ); + return $form; + } + + public function getReferencableEntities($match = NULL, $match_operator = 'CONTAINS', $limit = 0) { + return array(); + } + + public function countReferencableEntities($match = NULL, $match_operator = 'CONTAINS') { + return 0; + } + + public function validateReferencableEntities(array $ids) { + return array(); + } + + public function validateAutocompleteInput($input, &$element, &$form_state, $form) { + return NULL; + } + + public function entityFieldQueryAlter(SelectQueryInterface $query) {} + + public function getLabel($entity) { + return ''; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/selection/base.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/selection/base.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,7 @@ + t('Simple (with optional filter by bundle)'), + 'class' => 'EntityReference_SelectionHandler_Generic', + 'weight' => -100, +); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/plugins/selection/views.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/plugins/selection/views.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,9 @@ + t('Views: Filter by an entity reference view'), + 'class' => 'EntityReference_SelectionHandler_Views', + 'weight' => 0, + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/tests/entityreference.admin.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/tests/entityreference.admin.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,114 @@ + 'Entity Reference UI', + 'description' => 'Tests for the administrative UI.', + 'group' => 'Entity Reference', + ); + } + + public function setUp() { + parent::setUp(array('field_ui', 'entity', 'ctools', 'entityreference')); + + // Create test user. + $this->admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); + $this->drupalLogin($this->admin_user); + + // Create content type, with underscores. + $type_name = strtolower($this->randomName(8)) . '_test'; + $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name)); + $this->type = $type->type; + // Store a valid URL name, with hyphens instead of underscores. + $this->hyphen_type = str_replace('_', '-', $this->type); + } + + protected function assertFieldSelectOptions($name, $expected_options) { + $xpath = $this->buildXPathQuery('//select[@name=:name]', array(':name' => $name)); + $fields = $this->xpath($xpath); + if ($fields) { + $field = $fields[0]; + $options = $this->getAllOptionsList($field); + return $this->assertIdentical($options, $expected_options); + } + else { + return $this->fail(t('Unable to find field @name', array('@name' => $name))); + } + } + + /** + * Extract all the options of a select element. + */ + protected function getAllOptionsList($element) { + $options = array(); + // Add all options items. + foreach ($element->option as $option) { + $options[] = (string) $option['value']; + } + // TODO: support optgroup. + return $options; + } + + public function testFieldAdminHandler() { + $bundle_path = 'admin/structure/types/manage/' . $this->hyphen_type; + + // First step: 'Add new field' on the 'Manage fields' page. + $this->drupalPost($bundle_path . '/fields', array( + 'fields[_add_new_field][label]' => 'Test label', + 'fields[_add_new_field][field_name]' => 'test', + 'fields[_add_new_field][type]' => 'entityreference', + 'fields[_add_new_field][widget_type]' => 'entityreference_autocomplete', + ), t('Save')); + + // Node should be selected by default. + $this->assertFieldByName('field[settings][target_type]', 'node'); + // The base handler should be selected by default. + $this->assertFieldByName('field[settings][handler]', 'base'); + + // The base handler settings should be diplayed. + $entity_type = 'node'; + $entity_info = entity_get_info($entity_type); + foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { + $this->assertFieldByName('field[settings][handler_settings][target_bundles][' . $bundle_name . ']'); + } + + // Test the sort settings. + $options = array('none', 'property', 'field'); + $this->assertFieldSelectOptions('field[settings][handler_settings][sort][type]', $options); + // Option 0: no sort. + $this->assertFieldByName('field[settings][handler_settings][sort][type]', 'none'); + $this->assertNoFieldByName('field[settings][handler_settings][sort][property]'); + $this->assertNoFieldByName('field[settings][handler_settings][sort][field]'); + $this->assertNoFieldByName('field[settings][handler_settings][sort][direction]'); + // Option 1: sort by property. + $this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'property'), 'field[settings][handler_settings][sort][type]'); + $this->assertFieldByName('field[settings][handler_settings][sort][property]', ''); + $this->assertNoFieldByName('field[settings][handler_settings][sort][field]'); + $this->assertFieldByName('field[settings][handler_settings][sort][direction]', 'ASC'); + // Option 2: sort by field. + $this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'field'), 'field[settings][handler_settings][sort][type]'); + $this->assertNoFieldByName('field[settings][handler_settings][sort][property]'); + $this->assertFieldByName('field[settings][handler_settings][sort][field]', ''); + $this->assertFieldByName('field[settings][handler_settings][sort][direction]', 'ASC'); + // Set back to no sort. + $this->drupalPostAJAX(NULL, array('field[settings][handler_settings][sort][type]' => 'none'), 'field[settings][handler_settings][sort][type]'); + + // Second step: 'Instance settings' form. + $this->drupalPost(NULL, array(), t('Save field settings')); + + // Third step: confirm. + $this->drupalPost(NULL, array(), t('Save settings')); + + // Check that the field appears in the overview form. + $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', 'Test label', t('Field was created and appears in the overview page.')); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/tests/entityreference.handlers.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/tests/entityreference.handlers.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,575 @@ + 'Entity Reference Handlers', + 'description' => 'Tests for the base handlers provided by Entity Reference.', + 'group' => 'Entity Reference', + ); + } + + public function setUp() { + parent::setUp('entityreference'); + } + + protected function assertReferencable($field, $tests, $handler_name) { + $handler = entityreference_get_selection_handler($field); + + foreach ($tests as $test) { + foreach ($test['arguments'] as $arguments) { + $result = call_user_func_array(array($handler, 'getReferencableEntities'), $arguments); + $this->assertEqual($result, $test['result'], format_string('Valid result set returned by @handler.', array('@handler' => $handler_name))); + + $result = call_user_func_array(array($handler, 'countReferencableEntities'), $arguments); + if (!empty($test['result'])) { + $bundle = key($test['result']); + $count = count($test['result'][$bundle]); + } + else { + $count = 0; + } + + $this->assertEqual($result, $count, format_string('Valid count returned by @handler.', array('@handler' => $handler_name))); + } + } + } + + /** + * Test the node-specific overrides of the entity handler. + */ + public function testNodeHandler() { + // Build a fake field instance. + $field = array( + 'translatable' => FALSE, + 'entity_types' => array(), + 'settings' => array( + 'handler' => 'base', + 'target_type' => 'node', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + 'field_name' => 'test_field', + 'type' => 'entityreference', + 'cardinality' => '1', + ); + + // Build a set of test data. + // Titles contain HTML-special characters to test escaping. + $nodes = array( + 'published1' => (object) array( + 'type' => 'article', + 'status' => 1, + 'title' => 'Node published1 (<&>)', + 'uid' => 1, + ), + 'published2' => (object) array( + 'type' => 'article', + 'status' => 1, + 'title' => 'Node published2 (<&>)', + 'uid' => 1, + ), + 'unpublished' => (object) array( + 'type' => 'article', + 'status' => 0, + 'title' => 'Node unpublished (<&>)', + 'uid' => 1, + ), + ); + + $node_labels = array(); + foreach ($nodes as $key => $node) { + node_save($node); + $node_labels[$key] = check_plain($node->title); + } + + // Test as a non-admin. + $normal_user = $this->drupalCreateUser(array('access content')); + $GLOBALS['user'] = $normal_user; + $referencable_tests = array( + array( + 'arguments' => array( + array(NULL, 'CONTAINS'), + ), + 'result' => array( + 'article' => array( + $nodes['published1']->nid => $node_labels['published1'], + $nodes['published2']->nid => $node_labels['published2'], + ), + ), + ), + array( + 'arguments' => array( + array('published1', 'CONTAINS'), + array('Published1', 'CONTAINS'), + ), + 'result' => array( + 'article' => array( + $nodes['published1']->nid => $node_labels['published1'], + ), + ), + ), + array( + 'arguments' => array( + array('published2', 'CONTAINS'), + array('Published2', 'CONTAINS'), + ), + 'result' => array( + 'article' => array( + $nodes['published2']->nid => $node_labels['published2'], + ), + ), + ), + array( + 'arguments' => array( + array('invalid node', 'CONTAINS'), + ), + 'result' => array(), + ), + array( + 'arguments' => array( + array('Node unpublished', 'CONTAINS'), + ), + 'result' => array(), + ), + ); + $this->assertReferencable($field, $referencable_tests, 'Node handler'); + + // Test as an admin. + $admin_user = $this->drupalCreateUser(array('access content', 'bypass node access')); + $GLOBALS['user'] = $admin_user; + $referencable_tests = array( + array( + 'arguments' => array( + array(NULL, 'CONTAINS'), + ), + 'result' => array( + 'article' => array( + $nodes['published1']->nid => $node_labels['published1'], + $nodes['published2']->nid => $node_labels['published2'], + $nodes['unpublished']->nid => $node_labels['unpublished'], + ), + ), + ), + array( + 'arguments' => array( + array('Node unpublished', 'CONTAINS'), + ), + 'result' => array( + 'article' => array( + $nodes['unpublished']->nid => $node_labels['unpublished'], + ), + ), + ), + ); + $this->assertReferencable($field, $referencable_tests, 'Node handler (admin)'); + } + + /** + * Test the user-specific overrides of the entity handler. + */ + public function testUserHandler() { + // Build a fake field instance. + $field = array( + 'translatable' => FALSE, + 'entity_types' => array(), + 'settings' => array( + 'handler' => 'base', + 'target_type' => 'user', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + 'field_name' => 'test_field', + 'type' => 'entityreference', + 'cardinality' => '1', + ); + + // Build a set of test data. + $users = array( + 'anonymous' => user_load(0), + 'admin' => user_load(1), + 'non_admin' => (object) array( + 'name' => 'non_admin <&>', + 'mail' => 'non_admin@example.com', + 'roles' => array(), + 'pass' => user_password(), + 'status' => 1, + ), + 'blocked' => (object) array( + 'name' => 'blocked <&>', + 'mail' => 'blocked@example.com', + 'roles' => array(), + 'pass' => user_password(), + 'status' => 0, + ), + ); + + // The label of the anonymous user is variable_get('anonymous'). + $users['anonymous']->name = variable_get('anonymous', t('Anonymous')); + + $user_labels = array(); + foreach ($users as $key => $user) { + if (!isset($user->uid)) { + $users[$key] = $user = user_save(drupal_anonymous_user(), (array) $user); + } + $user_labels[$key] = check_plain($user->name); + } + + // Test as a non-admin. + $GLOBALS['user'] = $users['non_admin']; + $referencable_tests = array( + array( + 'arguments' => array( + array(NULL, 'CONTAINS'), + ), + 'result' => array( + 'user' => array( + $users['admin']->uid => $user_labels['admin'], + $users['non_admin']->uid => $user_labels['non_admin'], + ), + ), + ), + array( + 'arguments' => array( + array('non_admin', 'CONTAINS'), + array('NON_ADMIN', 'CONTAINS'), + ), + 'result' => array( + 'user' => array( + $users['non_admin']->uid => $user_labels['non_admin'], + ), + ), + ), + array( + 'arguments' => array( + array('invalid user', 'CONTAINS'), + ), + 'result' => array(), + ), + array( + 'arguments' => array( + array('blocked', 'CONTAINS'), + ), + 'result' => array(), + ), + ); + $this->assertReferencable($field, $referencable_tests, 'User handler'); + + $GLOBALS['user'] = $users['admin']; + $referencable_tests = array( + array( + 'arguments' => array( + array(NULL, 'CONTAINS'), + ), + 'result' => array( + 'user' => array( + $users['anonymous']->uid => $user_labels['anonymous'], + $users['admin']->uid => $user_labels['admin'], + $users['non_admin']->uid => $user_labels['non_admin'], + $users['blocked']->uid => $user_labels['blocked'], + ), + ), + ), + array( + 'arguments' => array( + array('blocked', 'CONTAINS'), + ), + 'result' => array( + 'user' => array( + $users['blocked']->uid => $user_labels['blocked'], + ), + ), + ), + array( + 'arguments' => array( + array('Anonymous', 'CONTAINS'), + array('anonymous', 'CONTAINS'), + ), + 'result' => array( + 'user' => array( + $users['anonymous']->uid => $user_labels['anonymous'], + ), + ), + ), + ); + $this->assertReferencable($field, $referencable_tests, 'User handler (admin)'); + } + + /** + * Test the comment-specific overrides of the entity handler. + */ + public function testCommentHandler() { + // Build a fake field instance. + $field = array( + 'translatable' => FALSE, + 'entity_types' => array(), + 'settings' => array( + 'handler' => 'base', + 'target_type' => 'comment', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + 'field_name' => 'test_field', + 'type' => 'entityreference', + 'cardinality' => '1', + ); + + // Build a set of test data. + $nodes = array( + 'published' => (object) array( + 'type' => 'article', + 'status' => 1, + 'title' => 'Node published', + 'uid' => 1, + ), + 'unpublished' => (object) array( + 'type' => 'article', + 'status' => 0, + 'title' => 'Node unpublished', + 'uid' => 1, + ), + ); + foreach ($nodes as $node) { + node_save($node); + } + + $comments = array( + 'published_published' => (object) array( + 'nid' => $nodes['published']->nid, + 'uid' => 1, + 'cid' => NULL, + 'pid' => 0, + 'status' => COMMENT_PUBLISHED, + 'subject' => 'Comment Published <&>', + 'hostname' => ip_address(), + 'language' => LANGUAGE_NONE, + ), + 'published_unpublished' => (object) array( + 'nid' => $nodes['published']->nid, + 'uid' => 1, + 'cid' => NULL, + 'pid' => 0, + 'status' => COMMENT_NOT_PUBLISHED, + 'subject' => 'Comment Unpublished <&>', + 'hostname' => ip_address(), + 'language' => LANGUAGE_NONE, + ), + 'unpublished_published' => (object) array( + 'nid' => $nodes['unpublished']->nid, + 'uid' => 1, + 'cid' => NULL, + 'pid' => 0, + 'status' => COMMENT_NOT_PUBLISHED, + 'subject' => 'Comment Published on Unpublished node <&>', + 'hostname' => ip_address(), + 'language' => LANGUAGE_NONE, + ), + ); + + $comment_labels = array(); + foreach ($comments as $key => $comment) { + comment_save($comment); + $comment_labels[$key] = check_plain($comment->subject); + } + + // Test as a non-admin. + $normal_user = $this->drupalCreateUser(array('access content', 'access comments')); + $GLOBALS['user'] = $normal_user; + $referencable_tests = array( + array( + 'arguments' => array( + array(NULL, 'CONTAINS'), + ), + 'result' => array( + 'comment_node_article' => array( + $comments['published_published']->cid => $comment_labels['published_published'], + ), + ), + ), + array( + 'arguments' => array( + array('Published', 'CONTAINS'), + ), + 'result' => array( + 'comment_node_article' => array( + $comments['published_published']->cid => $comment_labels['published_published'], + ), + ), + ), + array( + 'arguments' => array( + array('invalid comment', 'CONTAINS'), + ), + 'result' => array(), + ), + array( + 'arguments' => array( + array('Comment Unpublished', 'CONTAINS'), + ), + 'result' => array(), + ), + ); + $this->assertReferencable($field, $referencable_tests, 'Comment handler'); + + // Test as a comment admin. + $admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments')); + $GLOBALS['user'] = $admin_user; + $referencable_tests = array( + array( + 'arguments' => array( + array(NULL, 'CONTAINS'), + ), + 'result' => array( + 'comment_node_article' => array( + $comments['published_published']->cid => $comment_labels['published_published'], + $comments['published_unpublished']->cid => $comment_labels['published_unpublished'], + ), + ), + ), + ); + $this->assertReferencable($field, $referencable_tests, 'Comment handler (comment admin)'); + + // Test as a node and comment admin. + $admin_user = $this->drupalCreateUser(array('access content', 'access comments', 'administer comments', 'bypass node access')); + $GLOBALS['user'] = $admin_user; + $referencable_tests = array( + array( + 'arguments' => array( + array(NULL, 'CONTAINS'), + ), + 'result' => array( + 'comment_node_article' => array( + $comments['published_published']->cid => $comment_labels['published_published'], + $comments['published_unpublished']->cid => $comment_labels['published_unpublished'], + $comments['unpublished_published']->cid => $comment_labels['unpublished_published'], + ), + ), + ), + ); + $this->assertReferencable($field, $referencable_tests, 'Comment handler (comment + node admin)'); + } + + /** + * Assert sorting by field works for non-admins. + * + * Since we are sorting on a field, we need to make sure the base-table + * is added, and access-control is behaving as expected. + */ + public function testSortByField() { + // Add text field to entity, to sort by. + $field_info = array( + 'field_name' => 'field_text', + 'type' => 'text', + 'entity_types' => array('node'), + ); + field_create_field($field_info); + + $instance = array( + 'label' => 'Text Field', + 'field_name' => 'field_text', + 'entity_type' => 'node', + 'bundle' => 'article', + 'settings' => array(), + 'required' => FALSE, + ); + field_create_instance($instance); + + + // Build a fake field instance. + $field = array( + 'translatable' => FALSE, + 'entity_types' => array(), + 'settings' => array( + 'handler' => 'base', + 'target_type' => 'node', + 'handler_settings' => array( + 'target_bundles' => array(), + // Add sorting. + 'sort' => array( + 'type' => 'field', + 'field' => 'field_text:value', + 'direction' => 'DESC', + ), + ), + ), + 'field_name' => 'test_field', + 'type' => 'entityreference', + 'cardinality' => '1', + ); + + // Build a set of test data. + $nodes = array( + 'published1' => (object) array( + 'type' => 'article', + 'status' => 1, + 'title' => 'Node published1 (<&>)', + 'uid' => 1, + 'field_text' => array( + LANGUAGE_NONE => array( + array( + 'value' => 1, + ), + ), + ), + ), + 'published2' => (object) array( + 'type' => 'article', + 'status' => 1, + 'title' => 'Node published2 (<&>)', + 'uid' => 1, + 'field_text' => array( + LANGUAGE_NONE => array( + array( + 'value' => 2, + ), + ), + ), + ), + 'unpublished' => (object) array( + 'type' => 'article', + 'status' => 0, + 'title' => 'Node unpublished (<&>)', + 'uid' => 1, + 'field_text' => array( + LANGUAGE_NONE => array( + array( + 'value' => 3, + ), + ), + ), + ), + ); + + $node_labels = array(); + foreach ($nodes as $key => $node) { + node_save($node); + $node_labels[$key] = check_plain($node->title); + } + + // Test as a non-admin. + $normal_user = $this->drupalCreateUser(array('access content')); + $GLOBALS['user'] = $normal_user; + + $handler = entityreference_get_selection_handler($field); + + // Not only assert the result, but make sure the keys are sorted as + // expected. + $result = $handler->getReferencableEntities(); + $expected_result = array( + $nodes['published2']->nid => $node_labels['published2'], + $nodes['published1']->nid => $node_labels['published1'], + ); + $this->assertIdentical($result['article'], $expected_result, 'Query sorted by field returned expected values for non-admin.'); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/tests/entityreference.taxonomy.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/tests/entityreference.taxonomy.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,115 @@ + 'Entity Reference Taxonomy', + 'description' => 'Tests nodes with reference to terms as indexed.', + 'group' => 'Entity Reference', + ); + } + + public function setUp() { + parent::setUp('entityreference', 'taxonomy'); + + // Create an entity reference field. + $field = array( + 'entity_types' => array('node'), + 'settings' => array( + 'handler' => 'base', + 'target_type' => 'taxonomy_term', + 'handler_settings' => array( + 'target_bundles' => array(), + ), + ), + 'field_name' => 'field_entityreference_term', + 'type' => 'entityreference', + ); + $field = field_create_field($field); + $instance = array( + 'field_name' => 'field_entityreference_term', + 'bundle' => 'article', + 'entity_type' => 'node', + ); + + // Enable the taxonomy-index behavior. + $instance['settings']['behaviors']['taxonomy-index']['status'] = TRUE; + field_create_instance($instance); + + // Create a term reference field. + $field = array( + 'translatable' => FALSE, + 'entity_types' => array('node'), + 'settings' => array( + 'allowed_values' => array( + array( + 'vocabulary' => 'terms', + 'parent' => 0, + ), + ), + ), + 'field_name' => 'field_taxonomy_term', + 'type' => 'taxonomy_term_reference', + ); + $field = field_create_field($field); + $instance = array( + 'field_name' => 'field_taxonomy_term', + 'bundle' => 'article', + 'entity_type' => 'node', + ); + field_create_instance($instance); + + // Create a terms vocobulary. + $vocabulary = new stdClass(); + $vocabulary->name = 'Terms'; + $vocabulary->machine_name = 'terms'; + taxonomy_vocabulary_save($vocabulary); + + // Create term. + for ($i = 1; $i <= 2; $i++) { + $term = new stdClass(); + $term->name = "term $i"; + $term->vid = 1; + taxonomy_term_save($term); + } + } + + /** + * Test referencing a term using entity reference field. + */ + public function testNodeIndex() { + // Asert node insert with reference to term. + $settings = array(); + $settings['type'] = 'article'; + $settings['field_entityreference_term'][LANGUAGE_NONE][0]['target_id'] = 1; + $node = $this->drupalCreateNode($settings); + + $this->assertEqual(taxonomy_select_nodes(1), array($node->nid)); + + // Asert node update with reference to term. + node_save($node); + $this->assertEqual(taxonomy_select_nodes(1), array($node->nid)); + + // Assert node update with reference to term and taxonomy reference to + // another term. + $wrapper = entity_metadata_wrapper('node', $node); + $wrapper->field_taxonomy_term->set(2); + $wrapper->save(); + + $this->assertEqual(taxonomy_select_nodes(1), array($node->nid)); + $this->assertEqual(taxonomy_select_nodes(2), array($node->nid)); + + // Assert node update with reference to term and taxonomy reference to + // same term. + $wrapper->field_taxonomy_term->set(1); + $wrapper->save(); + $this->assertEqual(taxonomy_select_nodes(1), array($node->nid)); + + $wrapper->delete(); + $this->assertFalse(taxonomy_select_nodes(1)); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/views/entityreference.views.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/views/entityreference.views.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,136 @@ + $table_data) { + if (isset($entity_info['base table'])) { + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + + $field_name = $field['field_name'] . '_target_id'; + $parameters = array('@entity' => $entity, '!field_name' => $field['field_name']); + $data[$table_name][$field_name]['relationship'] = array( + 'handler' => 'views_handler_relationship', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('@entity entity referenced from !field_name', $parameters), + 'group' => t('Entity Reference'), + 'title' => t('Referenced Entity'), + 'help' => t('A bridge to the @entity entity that is referenced via !field_name', $parameters), + ); + } + } + + // Invoke the behaviors to allow them to change the properties. + foreach (entityreference_get_behavior_handlers($field) as $handler) { + $handler->views_data_alter($data, $field); + } + + return $data; +} + +/** + * Options callback for Views handler views_handler_filter_in_operator. + */ +function entityreference_views_handler_options_list($field_name) { + $field = field_info_field($field_name); + return entityreference_options_list($field); +} + +/** + * Implements hook_field_views_data_views_data_alter(). + * + * Views integration to provide reverse relationships on entityreference fields. + */ +function entityreference_field_views_data_views_data_alter(&$data, $field) { + foreach ($field['bundles'] as $entity_type => $bundles) { + $target_entity_info = entity_get_info($field['settings']['target_type']); + if (isset($target_entity_info['base table'])) { + $entity_info = entity_get_info($entity_type); + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + $target_entity = $target_entity_info['label']; + if ($target_entity == t('Node')) { + $target_entity = t('Content'); + } + + $pseudo_field_name = 'reverse_' . $field['field_name'] . '_' . $entity_type; + $replacements = array('@entity' => $entity, '@target_entity' => $target_entity, '!field_name' => $field['field_name']); + $data[$target_entity_info['base table']][$pseudo_field_name]['relationship'] = array( + 'handler' => 'views_handler_relationship_entity_reverse', + 'field_name' => $field['field_name'], + 'field table' => _field_sql_storage_tablename($field), + 'field field' => $field['field_name'] . '_target_id', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('@entity referencing @target_entity from !field_name', $replacements), + 'group' => t('Entity Reference'), + 'title' => t('Referencing entity'), + 'help' => t('A bridge to the @entity entity that is referencing @target_entity via !field_name', $replacements), + ); + } + } +} + +/** + * Implements hook_views_plugins(). + */ +function entityreference_views_plugins() { + $plugins = array( + 'display' => array( + 'entityreference' => array( + 'title' => t('Entity Reference'), + 'admin' => t('Entity Reference Source'), + 'help' => 'Selects referenceable entities for an entity reference field', + 'handler' => 'entityreference_plugin_display', + 'uses hook menu' => FALSE, + 'use ajax' => FALSE, + 'use pager' => FALSE, + 'accept attachments' => FALSE, + // Custom property, used with views_get_applicable_views() to retrieve + // all views with a 'Entity Reference' display. + 'entityreference display' => TRUE, + ), + ), + 'style' => array( + 'entityreference_style' => array( + 'title' => t('Entity Reference list'), + 'help' => 'Returns results as a PHP array of labels and rendered rows.', + 'handler' => 'entityreference_plugin_style', + 'theme' => 'views_view_unformatted', + 'uses row plugin' => TRUE, + 'uses fields' => TRUE, + 'uses options' => TRUE, + 'type' => 'entityreference', + 'even empty' => TRUE, + ), + ), + 'row' => array( + 'entityreference_fields' => array( + 'title' => t('Inline fields'), + 'help' => t('Displays the fields with an optional template.'), + 'handler' => 'entityreference_plugin_row_fields', + 'theme' => 'views_view_fields', + 'theme path' => drupal_get_path('module', 'views') . '/theme', + 'theme file' => 'theme.inc', + 'uses fields' => TRUE, + 'uses options' => TRUE, + 'type' => 'entityreference', + ), + ), + ); + return $plugins; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/views/entityreference_plugin_display.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/views/entityreference_plugin_display.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,118 @@ +view->render($this->display->id); + } + + function render() { + if (!empty($this->view->result) || !empty($this->view->style_plugin->definition['even empty'])) { + return $this->view->style_plugin->render($this->view->result); + } + return ''; + } + + function uses_exposed() { + return FALSE; + } + + function query() { + $options = $this->get_option('entityreference_options'); + + // Play nice with Views UI 'preview' : if the view is not executed through + // EntityReference_SelectionHandler_Views::getReferencableEntities(), + // don't alter the query. + if (empty($options)) { + return; + } + + // Make sure the id field is included in the results, and save its alias + // so that references_plugin_style can retrieve it. + $this->id_field_alias = $id_field = $this->view->query->add_field($this->view->base_table, $this->view->base_field); + if (strpos($id_field, '.') === FALSE) { + $id_field = $this->view->base_table . '.' . $this->id_field_alias; + } + + // Restrict the autocomplete options based on what's been typed already. + if (isset($options['match'])) { + $style_options = $this->get_option('style_options'); + $value = db_like($options['match']) . '%'; + if ($options['match_operator'] != 'STARTS_WITH') { + $value = '%' . $value; + } + + // Multiple search fields are OR'd together + $conditions = db_or(); + + // Build the condition using the selected search fields + foreach ($style_options['search_fields'] as $field_alias) { + if (!empty($field_alias)) { + // Get the table and field names for the checked field + $field = $this->view->query->fields[$this->view->field[$field_alias]->field_alias]; + // Add an OR condition for the field + $conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE'); + } + } + + $this->view->query->add_where(NULL, $conditions); + } + + // Add an IN condition for validation. + if (!empty($options['ids'])) { + $this->view->query->add_where(NULL, $id_field, $options['ids']); + } + + $this->view->set_items_per_page($options['limit']); + } + + /** + * Extend the default validation. + */ + function validate() { + $errors = parent::validate(); + // Verify that search fields are set up. + $style_options = $this->get_option('style_options'); + if (!isset($style_options['search_fields'])) { + $errors[] = t('Display "@display" needs a selected search fields to work properly. See the settings for the Entity Reference list format.', array('@display' => $this->display->display_title)); + } + else { + // Verify that the search fields used actually exist. + //$fields = array_keys($this->view->get_items('field')); + $fields = array_keys($this->handlers['field']); + foreach ($style_options['search_fields'] as $field_alias => $enabled) { + if ($enabled && !in_array($field_alias, $fields)) { + $errors[] = t('Display "@display" uses field %field as search field, but the field is no longer present. See the settings for the Entity Reference list format.', array('@display' => $this->display->display_title, '%field' => $field_alias)); + } + } + } + return $errors; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/views/entityreference_plugin_row_fields.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/views/entityreference_plugin_row_fields.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,36 @@ + '-'); + + return $options; + } + + /** + * Provide a form for setting options. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + // Expand the description of the 'Inline field' checkboxes. + $form['inline']['#description'] .= '
    ' . t("Note: In 'Entity Reference' displays, all fields will be displayed inline unless an explicit selection of inline fields is made here." ); + } + + function pre_render($row) { + // Force all fields to be inline by default. + if (empty($this->options['inline'])) { + $fields = $this->view->get_items('field', $this->display->id); + $this->options['inline'] = drupal_map_assoc(array_keys($fields)); + } + + return parent::pre_render($row); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference/views/entityreference_plugin_style.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference/views/entityreference_plugin_style.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ + NULL); + + return $options; + } + + // Create the options form. + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + $options = array(); + + if (isset($form['grouping'])) { + $options = $form['grouping'][0]['field']['#options']; + unset($options['']); + $form['search_fields'] = array( + '#type' => 'checkboxes', + '#title' => t('Search fields'), + '#options' => $options, + '#required' => TRUE, + '#default_value' => $this->options['search_fields'], + '#description' => t('Select the field(s) that will be searched when using the autocomplete widget.'), + '#weight' => -3, + ); + } + } + + function render() { + $options = $this->display->handler->get_option('entityreference_options'); + + // Play nice with Views UI 'preview' : if the view is not executed through + // EntityReference_SelectionHandler_Views::getReferencableEntities(), just + // display the HTML. + if (empty($options)) { + return parent::render(); + } + + // Group the rows according to the grouping field, if specified. + $sets = $this->render_grouping($this->view->result, $this->options['grouping']); + + // Grab the alias of the 'id' field added by entityreference_plugin_display. + $id_field_alias = $this->display->handler->id_field_alias; + + // @todo We don't display grouping info for now. Could be useful for select + // widget, though. + $results = array(); + $this->view->row_index = 0; + foreach ($sets as $records) { + foreach ($records as $values) { + // Sanitize html, remove line breaks and extra whitespace. + $results[$values->{$id_field_alias}] = filter_xss_admin(preg_replace('/\s\s+/', ' ', str_replace("\n", '', $this->row_plugin->render($values)))); + $this->view->row_index++; + } + } + unset($this->view->row_index); + return $results; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,42 @@ +Description +=========== +Allows the contents of an "Entity Reference" field to be pre-populated by +taking a parameter from the URL path. + +Install +======= +1. Download and enable the module. +2. Visit admin/structure/types/manage/[ENTITY-TYPE]/fields/[FIELD-NAME] +3. Enable "Entity reference prepopulate" under the instance settings. + + +Configuration +============= +Enable Entity reference prepopulate: + Check this to enable Entity reference prepopulate on this field. +Action + Using the select box choose the action to take if the entity reference + field is pre-populated. +Fallback behaviour + Select what to do if the URL path does NOT contain a parameter to + pre-populate the field. +Skip access permission + This is a fallback overide, the fallback behaviour will not be followed + for users with the specified permission. + +Usage +===== +In order to pre-populate an entity reference field you have to supply the +paramater in the URL. + +The structure is +node/add/article?[field_ref]=[id] + +Where [field_ref] is the name of the entity reference field and [id] is +the id of the entity being referenced. + +Examples: +node/add/article?field_foo=1 +node/add/page?field_bar=1,2,3 + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/entityreference_prepopulate.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/entityreference_prepopulate.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = Entity reference prepopulate +description = Prepopulate entity reference values from URL. +core = 7.x +package = Fields +dependencies[] = entityreference + +files[] = entityreference_prepopulate.test + +; Information added by drupal.org packaging script on 2013-04-22 +version = "7.x-1.3" +core = "7.x" +project = "entityreference_prepopulate" +datestamp = "1366630855" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/entityreference_prepopulate.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/entityreference_prepopulate.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,429 @@ + $value) { + $instance = $value['instance']; + if (empty($instance['settings']['behaviors']['prepopulate']['status'])) { + continue; + } + $settings = $instance['settings']['behaviors']['prepopulate']; + + if ((!empty($settings['skip_perm']) && user_access($settings['skip_perm'])) || ($id && empty($settings['action_on_edit']))) { + // User has access to skip the action, or the entity is already + // saved, but "Apply action on edit", is disabled. + continue; + } + + $field = $value['field']; + + // Store prepopulated values in the form state to make them persistent, + // in case the form is rebuilt by AJAX requests. + $field_name = $field['field_name']; + + if ($ids = entityreference_prepopulate_get_values($field, $instance)) { + $form_state['entityreference_prepopulate'][$instance['entity_type']][$instance['bundle']][$field_name] = $ids; + } + + if ($ids || ($id && !empty($settings['action_on_edit']))) { + // New entity with prepopualte values, or an existing entity, + // we might need to disable/ hide the group-audience field. + if ($settings['action'] == 'disable') { + $form[$field_name][$lang]['#disabled'] = TRUE; + } + elseif ($settings['action'] == 'hide') { + // We don't hide the field via hook_field_access(), as the + // default value won't be set. + $form[$field_name]['#access'] = FALSE; + } + } + elseif (in_array($settings['fallback'], array('form_error', 'redirect'))) { + $message = t('Field @label must be populated via URL.', array('@label' => $instance['label'])); + if ($settings['fallback'] == 'form_error') { + form_error($form, $message); + } + elseif ($settings['fallback'] == 'redirect') { + drupal_set_message($message, 'notice'); + drupal_goto(); + } + } + } + } +} + +/** + * Implements hook_field_access(). + */ +function entityreference_prepopulate_field_access($op, $field, $entity_type, $entity, $account) { + if ($op != 'edit' || $field['type'] != 'entityreference') { + return; + } + + if (empty($entity)) { + // $entity might be NULL, so return early. + // @see field_access(). + return; + } + + list($id,,$bundle) = entity_extract_ids($entity_type, $entity); + if ($id) { + // Entity is already saved. + return; + } + + $instance = field_info_instance($entity_type, $field['field_name'], $bundle); + if (empty($instance['settings']['behaviors']['prepopulate']['status'])) { + return; + } + + $settings = $instance['settings']['behaviors']['prepopulate']; + if (!empty($settings['skip_perm']) && user_access($settings['skip_perm'])) { + return; + } + $ids = entityreference_prepopulate_get_values($field, $instance); + + if (!$ids && $settings['fallback'] == 'hide') { + return FALSE; + } +} + +/** + * Field default value callback. + * + * Set the default from the URL context. This works even if the widget is + * not shown, e.g. due to restricted field access. + * + * @todo Check field cardinality. + */ +function entityreference_prepopulate_field_default_value($entity_type, $entity, $field, $instance, $langcode) { + $items = array(); + if ($ids = entityreference_prepopulate_get_values($field, $instance)) { + $items = array(); + foreach ($ids as $id) { + $items[] = array('target_id' => $id); + } + } + return $items; +} + +/** + * Wrapper function to get context (e.g. from URL or OG-context). + * + * @param $entity_type + * The entity type the entity. + * @param $entity + * The entity object that is being checked. + * @param $field + * The field info array. + * @param $instance + * The instance info array. + * @param $validate + * Determine if access validation should be performed. Defaults to TRUE. + * + * @return + * Array of IDs a user may view. + */ +function entityreference_prepopulate_get_values($field, $instance, $validate = TRUE) { + if (!$instance['settings']['behaviors']['prepopulate']['status']) { + // Do nothing when prepopulate is disabled for this field. + return; + } + + $field_name = $field['field_name']; + + $cache = &drupal_static(__FUNCTION__, array()); + $identifier = array( + $instance['entity_type'], + $instance['bundle'], + $field_name, + $validate, + ); + + if (module_exists('og') && og_is_group_audience_field($field_name)) { + if (empty($instance['field_mode'])) { + // Group audience field, but no field-mode provided. + // So we iterate over the "default" and possibly "admin" field-modes, + // and return those values together. + $ids = array(); + $field_modes = !user_access('administer group') ? array('default') : array('default', 'admin'); + foreach ($field_modes as $field_mode) { + $instance['field_mode'] = $field_mode; + if ($og_ids = entityreference_prepopulate_get_values($field, $instance)) { + $ids = array_merge($ids, $og_ids); + } + } + + // Return the values. + return $ids; + } + + $identifier[] = $instance['field_mode']; + } + + $identifier = implode(':', $identifier); + + if (isset($cache[$identifier])) { + return $cache[$identifier]; + } + + $cache[$identifier] = $ids = array(); + + // Check if we have cached values. + if (!$ids) { + $ids = entityreference_prepopulate_get_values_from_cache($field, $instance); + } + + // Check if we have OG-context integration. + if (!$ids) { + $ids = entityreference_prepopulate_get_values_from_og_context($field, $instance); + } + + // Check if there are values in the URL. + if (!$ids) { + $ids = entityreference_prepopulate_get_values_from_url($field, $instance); + } + + if (!$ids || !$validate) { + // No IDs found, or no validation is needed. + $cache[$identifier] = $ids; + return $ids; + } + + $handler = entityreference_get_selection_handler($field, $instance); + if (!$ids = $handler->validateReferencableEntities($ids)) { + $cache[$identifier] = FALSE; + return; + } + + // Check access to the provided entities. + $target_type = $field['settings']['target_type']; + entity_load($target_type, $ids); + foreach ($ids as $delta => $id) { + $entity = entity_load_single($target_type, $id); + if (!$entity || !entity_access('view', $target_type, $entity)) { + unset($ids[$delta]); + } + } + + $cache[$identifier] = $ids; + return $ids; +} + +/** + * Get the values from the cached form. + * + * @param $field + * The field info array. + * @param $instance + * The instance info array. + * + * @see + * entityreference_prepopulate_get_values() + */ +function entityreference_prepopulate_get_values_from_cache($field, $instance) { + // Try to get the form out of cache. + if (!$form_build_id = isset($_GET['form_build_id']) ? $_GET['form_build_id'] : isset($_POST['form_build_id']) ? $_POST['form_build_id'] : NULL) { + return; + } + + $field_name = $field['field_name']; + + $form_state = array(); + form_get_cache($form_build_id, $form_state); + + // If successful, get the value from the form_state. + return isset($form_state['entityreference_prepopulate'][$instance['entity_type']][$instance['bundle']][$field_name]) ? $form_state['entityreference_prepopulate'][$instance['entity_type']][$instance['bundle']][$field_name] : FALSE; +} + +/** + * Get values for prepopulating fields via URL. + * + * @param $field + * The field info array. + * @param $instance + * The instance info array. + * + * @see + * entityreference_prepopulate_get_values() + */ +function entityreference_prepopulate_get_values_from_url($field, $instance) { + $field_name = $field['field_name']; + if (!empty($_GET[$field_name]) && is_string($_GET[$field_name])) { + return explode(',', $_GET[$field_name]); + } +} + + +/** + * Get values for prepopulating fields OG-context. + * + * @param $field + * The field info array. + * @param $instance + * The instance info array. + * + * @see + * entityreference_prepopulate_get_values() + */ +function entityreference_prepopulate_get_values_from_og_context($field, $instance) { + $field_name = $field['field_name']; + + if (!module_exists('og_context') || !og_is_group_audience_field($field_name) || !$og_context = og_context()) { + return; + } + + if ($og_context['group_type'] != $field['settings']['target_type']) { + // Context is of invalid group-type. + return; + } + + return array($og_context['gid']); +} + + +/** + * Return a form element with crafted links to create nodes for a group. + * + * @param $entity_type + * The entity type of the referenced entity. + * @param $entity_id + * The entity ID of the referenced entity. + * @param $destination + * Optional; The destination after a node is created. Defaults to the + * destination passed in the URL if exists, otherwise back to the current + * page. + * @param $types + * Optional; An array of type names. Restrict the created links to the given + * types. + */ +function entityreference_prepopulate_create_node_links($entity_type, $entity_id, $field_name, $destination = NULL, $types = NULL) { + $wrapper = entity_metadata_wrapper($entity_type, $entity_id); + $field = field_info_field($field_name); + + $entity = entity_load_single($entity_type, $entity_id); + list(,, $bundle) = entity_extract_ids($entity_type, $entity); + + $types = isset($types) ? $types : array_keys(node_type_get_types()); + $names = array(); + foreach ($types as $type_name) { + if ($field['settings']['target_type'] != $entity_type) { + // The entity type isn't referenced by the field. + continue; + } + + if (!empty($field['settings']['handler_settings']['target_bundles']) && !in_array($bundle, $field['settings']['handler_settings']['target_bundles'])) { + // The entity bundle isn't referenced by the field. + continue; + } + + $instance = field_info_instance('node', $field_name, $type_name); + if (empty($instance['settings']['behaviors']['prepopulate']['status'])) { + // The field doesn't exist on the node type, or doesn't have prepopulate + // enabled. + continue; + } + + if (!node_access('create', $type_name)) { + continue; + } + + $names[$type_name] = node_type_get_name($type_name); + } + + if (empty($names)) { + return; + } + + // Sort names. + asort($names); + + // Build links. + $options = array( + 'query' => array($field_name => $entity_id) + drupal_get_destination(), + ); + + $items = array(); + foreach ($names as $type => $name) { + $items[] = array('data' => l($name, 'node/add/' . str_replace('_', '-', $type), $options)); + } + + $element = array(); + $element['entityreference_prepopulate'] = array( + '#theme' => 'item_list', + '#items' => $items, + ); + + return $element; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/entityreference_prepopulate.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/entityreference_prepopulate.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,67 @@ + 'OG-context integration', + 'description' => 'Test the OG-context integration, which allows prepopulating by the group context.', + 'group' => 'Entity reference prepopulate', + 'dependencies' => array('og'), + ); + } + + function setUp() { + parent::setUp('og_context', 'entityreference_prepopulate_test'); + + $this->user1 = $this->drupalCreateUser(); + $this->user2 = $this->drupalCreateUser(array('bypass node access', 'administer group')); + + $type = $this->drupalCreateContentType(); + $this->group_type = $type->type; + og_create_field(OG_GROUP_FIELD, 'node', $this->group_type); + + $type = $this->drupalCreateContentType(); + $this->group_content_type = $type->type; + + $og_field = og_fields_info(OG_AUDIENCE_FIELD); + // Enable the prepopulate behavior. + $og_field['instance']['settings']['behaviors']['prepopulate'] = array( + 'status' => TRUE, + 'action' => 'none', + 'fallback' => 'none', + 'skip_perm' => FALSE, + 'og_context' => TRUE, + ); + og_create_field(OG_AUDIENCE_FIELD, 'node', $this->group_content_type, $og_field); + } + + /** + * Test the OG-context integration. + */ + function testPrepopulate() { + $settings = array( + 'type' => $this->group_type, + 'uid' => $this->user1->uid, + ); + $settings[OG_GROUP_FIELD][LANGUAGE_NONE][0]['value'] = 1; + $group1 = $this->drupalCreateNode($settings); + + og_group('node', $group1, array('entity_type' => 'user', 'entity' => $this->user2)); + + $this->drupalLogin($this->user2); + $this->drupalGet('node/add/' . str_replace('_', '-', $this->group_content_type)); + $this->assertNoOptionSelected('edit-og-group-ref-und-0-default', 1, 'Group-audience fields is not selected.'); + + // Set the OG-context(); + // See entityreference_prepopulate_init(). + $options = array( + 'query' => array( + 'gid' => $group1->nid, + ), + ); + + $this->drupalGet('node/add/' . str_replace('_', '-', $this->group_content_type), $options); + $this->assertOptionSelected('edit-og-group-ref-und-0-default', 1, 'Group-audience fields is selected.'); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/plugins/behavior/EntityReferencePrepopulateInstanceBehavior.class.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/plugins/behavior/EntityReferencePrepopulateInstanceBehavior.class.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,78 @@ + 'select', + '#title' => t('Action'), + '#options' => array( + 'none' => t('Do nothing'), + 'hide' => t('Hide field'), + 'disable' => t('Disable field'), + ), + '#description' => t('Action to take when prepopulating field with values via URL.'), + ); + $form['action_on_edit'] = array( + '#type' => 'checkbox', + '#title' => t('Apply action on edit'), + '#description' => t('Apply action when editing an existing entity.'), + '#states' => array( + 'invisible' => array( + ':input[name="instance[settings][behaviors][prepopulate][action]"]' => array('value' => 'none'), + ), + ), + ); + $form['fallback'] = array( + '#type' => 'select', + '#title' => t('Fallback behaviour'), + '#description' => t('Determine what should happen if no values are provided via URL.'), + '#options' => array( + 'none' => t('Do nothing'), + 'hide' => t('Hide field'), + 'form_error' => t('Set form error'), + 'redirect' => t('Redirect'), + ), + ); + + // Get list of permissions. + $perms = array(); + $perms[0] = t('- None -'); + foreach (module_list(FALSE, FALSE, TRUE) as $module) { + // By keeping them keyed by module we can use optgroups with the + // 'select' type. + if ($permissions = module_invoke($module, 'permission')) { + foreach ($permissions as $id => $permission) { + $perms[$module][$id] = strip_tags($permission['title']); + } + } + } + + $form['skip_perm'] = array( + '#type' => 'select', + '#title' => t('Skip access permission'), + '#description' => t('Set a permission that will not be affected by the fallback behavior.'), + '#options' => $perms, + ); + + $description = t('Determine if values that should be prepopulated should "listen" to the OG-context.'); + + if ($disabled = !module_exists('og_context') || !og_is_group_audience_field($field_name)) { + $description .= '
    ' . t('Organic groups integration: Enable OG-context and set "Entity selection mode" to "Organic groups" to enable this selection.'); + } + + $form['og_context'] = array( + '#type' => 'checkbox', + '#title' => t('OG context'), + '#description' => $description, + '#disabled' => $disabled, + ); + + return $form; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/plugins/behavior/prepopulate.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/plugins/behavior/prepopulate.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,8 @@ + t('Entity reference prepopulate'), + 'description' => t('Prepopulate entity reference values from URL.'), + 'class' => 'EntityReferencePrepopulateInstanceBehavior', + 'behavior type' => 'instance', +); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/plugins/content_types/node_prepopulate.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/plugins/content_types/node_prepopulate.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,100 @@ + t('Content prepopulate links'), + 'description' => t('Crafted links to create content (nodes) for an entity reference field.'), + 'required context' => new ctools_context_required(t('Node'), 'node'), + 'category' => t('Entity reference'), + 'defaults' => array( + 'types' => array(), + 'field_name' => '', + ), +); + +/** + * Render callback. + */ +function entityreference_prepopulate_node_prepopulate_content_type_render($subtype, $conf, $args, $context) { + if (empty($context->data)) { + return; + } + + $node = $context->data; + $links = entityreference_prepopulate_create_node_links('node', $node->nid, $conf['field_name'], NULL, !empty($conf['types']) ? $conf['types'] : NULL); + if (!$links) { + return; + } + + $module = 'entityreference_prepopulate'; + $block = new stdClass(); + $block->module = $module; + $block->title = t('Content create links'); + $block->content = $links; + return $block; +} + +/** + * Edit form. + */ +function entityreference_prepopulate_node_prepopulate_content_type_edit_form($form, &$form_state) { + $conf = $form_state['conf']; + + $options = array(); + $bundles = array(); + // Use CTools to get the best matching field name. + ctools_include('fields'); + foreach (field_info_instances('node') as $node_type => $instances) { + foreach ($instances as $field_name => $instance) { + if (empty($instance['settings']['behaviors']['prepopulate']['status'])) { + continue; + } + + $field = field_info_field($field_name); + + if ($field['settings']['target_type'] != 'node') { + // Field doesn't reference a node. + continue; + } + + $bundles[] = $instance['bundle']; + if (empty($options[$field_name])) { + $options[$field_name] = ctools_field_label($field_name) . ' (' . $field_name . ')'; + } + } + } + + $form['field_name'] = array( + '#title' => t('Field name'), + '#type' => 'select', + '#options' => $options, + '#default_value' => $conf['field_name'], + '#description' => $options ? t('The entity reference field to prepopulate.') : t('There are no entity reference fields, with prepopulate enabled'), + '#required' => TRUE, + ); + + $options = array(); + foreach (node_type_get_types() as $type) { + if (in_array($type->type, $bundles)) { + $options[$type->type] = check_plain($type->name); + } + } + $form['types'] = array( + '#title' => t('Restrict to content types'), + '#type' => 'checkboxes', + '#options' => $options, + '#default_value' => $conf['types'], + '#description' => t('If left empty, all possible content types are shown.'), + ); + return $form; +} + +/** + * Edit form submit callback. + */ +function entityreference_prepopulate_node_prepopulate_content_type_edit_form_submit($form, &$form_state) { + $form_state['conf']['field_name'] = $form_state['values']['field_name']; + $form_state['conf']['types'] = array_filter($form_state['values']['types']); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/tests/entityreference_prepopulate_test.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/tests/entityreference_prepopulate_test.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +name = Entity reference prepopulate test module +description = Functionality to assist Entity reference prepopulate testing. +core = 7.x +dependencies[] = entityreference_prepopulate +dependencies[] = og_context +hidden = TRUE + +; Information added by drupal.org packaging script on 2013-04-22 +version = "7.x-1.3" +core = "7.x" +project = "entityreference_prepopulate" +datestamp = "1366630855" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/entityreference_prepopulate/tests/entityreference_prepopulate_test.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/entityreference_prepopulate/tests/entityreference_prepopulate_test.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,18 @@ + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,235 @@ + +Current state of Features for Drupal 7 +-------------------------------------- +Work on Features for D7 is currently aimed at getting to a point where Features +can be used on a new install of Drupal 7 with features that were created on D7. +Once this has been achieved, we will begin working on supporting D6 features as +well as possibly supporting upgrades & migrations between legacy components and +new equivalents (e.g. CCK to fields, imagecache to core image styles). + +### Working components + +- ctools +- dependencies +- field +- filter +- image +- menu_custom +- menu_links +- node +- taxonomy +- user_permission +- user_role +- views + +### Has changes to export format between D6 and D7 + +(@TODO legacy export compatibility) + +- filter +- taxonomy + +### Requires upgrade/migration path + +- imagecache > image +- content > field + +Note on the "Generate Feature" capability +----------------------------------------- +Features 7.x-2.x includes the ability to "Generate a feature" which saves it +to the server disk. This can be a time-saving task in development. It requires +the webserver to be able to write to the very code running the site and is +not recommended for any environment other than a firewalled-off, local +development environment (e.g. a person working alone on their laptop). + +Features 1.x for Drupal 7.x +--------------------------- +The features module enables the capture and management of features in Drupal. A +feature is a collection of Drupal entities which taken together satisfy a +certain use-case. + +Features provides a UI and API for taking different site building components +from modules with exportables and bundling them together in a single feature +module. A feature module is like any other Drupal module except that it declares +its components (e.g. views, contexts, CCK fields, etc.) in its `.info` file so +that it can be checked, updated, or reverted programmatically. + +Examples of features might be: + +- A blog +- A pressroom +- An image gallery +- An e-commerce t-shirt store + + +Installation +------------ +Features can be installed like any other Drupal module -- place it in the +modules directory for your site and enable it on the `admin/build/modules` page. +To take full advantage of some of the workflow benefits provided by Features, +you should install [Drush][1]. + +If you plan on creating or working with very large features (greater than 1000 +items), you may need to increase PHP's max_input_vars configuration directive. +For example, adding the following line to your .htaccess file will increase the +max_input_vars directive to 3000: + +php_value max_input_vars 3000 + +If you are using Suhosin, increasing suhosin.get.max_vars, +suhosin.post.max_vars, and suhosin.request.max_vars may also be necessary. + + +Basic usage +----------- +Features is geared toward usage by developers and site builders. It +is not intended to be used by the general audience of your Drupal site. +Features provides tools for accomplishing two important tasks: + +### Task 1: Export features + +You can build features in Drupal by using site building tools that are supported +(see a short list under the *Compatibility* section). + +Once you've built and configured functionality on a site, you can export it into +a feature module by using the feature create page at +`admin/structure/features/create`. + + +### Task 2: Manage features + +The features module also provides a way to manage features through a more +targeted interface than `admin/modules`. The interface at +`admin/structure/features` shows you only feature modules, and will also inform you +if any of their components have been overridden. If this is the case, you can +also re-create features to bring the module code up to date with any changes +that have occurred in the database. + + +Including custom code and adding to your feature +------------------------------------------------ +Once you've exported your feature you will see that you have several files: + + myfeature.info + myfeature.module + myfeature.[*].inc + +You can add custom code (e.g. custom hook implementations, other functionality, +etc.) to your feature in `myfeature.module` as you would with any other module. +Do not change or add to any of the features `.inc` files unless you know what +you are doing. These files are written to by features on updates so any custom +changes may be overwritten. + + +Using Features to manage development +------------------------------------ +Because Features provides a centralized way to manage exportable components and +write them to code it can be used during development in conjunction with a +version control like SVN or git as a way to manage changes between development, +staging and production sites. An example workflow for a developer using Features +is to: + +1. Make configuration changes to a feature on her local development site. +2. Update her local feature codebase using `drush features-update`. +3. Commit those changes using `svn commit`. +4. Roll out her changes to the development site codebase by running `svn update` + on the server. Other collaborating developers can also get her changes with + `svn update`. +5. Reverting any configuration on the staging site to match the updated codebase +by running `drush features-revert`. +6. Rinse, repeat. + +Features also provides integration with the [Diff][3] module if enabled to show +differences between configuration in the database and that in code. For site +builders interested in using Features for development, enabling the diff module +and reading `API.txt` for more details on the inner workings of Features is +highly recommended. + + +Drush usage +----------- +(requires Drush v4.5 or higher) + +Features provides several useful drush commands: + +- `drush features` + + List all the available features on your site and their status. + +- `drush features-export [feature name] [component list]` + + Write a new feature in code containing the components listed. + If called with no arguments, display a list of available components. + If called with one argument, take the argument as a component name and + attempt to create a feature with the same name. + + The option '--destination=foo' may be used to specify the path (from Drupal + root) where the feature should be created. The default destination is + 'sites/all/modules'. + +- `drush features-update [feature name]` + + Update the code of an existing feature to include any overrides/changes in + your database (e.g. a new view). + +- `drush features-revert [feature name]` + + Revert the components of a feature in your site's database to the state + described in your feature module's defaults. + +- `drush features-diff [feature name]` + + Show a diff between a feature's database components and those in code. + Requires the Diff module. + +Additional commands and options can be found using `drush help`. + + +Compatibility +------------- +Features provides integration for the following exportables: + +- CTools export API implementers (Context, Spaces, Boxes, Strongarm, Page + Manager) +- ImageCache +- Views +- [Other contributed modules][2] + +Features also provides faux-exportable functionality for the following Drupal +core and contrib components: + +- Fields +- Content types +- Input filters +- User roles/permissions +- Custom menus and menu links * +- Taxonomy vocabularies + +* Currently in development. + + +Security Concerns +----------------- +If you are using Features to export Roles and also use those Roles in other +exportable code (like Views filters) you can wind up with an unintended +security hole. When you import your Feature, if the Roles do not get created +with the exact same Role IDs then your Views filters (or other component) will +be referencing a different Role than you intended. + + +For developers +-------------- +Please read `API.txt` for more information about the concepts and integration +points in the Features module. + + +Maintainers +----------- +- febbraro (Frank Febbraro) +- hefox (Fox) +- mpotter (Mike Potter) +- timplunkett (Tim Plunkett) + + +[1]: http://drupal.org/project/drush +[2]: (http://drupal.org/taxonomy/term/11478) diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.admin.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1483 @@ + 'fieldset', + '#title' => t('Show components on create/edit feature form.'), + '#description' => t('Components with no options will not be shown no matter the setting below. Disabled components cannot be used with admin form.') + ); + foreach ($components as $compontent => $info) { + if (empty($info['feature_source']) && empty($info['features_source'])) { + continue; + } + $form['show_components']['features_admin_show_component_' . $compontent] = array( + '#title' => t('@name (@machine)', array('@name' => $info['name'], '@machine' => $compontent)), + '#type' => 'checkbox', + '#default_value' => variable_get('features_admin_show_component_' . $compontent, TRUE), + ); + if ($compontent == 'menu_links' && ($menus = menu_get_menus())) { + $form['show_components']['features_admin_menu_links'] = array( + '#title' => t('Advanced Menu Link Settings'), + '#type' => 'fieldset', + '#collapsed' => TRUE, + '#collapsible' => TRUE, + '#states' => array( + 'invisible' => array( + 'input[name="features_admin_show_component_menu_links"]' => array('checked' => FALSE), + ), + ), + ); + $form['show_components']['features_admin_menu_links']['features_admin_menu_links_menus'] = array( + '#title' => t('Allowed menus for menu links'), + '#type' => 'checkboxes', + '#options' => array_map('check_plain', $menus), + '#default_value' => variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus())), + ); + } + } + + $form['features_rebuild_on_flush'] = array( + '#type' => 'checkbox', + '#title' => t('Rebuild features on cache clear'), + '#default_value' => variable_get('features_rebuild_on_flush', TRUE), + '#description' => t('If you have a large site with many features, you may experience lag on full cache clear. If disabled, features will rebuild only when viewing the features list or saving the modules list.'), + ); + + return system_settings_form($form); +} + +/** + * Form constructor for features export form. + * + * Acts as a router based on the form_state. + * + * @param object|null $feature + * The feature object, if available. NULL by default. + * + * @see features_export_build_form_submit() + * @ingroup forms + */ +function features_export_form($form, $form_state, $feature = NULL) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + + $feature_name = !empty($feature->name) ? $feature->name : ''; + $form = array( + '#attributes' => array('class' => array('features-export-form')), + '#feature' => isset($feature) ? $feature : NULL, + ); + $form['info'] = array( + '#type' => 'fieldset', + '#title' => t('General Information'), + '#tree' => FALSE, + '#weight' => 2, + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#prefix' => "
    ", + '#suffix' => '
    ', + ); + $form['info']['name'] = array( + '#title' => t('Name'), + '#description' => t('Example: Image gallery') . ' (' . t('Do not begin name with numbers.') . ')', + '#type' => 'textfield', + '#default_value' => !empty($feature->info['name']) ? $feature->info['name'] : '', + '#attributes' => array('class' => array('feature-name')), + ); + $form['info']['module_name'] = array( + '#type' => 'textfield', + '#title' => t('Machine-readable name'), + '#description' => t('Example: image_gallery') . '
    ' . t('May only contain lowercase letters, numbers and underscores. Try to avoid conflicts with the names of existing Drupal projects.'), + '#required' => TRUE, + '#default_value' => $feature_name, + '#attributes' => array('class' => array('feature-module-name')), + '#element_validate' => array('features_export_form_validate_field'), + ); + // If recreating this feature, disable machine name field and blank out + // js-attachment classes to ensure the machine name cannot be changed. + if (isset($feature)) { + $form['info']['module_name']['#value'] = $feature_name; + $form['info']['module_name']['#disabled'] = TRUE; + $form['info']['name']['#attributes'] = array(); + } + $form['info']['description'] = array( + '#title' => t('Description'), + '#description' => t('Provide a short description of what users should expect when they enable your feature.'), + '#type' => 'textfield', + '#default_value' => !empty($feature->info['description']) ? $feature->info['description'] : '', + ); + $form['info']['package'] = array( + '#title' => t('Package'), + '#description' => t('Organize your features in groups.'), + '#type' => 'textfield', + '#autocomplete_path' => 'features/autocomplete/packages', + '#default_value' => !empty($feature->info['package']) ? $feature->info['package'] : 'Features', + ); + $form['info']['version'] = array( + '#title' => t('Version'), + '#description' => t('Examples: 7.x-1.0, 7.x-1.0-beta1'), + '#type' => 'textfield', + '#required' => FALSE, + '#default_value' => !empty($feature->info['version']) ? $feature->info['version'] : '', + '#size' => 30, + '#element_validate' => array('features_export_form_validate_field'), + ); + $form['advanced'] = array( + '#type' => 'fieldset', + '#title' => t('Advanced Options'), + '#tree' => FALSE, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#weight' => 10, + '#prefix' => "
    ", + '#suffix' => '
    ', + ); + $form['advanced']['project_status_url'] = array( + '#title' => t('URL of update XML'), + '#description' => t('URL of Feature Server. For Example: http://mywebsite.com/fserver'), + '#type' => 'textfield', + '#required' => FALSE, + '#default_value' => !empty($feature->info['project status url']) ? $feature->info['project status url'] : '', + '#element_validate' => array('features_export_form_validate_field'), + ); + $directory = (!empty($feature->filename)) ? dirname($feature->filename) : 'sites/all/modules/features'; + if (!empty($feature_name) && substr_compare($directory, $feature_name, strlen($directory)-strlen($feature_name), strlen($feature_name)) === 0) { + // if path ends with module_name, strip it + $directory = dirname($directory); + } + if (user_access('generate features')) { + $form['advanced']['generate_path'] = array( + '#title' => t('Path to Generate feature module'), + '#description' => t('File path for feature module. For Example: sites/all/modules/features or /tmp. ' . + t('Leave blank for @path', array('@path' => $directory))), + '#type' => 'textfield', + '#required' => FALSE, + '#default_value' => !empty($feature->info['project path']) ? $feature->info['project path'] : '', + ); + $form['advanced']['generate'] = array( + '#type' => 'submit', + '#value' => t('Generate feature'), + '#submit' => array('features_export_build_form_submit'), + ); + } + // build the Component Listing panel on the right + _features_export_form_components($form, $form_state); + + $form['advanced']['info-preview'] = array( + '#type' => 'button', + '#value' => t('Preview .info file'), + '#ajax' => array( + 'callback' => 'features_info_file_preview', + 'wrapper' => 'features-export-wrapper', + ), + ); + //Info dialog + $form['advanced']['info-file'] = array( + '#prefix' => '
    ', + 'text' => array( + '#type' => 'textarea', + '#default_value' => '', + '#resizable' => FALSE, + ), + '#suffix' => '
    ', + ); + + $form['buttons'] = array( + '#theme' => 'features_form_buttons', + '#tree' => FALSE, + '#weight' => 99, + '#prefix' => "
    ", + '#suffix' => '
    ', + ); + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Download feature'), + '#weight' => 10, + '#submit' => array('features_export_build_form_submit'), + ); + + $form['#attached']['library'][] = array('system', 'ui.dialog'); + + return $form; +} + +/** + * Return the render array elements for the Components selection on the Export form + * @param array $feature - feature associative array + * @param array $components - array of components in feature + */ +function _features_export_form_components(&$form, &$form_state) { + global $features_ignore_conflicts; + drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); + drupal_add_js(drupal_get_path('module', 'features') . '/features.js'); + + $feature = $form['#feature']; + + // keep the allow_conflict variable around in the session + if (isset($form_state['values']['features_allow_conflicts'])) { + $_SESSION['features_allow_conflicts'] = $form_state['values']['features_allow_conflicts']; + $features_ignore_conflicts = $_SESSION['features_allow_conflicts']; + } + + $form['export'] = array( + '#type' => 'fieldset', + '#title' => t('Components'), + '#description' => t('Expand each component section and select which items should be included in this feature export.'), + '#tree' => FALSE, + '#prefix' => "
    ", + '#suffix' => '
    ', + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#weight' => 1, + ); + + // filter field used in javascript, so javascript will unhide it + $form['export']['features_filter_wrapper'] = array( + '#type' => 'fieldset', + '#title' => t('Filters'), + '#tree' => FALSE, + '#prefix' => "
    ", + '#suffix' => '
    ', + '#collapsible' => FALSE, + '#collapsed' => FALSE, + '#weight' => -10, + ); + $form['export']['features_filter_wrapper']['features_filter'] = array( + '#type' => 'textfield', + '#title' => t('Search'), + '#hidden' => TRUE, + '#default_value' => '', + '#suffix' => "". t('Clear') ."", + ); + $form['export']['features_filter_wrapper']['checkall'] = array( + '#type' => 'checkbox', + '#default_value' => FALSE, + '#hidden' => TRUE, + '#title' => t('Select all'), + '#attributes' => array( + 'class' => array('features-checkall'), + ) + ); + + $form['advanced']['features_autodetect_wrapper'] = array( + '#type' => 'fieldset', + '#tree' => FALSE, + '#prefix' => "
    ", + '#suffix' => '
    ', + '#collapsible' => FALSE, + '#collapsed' => FALSE, + ); + $form['advanced']['features_autodetect_wrapper']['autodetect'] = array( + '#title' => t('Add auto-detected dependencies'), + '#type' => 'checkbox', + '#default_value' => !empty($feature->info['no autodetect']) ? FALSE : TRUE, + ); + + // this refresh button will rebuild the form. + // this button is hidden by javascript since it is only needed when + // javascript is not available + $form['advanced']['features_autodetect_wrapper']['features_refresh'] = array( + '#type' => 'submit', + '#value' => t('Refresh'), + '#name' => 'features-refresh', + '#attributes' => array( + 'title' => t("Refresh the list of auto-detected items."), + 'class' => array('features-refresh-button'), + ), + '#submit' => array('features_export_form_rebuild'), + '#prefix' => "
    ", + '#suffix' => "
    ", + '#ajax' => array( + 'callback' => 'features_export_form_ajax', + 'wrapper' => 'features-export-wrapper', + ), + ); + + // generate the export array for the current feature and user selections + $export = _features_export_build($feature, $form_state); + + $form['advanced']['features_allow_conflicts'] = array( + '#title' => t('Allow conflicts to be added'), + '#type' => 'checkbox', + '#default_value' => $features_ignore_conflicts, + '#ajax' => array( + 'callback' => 'features_export_form_ajax', + 'wrapper' => 'features-export-wrapper', + ), + ); + + if (isset($form_state['values']['op']) && ($form_state['values']['op'] == $form_state['values']['info-preview'])) { + // handle clicking Preview button + module_load_include('inc', 'features', 'features.export'); + + $feature_export = _features_export_generate($export, $form_state, $feature); + $feature_export = features_export_prepare($feature_export, $feature->name, TRUE); + $info = features_export_info($feature_export); + + drupal_add_js(array('features' => array('info' => $info)), 'setting'); + } + + // determine any components that are deprecated + $deprecated = features_get_deprecated($export['components']); + + $sections = array('included', 'detected', 'added'); + foreach ($export['components'] as $component => $component_info) { + if (!variable_get('features_admin_show_component_' . $component, TRUE)) { + continue; + } + $label = (isset($component_info['name']) ? + $component_info['name'] . " (" . check_plain($component) . ")" : check_plain($component)); + + $count = 0; + foreach ($sections as $section) { + $count += count($component_info['options'][$section]); + } + $extra_class = ($count == 0) ? 'features-export-empty' : ''; + $component_name = str_replace('_', '-', check_plain($component)); + + if ($count + count($component_info['options']['sources']) > 0) { + + if (!empty($deprecated[$component])) { + // only show deprecated component if it has some exports + if (!empty($component_info['options']['included'])) { + $form['export'][$component] = array( + '#markup' => '', + '#tree' => TRUE, + ); + + $form['export'][$component]['deprecated'] = array( + '#type' => 'fieldset', + '#title' => $label . " (" . t('DEPRECATED') . ")", + '#tree' => TRUE, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#attributes' => array('class' => array('features-export-component')), + ); + $list = ' '; + foreach ($component_info['options']['included'] as $key) { + $list .= "$key"; + } + $form['export'][$component]['deprecated']['selected'] = array( + '#prefix' => "
    ", + '#markup' => $list, + '#suffix' => "
    ", + ); + } + } + else { + $form['export'][$component] = array( + '#markup' => '', + '#tree' => TRUE, + ); + + $form['export'][$component]['sources'] = array( + '#type' => 'fieldset', + '#title' => $label, + '#tree' => TRUE, + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#attributes' => array('class' => array('features-export-component')), + '#prefix' => "
    ", + ); + $form['export'][$component]['sources']['selected'] = array( + '#type' => 'checkboxes', + '#id' => "edit-sources-$component_name", + '#options' => features_dom_encode_options($component_info['options']['sources']), + '#default_value' => features_dom_encode_options($component_info['selected']['sources'], FALSE), + '#attributes' => array( + 'class' => array('component-select'), + ), + ); + + foreach ($sections as $section) { + $form['export'][$component][$section] = array( + '#type' => 'checkboxes', + '#options' => !empty($component_info['options'][$section]) ? + features_dom_encode_options($component_info['options'][$section]) : array(), + '#default_value' => !empty($component_info['selected'][$section]) ? + features_dom_encode_options($component_info['selected'][$section], FALSE) : array(), + '#attributes' => array('class' => array('component-' . $section)), + ); + } + $form['export'][$component][$sections[0]]['#prefix'] = + "
    "; + $form['export'][$component][$sections[count($sections)-1]]['#suffix'] = '
    '; + } + } + } + $form['export']['features_legend'] = array( + '#type' => 'fieldset', + '#title' => t('Legend'), + '#tree' => FALSE, + '#prefix' => "
    ", + '#suffix' => '
    ', + '#collapsible' => FALSE, + '#collapsed' => FALSE, + ); + $form['export']['features_legend']['legend'] = array( + '#markup' => + "Normal " . + "Changed " . + "Auto detected " . + "Conflict ", + ); +} + +/** + * Return the full feature export array based upon user selections in form_state + * @param array $feature Feature array to be exported + * @param array $form_state Optional form_state information for user selections + * can be updated to reflect new selection status + * @return array New export array to be exported + * array['components'][$component_name] = $component_info + * $component_info['options'][$section] is list of available options + * $component_info['selected'][$section] is option state TRUE/FALSE + * $section = array('sources', included', 'detected', 'added') + * sources - options that are available to be added to the feature + * included - options that have been previously exported to the feature + * detected - options that have been auto-detected + * added - newly added options to the feature + * + * NOTE: This routine gets a bit complex to handle all of the different possible + * user checkbox selections and de-selections. + * Cases to test: + * 1a) uncheck Included item -> mark as Added but unchecked + * 1b) re-check unchecked Added item -> return it to Included check item + * 2a) check Sources item -> mark as Added and checked + * 2b) uncheck Added item -> return it to Sources as unchecked + * 3a) uncheck Included item that still exists as auto-detect -> mark as Detected but unchecked + * 3b) re-check Detected item -> return it to Included and checked + * 4a) check Sources item should also add any auto-detect items as Detected and checked + * 4b) uncheck Sources item with auto-detect and auto-detect items should return to Sources and unchecked + * 5a) uncheck a Detected item -> refreshing page should keep it as unchecked Detected + * 6) when nothing changes, refresh should not change any state + * 7) should never see an unchecked Included item + */ +function _features_export_build($feature, &$form_state) { + global $features_ignore_conflicts; + // set a global to effect features_get_component_map when building feature + // hate to use a global, but it's just for an admin screen so probably ok + if (isset($_SESSION['features_allow_conflicts'])) { + $features_ignore_conflicts = $_SESSION['features_allow_conflicts']; + } + + $feature_name = isset($feature->name) ? $feature->name : NULL; + $conflicts = _features_get_used($feature_name); + $reset = FALSE; + if (isset($form_state['triggering_element']['#name']) && ($form_state['triggering_element']['#name'] == 'features_allow_conflicts')) { + // when clicking the Allow Conflicts button, reset the feature back to it's original state + $reset = TRUE; + } + + module_load_include('inc', 'features', 'features.export'); + features_include(); + + $components = features_get_components(); + uasort($components, 'features_compare_component_name'); + + // Assemble the combined component list + $stub = array(); + $sections = array('sources', 'included', 'detected', 'added'); + + // create a new feature "stub" to populate + + $stub_count = array(); + foreach ($components as $component => $component_info) { + if ($reset) { + unset($form_state['values'][$component]); + } + if (!variable_get('features_admin_show_component_' . $component, TRUE)) { + unset($components[$component]); + continue; + } + // User-selected components take precedence. + $stub[$component] = array(); + $stub_count[$component] = 0; + // add selected items from Sources checkboxes + if (!empty($form_state['values'][$component]['sources']['selected'])) { + $stub[$component] = array_merge($stub[$component], features_dom_decode_options(array_filter($form_state['values'][$component]['sources']['selected']))); + $stub_count[$component]++; + } + // add selected items from already Included and newly Added checkboxes + foreach (array('included', 'added') as $section) { + if (!empty($form_state['values'][$component][$section])) { + $stub[$component] = array_merge($stub[$component], features_dom_decode_options(array_filter($form_state['values'][$component][$section]))); + $stub_count[$component]++; + } + } + // count any detected items + if (!empty($form_state['values'][$component]['detected'])) { + $stub_count[$component]++; + } + // Only fallback to an existing feature's values if there are no export options for the component. + if ($component == 'dependencies') { + if (($stub_count[$component] == 0) && !empty($feature->info['dependencies'])) { + $stub[$component] = drupal_map_assoc($feature->info['dependencies']); + } + } + elseif (($stub_count[$component] == 0) && !empty($feature->info['features'][$component])) { + $stub[$component] = drupal_map_assoc($feature->info['features'][$component]); + } + } + // Generate new populated feature + $export = features_populate(array('features' => $stub, 'dependencies' => $stub['dependencies']), $feature_name); + + // Components that are already exported to feature + $exported_features_info = !empty($feature->info['features']) ? $feature->info['features'] : array(); + $exported_features_info['dependencies'] = !empty($feature->info['dependencies']) ? $feature->info['dependencies'] : array(); + // Components that should be exported + $new_features_info = !empty($export['features']) ? $export['features'] : array(); + $new_features_info['dependencies'] = !empty($export['dependencies']) ? $export['dependencies'] : array(); + $excluded = !empty($feature->info['features_exclude']) ? $feature->info['features_exclude'] : array(); + + // now fill the $export with categorized sections of component options + // based upon user selections and de-selections + + foreach ($components as $component => $component_info) { + $component_export = $component_info; + foreach ($sections as $section) { + $component_export['options'][$section] = array(); + $component_export['selected'][$section] = array(); + } + $options = features_invoke($component, 'features_export_options'); + if (!empty($options)) { + $exported_components = !empty($exported_features_info[$component]) ? $exported_features_info[$component] : array(); + $new_components = !empty($new_features_info[$component]) ? $new_features_info[$component] : array(); + + // Find all default components that are not provided by this feature and + // strip them out of the possible options. + if ($map = features_get_default_map($component)) { + foreach ($map as $k => $v) { + if (isset($options[$k]) && (!isset($feature->name) || $v !== $feature->name)) { + unset($options[$k]); + } + } + } + foreach ($options as $key => $value) { + // use the $clean_key when accessing $form_state + $clean_key = features_dom_encode($key); + // if checkbox in Sources is checked, move it to Added section + if (!empty($form_state['values'][$component]['sources']['selected'][$clean_key])) { + unset($form_state['input'][$component]['sources']['selected'][$clean_key]); + $form_state['values'][$component]['sources']['selected'][$clean_key] = FALSE; + $form_state['values'][$component]['added'][$clean_key] = 1; + $form_state['input'][$component]['added'][$clean_key] = $clean_key; + $component_export['options']['added'][$key] = check_plain($value); + $component_export['selected']['added'][$key] = $key; + } + elseif (in_array($key, $new_components)) { + // option is in the New exported array + if (in_array($key, $exported_components)) { + // option was already previously exported + // so it's part of the Included checkboxes + $section = 'included'; + $default_value = $key; + if ($reset) { + // leave it included + } + // if Included item was un-selected (removed from export $stub) + // but was re-detected in the $new_components + // means it was an auto-detect that was previously part of the export + // and is now de-selected in UI + elseif (!empty($form_state['values']) && + (isset($form_state['values'][$component]['included'][$clean_key]) || + empty($form_state['values'][$component]['detected'][$clean_key])) && + empty($stub[$component][$key])) { + $section = 'detected'; + $default_value = FALSE; + } + // unless it's unchecked in the form, then move it to Newly disabled item + elseif (!empty($form_state['values']) && + empty($form_state['values'][$component]['added'][$clean_key]) && + empty($form_state['values'][$component]['detected'][$clean_key]) && + empty($form_state['values'][$component]['included'][$clean_key])) { + $section = 'added'; + $default_value = FALSE; + } + } + else { + // option was in New exported array, but NOT in already exported + // so it's a user-selected or an auto-detect item + $section = 'detected'; + // check for item explicity excluded + if (isset($excluded[$component][$key]) && !isset($form_state['values'][$component]['detected'][$clean_key])) { + $default_value = FALSE; + } + else { + $default_value = $key; + } + // if it's already checked in Added or Sources, leave it in Added as checked + if (!empty($form_state['values']) && + (!empty($form_state['values'][$component]['added'][$clean_key]) || + !empty($form_state['values'][$component]['sources']['selected'][$clean_key]))) { + $section = 'added'; + $default_value = $key; + } + // if it's already been unchecked, leave it unchecked + elseif (!empty($form_state['values']) && + empty($form_state['values'][$component]['sources']['selected'][$clean_key]) && + empty($form_state['values'][$component]['detected'][$clean_key]) && + !isset($form_state['values'][$component]['added'][$clean_key])) { + $section = 'detected'; + $default_value = FALSE; + } + } + $component_export['options'][$section][$key] = check_plain($value); + $component_export['selected'][$section][$key] = $default_value; + // save which dependencies are specifically excluded from auto-detection + if (($section == 'detected') && ($default_value === FALSE)) { + $excluded[$component][$key] = $key; + // remove excluded item from export + if ($component == 'dependencies') { + unset($export['dependencies'][$key]); + } + else { + unset($export['features'][$component][$key]); + } + } + else { + unset($excluded[$component][$key]); + } + // remove the 'input' and set the 'values' so Drupal stops looking at 'input' + if (isset($form_state['values'])) { + if (!$default_value) { + unset($form_state['input'][$component][$section][$clean_key]); + $form_state['values'][$component][$section][$clean_key] = FALSE; + } + else { + $form_state['input'][$component][$section][$clean_key] = $clean_key; + $form_state['values'][$component][$section][$clean_key] = 1; + } + } + } + else { + // option was not part of the new export + $added = FALSE; + foreach (array('included', 'added') as $section) { + // restore any user-selected checkboxes + if (!empty($form_state['values'][$component][$section][$clean_key])) { + $component_export['options'][$section][$key] = check_plain($value); + $component_export['selected'][$section][$key] = $key; + $added = TRUE; + } + } + if (!$added) { + // if not Included or Added, then put it back in the unchecked Sources checkboxes + $component_export['options']['sources'][$key] = check_plain($value); + $component_export['selected']['sources'][$key] = FALSE; + } + } + } + } + $export['components'][$component] = $component_export; + } + $export['features_exclude'] = $excluded; + + // make excluded list and conflicts available for javascript to pass to our ajax callback + drupal_add_js(array('features' => array( + 'excluded' => $excluded, + 'conflicts' => $conflicts, + )), 'setting'); + + return $export; +} + +/** + * AJAX callback for features_export_form. + */ +function features_export_form_ajax($form, &$form_state) { + return $form['export']; +} + +/** + * Tells the ajax form submission to rebuild form state. + */ +function features_export_form_rebuild($form, &$form_state) { + $form_state['rebuild'] = TRUE; +} + +function features_export_components_json($feature_name) { + module_load_include('inc', 'features', 'features.export'); + $export = array(); + if (!empty($_POST['items'])) { + $excluded = (!empty($_POST['excluded'])) ? $_POST['excluded'] : array(); + $stub = array(); + foreach ($_POST['items'] as $key) { + preg_match('/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/', $key, $matches); + if (!empty($matches[1]) && !empty($matches[4])) { + $component = $matches[1]; + $item = features_dom_decode($matches[4]); + if (empty($stub[$component])) { + $stub[$component] = array($item); + } + else { + $stub[$component] = array_merge($stub[$component], array($item)); + } + } + } + + $stub['dependencies'] = isset($stub['dependencies']) ? $stub['dependencies'] : array(); + $export = features_populate(array('features' => $stub, 'dependencies' => $stub['dependencies']), $feature_name); + $export['features']['dependencies'] = $export['dependencies']; + + // uncheck any detected item that is in the excluded list + foreach ($export['features'] as $component => $value) { + foreach ($value as $key => $item) { + $clean_key = features_dom_encode($key); + if ($key != $clean_key) { + // need to move key to a cleankey for javascript + $export['features'][$component][$clean_key] = $export['features'][$component][$key]; + unset($export['features'][$component][$key]); + } + if (isset($excluded[$component][$key])) { + $export['features'][$component][$clean_key] = FALSE; + } + } + } + } + print drupal_json_encode($export['features']); +} + +/** + * AJAX callback to get .info file preview. + */ +function features_info_file_preview($form, &$form_state){ + return $form['export']; +} + +/** + * Render API callback: Validates a project field. + * + * This function is assigned as an #element_validate callback in + * features_export_form(). + */ +function features_export_form_validate_field($element, &$form_state) { + switch ($element['#name']) { + case 'module_name': + if (!preg_match('!^[a-z0-9_]+$!', $element['#value'])) { + form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); + } + // If user is filling out the feature name for the first time and uses + // the name of an existing module throw an error. + else if (empty($element['#default_value']) && features_get_info('module', $element['#value'])) { + form_error($element, t('A module by the name @name already exists on your site. Please choose a different name.', array('@name' => $element['#value']))); + } + break; + case 'project_status_url': + if (!empty($element['#value']) && !valid_url($element['#value'])) { + form_error($element, t('The URL %url is invalid. Please enter a fully-qualified URL, such as http://www.example.com/feed.xml.', array('%url' => $element['#value']))); + } + break; + case 'version': + preg_match('/^(?P\d+\.x)-(?P\d+)\.(?P\d+)-?(?P\w+)?$/', $element['#value'], $matches); + if (!empty($element['#value']) && !isset($matches['core'], $matches['major'])) { + form_error($element, t('Please enter a valid version with core and major version number. Example: @example', array('@example' => '7.x-1.0'))); + }; + break; + } +} + +/** + * Return the $export array to be rendered for the feature export + */ +function _features_export_generate($export, $form_state, $feature = NULL) { + unset($export['components']); // remove the UI data that we are not saving to disk + + $module_name = $form_state['values']['module_name']; + // Directly copy the following attributes from form_state + $attr = array('name', 'description', 'package', 'project path'); + foreach ($attr as $key) { + $export[$key] = isset($form_state['values'][$key]) ? $form_state['values'][$key] : NULL; + } + // Directly copy the following attributes from the original feature + $attr = array('scripts' , 'stylesheets'); + foreach ($attr as $key) { + $export[$key] = isset($feature->info[$key]) ? $feature->info[$key] : NULL; + } + // If either update status-related keys are provided, add a project key + // corresponding to the module name. + if (!empty($form_state['values']['version']) || !empty($form_state['values']['project_status_url'])) { + $export['project'] = $form_state['values']['module_name']; + } + if (!empty($form_state['values']['version'])) { + $export['version'] = $form_state['values']['version']; + } + if (!empty($form_state['values']['project_status_url'])) { + $export['project status url'] = $form_state['values']['project_status_url']; + } + $export['no autodetect'] = empty($form_state['values']['autodetect']) ? 1 : NULL; + $export['project path'] = !empty($form_state['values']['generate_path']) ? $form_state['values']['generate_path'] : NULL; + return $export; +} + +/** + * Form submission handler for features_export_form(). + */ +function features_export_build_form_submit($form, &$form_state) { + $feature = $form['#feature']; + $export = _features_export_build($feature, $form_state); + $export = _features_export_generate($export, $form_state, $feature); + $generate = ($form_state['values']['op'] == $form_state['values']['generate']); + $module_name = $form_state['values']['module_name']; + + if ($generate && !user_access('generate features')) { + drupal_set_message(t("No permission for generating features.")); + return; + } + + // Generate download + if ($files = features_export_render($export, $module_name, TRUE)) { + $filename = (!empty($export['version']) ? "{$module_name}-{$export['version']}" : $module_name) . '.tar'; + + if ($generate) { + $success = TRUE; + $destination = 'sites/all/modules/features'; + $directory = (!empty($export['project path'])) ? $export['project path'] . '/' . $module_name : + (isset($feature->filename) ? dirname($feature->filename) : $destination . '/' . $module_name); + if (!is_dir($directory)) { + if (mkdir($directory, 0777, true) === FALSE) { + $success = FALSE; + } + } + } + else { + // Clear out output buffer to remove any garbage from tar output. + if (ob_get_level()) { + ob_end_clean(); + } + + drupal_add_http_header('Content-type', 'application/x-tar'); + drupal_add_http_header('Content-Disposition', 'attachment; filename="'. $filename .'"'); + drupal_send_headers(); + } + + $tar = array(); + $filenames = array(); + foreach ($files as $extension => $file_contents) { + if (!in_array($extension, array('module', 'info'))) { + $extension .= '.inc'; + } + $filenames[] = "{$module_name}.$extension"; + if ($generate) { + if (file_put_contents("{$directory}/{$module_name}.$extension", $file_contents) === FALSE) { + $success = FALSE; + } + } + else { + print features_tar_create("{$module_name}/{$module_name}.$extension", $file_contents); + } + } + if (features_get_modules($module_name, TRUE)) { + // prevent deprecated component files from being included in download + $deprecated = features_get_deprecated(); + foreach ($deprecated as $component) { + $info = features_get_components($component); + $filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}"; + $filename .= '.inc'; + $filenames[] = "{$module_name}.$filename"; + } + $module_path = drupal_get_path('module', $module_name); + // file_scan_directory() can throw warnings when using PHP 5.3, messing + // up the output of our file stream. Suppress errors in this one case in + // order to produce valid output. + foreach (@file_scan_directory($module_path, '/.*/') as $file) { + $filename = substr($file->uri, strlen($module_path) + 1); + if (!in_array($filename, $filenames)) { + // Add this file. + $contents = file_get_contents($file->uri); + if ($generate) { + if (file_put_contents("{$directory}/{$filename}", $contents) === FALSE) { + $success = FALSE; + } + } + else { + print features_tar_create("{$module_name}/{$filename}", $contents); + } + unset($contents); + } + } + } + if ($generate) { + if ($success) { + drupal_set_message(t("Module @name written to @directory", + array('@name' => $export['name'], '@directory' => $directory))); + } + else { + drupal_set_message( + t("Could not write module to @path. ", array('@path' => $directory)) . + t("Ensure your file permissions allow the web server to write to that directory."), "error"); + } + } + else { + print pack("a1024",""); + exit; + } + } +} + +/** + * array_filter() callback for excluding hidden modules. + */ +function features_filter_hidden($module) { + return empty($module->info['hidden']); +} + +/** + * Form constructor for the features configuration form. + */ +function features_admin_form($form, $form_state) { + // Load export functions to use in comparison. + module_load_include('inc', 'features', 'features.export'); + + // Clear & rebuild key caches + features_get_info(NULL, NULL, TRUE); + features_rebuild(); + + $modules = array_filter(features_get_modules(), 'features_filter_hidden'); + $features = array_filter(features_get_features(), 'features_filter_hidden'); + $conflicts = features_get_conflicts(); + + foreach ($modules as $key => $module) { + if ($module->status && !empty($module->info['dependencies'])) { + foreach ($module->info['dependencies'] as $dependent) { + if (isset($features[$dependent])) { + $features[$dependent]->dependents[$key] = $module->info['name']; + } + } + } + } + + if ( empty($features) ) { + $form['no_features'] = array( + '#markup' => t('No Features were found. Please use the !create_link link to create + a new Feature module, or upload an existing Feature to your modules directory.', + array('!create_link' => l(t('Create Feature'), 'admin/structure/features/create'))), + ); + return $form ; + } + + $form = array('#features' => $features); + + // Generate features form. Features are sorted by dependencies, resort alpha + ksort($features); + foreach ($features as $name => $module) { + $package_title = !empty($module->info['package']) ? $module->info['package'] : t('Other'); + $package = strtolower(preg_replace('/[^a-zA-Z0-9-]+/', '-', $package_title)); + + // Set up package elements + if (!isset($form[$package])) { + $form[$package] = array( + '#tree' => FALSE, + '#title' => check_plain($package_title), + '#theme' => 'features_form_package', + '#type' => 'fieldset', + '#group' => 'packages', + ); + $form[$package]['links'] = + $form[$package]['version'] = + $form[$package]['weight'] = + $form[$package]['status'] = + $form[$package]['action'] = array('#tree' => TRUE); + } + + $disabled = FALSE; + $description = isset($module->info['description']) ? check_plain($module->info['description']) : ''; + + // Detect unmet dependencies + if (!empty($module->info['dependencies'])) { + $unmet_dependencies = array(); + $dependencies = _features_export_maximize_dependencies($module->info['dependencies']); + foreach ($dependencies as $dependency) { + if (empty($modules[$dependency])) { + $unmet_dependencies[] = theme('features_module_status', array('status' => FEATURES_MODULE_MISSING, 'module' => $dependency)); + } + } + if (!empty($unmet_dependencies)) { + $description .= "
    " . t('Unmet dependencies: !dependencies', array('!dependencies' => implode(', ', $unmet_dependencies))) . "
    "; + $disabled = TRUE; + } + } + + if (!empty($module->dependents)) { + $disabled = TRUE; + $description .= "
    ". t('Required by: !dependents', array('!dependents' => implode(', ', $module->dependents))) ."
    "; + } + + // Detect potential conflicts + if (!empty($conflicts[$name])) { + $module_conflicts = array(); + foreach ($conflicts[$name] as $conflict => $components) { + $component_strings = array(); + foreach ($components as $component => $component_conflicts) { + $component_strings[] = t('@component [@items]', array('@component' => $component, '@items' => implode(', ', $component_conflicts))); + } + $component_strings = implode(', ', $component_strings); + // If conflicting module is disabled, indicate so in feature listing + $status = !module_exists($conflict) ? FEATURES_MODULE_DISABLED : FEATURES_MODULE_CONFLICT; + $module_conflicts[] = theme('features_module_status', array('status' => $status, 'module' => $conflict)) . t(' in ') . $component_strings; + // Only disable modules with conflicts if they are not already enabled. + // If they are already enabled, somehow the user got themselves into a + // bad situation and they need to be able to disable a conflicted module. + if (module_exists($conflict) && !module_exists($name)) { + $disabled = TRUE; + } + } + $description .= "
    ". t('Conflicts with: !conflicts', array('!conflicts' => implode(', ', $module_conflicts))) ."
    "; + } + + $href = "admin/structure/features/{$name}"; + $module_name = (user_access('administer features')) ? l($module->info['name'], $href) : $module->info['name']; + $form[$package]['status'][$name] = array( + '#type' => 'checkbox', + '#title' => $module_name, + '#description' => $description, + '#default_value' => $module->status, + '#disabled' => $disabled, + ); + + if (!empty($module->info['project status url'])) { + $uri = l(truncate_utf8($module->info['project status url'], 35, TRUE, TRUE), $module->info['project status url']); + } + else if (isset($module->info['project'], $module->info['version'], $module->info['datestamp'])) { + $uri = l('http://drupal.org', 'http://drupal.org/project/' . $module->info['project']); + } + else { + $uri = t('Unavailable'); + } + $version = !empty($module->info['version']) ? $module->info['version'] : ''; + $version = !empty($version) ? "
    $version
    " : ''; + $form[$package]['sign'][$name] = array('#markup' => "{$uri} {$version}"); + + if (user_access('administer features')) { + // Add status link + if ($module->status) { + $state = theme('features_storage_link', array('storage' => FEATURES_CHECKING, 'path' => $href)); + $state .= l(t('Check'), "admin/structure/features/{$name}/status", array('attributes' => array('class' => array('admin-check')))); + $state .= theme('features_storage_link', array('storage' => FEATURES_REBUILDING, 'path' => $href)); + $state .= theme('features_storage_link', array('storage' => FEATURES_NEEDS_REVIEW, 'path' => $href)); + $state .= theme('features_storage_link', array('storage' => FEATURES_OVERRIDDEN, 'path' => $href)); + $state .= theme('features_storage_link', array('storage' => FEATURES_DEFAULT, 'path' => $href)); + } + elseif (!empty($conflicts[$name])) { + $state = theme('features_storage_link', array('storage' => FEATURES_CONFLICT, 'path' => $href)); + } + else { + $state = theme('features_storage_link', array('storage' => FEATURES_DISABLED, 'path' => $href)); + } + $form[$package]['state'][$name] = array( + '#markup' => !empty($state) ? $state : '', + ); + + // Add in recreate link + $form[$package]['actions'][$name] = array( + '#markup' => l(t('Recreate'), "admin/structure/features/{$name}/recreate", array('attributes' => array('class' => array('admin-update')))), + ); + } + } + ksort($form); + + // As of 7.0 beta 2 it matters where the "vertical_tabs" element lives on the + // the array. We add it late, but at the beginning of the array because that + // keeps us away from trouble. + $form = array('packages' => array('#type' => 'vertical_tabs')) + $form; + + $form['buttons'] = array( + '#theme' => 'features_form_buttons', + ); + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save settings'), + '#submit' => array('features_form_submit'), + '#validate' => array('features_form_validate'), + ); + return $form; +} + +/** + * Display the components of a feature. + */ +function features_admin_components($form, $form_state, $feature) { + // Breadcrumb navigation + $breadcrumb[] = l(t('Home'), NULL); + $breadcrumb[] = l(t('Administration'), 'admin'); + $breadcrumb[] = l(t('Structure'), 'admin/structure'); + $breadcrumb[] = l(t('Features'), 'admin/structure/features'); + drupal_set_breadcrumb($breadcrumb); + + module_load_include('inc', 'features', 'features.export'); + $form = array(); + + // Store feature info for theme layer. + $form['module'] = array('#type' => 'value', '#value' => $feature->name); + $form['#info'] = $feature->info; + $form['#dependencies'] = array(); + if (!empty($feature->info['dependencies'])) { + foreach ($feature->info['dependencies'] as $dependency) { + $parsed_dependency = drupal_parse_dependency($dependency); + $dependency = $parsed_dependency['name']; + $status = features_get_module_status($dependency); + $form['#dependencies'][$dependency] = $status; + } + } + + $conflicts = features_get_conflicts(); + if (!module_exists($form['module']['#value']) && isset($form['module']['#value']) && !empty($conflicts[$form['module']['#value']])) { + $module_conflicts = $conflicts[$form['module']['#value']]; + $conflicts = array(); + foreach ($module_conflicts as $conflict) { + $conflicts = array_merge_recursive($conflict, $conflicts); + } + } + else { + $conflicts = array(); + } + $form['#conflicts'] = $conflicts; + + $review = $revert = FALSE; + + // Iterate over components and retrieve status for display + $states = features_get_component_states(array($feature->name), FALSE); + $form['revert']['#tree'] = TRUE; + foreach ($feature->info['features'] as $component => $items) { + if (user_access('administer features') && array_key_exists($component, $states[$feature->name]) && in_array($states[$feature->name][$component], array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW))) { + switch ($states[$feature->name][$component]) { + case FEATURES_OVERRIDDEN: + $revert = TRUE; + break; + case FEATURES_NEEDS_REVIEW: + $review = TRUE; + break; + } + $form['revert'][$component] = array( + '#type' => 'checkbox', + '#default_value' => FALSE, + ); + } + if (module_exists('diff')) { + $diffpath = "admin/structure/features/{$feature->name}/diff/{$component}"; + $item = menu_get_item($diffpath); + $path = ($item && $item['access']) ? $diffpath : NULL; + } + else { + $path = NULL; + } + + $storage = FEATURES_DEFAULT; + if (array_key_exists($component, $states[$feature->name])) { + $storage = $states[$feature->name][$component]; + } + else if (array_key_exists($component, $conflicts)) { + $storage = FEATURES_CONFLICT; + } + $form['components'][$component] = array( + '#markup' => theme('features_storage_link', array('storage' => $storage, 'path' => $path)), + ); + } + + if ($review || $revert) { + $form['buttons'] = array('#theme' => 'features_form_buttons', '#tree' => TRUE); + if ($revert || $review) { + $form['buttons']['revert'] = array( + '#type' => 'submit', + '#value' => t('Revert components'), + '#submit' => array('features_admin_components_revert'), + ); + } + if ($review) { + $form['buttons']['review'] = array( + '#type' => 'submit', + '#value' => t('Mark as reviewed'), + '#submit' => array('features_admin_components_review'), + ); + } + } + return $form; +} + +/** + * Submit handler for revert form. + */ +function features_admin_components_revert(&$form, &$form_state) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + $module = $form_state['values']['module']; + $revert = array($module => array()); + foreach (array_filter($form_state['values']['revert']) as $component => $status) { + $revert[$module][] = $component; + drupal_set_message(t('Reverted all @component components for @module.', array('@component' => $component, '@module' => $module))); + } + if (empty($revert[$module])) { + drupal_set_message(t('Please select which components to revert.'), 'warning'); + } + features_revert($revert); + $form_state['redirect'] = 'admin/structure/features/' . $module; +} + +/** + * Submit handler for revert form. + */ +function features_admin_components_review(&$form, &$form_state) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + $module = $form_state['values']['module']; + $revert = array(); + foreach (array_filter($form_state['values']['revert']) as $component => $status) { + features_set_signature($module, $component); + drupal_set_message(t('All @component components for @module reviewed.', array('@component' => $component, '@module' => $module))); + } + $form_state['redirect'] = 'admin/structure/features/' . $module; +} + +/** + * Validate handler for the 'manage features' form. + */ +function features_form_validate(&$form, &$form_state) { + include_once './includes/install.inc'; + $conflicts = features_get_conflicts(); + foreach ($form_state['values']['status'] as $module => $status) { + if ($status) { + if (!empty($conflicts[$module])) { + foreach (array_keys($conflicts[$module]) as $conflict) { + if (!empty($form_state['values']['status'][$conflict])) { + form_set_error('status', t('The feature @module cannot be enabled because it conflicts with @conflict.', array('@module' => $module, '@conflict' => $conflict))); + } + } + } + if (!drupal_check_module($module)) { + form_set_error('status', t('The feature @module cannot be enabled because it has unmet requirements.', array('@module' => $module))); + } + } + } +} + +/** + * Submit handler for the 'manage features' form + */ +function features_form_submit(&$form, &$form_state) { + // Clear drupal caches after enabling a feature. We do this in a separate + // page callback rather than as part of the submit handler as some modules + // have includes/other directives of importance in hooks that have already + // been called in this page load. + $form_state['redirect'] = 'admin/structure/features/cleanup/clear'; + + $features = $form['#features']; + if (!empty($features)) { + $status = $form_state['values']['status']; + $install = array_keys(array_filter($status)); + $disable = array_diff(array_keys($status), $install); + + // Disable first. If there are any features that are disabled that are + // dependencies of features that have been queued for install, they will + // be re-enabled. + module_disable($disable); + features_install_modules($install); + } +} + +/** + * Form for clearing cache after enabling a feature. + */ +function features_cleanup_form($form, $form_state, $cache_clear = FALSE) { + // Clear caches if we're getting a post-submit redirect that requests it. + if ($cache_clear) { + drupal_flush_all_caches(); + + // The following functions need to be run because drupal_flush_all_caches() + // runs rebuilds in the wrong order. The node type cache is rebuilt *after* + // the menu is rebuilt, meaning that the menu tree is stale in certain + // circumstances after drupal_flush_all_caches(). We rebuild again. + menu_rebuild(); + } + + drupal_goto('admin/structure/features'); +} + +/** + * Page callback to display the differences between what's in code and + * what is in the db. + * + * @param $feature + * A loaded feature object to display differences for. + * @param $component + * (optional) Specific component to display differences for. If excluded, all + * components are used. + * + * @return + * Themed display of what is different. + */ +function features_feature_diff($feature, $component = NULL) { + drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); + module_load_include('inc', 'features', 'features.export'); + drupal_set_title($feature->info['name']); + + $overrides = features_detect_overrides($feature); + + $output = ''; + if (!empty($overrides)) { + // Filter overrides down to specified component. + if (isset($component) && isset($overrides[$component])) { + $overrides = array($component => $overrides[$component]); + } + + module_load_include('inc', 'diff', 'diff.engine'); + $formatter = new DrupalDiffFormatter(); + + $rows = array(); + foreach ($overrides as $component => $items) { + $rows[] = array(array('data' => $component, 'colspan' => 4, 'header' => TRUE)); + $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal'])); + $rows = array_merge($rows, $formatter->format($diff)); + } + $header = array( + array('data' => t('Default'), 'colspan' => 2), + array('data' => t('Overrides'), 'colspan' => 2), + ); + $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('diff', 'features-diff')))); + } + else { + $output = "
    " . t('No changes have been made to this feature.') . "
    "; + } + $output = array('page' => array('#markup' => "
    {$output}
    ")); + return $output; +} + +/** + * Compare the component names. Used to sort alphabetically. + */ +function features_compare_component_name($a, $b) { + return strcasecmp($a['name'], $b['name']); +} + +/** + * Javascript callback that returns the status of a feature. + */ +function features_feature_status($feature) { + module_load_include('inc', 'features', 'features.export'); + return drupal_json_output(array('storage' => features_get_storage($feature->name))); +} + +/** + * Make a Drupal options array safe for usage with jQuery DOM selectors. + * Encodes known bad characters into __[ordinal]__ so that they may be + * safely referenced by JS behaviors. + */ +function features_dom_encode_options($options = array(), $keys_only = TRUE) { + $replacements = features_dom_encode_map(); + $encoded = array(); + foreach ($options as $key => $value) { + $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements); + } + return $encoded; +} + +function features_dom_encode($key) { + $replacements = features_dom_encode_map(); + return strtr($key, $replacements); +} + +function features_dom_decode($key) { + $replacements = array_flip(features_dom_encode_map()); + return strtr($key, $replacements); +} + +/** + * Decode an array of option values that have been encoded by + * features_dom_encode_options(). + */ +function features_dom_decode_options($options, $keys_only = FALSE) { + $replacements = array_flip(features_dom_encode_map()); + $encoded = array(); + foreach ($options as $key => $value) { + $encoded[strtr($key, $replacements)] = $keys_only ? $value : strtr($value, $replacements); + } + return $encoded; +} + +/** + * Returns encoding map for decode and encode options. + */ +function features_dom_encode_map() { + return array( + ':' => '__' . ord(':') . '__', + '/' => '__' . ord('/') . '__', + ',' => '__' . ord(',') . '__', + '.' => '__' . ord('.') . '__', + '<' => '__' . ord('<') . '__', + '>' => '__' . ord('>') . '__', + '%' => '__' . ord('%') . '__', + ')' => '__' . ord(')') . '__', + '(' => '__' . ord('(') . '__', + ); +} + +/** + * Page callback: Autocomplete field for features package. + * + * @param $search_string + * The char or string that user have written in autocomplete field, + * this is the string this function uses for filter. + * + * @see features_menu() + */ +function features_autocomplete_packages($search_string) { + $matched_packages = array(); + //fetch all modules that are features and copy the package name into a new array. + foreach (features_get_features(NULL, TRUE) as $value) { + if (preg_match('/' . $search_string . '/i', $value->info['package'])) { + $matched_packages[$value->info['package']] = $value->info['package']; + } + } + //removes duplicated package, we wont a list of all unique packages. + $matched_packages = array_unique($matched_packages); + drupal_json_output($matched_packages); +} + +/** + * Return a list of all used components/items not matching a given feature module + * similar to features_get_conflicts but returns all component items "in use" + */ +function _features_get_used($module_name = NULL) { + + global $features_ignore_conflicts; + // make sure we turn off the ignore_conflicts global to get full list of used components + // hate to use global, but since this is just for an admin screen it's not a real problem + $old_value = $features_ignore_conflicts; + $features_ignore_conflicts = FALSE; + + $conflicts = array(); + $component_info = features_get_components(); + $map = features_get_component_map(); + + foreach ($map as $type => $components) { + // Only check conflicts for components we know about. + if (isset($component_info[$type])) { + foreach ($components as $component => $modules) { + foreach ($modules as $module) { + // only for enabled modules + if (module_exists($module) && (empty($module_name) || ($module_name != $module))) { + if (!isset($conflicts[$module])) { + $conflicts[$module] = array(); + } + $conflicts[$module][$type][] = $component; + } + } + } + } + } + + // restore previous value of global + $features_ignore_conflicts = $old_value; + return $conflicts; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.api.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,476 @@ + array( + 'default_hook' => 'mycomponent_defaults', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => TRUE, + 'file' => drupal_get_path('module', 'mycomponent') . '/mycomponent.features.inc', + ), + ); +} + +/** + * Component hook. The hook should be implemented using the name of the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Process the export array for a given component. Implementations of this hook + * have three key tasks: + * + * 1. Determine module dependencies for any of the components passed to it + * e.g. the views implementation iterates over each views' handlers and + * plugins to determine which modules need to be added as dependencies. + * + * 2. Correctly add components to the export array. In general this is usually + * adding all of the items in $data to $export['features']['my_key'], but + * can become more complicated if components are shared between features + * or modules. + * + * 3. Delegating further detection and export tasks to related or derivative + * components. + * + * Each export processor can kickoff further export processors by returning a + * keyed array (aka the "pipe") where the key is the next export processor hook + * to call and the value is an array to be passed to that processor's $data + * argument. This allows an export process to start simply at a few objects: + * + * [context] + * + * And then branch out, delegating each component to its appropriate hook: + * + * [context]--------+------------+ + * | | | + * [node] [block] [views] + * | + * [CCK] + * | + * [imagecache] + * + * @param array $data + * An array of machine names for the component in question to be exported. + * @param array &$export + * By reference. An array of all components to be exported with a given + * feature. Component objects that should be exported should be added to + * this array. + * @param string $module_name + * The name of the feature module to be generated. + * @return array + * The pipe array of further processors that should be called. + */ +function hook_features_export($data, &$export, $module_name) { + // The following is the simplest implementation of a straight object export + // with no further export processors called. + foreach ($data as $component) { + $export['features']['mycomponent'][$component] = $component; + } + return array(); +} + +/** + * Component hook. The hook should be implemented using the name of the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * List all objects for a component that may be exported. + * + * @return array + * A keyed array of items, suitable for use with a FormAPI select or + * checkboxes element. + */ +function hook_features_export_options() { + $options = array(); + foreach (mycomponent_load() as $mycomponent) { + $options[$mycomponent->name] = $mycomponent->title; + } + return $options; +} + +/** + * Component hook. The hook should be implemented using the name of the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Render one or more component objects to code. + * + * @param string $module_name + * The name of the feature module to be exported. + * @param array $data + * An array of machine name identifiers for the objects to be rendered. + * @param array $export + * The full export array of the current feature being exported. This is only + * passed when hook_features_export_render() is invoked for an actual feature + * update or recreate, not during state checks or other operations. + * @return array + * An associative array of rendered PHP code where the key is the name of the + * hook that should wrap the PHP code. The hook should not include the name + * of the module, e.g. the key for `hook_example` should simply be `example` + * The values in the array can also be in the form of an associative array + * with the required key of 'code' and optional key of 'args', if 'args' need + * to be added to the hook. + */ +function hook_features_export_render($module_name, $data, $export = NULL) { + $code = array(); + $code[] = '$mycomponents = array();'; + foreach ($data as $name) { + $code[] = " \$mycomponents['{$name}'] = " . features_var_export(mycomponent_load($name)) .";"; + } + $code[] = "return \$mycomponents;"; + $code = implode("\n", $code); + return array('mycomponent_defaults' => $code); +} + +/** + * Component hook. The hook should be implemented using the name of the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Revert all component objects for a given feature module. + * + * @param string $module_name + * The name of the feature module whose components should be reverted. + * @return boolean + * TRUE or FALSE for whether the components were successfully reverted. + * NOTE: This return value is no longer used in the latest Features so + * modules should no longer count on this value + */ +function hook_features_revert($module_name) { + $mycomponents = module_invoke($module_name, 'mycomponent_defaults'); + if (!empty($mycomponents)) { + foreach ($mycomponents as $mycomponent) { + mycomponent_delete($mycomponent); + } + } +} + +/** + * Component hook. The hook should be implemented using the name of the + * component, not the module, eg. [component]_features_export() rather than + * [module]_features_export(). + * + * Rebuild all component objects for a given feature module. Should only be + * implemented for 'faux-exportable' components. + * + * This hook is called at points where Features determines that it is safe + * (ie. the feature is in state `FEATURES_REBUILDABLE`) for your module to + * replace objects in the database with defaults that you collect from your + * own defaults hook. See API.txt for how Features determines whether a + * rebuild of components is possible. + * + * @param string $module_name + * The name of the feature module whose components should be rebuilt. + */ +function hook_features_rebuild($module_name) { + $mycomponents = module_invoke($module_name, 'mycomponent_defaults'); + if (!empty($mycomponents)) { + foreach ($mycomponents as $mycomponent) { + mycomponent_save($mycomponent); + } + } +} + +/** + * Alter the final array of Component names to be exported, just prior to + * the rendering of defaults. Allows modules a final say in whether or not + * certain Components are exported (the Components' actual data, however, + * cannot be altered by this hook). + * + * @param array &$export + * By reference. An array of all component names to be exported with a given + * feature. + * @param array $module_name + * The name of the feature module to be generated. + */ +function hook_features_export_alter(&$export, $module_name) { + // Example: do not allow the page content type to be exported, ever. + if (!empty($export['features']['node']['page'])) { + unset($export['features']['node']['page']); + } +} + +/** + * Alter the pipe array for a given component. This hook should be implemented + * with the name of the component type in place of `component` in the function + * name, e.g. `features_pipe_views_alter()` will alter the pipe for the Views + * component. + * + * @param array &$pipe + * By reference. The pipe array of further processors that should be called. + * @param array $data + * An array of machine names for the component in question to be exported. + * @param array &$export + * By reference. An array of all components to be exported with a given + * feature. + */ +function hook_features_pipe_COMPONENT_alter(&$pipe, $data, $export) { + if (in_array($data, 'my-node-type')) { + $pipe['dependencies'][] = 'mymodule'; + } +} + +/** + * Alter the pipe array for a given component. + * + * @param array &$pipe + * By reference. The pipe array of further processors that should be called. + * @param array $data + * An array of machine names for the component in question to be exported. + * @param array &$export + * By reference. An array of all components to be exported with a given + * feature. + * + * The component being exported is contained in $export['component']. + * The module being exported contained in $export['module_name']. + */ +function hook_features_pipe_alter(&$pipe, $data, $export) { + if ($export['component'] == 'node' && in_array($data, 'my-node-type')) { + $pipe['dependencies'][] = 'mymodule'; + } +} + +/** + * @defgroup features_component_alter_hooks Feature's component alter hooks + * @{ + * Hooks to modify components defined by other features. These come in the form + * hook_COMPONENT_alter where COMPONENT is the default_hook declared by any of + * components within features. + * + * CTools also has a variety of hook_FOO_alters. + * + * Note: While views is a component of features, it declares it's own alter + * function which takes a similar form: + * hook_views_default_views_alter(&$views) + */ + +/** + * Alter the default fields right before they are cached into the database. + * + * @param &$fields + * By reference. The fields that have been declared by another feature. + */ +function hook_field_default_fields_alter(&$fields) { +} + +/** + * Alter the default fieldgroup groups right before they are cached into the + * database. + * + * @param &$groups + * By reference. The fieldgroup groups that have been declared by another + * feature. + */ +function hook_fieldgroup_default_groups_alter(&$groups) { +} + +/** + * Alter the default filter formats right before they are cached into the + * database. + * + * @param &$formats + * By reference. The formats that have been declared by another feature. + */ +function hook_filter_default_formats_alter(&$formats) { +} + +/** + * Alter the default menus right before they are cached into the database. + * + * @param &$menus + * By reference. The menus that have been declared by another feature. + */ +function hook_menu_default_menu_custom_alter(&$menus) { +} + +/** + * Alter the default menu links right before they are cached into the database. + * + * @param &$links + * By reference. The menu links that have been declared by another feature. + */ +function hook_menu_default_menu_links_alter(&$links) { +} + +/** + * Alter the default menu items right before they are cached into the database. + * + * @param &$items + * By reference. The menu items that have been declared by another feature. + */ +function hook_menu_default_items_alter(&$items) { +} + +/** + * Alter the default vocabularies right before they are cached into the + * database. + * + * @param &$vocabularies + * By reference. The vocabularies that have been declared by another feature. + */ +function hook_taxonomy_default_vocabularies_alter(&$vocabularies) { +} + +/** + * Alter the default permissions right before they are cached into the + * database. + * + * @param &$permissions + * By reference. The permissions that have been declared by another feature. + */ +function hook_user_default_permissions_alter(&$permissions) { +} + +/** + * Alter the default roles right before they are cached into the database. + * + * @param &$roles + * By reference. The roles that have been declared by another feature. + */ +function hook_user_default_roles_alter(&$roles) { +} + +/** + * @} + */ + + +/** + * @defgroup features_module_hooks Feature module hooks + * @{ + * Hooks invoked on Feature modules when that module is enabled, disabled, + * rebuilt, or reverted. These are ONLY invoked on the Features module on + * which these actions are taken. + */ + +/** + * Feature module hook. Invoked on a Feature module before that module is + * reverted. + * + * @param $component + * String name of the component that is about to be reverted. + */ +function hook_pre_features_revert($component) { +} + +/** + * Feature module hook. Invoked on a Feature module after that module is + * reverted. + * + * @param $component + * String name of the component that has just been reverted. + */ +function hook_post_features_revert($component) { +} + +/** + * Feature module hook. Invoked on a Feature module before that module is + * rebuilt. + * + * @param $component + * String name of the component that is about to be rebuilt. + */ +function hook_pre_features_rebuild($component) { +} + +/** + * Feature module hook. Invoked on a Feature module after that module is + * rebuilt. + * + * @param $component + * String name of the component that has just been rebuilt. + */ +function hook_post_features_rebuild($component) { +} + +/** + * Feature module hook. Invoked on a Feature module before that module is + * disabled. + * + * @param $component + * String name of the component that is about to be disabled. + */ +function hook_pre_features_disable_feature($component) { +} + +/** + * Feature module hook. Invoked on a Feature module after that module is + * disabled. + * + * @param $component + * String name of the component that has just been disabled. + */ +function hook_post_features_disable_feature($component) { +} + +/** + * Feature module hook. Invoked on a Feature module before that module is + * enabled. + * + * @param $component + * String name of the component that is about to be enabled. + */ +function hook_pre_features_enable_feature($component) { +} + +/** + * Feature module hook. Invoked on a Feature module after that module is + * enabled. + * + * @param $component + * String name of the component that has just been enabled. + */ +function hook_post_features_enable_feature($component) { +} + +/** + * @} + */ diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,561 @@ +/** + * Features packages. + */ +div.features-form-links { + width:20%; + float:left; +} + +div.features-form-content { + width:80%; + float:right; +} + +/** + * Package links. + */ +div.features-form-links ul#features-form-links, +div.features-form-links ul#features-form-links li, +div.features-form-links ul#features-form-links li a { + display:block; + float:none; + padding:0px; + margin:0px; +} + +div.features-form-links ul#features-form-links { + margin:0px 0px 10px; +} + +div.features-form-links ul#features-form-links li a { + background:#f8f8f8; + padding:5px 5px 4px 4px; + border-left:1px solid #eee; + border-bottom:1px solid #eee; + color: #000; + cursor:pointer; +} + +div.features-form-links ul#features-form-links li a.features-package-active { + padding:4px 5px 4px 4px; + background:#fff; + border:1px solid #ccc; + border-right:0px; + color: #000; + margin-right:-1px; +} + +/* Packages */ +div.features-form-package { + border:1px solid #ccc; + background:#fff; + color: #000; + padding:10px; + margin:0px 0px 20px; + display:none; +} + +div.features-package-active { + display:block; +} + +/** + * Features management form (admin/build/features). + */ +div.features-empty { + margin:15px 0px; + font-size:1.5em; + text-align:center; + color:#999; +} + +form div.buttons { + text-align:center; +} + +table.features .admin-loading, +table.features tr.disabled { + color:#999; +} + +table.features a.configure { + float:right; + font-style:italic; +} +table.features div.feature { + float:left; width:85%; +} +table.features div.feature strong { + font-size:13px; +} +table.features div.feature div.description { + font-size:11px; margin:0px; +} + +table.features-manage td.name { + width:80%; +} +table.features-manage td.sign { + width:20%; +} + +table.features-admin td.name { + width:60%; +} +table.features-admin td.sign { + width:20%; +} +table.features-admin td.state { + width:10%; +} +table.features-admin td.actions { + width:10%; +} + +table.features td.sign { + font-size:9px; + line-height:15px; + white-space:nowrap; +} + +table.features td.sign * { + margin:0px; +} + +table.features .admin-check, +table.features .admin-default, +table.features .admin-overridden, +table.features .admin-rebuilding, +table.features .admin-needs-review { + display:none; +} + +/** + * Feature export form (admin/build/features/export). + */ +form.features-export-form table td { + width:50%; +} +form.features-export-form table td { + vertical-align:top; +} +form.features-export-form table div.description { + white-space:normal; +} + +table.features-export div.form-item { + white-space:normal; +} +table.features-export select { + width:90%; +} +table.features-export td { + vertical-align:top; +} + +form.features-export-form div.features-select { + display:none; +} + +/* +form.features-export-form div.form-checkboxes { + overflow-x:hidden; + overflow-y:auto; + height:20em; + } + */ + +form.features-export-form div#edit-components-wrapper, +form.features-export-form div.features-select { + padding-right:20px; +} + +/** + * Feature component display (admin/build/features/%feature). + */ +div.features-components div.column { + float:left; + width:50%; +} + +div.features-components div.column div.info { + padding-right:20px; +} +div.features-components div.column div.components { + padding-left:20px; +} + +h3.features-download, +div.features-comparison h3, +div.features-components h3 { + font-size:2em; + font-weight:bold; + letter-spacing:-1px; + margin:15px 0px; +} + +h3.features-download { + text-align:center; +} + +div.features-components div.description { + font-size:11px; + margin:15px 0px; +} + +div.features-components table td { + font-size:11px; +} +div.features-components table td.component { + padding-left:20px; +} + +/** + * Features component lists. + */ +span.features-component-key { + font-size:11px; +} + +a.admin-update, +a.features-storage, +span.features-storage, +span.features-component-list span { + white-space:nowrap; + margin-right:5px; + padding:2px 5px; + background:#eee; + -moz-border-radius:5px; + -webkit-border-radius:5px; +} + +div.features-key span.admin-conflict, +span.features-component-list span.features-conflict { + background-color: #c30 !important; + color: #fff; +} + +#features-export-wrapper .features-conflict, +#features-export-wrapper .features-conflict label.option{ + color: #c30 !important; + font-weight: bold !important; +} + +div.conflicts span.admin-disabled { + color: #955; +} + +a.admin-update { + background:transparent; +} + +/* These pseudo selectors are necessary for themes like Garland. */ +a.admin-overridden:link, +a.admin-overridden:visited, +span.admin-overridden { + color:#fff; + background:#666; +} + +a.admin-needs-review:link, +a.admin-needs-review:visited, +span.admin-needs-review { + color:#963; + background:#fe6; +} + +a.admin-rebuilding:link, +a.admin-rebuilding:visited, +span.admin-rebuilding { + color:#fff; + background:#699; +} + +a.admin-conflict:link, +a.admin-conflict:visited, +span.admin-conflict { + color:#c30; +} + +table.features-diff td.diff-addedline, +span.features-component-list .features-detected { + color:#68a; + background:#def; +} + +table.features-diff td.diff-deletedline, +span.features-component-list .features-dependency { + color:#999; + background:#f8f8f8; +} + +/** + * Features diff. + */ +table.features-diff { + font-size:11px; +} + +table.features-diff td { + padding:0px 5px; +} + +table.features-diff td.diff-deletedline, +table.features-diff td.diff-addedline, +table.features-diff td.diff-context { + width:50%; + font-family:'Andale Mono',monospace; +} + +/** + * New UI component export list + */ +#features-export-wrapper div.features-export-parent { + clear: both; +} +#features-export-form .fieldset-content.fieldset-wrapper { + padding-top: 5px; +} +html.js #features-export-form fieldset.collapsed { + min-height: 2em; + margin-bottom: 0; + top: 0; + padding: 0 0 0 0; +} +fieldset.features-export-component { + background: #F3F8FB; + margin: 0.5em 0; + font-weight: normal; + font-size: 12px; + top: 0; +} +fieldset.features-export-component.collapsed { + background: #F3F8FB; +} +fieldset.features-export-component legend { + top: 0; +} +fieldset.features-export-component.collapsed .fieldset-wrapper { + margin: 0; + padding: 0 13px 6px 15px; +} +fieldset.features-export-component .fieldset-legend { + white-space: nowrap; +} +fieldset.features-export-component .fieldset-title span { + font-size: 11px; + text-transform: none; + font-weight: normal; +} +#features-export-wrapper .fieldset-wrapper { + font-size: 12px; +} +div.features-export-list { + font-weight: normal; + font-size: 12px; + border: 1px solid #CCC; + border-top-width: 0; + overflow: hidden; +} +#features-export-form .fieldset-description { + margin: 5px 0; + line-height: 1.231em; + font-size: 0.923em; + color: #666; +} +div.features-export-empty { + display: none; +} +fieldset.features-export-component .fieldset-wrapper .form-checkboxes { + max-height: 20em; + overflow: auto; + min-width: 100px; +} + +#features-export-wrapper .component-select .form-type-checkbox { + overflow: hidden; + padding: 0 0 0 2px; +} + +#features-export-wrapper .component-added .form-type-checkbox, +#features-export-wrapper .component-detected .form-type-checkbox, +#features-export-wrapper .component-included .form-type-checkbox { + float: left; + white-space: normal; + margin: 2px 5px; + padding: 0 5px; + background: transparent; +} + +#features-export-wrapper .component-added .form-type-checkbox { + font-weight: bold; + background: #EEE; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} +#features-export-wrapper div.component-added label.option { + font-weight: bold; +} +#features-export-wrapper .component-detected .form-type-checkbox { + font-style: italic; + color:#68a; + background:#def; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; +} + +#features-export-info { + width: 49%; + float: left; + position: relative; +} +#features-export-wrapper { + width: 49%; + float: right; + clear: both; + position: relative; +} +#features-export-advanced { + width: 49%; + float: left; + clear: left; + margin-top: 0.5em; + position: relative; +} +#features-export-buttons { + width: 49%; + float: left; + margin-top: 0.5em; + position: relative; + text-align: center; +} +#features-export-buttons input { + margin: 0 3px; +} + +#features-export-wrapper .component-added label a, +#features-export-wrapper .component-detected label a, +#features-export-wrapper .component-included label a { + display: inline; + float: none; +} +#features-export-wrapper label.component-added { + font-weight: bold; +} +#features-export-form input[size="60"].form-text { + width: 100%; +} +input.form-submit.features-refresh-button { + font-size: 0.7em; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; + background-color: transparent; + margin-left: 1em; +} +.features-refresh-wrapper .ajax-progress { + font-size: 10px; + width: 10em; +} +#features-export-advanced .ajax-progress { + font-size: 10px; + width: 10em; +} +#features-export-wrapper div.fieldset-description, +#features-export-wrapper div.description { + clear: both; +} +#features-filter input[size="60"].form-text { + width: 200px; +} +#features-filter .fieldset-content, +#features-filter .fieldset-wrapper, +#features-filter fieldset { + border: 0; + padding: 0; + margin: 0; +} +#features-filter fieldset legend { + display: none; +} +#features-filter label, +#features-filter input { + display: inline; + width: auto; +} +#features-filter .form-item { + float: left; + margin: 5px 0; + padding: 0; +} +#features-filter .form-item.form-type-checkbox { + margin: 5px 0; +} +#features-filter span { + float: left; + white-space: normal; + margin: 5px 5px; + padding: 0 5px; + background: transparent; + background: #EEE; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + cursor: pointer; +} +#features-filter span:hover { + background:#def; +} +#features-autodetect .form-item { + float: left; + margin: 0 0; + padding: 0; +} +#features-autodetect .fieldset-content, +#features-autodetect .fieldset-wrapper, +#features-autodetect fieldset { + border: 0; + padding: 0; + margin: 1em 0 0 0; + top: 0; +} +#features-autodetect fieldset legend { + display: none; +} +#features-export-advanced .form-item.form-item-generate-path { + margin-bottom: 0; +} +#features-info-file .form-textarea-wrapper, +#features-info-file textarea { + height: 100%; +} + +#features-info-file .form-type-textarea { + height: 100%; + margin: 0; +} +#edit-info-preview { + margin: 1em 0; +} +#features-legend .fieldset-wrapper span { + font-style: normal; + color: black; + display: inline-block; + background: transparent; + border: 1px solid #DDD; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + white-space: nowrap; + padding: 0 8px; + margin: 0 10px 0 0; +} +#features-legend .fieldset-wrapper .component-detected { + font-style: italic; + color:#68a; + background:#def; + border-width: 0; +} +#features-legend .fieldset-wrapper .component-added { + font-weight: bold; + background: #EEE; + border-width: 0; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.drush.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.drush.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,870 @@ + "List all the available features for your site.", + 'options' => array( + 'status' => "Feature status, can be 'enabled', 'disabled' or 'all'", + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fl', 'features'), + ); + $items['features-export'] = array( + 'description' => "Export a feature from your site into a module.", + 'arguments' => array( + 'feature' => 'Feature name to export.', + 'components' => 'Patterns of components to include, see features-components for the format of patterns.' + ), + 'options' => array( + 'destination' => "Destination path (from Drupal root) of the exported feature. Defaults to 'sites/all/modules/features'", + 'version-set' => "Specify a version number for the feature.", + 'version-increment' => "Increment the feature's version number.", + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fe'), + ); + $items['features-add'] = array( + 'description' => "Add a component to a feature module. (DEPRECATED: use features-export)", + 'arguments' => array( + 'feature' => 'Feature name to add to.', + 'components' => 'List of components to add.', + ), + 'options' => array( + 'version-set' => "Specify a version number for the feature.", + 'version-increment' => "Increment the feature's version number.", + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fa'), + ); + $items['features-components'] = array( + 'description' => 'List features components.', + 'arguments' => array( + 'patterns' => 'The features components type to list. Omit this argument to list all components.', + ), + 'options' => array( + 'exported' => array( + 'description' => 'Show only components that have been exported.', + ), + 'not-exported' => array( + 'description' => 'Show only components that have not been exported.', + ), + ), + 'aliases' => array('fc'), + ); + $items['features-update'] = array( + 'description' => "Update a feature module on your site.", + 'arguments' => array( + 'feature' => 'A space delimited list of features.', + ), + 'options' => array( + 'version-set' => "Specify a version number for the feature.", + 'version-increment' => "Increment the feature's version number.", + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fu'), + ); + $items['features-update-all'] = array( + 'description' => "Update all feature modules on your site.", + 'arguments' => array( + 'feature_exclude' => 'A space-delimited list of features to exclude from being updated.', + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fu-all', 'fua'), + ); + $items['features-revert'] = array( + 'description' => "Revert a feature module on your site.", + 'arguments' => array( + 'feature' => 'A space delimited list of features or feature.component pairs.', + ), + 'options' => array( + 'force' => "Force revert even if Features assumes components' state are default.", + ), + 'examples' => array( + 'drush fr foo.node foo.taxonomy bar' => 'Revert node and taxonomy components of feature "foo", but only if they are overriden. Revert all overriden components of feature "bar".', + 'drush fr foo.node foo.taxonomy bar --force' => 'Revert node and taxonomy components of feature "foo". Revert all components of feature "bar".', + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fr'), + ); + $items['features-revert-all'] = array( + 'description' => "Revert all enabled feature module on your site.", + 'arguments' => array( + 'feature_exclude' => 'A space-delimited list of features to exclude from being reverted.', + ), + 'options' => array( + 'force' => "Force revert even if Features assumes components' state are default.", + ), + 'drupal dependencies' => array('features'), + 'aliases' => array('fr-all', 'fra'), + ); + $items['features-diff'] = array( + 'description' => "Show the difference between the default and overridden state of a feature.", + 'arguments' => array( + 'feature' => 'The feature in question.', + ), + 'options' => array( + 'lines' => 'Generate diffs with lines of context instead of the usual two.', + ), + 'drupal dependencies' => array('features', 'diff'), + 'aliases' => array('fd'), + ); + + return $items; +} + +/** + * Implements hook_drush_help(). + */ +function features_drush_help($section) { + switch ($section) { + case 'drush:features': + return dt("List all the available features for your site."); + case 'drush:features-export': + return dt("Export a feature from your site into a module. If called with no arguments, display a list of available components. If called with a single argument, attempt to create a feature including the given component with the same name. The option '--destination=foo' may be used to specify the path (from Drupal root) where the feature should be created. The default destination is 'sites/all/modules/features'. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number."); + case 'drush:features-components': + return dt("List feature components matching patterns. The listing may be limited to exported/not-exported components. + +A component pattern consists of a source, a colon and a component. Both source and component may be a full name (as in \"dependencies\"), a shorthand (for instance \"dep\") or a pattern (like \"%denci%\"). + +Shorthands are unique shortenings of a name. They will only match if exactly one option contains the shorthand. So in a standard installation, \"dep\" will work for dependencies, but \"user\" wont, as it matches both user_permission and user_role. + +Patterns uses * or % for matching multiple sources/components. Unlike shorthands, patterns must match the whole name, so \"field:%article%\" should be used to select all fields containing \"article\" (which could both be those on the node type article, as well as those fields named article). * and % are equivalent, but the latter doesn't have to be escaped in UNIX shells. + +Lastly, a pattern without a colon is interpreted as having \":%\" appended, for easy listing of all components of a source. +"); + case 'drush:features-update': + return dt("Update a feature module on your site. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number."); + case 'drush:features-update-all': + return dt("Update all feature modules on your site."); + case 'drush:features-revert': + return dt("Revert a feature module on your site."); + case 'drush:features-revert-all': + return dt("Revert all enabled feature module on your site."); + case 'drush:features-diff': + return dt("Show a diff of a feature module."); + case 'drush:features-add': + return dt("Add a component to a feature module. The option '--version-set=foo' may be used to specify a version number for the feature or the option '--version-increment' may also to increment the feature's version number."); + } +} + +/** + * Get a list of all feature modules. + */ +function drush_features_list() { + $status = drush_get_option('status') ? drush_get_option('status') : 'all'; + if (!in_array($status, array('enabled', 'disabled', 'all'))) { + return drush_set_error('', dt('!status is not valid', array('!status' => $status))); + } + + module_load_include('inc', 'features', 'features.export'); + $rows = array(array(dt('Name'), dt('Feature'), dt('Status'), dt('Version'), dt('State'))); + + // Sort the Features list before compiling the output. + $features = features_get_features(NULL, TRUE); + ksort($features); + + foreach ($features as $k => $m) { + switch (features_get_storage($m->name)) { + case FEATURES_DEFAULT: + case FEATURES_REBUILDABLE: + $storage = ''; + break; + case FEATURES_OVERRIDDEN: + $storage = dt('Overridden'); + break; + case FEATURES_NEEDS_REVIEW: + $storage = dt('Needs review'); + break; + } + if ( + ($m->status == 0 && ($status == 'all' || $status == 'disabled')) || + ($m->status == 1 && ($status == 'all' || $status == 'enabled')) + ) { + $rows[] = array( + $m->info['name'], + $m->name, + $m->status ? dt('Enabled') : dt('Disabled'), + $m->info['version'], + $storage + ); + } + } + drush_print_table($rows, TRUE); +} + +/** + * List components, with pattern matching. + */ +function drush_features_components() { + $args = func_get_args(); + $components = _drush_features_component_list(); + // If no args supplied, prompt with a list. + if (empty($args)) { + $types = array_keys($components); + array_unshift($types, 'all'); + $choice = drush_choice($types, 'Enter a number to choose which component type to list.'); + if ($choice === FALSE) { + return; + } + + $args = ($choice == 0) ? array('*') : array($types[$choice]); + } + $options = array( + 'provided by' => TRUE, + ); + if (drush_get_option(array('exported', 'e'), NULL)) { + $options['not exported'] = FALSE; + } + elseif (drush_get_option(array('not-exported', 'o'), NULL)) { + $options['exported'] = FALSE; + } + + $filtered_components = _drush_features_component_filter($components, $args, $options); + if ($filtered_components){ + _drush_features_component_print($filtered_components); + } +} + +/** + * Returns a listing of all known components, indexed by source. + */ +function _drush_features_component_list() { + $components = array(); + foreach (features_get_feature_components() as $source => $info) { + if ($options = features_invoke($source, 'features_export_options')) { + foreach ($options as $name => $title) { + $components[$source][$name] = $title; + } + } + } + return $components; +} + +/** + * Filters components by patterns. + */ +function _drush_features_component_filter($all_components, $patterns = array(), $options = array()) { + $options += array( + 'exported' => TRUE, + 'not exported' => TRUE, + 'provided by' => FALSE, + ); + $pool = array(); + // Maps exported components to feature modules. + $components_map = features_get_component_map(); + // First filter on exported state. + foreach ($all_components as $source => $components) { + foreach ($components as $name => $title) { + $exported = sizeof($components_map[$source][$name]) > 0; + if ($exported) { + if ($options['exported']) { + $pool[$source][$name] = $title; + } + } + else { + if ($options['not exported']) { + $pool[$source][$name] = $title; + } + } + } + } + + $state_string = ''; + + if (!$options['exported']) { + $state_string = 'unexported'; + } + elseif (!$options['not exported']) { + $state_string = 'exported'; + } + + $selected = array(); + foreach ($patterns as $pattern) { + // Rewrite * to %. Let users use both as wildcard. + $pattern = strtr($pattern, array('*' => '%')); + $sources = array(); + list($source_pattern, $component_pattern) = explode(':', $pattern, 2); + // If source is empty, use a pattern. + if ($source_pattern == '') { + $source_pattern = '%'; + } + if ($component_pattern == '') { + $component_pattern = '%'; + } + + $preg_source_pattern = strtr(preg_quote($source_pattern, '/'), array('%' => '.*')); + $preg_component_pattern = strtr(preg_quote($component_pattern, '/'), array('%' => '.*')); + /* + * If it isn't a pattern, but a simple string, we don't anchor the + * pattern, this allows for abbreviating. Else, we do, as this seems more + * natural for patterns. + */ + if (strpos($source_pattern, '%') !== FALSE) { + $preg_source_pattern = '^' . $preg_source_pattern . '$'; + } + if (strpos($component_pattern, '%') !== FALSE) { + $preg_component_pattern = '^' . $preg_component_pattern . '$'; + } + $matches = array(); + + // Find the sources. + $all_sources = array_keys($pool); + $matches = preg_grep('/' . $preg_source_pattern . '/', $all_sources); + if (sizeof($matches) > 0) { + // If we have multiple matches and the source string wasn't a + // pattern, check if one of the matches is equal to the pattern, and + // use that, or error out. + if (sizeof($matches) > 1 and $preg_source_pattern[0] != '^') { + if (in_array($source_pattern, $matches)) { + $matches = array($source_pattern); + } + else { + return drush_set_error('', dt('Ambiguous source "!source", matches !matches', array('!source' => $source_pattern, '!matches' => join(', ', $matches)))); + } + } + // Loose the indexes preg_grep preserved. + $sources = array_values($matches); + } + else { + return drush_set_error('', dt('No !state sources match "!source"', array('!state' => $state_string, '!source' => $source_pattern))); + } + + + // Now find the components. + foreach ($sources as $source) { + // Find the components. + $all_components = array_keys($pool[$source]); + // See if there's any matches. + $matches = preg_grep('/' . $preg_component_pattern . '/', $all_components); + if (sizeof($matches) > 0) { + // If we have multiple matches and the components string wasn't a + // pattern, check if one of the matches is equal to the pattern, and + // use that, or error out. + if (sizeof($matches) > 1 and $preg_component_pattern[0] != '^') { + if (in_array($component_pattern, $matches)) { + $matches = array($component_pattern); + } + else { + return drush_set_error('', dt('Ambiguous component "!component", matches !matches', array('!component' => $component_pattern, '!matches' => join(', ', $matches)))); + } + } + if (!is_array($selected[$source])) { + $selected[$source] = array(); + } + $selected[$source] += array_intersect_key($pool[$source], array_flip($matches)); + } + else { + // No matches. If the source was a pattern, just carry on, else + // error out. Allows for patterns like :*field* + if ($preg_source_pattern[0] != '^') { + return drush_set_error('', dt('No !state !source components match "!component"', array('!state' => $state_string, '!component' => $component_pattern, '!source' => $source))); + } + } + } + } + + // Lastly, provide feature module information on the selected components, if + // requested. + $provided_by = array(); + if ($options['provided by'] && $options['exported'] ) { + foreach ($selected as $source => $components) { + foreach ($components as $name => $title) { + $exported = sizeof($components_map[$source][$name]) > 0; + if ($exported) { + $provided_by[$source . ':' . $name] = join(', ', $components_map[$source][$name]); + } + } + } + } + + return array( + 'components' => $selected, + 'sources' => $provided_by, + ); +} + +/** + * Prints a list of filtered components. + */ +function _drush_features_component_print($filtered_components) { + $rows = array(array(dt('Available sources'))); + foreach ($filtered_components['components'] as $source => $components) { + foreach ($components as $name => $value) { + $row = array($source .':'. $name); + if (isset($filtered_components['sources'][$source .':'. $name])) { + $row[] = dt('Provided by') . ': ' . $filtered_components['sources'][$source .':'. $name]; + } + $rows[] = $row; + } + } + + drush_print_table($rows, TRUE); +} + +/** + * Add a component to a features module, or create a new module with + * the selected components. + */ +function drush_features_export() { + if ($args = func_get_args()) { + $module = array_shift($args); + if (empty($args)) { + return drush_set_error('', 'No components supplied.'); + } + $components = _drush_features_component_list(); + $options = array( + 'exported' => FALSE, + ); + + $filtered_components = _drush_features_component_filter($components, $args, $options); + $items = $filtered_components['components']; + + if (empty($items)) { + return drush_set_error('', 'No components to add.'); + } + + $items = array_map('array_keys', $items); + + if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) { + module_load_include('inc', 'features', 'features.export'); + _features_populate($items, $feature->info, $feature->name); + _drush_features_export($feature->info, $feature->name, dirname($feature->filename)); + } + elseif ($feature) { + _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); + } + else { + // Same logic as in _drush_features_export. Should be refactored. + $destination = drush_get_option(array('destination'), 'sites/all/modules/features'); + $directory = isset($directory) ? $directory : $destination . '/' . $module; + drush_print(dt('Will create a new module in !dir', array('!dir' => $directory))); + if (!drush_confirm(dt('Do you really want to continue?'))) { + drush_die('Aborting.'); + } + $export = _drush_features_generate_export($items, $module); + _features_populate($items, $export[info], $export[name]); + _drush_features_export($export['info'], $module, $directory); + } + } + else { + return drush_set_error('', 'No feature name given.'); + } +} + +/** + * Add a component to a features module + * the selected components. + * + * This is DEPRECATED, but keeping it around for a bit longer for user migration + */ +function drush_features_add() { + drush_print(dt('features-add is DEPRECATED.')); + drush_print(dt('Calling features-export instead.')); + drush_features_export(); +} + + +/** + * Update an existing feature module. + */ +function drush_features_update() { + if ($args = func_get_args()) { + foreach ($args as $module) { + if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) { + _drush_features_export($feature->info, $feature->name, dirname($feature->filename)); + } + else if ($feature) { + _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); + } + else { + _features_drush_set_error($module); + } + } + } + else { + // By default just show contexts that are available. + $rows = array(array(dt('Available features'))); + foreach (features_get_features(NULL, TRUE) as $name => $info) { + $rows[] = array($name); + } + drush_print_table($rows, TRUE); + } +} + +/** + * Update all enabled features. Optionally pass in a list of features to + * exclude from being updated. + */ +function drush_features_update_all() { + $features_to_update = array(); + $features_to_exclude = func_get_args(); + foreach (features_get_features() as $module) { + if ($module->status && !in_array($module->name, $features_to_exclude)) { + $features_to_update[] = $module->name; + } + } + drush_print(dt('The following modules will be updated: !modules', array('!modules' => implode(', ', $features_to_update)))); + if (drush_confirm(dt('Do you really want to continue?'))) { + foreach ($features_to_update as $module_name) { + drush_invoke_process(drush_sitealias_get_record('@self'), 'features-update', array($module_name)); + } + } + else { + drush_die('Aborting.'); + } +} + +/** + * Write a module to the site dir. + * + * @param $info + * The feature info associative array. + * @param $module_name + * Optional. The name for the exported module. + */ +function _drush_features_export($info, $module_name = NULL, $directory = NULL) { + $root = drush_get_option(array('r', 'root'), drush_locate_root()); + if ($root) { + $destination = drush_get_option(array('destination'), 'sites/all/modules/features'); + $directory = isset($directory) ? $directory : $destination . '/' . $module_name; + if (is_dir($directory)) { + drush_print(dt('Module appears to already exist in !dir', array('!dir' => $directory))); + if (!drush_confirm(dt('Do you really want to continue?'))) { + drush_die('Aborting.'); + } + } + else { + drush_op('mkdir', $directory); + } + if (is_dir($directory)) { + drupal_flush_all_caches(); + $export = _drush_features_generate_export($info, $module_name); + $files = features_export_render($export, $module_name, TRUE); + foreach ($files as $extension => $file_contents) { + if (!in_array($extension, array('module', 'info'))) { + $extension .= '.inc'; + } + drush_op('file_put_contents', "{$directory}/{$module_name}.$extension", $file_contents); + } + drush_log(dt("Created module: !module in !directory", array('!module' => $module_name, '!directory' => $directory)), 'ok'); + } + else { + drush_die(dt('Couldn\'t create directory !directory', array('!directory' => $directory))); + } + } + else { + drush_die(dt('Couldn\'t locate site root')); + } +} + +/** + * Helper function for _drush_feature_export. + * + * @param $info + * The feature info associative array. + * @param $module_name + * Optional. The name for the exported module. + */ +function _drush_features_generate_export(&$info, &$module_name) { + module_load_include('inc', 'features', 'features.export'); + $export = features_populate($info, $module_name); + if (!features_load_feature($module_name)) { + $export['name'] = $module_name; + } + // Set the feature version if the --version-set or --version-increment option is passed. + if ($version = drush_get_option(array('version-set'))) { + preg_match('/^(?P\d+\.x)-(?P\d+)\.(?P\d+)-?(?P\w+)?$/', $version, $matches); + if (!isset($matches['core'], $matches['major'])) { + drush_die(dt('Please enter a valid version with core and major version number. Example: !example', array('!example' => '7.x-1.0'))); + } + $export['version'] = $version; + } + else if ($version = drush_get_option(array('version-increment'))) { + // Determine current version and increment it. + $export_load = features_export_prepare($export, $module_name); + $version = $export_load['version']; + $version_explode = explode('.', $version); + $version_minor = array_pop($version_explode); + // Increment minor version number if numeric or not a dev release. + if (is_numeric($version_minor) || strpos($version_minor, 'dev') !== (strlen($version_minor) - 3)) { + // Check for prefixed versions (alpha, beta, rc). + if (ctype_digit($version_minor)) { + ++$version_minor; + } + else { + // Split version number parts. + $pattern = '/([0-9]-[a-z]+([0-9])+)/'; + $matches = array(); + preg_match($pattern, $version_minor, $matches); + $number = array_pop($matches); + ++$number; + $pattern = '/[0-9]+$/'; + $version_minor = preg_replace($pattern, $number, $version_minor); + } + } + array_push($version_explode, $version_minor); + // Rebuild version string. + $version = implode('.', $version_explode); + $export['version'] = $version; + } + return $export; +} + +/** + * Revert a feature to it's code definition. + * Optionally accept a list of components to revert. + */ +function drush_features_revert() { + if ($args = func_get_args()) { + module_load_include('inc', 'features', 'features.export'); + features_include(); + + // Determine if revert should be forced. + $force = drush_get_option('force'); + + // Parse list of arguments. + $modules = array(); + foreach ($args as $arg) { + $arg = explode('.', $arg); + $module = array_shift($arg); + $component = array_shift($arg); + + if (isset($module)) { + if (empty($component)) { + // If we received just a feature name, this means that we need all of it's components. + $modules[$module] = TRUE; + } + elseif ($modules[$module] !== TRUE) { + if (!isset($modules[$module])) { + $modules[$module] = array(); + } + $modules[$module][] = $component; + } + } + } + + // Process modules. + foreach ($modules as $module => $components_needed) { + if (($feature = features_load_feature($module, TRUE)) && module_exists($module)) { + + $components = array(); + // Forcefully revert all components of a feature. + if ($force) { + foreach (array_keys($feature->info['features']) as $component) { + if (features_hook($component, 'features_revert')) { + $components[] = $component; + } + } + } + // Only revert components that are detected to be Overridden/Needs review/rebuildable. + else { + $states = features_get_component_states(array($feature->name), FALSE); + foreach ($states[$feature->name] as $component => $state) { + if (in_array($state, array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW, FEATURES_REBUILDABLE)) && features_hook($component, 'features_revert')) { + $components[] = $component; + } + } + } + + if (!empty($components_needed) && is_array($components_needed)) { + $components = array_intersect($components, $components_needed); + } + if (empty($components)) { + drush_log(dt('Current state already matches defaults, aborting.'), 'ok'); + } + else { + foreach ($components as $component) { + if (drush_confirm(dt('Do you really want to revert @component?', array('@component' => $component)))) { + features_revert(array($module => array($component))); + drush_log(dt('Reverted @component.', array('@component' => $component)), 'ok'); + } + else { + drush_log(dt('Skipping @component.', array('@component' => $component)), 'ok'); + } + } + } + } + else if ($feature) { + _features_drush_set_error($module, 'FEATURES_FEATURE_NOT_ENABLED'); + } + else { + _features_drush_set_error($module); + } + } + } + else { + drush_features_list(); + return; + } +} + +/** + * Revert all enabled features to their definitions in code. + * + * @param ... + * (Optional) A list of features to exclude from being reverted. + */ +function drush_features_revert_all() { + module_load_include('inc', 'features', 'features.export'); + $force = drush_get_option('force'); + $features_to_exclude = func_get_args(); + + $features_to_revert = array(); + foreach (features_get_features(NULL, TRUE) as $module) { + if ($module->status && !in_array($module->name, $features_to_exclude)) { + // If forced, add module regardless of status. + if ($force) { + $features_to_revert[] = $module->name; + } + else { + switch (features_get_storage($module->name)) { + case FEATURES_OVERRIDDEN: + case FEATURES_NEEDS_REVIEW: + case FEATURES_REBUILDABLE: + $features_to_revert[] = $module->name; + break; + } + } + } + } + + if ($features_to_revert) { + drush_print(dt('The following modules will be reverted: !modules', array('!modules' => implode(', ', $features_to_revert)))); + if (drush_confirm(dt('Do you really want to continue?'))) { + foreach ($features_to_revert as $module) { + drush_invoke_process(drush_sitealias_get_record('@self'), 'features-revert', array($module), array('force' => $force, '#integrate' => TRUE)); + } + } + else { + return drush_user_abort('Aborting.'); + } + } + else { + drush_log(dt('Current state already matches defaults, aborting.'), 'ok'); + } +} + +/** + * Show the diff of a feature module. + */ +function drush_features_diff() { + if (!$args = func_get_args()) { + drush_features_list(); + return; + } + $module = $args[0]; + $feature = features_load_feature($module); + if (!module_exists($module)) { + drush_log(dt('No such feature is enabled: ' . $module), 'error'); + return; + } + module_load_include('inc', 'features', 'features.export'); + $overrides = features_detect_overrides($feature); + if (empty($overrides)) { + drush_log(dt('Feature is in its default state. No diff needed.'), 'ok'); + return; + } + module_load_include('inc', 'diff', 'diff.engine'); + + if (!class_exists('DiffFormatter')) { + if (drush_confirm(dt('It seems that the Diff module is not available. Would you like to download and enable it?'))) { + // Download it if it's not already here. + $project_info = drush_get_projects(); + if (empty($project_info['diff']) && !drush_invoke_process(drush_sitealias_get_record('@self'), 'dl', array('diff'), array('#integrate' => TRUE))) { + return drush_set_error(dt('Diff module could not be downloaded.')); + } + + if (!drush_invoke_process(drush_sitealias_get_record('@self'), 'en', array('diff'), array('#integrate' => TRUE))) { + return drush_set_error(dt('Diff module could not be enabled.')); + } + } + else { + return drush_set_error(dt('Diff module is not enabled.')); + } + // If we're still here, now we can include the diff.engine again. + module_load_include('inc', 'diff', 'diff.engine'); + } + + $lines = (int) drush_get_option('lines'); + $lines = $lines > 0 ? $lines : 2; + + $formatter = new DiffFormatter(); + $formatter->leading_context_lines = $lines; + $formatter->trailing_context_lines = $lines; + $formatter->show_header = FALSE; + + if (drush_get_context('DRUSH_NOCOLOR')) { + $red = $green = "%s"; + } + else { + $red = "\033[31;40m\033[1m%s\033[0m"; + $green = "\033[0;32;40m\033[1m%s\033[0m"; + } + + // Print key for colors + drush_print(dt('Legend: ')); + drush_print(sprintf($red, dt('Code: drush features-revert will remove the overrides.'))); + drush_print(sprintf($green, dt('Overrides: drush features-update will update the exported feature with the displayed overrides'))); + drush_print(); + + foreach ($overrides as $component => $items) { + $diff = new Diff(explode("\n", $items['default']), explode("\n", $items['normal'])); + drush_print(); + drush_print(dt("Component: !component", array('!component' => $component))); + $rows = explode("\n", $formatter->format($diff)); + foreach ($rows as $row) { + if (strpos($row, '>') === 0) { + drush_print(sprintf($green, $row)); + } + elseif (strpos($row, '<') === 0) { + drush_print(sprintf($red, $row)); + } + else { + drush_print($row); + } + } + } +} + +/** + * Helper function to call drush_set_error(). + * + * @param $feature + * The string name of the feature. + * @param $error + * A text string identifying the type of error. + * @return + * FALSE. See drush_set_error(). + */ +function _features_drush_set_error($feature, $error = '') { + $args = array('!feature' => $feature); + + switch ($error) { + case 'FEATURES_FEATURE_NOT_ENABLED': + $message = 'The feature !feature is not enabled.'; + break; + case 'FEATURES_COMPONENT_NOT_FOUND': + $message = 'The given component !feature could not be found.'; + break; + default: + $error = 'FEATURES_FEATURE_NOT_FOUND'; + $message = 'The feature !feature could not be found.'; + } + + return drush_set_error($error, dt($message, $args)); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.export.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.export.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1005 @@ + array(), 'dependencies' => array(), 'conflicts' => array()) + $info + array('features_exclude' => array()); + $export = _features_populate($items, $stub, $module_name, TRUE); + + // Add Features API version. Any module with this entry in the .info file + // will be treated as a Feature and included in the admin/build/features UI. + $export['features']['features_api']['api:' . FEATURES_API] = TRUE; + // Allow other modules to alter the export. + drupal_alter('features_export', $export, $module_name); + + // Clean up and standardize order + foreach (array_keys($export['features']) as $k) { + ksort($export['features'][$k]); + } + ksort($export['features']); + ksort($export['dependencies']); + ksort($export['features_exclude']); + + return $export; +} + +/** + * Iterate and descend into a feature definition to extract module + * dependencies and feature definition. Calls hook_features_export for modules + * that implement it. + * + * @param $pipe + * Associative of array of module => info-for-module + * @param $export + * Associative array of items, and module dependencies which define a feature. + * Passed by reference. + * + * @return fully populated $export array. + */ +function _features_populate($pipe, &$export, $module_name = '', $reset = FALSE) { + static $processed = array(); + features_include(); + if ($reset) { + $processed = array(); + } + foreach ($pipe as $component => $data) { + // Convert already defined items to dependencies. +// _features_resolve_dependencies($data, $export, $module_name, $component); + // Remove any excluded items. + if (!empty($export['features_exclude'][$component])) { + $data = array_diff($data, $export['features_exclude'][$component]); + if ($component == 'dependencies' && !empty($export['dependencies'])) { + $export['dependencies'] = array_diff($export['dependencies'], $export['features_exclude'][$component]); + } + } + if (!empty($data) && $function = features_hook($component, 'features_export')) { + // Pass module-specific data and export array. + // We don't use features_invoke() here since we need to pass $export by reference. + $more = $function($data, $export, $module_name, $component); + // Add the context information. + $export['component'] = $component; + $export['module_name'] = $module_name; + // Allow other modules to manipulate the pipe to add in additional modules. + drupal_alter(array('features_pipe', 'features_pipe_' . $component), $more, $data, $export); + // Remove the component information. + unset($export['component']); + unset($export['module_name']); + // Allow for export functions to request additional exports, but avoid + // circular references on already processed components. + $processed[$component] = isset($processed[$component]) ? array_merge($processed[$component], $data) : $data; + + if (!empty($more)) { + // Remove already processed components. + foreach ($more as $component_name => $component_data) { + if (isset($processed[$component_name])) { + $more[$component_name] = array_diff($component_data, $processed[$component_name]); + } + } + if ($more = array_filter($more)) { + _features_populate($more, $export, $module_name); + } + } + } + } + return $export; +} + +/** + * Iterates over data and convert to dependencies if already defined elsewhere. + */ +function _features_resolve_dependencies(&$data, &$export, $module_name, $component) { + if ($map = features_get_default_map($component)) { + foreach ($data as $key => $item) { + // If this node type is provided by a different module, add it as a dependency + if (isset($map[$item]) && $map[$item] != $module_name) { + $export['dependencies'][$map[$item]] = $map[$item]; + unset($data[$key]); + } + } + } +} + +/** + * Iterates over a list of dependencies and kills modules that are + * captured by other modules 'higher up'. + */ +function _features_export_minimize_dependencies($dependencies, $module_name = '') { + // Ensure that the module doesn't depend upon itself + if (!empty($module_name) && !empty($dependencies[$module_name])) { + unset($dependencies[$module_name]); + } + + // Do some cleanup: + // - Remove modules required by Drupal core. + // - Protect against direct circular dependencies. + // - Remove "intermediate" dependencies. + $required = drupal_required_modules(); + foreach ($dependencies as $k => $v) { + if (empty($v) || in_array($v, $required)) { + unset($dependencies[$k]); + } + else { + $module = features_get_modules($v); + if ($module && !empty($module->info['dependencies'])) { + // If this dependency depends on the module itself, we have a circular dependency. + // Don't let it happen. Only you can prevent forest fires. + if (in_array($module_name, $module->info['dependencies'])) { + unset($dependencies[$k]); + } + // Iterate through the dependency's dependencies and remove any dependencies + // that are captured by it. + else { + foreach ($module->info['dependencies'] as $j => $dependency) { + if (array_search($dependency, $dependencies) !== FALSE) { + $position = array_search($dependency, $dependencies); + unset($dependencies[$position]); + } + } + } + } + } + } + return drupal_map_assoc(array_unique($dependencies)); +} + +/** + * Iterates over a list of dependencies and maximize the list of modules. + */ +function _features_export_maximize_dependencies($dependencies, $module_name = '', $maximized = array(), $first = TRUE) { + foreach ($dependencies as $k => $v) { + $parsed_dependency = drupal_parse_dependency($v); + $name = $parsed_dependency['name']; + if (!in_array($name, $maximized)) { + $maximized[] = $name; + $module = features_get_modules($name); + if ($module && !empty($module->info['dependencies'])) { + $maximized = array_merge($maximized, _features_export_maximize_dependencies($module->info['dependencies'], $module_name, $maximized, FALSE)); + } + } + } + return array_unique($maximized); +} + +/** + * Prepare a feature export array into a finalized info array. + * @param $export + * An exported feature definition. + * @param $module_name + * The name of the module to be exported. + * @param $reset + * Boolean flag for resetting the module cache. Only set to true when + * doing a final export for delivery. + */ +function features_export_prepare($export, $module_name, $reset = FALSE, $add_deprecated = TRUE) { + $existing = features_get_modules($module_name, $reset); + + // copy certain exports directly into info + $copy_list = array('scripts', 'stylesheets'); + foreach ($copy_list as $item) { + if(isset($export[$item])) { + $existing->info[$item] = $export[$item]; + } + } + + // Prepare info string -- if module exists, merge into its existing info file + $defaults = !empty($existing->info) ? $existing->info : array('core' => '7.x', 'package' => 'Features'); + $export = array_merge($defaults, $export); + + $deprecated = features_get_deprecated(); + // Cleanup info array + foreach ($export['features'] as $component => $data) { + // if performing the final export, do not export deprecated components + if (($reset || !$add_deprecated) && !empty($deprecated[$component])) { + unset($export['features'][$component]); + } + else { + $export['features'][$component] = array_keys($data); + } + } + if (isset($export['dependencies'])) { + $export['dependencies'] = array_values($export['dependencies']); + } + if (isset($export['conflicts'])) { + unset($export['conflicts']); + } + + // Order info array. + $standard_info = array(); + foreach (array_merge(array('name', 'description', 'core', 'package', 'version', 'project', 'dependencies'), $copy_list) as $item) { + if (isset($export[$item])) { + $standard_info[$item] = $export[$item]; + } + } + if (isset($export['php']) && ($export['php'] != DRUPAL_MINIMUM_PHP)) { + $standard_info['php'] = $export['php']; + } + unset($export['php']); + + $export = features_array_diff_assoc_recursive($export, $standard_info); + ksort($export); + return array_merge($standard_info, $export); +} + +/** + * Generate an array of hooks and their raw code. + */ +function features_export_render_hooks($export, $module_name, $reset = FALSE) { + features_include(); + $code = array(); + + // Sort components to keep exported code consistent + ksort($export['features']); + + foreach ($export['features'] as $component => $data) { + if (!empty($data)) { + // Sort the items so that we don't generate different exports based on order + asort($data); + if (features_hook($component, 'features_export_render')) { + $hooks = features_invoke($component, 'features_export_render', $module_name, $data, $export); + $code[$component] = $hooks; + } + } + } + return $code; +} + +/** + * Render feature export into an array representing its files. + * + * @param $export + * An exported feature definition. + * @param $module_name + * The name of the module to be exported. + * @param $reset + * Boolean flag for resetting the module cache. Only set to true when + * doing a final export for delivery. + * + * @return array of info file and module file contents. + */ +function features_export_render($export, $module_name, $reset = FALSE) { + $code = array(); + + // Generate hook code + $component_hooks = features_export_render_hooks($export, $module_name, $reset); + $components = features_get_components(); + $deprecated = features_get_deprecated($components); + + // Group component code into their respective files + foreach ($component_hooks as $component => $hooks) { + if ($reset && !empty($deprecated[$component])) { + // skip deprecated components on final export + continue; + } + $file = array('name' => 'features'); + if (isset($components[$component]['default_file'])) { + switch ($components[$component]['default_file']) { + case FEATURES_DEFAULTS_INCLUDED: + $file['name'] = "features.$component"; + break; + case FEATURES_DEFAULTS_CUSTOM: + $file['name'] = $components[$component]['default_filename']; + break; + } + } + + if (!isset($code[$file['name']])) { + $code[$file['name']] = array(); + } + + foreach ($hooks as $hook_name => $hook_info) { + $hook_code = is_array($hook_info) ? $hook_info['code'] : $hook_info; + $hook_args = is_array($hook_info) && !empty($hook_info['args']) ? $hook_info['args'] : ''; + $hook_file = is_array($hook_info) && !empty($hook_info['file']) ? $hook_info['file'] : $file['name']; + $code[$hook_file][$hook_name] = features_export_render_defaults($module_name, $hook_name, $hook_code, $hook_args); + } + } + + // Finalize strings to be written to files + $code = array_filter($code); + foreach ($code as $filename => $contents) { + $code[$filename] = "filename); + + // If the current module file does not reference the features.inc include, + // @TODO this way of checking does not account for the possibility of inclusion instruction being commented out. + if (isset($code['features']) && strpos($code['module'], "{$module_name}.features.inc") === FALSE) { + // If .module does not begin with "{$module_name}.module", '@include' => "{$module_name}.features.inc")), 'warning'); + } + else { + // Remove the old message if it exists, else just remove the name, $deprecated_files, TRUE)) { + features_log(t('The file @filename has been deprecated and can be removed.', array('@filename' => $file->filename)), 'status'); + } + elseif ($file->name === "{$module_name}.features" && empty($code['features'])) { + // Try and remove features.inc include. + if (strpos($code['module'], "{$module_name}.features.inc")) { + $code['module'] = str_replace($modulefile_features_inc, $modulefile_blank, $code['module']); + } + // If unable to remove the include, add a message to remove. + if (strpos($code['module'], "{$module_name}.features.inc")) { + $code['features'] = "name])) { + // Rebuild feature from .info file description and prepare an export from current DB state. + $export = features_populate($module->info, $module->name); + $export = features_export_prepare($export, $module->name, FALSE, FALSE); + + $overridden = array(); + + // Compare feature info + _features_sanitize($module->info); + _features_sanitize($export); + + $compare = array('normal' => features_export_info($export), 'default' => features_export_info($module->info)); + if ($compare['normal'] !== $compare['default']) { + $overridden['info'] = $compare; + } + + // Collect differences at a per-component level + $states = features_get_component_states(array($module->name), FALSE); + foreach ($states[$module->name] as $component => $state) { + if ($state != FEATURES_DEFAULT) { + $normal = features_get_normal($component, $module->name); + $default = features_get_default($component, $module->name); + _features_sanitize($normal); + _features_sanitize($default); + + $compare = array('normal' => features_var_export($normal), 'default' => features_var_export($default)); + if (_features_linetrim($compare['normal']) !== _features_linetrim($compare['default'])) { + $overridden[$component] = $compare; + } + } + } + $cache[$module->name] = $overridden; + } + return $cache[$module->name]; +} + +/** + * Gets the available default hooks keyed by components. + */ +function features_get_default_hooks($component = NULL, $reset = FALSE) { + return features_get_components($component, 'default_hook', $reset); +} + +/** + * Gets the available default hooks keyed by components. + */ +function features_get_default_alter_hook($component) { + $default_hook = features_get_components($component, 'default_hook'); + $alter_hook = features_get_components($component, 'alter_hook'); + $alter_type = features_get_components($component, 'alter_type'); + return empty($alter_type) || $alter_type != 'none' ? ($alter_hook ? $alter_hook : $default_hook) : FALSE; +} + +/** + * Return a code string representing an implementation of a defaults module hook. + */ +function features_export_render_defaults($module, $hook, $code, $args = '') { + $output = array(); + $output[] = "/**"; + $output[] = " * Implements hook_{$hook}()."; + $output[] = " */"; + $output[] = "function {$module}_{$hook}(" . $args . ") {"; + $output[] = $code; + $output[] = "}"; + return implode("\n", $output); +} + +/** + * Generate code friendly to the Drupal .info format from a structured array. + * + * @param $info + * An array or single value to put in a module's .info file. + * @param $parents + * Array of parent keys (internal use only). + * + * @return + * A code string ready to be written to a module's .info file. + */ +function features_export_info($info, $parents = array()) { + $output = ''; + if (is_array($info)) { + foreach ($info as $k => $v) { + $child = $parents; + $child[] = $k; + $output .= features_export_info($v, $child); + } + } + else if (!empty($info) && count($parents)) { + $line = array_shift($parents); + foreach ($parents as $key) { + $line .= is_numeric($key) ? "[]" : "[{$key}]"; + } + $line .= " = {$info}\n"; + return $line; + } + return $output; +} + +/** + * Tar creation function. Written by dmitrig01. + * + * @param $name + * Filename of the file to be tarred. + * @param $contents + * String contents of the file. + * + * @return + * A string of the tar file contents. + */ +function features_tar_create($name, $contents) { + /* http://www.mkssoftware.com/docs/man4/tar.4.asp */ + /* http://www.phpclasses.org/browse/file/21200.html */ + $tar = ''; + $bigheader = $header = ''; + if (strlen($name) > 100) { + $bigheader = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", + '././@LongLink', '0000000', '0000000', '0000000', + sprintf("%011o", strlen($name)), '00000000000', + ' ', 'L', '', 'ustar ', '0', + '', '', '', '', '', ''); + + $bigheader .= str_pad($name, floor((strlen($name) + 512 - 1) / 512) * 512, "\0"); + + $checksum = 0; + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($bigheader, $i, 1)); + } + $bigheader = substr_replace($bigheader, sprintf("%06o", $checksum)."\0 ", 148, 8); + } + $header = pack("a100a8a8a8a12a12a8a1a100a6a2a32a32a8a8a155a12", // book the memorie area + substr($name,0,100), // 0 100 File name + '100644 ', // File permissions + ' 765 ', // UID, + ' 765 ', // GID, + sprintf("%11s ", decoct(strlen($contents))), // Filesize, + sprintf("%11s", decoct(REQUEST_TIME)), // Creation time + ' ', // 148 8 Check sum for header block + '', // 156 1 Link indicator / ustar Type flag + '', // 157 100 Name of linked file + 'ustar ', // 257 6 USTAR indicator "ustar" + ' ', // 263 2 USTAR version "00" + '', // 265 32 Owner user name + '', // 297 32 Owner group name + '', // 329 8 Device major number + '', // 337 8 Device minor number + '', // 345 155 Filename prefix + ''); // 500 12 ?? + + $checksum = 0; + for ($i = 0; $i < 512; $i++) { + $checksum += ord(substr($header, $i, 1)); + } + $header = substr_replace($header, sprintf("%06o", $checksum)."\0 ", 148, 8); + $tar = $bigheader.$header; + + $buffer = str_split($contents, 512); + foreach ($buffer as $item) { + $tar .= pack("a512", $item); + } + return $tar; +} + +/** + * Export var function -- from Views. + */ +function features_var_export($var, $prefix = '', $init = TRUE, $count = 0) { + if ($count > 50) { + watchdog('features', 'Recursion depth reached in features_var_export', array()); + return ''; + } + + if (is_object($var)) { + $output = method_exists($var, 'export') ? $var->export() : features_var_export((array) $var, '', FALSE, $count+1); + } + else if (is_array($var)) { + if (empty($var)) { + $output = 'array()'; + } + else { + $output = "array(\n"; + foreach ($var as $key => $value) { + // Using normal var_export on the key to ensure correct quoting. + $output .= " " . var_export($key, TRUE) . " => " . features_var_export($value, ' ', FALSE, $count+1) . ",\n"; + } + $output .= ')'; + } + } + else if (is_bool($var)) { + $output = $var ? 'TRUE' : 'FALSE'; + } + else if (is_int($var)) { + $output = intval($var); + } + else if (is_numeric($var)) { + $output = floatval($var); + } + else if (is_string($var) && strpos($var, "\n") !== FALSE) { + // Replace line breaks in strings with a token for replacement + // at the very end. This protects whitespace in strings from + // unintentional indentation. + $var = str_replace("\n", "***BREAK***", $var); + $output = var_export($var, TRUE); + } + else { + $output = var_export($var, TRUE); + } + + if ($prefix) { + $output = str_replace("\n", "\n$prefix", $output); + } + + if ($init) { + $output = str_replace("***BREAK***", "\n", $output); + } + + return $output; +} + +/** + * Helper function to return an array of t()'d translatables strings. + * Useful for providing a separate array of translatables with your + * export so that string extractors like potx can detect them. + */ +function features_translatables_export($translatables, $indent = '') { + $output = ''; + $translatables = array_filter(array_unique($translatables)); + if (!empty($translatables)) { + $output .= "{$indent}// Translatables\n"; + $output .= "{$indent}// Included for use with string extractors like potx.\n"; + sort($translatables); + foreach ($translatables as $string) { + $output .= "{$indent}t(" . features_var_export($string) . ");\n"; + } + } + return $output; +} + +/** + * Get a summary storage state for a feature. + */ +function features_get_storage($module_name) { + // Get component states, and array_diff against array(FEATURES_DEFAULT). + // If the returned array has any states that don't match FEATURES_DEFAULT, + // return the highest state. + $states = features_get_component_states(array($module_name), FALSE); + $states = array_diff($states[$module_name], array(FEATURES_DEFAULT)); + $storage = !empty($states) ? max($states) : FEATURES_DEFAULT; + return $storage; +} + +/** + * Wrapper around features_get_[storage] to return an md5hash of a normalized + * defaults/normal object array. Can be used to compare normal/default states + * of a module's component. + */ +function features_get_signature($state = 'default', $module_name, $component, $reset = FALSE) { + switch ($state) { + case 'cache': + $codecache = variable_get('features_codecache', array()); + return isset($codecache[$module_name][$component]) ? $codecache[$module_name][$component] : FALSE; + case 'default': + $objects = features_get_default($component, $module_name, TRUE, $reset); + break; + case 'normal': + $objects = features_get_normal($component, $module_name, $reset); + break; + } + if (!empty($objects)) { + $objects = (array) $objects; + _features_sanitize($objects); + return md5(_features_linetrim(features_var_export($objects))); + } + return FALSE; +} + +/** + * Set the signature of a module/component pair in the codecache. + */ +function features_set_signature($module, $component, $signature = NULL) { + $var_codecache = variable_get('features_codecache', array()); + $signature = isset($signature) ? $signature : features_get_signature('default', $module, $component, TRUE); + $var_codecache[$module][$component] = $signature; + variable_set('features_codecache', $var_codecache); +} + +/** + * Processing semaphore operations. + */ +function features_semaphore($op, $component) { + // Note: we don't use variable_get() here as the inited variable + // static cache may be stale. Retrieving directly from the DB narrows + // the possibility of collision. + $semaphore = db_query("SELECT value FROM {variable} WHERE name = :name", array(':name' => 'features_semaphore'))->fetchField(); + $semaphore = !empty($semaphore) ? unserialize($semaphore) : array(); + + switch ($op) { + case 'get': + return isset($semaphore[$component]) ? $semaphore[$component] : FALSE; + case 'set': + $semaphore[$component] = REQUEST_TIME; + variable_set('features_semaphore', $semaphore); + break; + case 'del': + if (isset($semaphore[$component])) { + unset($semaphore[$component]); + variable_set('features_semaphore', $semaphore); + } + break; + } +} + +/** + * Get normal objects for a given module/component pair. + */ +function features_get_normal($component, $module_name, $reset = FALSE) { + static $cache; + if (!isset($cache) || $reset) { + $cache = array(); + } + if (!isset($cache[$module_name][$component])) { + features_include(); + $code = NULL; + $module = features_get_features($module_name); + + // Special handling for dependencies component. + if ($component === 'dependencies') { + $cache[$module_name][$component] = isset($module->info['dependencies']) ? array_filter($module->info['dependencies'], 'module_exists') : array(); + } + // All other components. + else { + $default_hook = features_get_default_hooks($component); + if ($module && $default_hook && isset($module->info['features'][$component]) && features_hook($component, 'features_export_render')) { + $code = features_invoke($component, 'features_export_render', $module_name, $module->info['features'][$component], NULL); + $cache[$module_name][$component] = isset($code[$default_hook]) ? eval($code[$default_hook]) : FALSE; + } + } + + // Clear out vars for memory's sake. + unset($code); + unset($module); + } + return isset($cache[$module_name][$component]) ? $cache[$module_name][$component] : FALSE; +} + +/** + * Get defaults for a given module/component pair. + */ +function features_get_default($component, $module_name = NULL, $alter = TRUE, $reset = FALSE) { + static $cache = array(); + $alter = !empty($alter); // ensure $alter is a true/false boolean + features_include(); + features_include_defaults($component); + $default_hook = features_get_default_hooks($component); + $components = features_get_components(); + + // Collect defaults for all modules if no module name was specified. + if (isset($module_name)) { + $modules = array($module_name); + } + else { + if ($component === 'dependencies') { + $modules = array_keys(features_get_features()); + } + else { + $modules = array(); + foreach (features_get_component_map($component) as $component_modules) { + $modules = array_merge($modules, $component_modules); + } + $modules = array_unique($modules); + } + } + + // Collect and cache information for each specified module. + foreach ($modules as $m) { + if (!isset($cache[$component][$alter][$m]) || $reset) { + // Special handling for dependencies component. + if ($component === 'dependencies') { + $module = features_get_features($m); + $cache[$component][$alter][$m] = isset($module->info['dependencies']) ? $module->info['dependencies'] : array(); + unset($module); + } + // All other components + else { + if ($default_hook && module_hook($m, $default_hook)) { + $cache[$component][$alter][$m] = call_user_func("{$m}_{$default_hook}"); + if (is_array($cache[$component][$alter][$m])) { + $alter_type = features_get_components('alter_type', $component); + if ($alter && (!isset($alter_type) || $alter_type == FEATURES_ALTER_TYPE_NORMAL)) { + if ($alter_hook = features_get_default_alter_hook($component)) { + drupal_alter($alter_hook, $cache[$component][$alter][$m]); + } + } + } + else { + $cache[$component][$alter][$m] = FALSE; + } + } + else { + $cache[$component][$alter][$m] = FALSE; + } + } + } + } + + // A specific module was specified. Retrieve only its components. + if (isset($module_name)) { + return isset($cache[$component][$alter][$module_name]) ? $cache[$component][$alter][$module_name] : FALSE; + } + // No module was specified. Retrieve all components. + $all_defaults = array(); + if (isset($cache[$component][$alter])) { + foreach (array_filter($cache[$component][$alter]) as $module_components) { + $all_defaults = array_merge($all_defaults, $module_components); + } + } + return $all_defaults; +} + +/** + * Get a map of components to their providing modules. + */ +function features_get_default_map($component, $attribute = NULL, $callback = NULL, $reset = FALSE) { + static $map = array(); + + global $features_ignore_conflicts; + if ($features_ignore_conflicts) { + return FALSE; + } + + features_include(); + features_include_defaults($component); + if ((!isset($map[$component]) || $reset) && $default_hook = features_get_default_hooks($component)) { + $map[$component] = array(); + foreach (module_implements($default_hook) as $module) { + if ($defaults = features_get_default($component, $module)) { + foreach ($defaults as $key => $object) { + if (isset($callback)) { + if ($object_key = $callback($object)) { + $map[$component][$object_key] = $module; + } + } + elseif (isset($attribute)) { + if (is_object($object) && isset($object->{$attribute})) { + $map[$component][$object->{$attribute}] = $module; + } + elseif (is_array($object) && isset($object[$attribute])) { + $map[$component][$object[$attribute]] = $module; + } + } + elseif (!isset($attribute) && !isset($callback)) { + if (!is_numeric($key)) { + $map[$component][$key] = $module; + } + } + else { + return FALSE; + } + } + } + } + } + return isset($map[$component]) ? $map[$component] : FALSE; +} + +/** + * Retrieve an array of features/components and their current states. + */ +function features_get_component_states($features = array(), $rebuild_only = TRUE, $reset = FALSE) { + static $cache; + if (!isset($cache) || $reset) { + $cache = array(); + } + + $features = !empty($features) ? $features : array_keys(features_get_features()); + + // Retrieve only rebuildable components if requested. + features_include(); + $components = array_keys(features_get_components()); + if ($rebuild_only) { + foreach ($components as $k => $component) { + if (!features_hook($component, 'features_rebuild')) { + unset($components[$k]); + } + } + } + + foreach ($features as $feature) { + $cache[$feature] = isset($cache[$feature]) ? $cache[$feature] : array(); + if (module_exists($feature)) { + foreach ($components as $component) { + if (!isset($cache[$feature][$component])) { + $normal = features_get_signature('normal', $feature, $component, $reset); + $default = features_get_signature('default', $feature, $component, $reset); + $codecache = features_get_signature('cache', $feature, $component, $reset); + $semaphore = features_semaphore('get', $component); + + // DB and code states match, there is nothing more to check. + if ($normal == $default) { + $cache[$feature][$component] = FEATURES_DEFAULT; + + // Stale semaphores can be deleted. + features_semaphore('del', $component); + + // Update code cache if it is stale, clear out semaphore if it stale. + if ($default != $codecache) { + features_set_signature($feature, $component, $default); + } + } + // Component properly implements exportables. + else if (!features_hook($component, 'features_rebuild')) { + $cache[$feature][$component] = FEATURES_OVERRIDDEN; + } + // Component does not implement exportables. + else { + if (empty($semaphore)) { + // Exception for dependencies. Dependencies are always rebuildable. + if ($component === 'dependencies') { + $cache[$feature][$component] = FEATURES_REBUILDABLE; + } + // All other rebuildable components require comparison. + else { + // Code has not changed, but DB does not match. User has DB overrides. + if ($codecache == $default) { + $cache[$feature][$component] = FEATURES_OVERRIDDEN; + } + // DB has no modifications to prior code state (or this is initial install). + else if (empty($codecache) || $codecache == $normal) { + $cache[$feature][$component] = FEATURES_REBUILDABLE; + } + // None of the states match. Requires user intervention. + else if ($codecache != $default) { + $cache[$feature][$component] = FEATURES_NEEDS_REVIEW; + } + } + } + else { + // Semaphore is still within processing horizon. Do nothing. + if ((REQUEST_TIME - $semaphore) < FEATURES_SEMAPHORE_TIMEOUT) { + $cache[$feature][$component] = FEATURES_REBUILDING; + } + // A stale semaphore means a previous rebuild attempt did not complete. + // Attempt to complete the rebuild. + else { + $cache[$feature][$component] = FEATURES_REBUILDABLE; + } + } + } + } + } + } + } + + // Filter cached components on the way out to ensure that even if we have + // cached more data than has been requested, the return value only reflects + // the requested features/components. + $return = $cache; + $return = array_intersect_key($return, array_flip($features)); + foreach ($return as $k => $v) { + $return[$k] = array_intersect_key($return[$k], array_flip($components)); + } + return $return; +} + +/** + * Helper function to eliminate whitespace differences in code. + */ +function _features_linetrim($code) { + $code = explode("\n", $code); + foreach ($code as $k => $line) { + $code[$k] = trim($line); + } + return implode("\n", $code); +} + +/** + * "Sanitizes" an array recursively, performing two key operations: + * - Sort an array by its keys (assoc) or values (non-assoc) + * - Remove any null or empty values for associative arrays (array_filter()). + */ +function _features_sanitize(&$array) { + if (is_array($array)) { + $is_assoc = _features_is_assoc($array); + if ($is_assoc) { + ksort($array, SORT_STRING); + $array = array_filter($array); + } + else { + sort($array); + } + foreach ($array as $k => $v) { + if (is_array($v)) { + _features_sanitize($array[$k]); + if ($is_assoc && empty($array[$k])) { + unset($array[$k]); + } + } + } + } +} + +/** + * Is the given array an associative array. This basically extracts the keys twice to get the + * numerically ordered keys. It then does a diff with the original array and if there is no + * key diff then the original array is not associative. + * + * NOTE: If you have non-sequential numerical keys, this will identify the array as assoc. + * + * Borrowed from: http://www.php.net/manual/en/function.is-array.php#96724 + * + * @return True is the array is an associative array, false otherwise + */ +function _features_is_assoc($array) { + return (is_array($array) && (0 !== count(array_diff_key($array, array_keys(array_keys($array)))) || count($array)==0)); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ +name = "Features" +description = "Provides feature management for Drupal." +core = 7.x +package = "Features" +files[] = tests/features.test + +configure = admin/structure/features/settings + +; Information added by drupal.org packaging script on 2013-08-26 +version = "7.x-2.0-rc3" +core = "7.x" +project = "features" +datestamp = "1377548845" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,121 @@ +fields(array('weight' => 20)) + ->condition('name', 'features') + ->condition('type', 'module') + ->execute(); +} + +/** + * Implements hook_uninstall(). + */ +function features_uninstall() { + variable_del('features_codecache'); + variable_del('features_semaphore'); + variable_del('features_ignored_orphans'); + if (db_table_exists('menu_custom')) { + db_delete('menu_custom') + ->condition('menu_name', 'features') + ->execute(); + } + db_delete('menu_links') + ->condition('module', 'features') + ->execute(); +} + +/** + * Create menu. See menu.install for an example. + */ +function _features_install_menu() { + if (db_table_exists('menu_custom') && !db_query("SELECT menu_name FROM {menu_custom} WHERE menu_name = :menu_name", array(':menu_name' => 'features'))->fetchField()) { + $t = get_t(); + $id = db_insert('menu_custom') + ->fields(array( + 'menu_name' => 'features', + 'title' => $t('Features'), + 'description' => $t('Menu items for any enabled features.'), + )) + ->execute(); + } +} + +/** + * Update 6100: Set module on all feature node types to 'features'. + + * This update can be re-run as needed to repair any node types that are not + * removed after disabling the associated feature. + * + * Any feature implementing a node component that was exported prior to this + * version of the features.module will need to have its 'module' declaration + * in hook_node_info() changed from 'node' to 'features'. + */ +function features_update_6100() { + $ret = array(); + + foreach (features_get_features(NULL, TRUE) as $feature) { + if (module_exists($feature->name) && $types = module_invoke($feature->name, 'node_info')) { + foreach ($types as $type => $type_data) { + $sql = "SELECT COUNT(*) FROM {node_type} WHERE module = 'node' AND type = '%s'"; + // Only update if the hook_node_info type's module is 'features' and the db type's + // module is 'node'. + if (($type_data['module'] == 'features') && db_query($sql, $type)->fetchField()) { + $ret[] = update_sql("UPDATE {node_type} SET module = 'features' WHERE type = '$type'"); + } + } + } + } + return $ret; +} + +/** + * Update 6101: Set codestate signature for all features. + * + * This update generates a codestate for all feature/component pairs which + * have been installed prior to this version of Features. This prevents + * automatic rebuilds from occurring against any rebuildable components + * that have been overridden. + */ +function features_update_6101() { + // Ensure all of our own API functions still exist in in this version + // of Features. It's possible that the "future me" will not have these + // functions, so I should check. + module_load_include('inc', 'features', "features.export"); + $functions = array( + 'features_include', + 'features_hook', + 'features_get_components', + 'features_get_features', + 'features_get_signature', + 'features_set_signature', + ); + $doit = TRUE; + foreach ($functions as $function) { + $doit = $doit && function_exists($function); + } + if ($doit) { + features_include(); + $features = array_keys(features_get_features(NULL, TRUE)); + $components = array_keys(features_get_components()); + foreach ($features as $feature) { + if (module_exists($feature)) { + foreach ($components as $component) { + if (features_hook($component, 'features_rebuild') && features_get_signature('cache', $feature, $component) === FALSE) { + features_set_signature($feature, $component, -1); + } + } + } + } + } + return array(); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,437 @@ +/** + * jQuery.fn.sortElements + * -------------- + * @param Function comparator: + * Exactly the same behaviour as [1,2,3].sort(comparator) + * + * @param Function getSortable + * A function that should return the element that is + * to be sorted. The comparator will run on the + * current collection, but you may want the actual + * resulting sort to occur on a parent or another + * associated element. + * + * E.g. $('td').sortElements(comparator, function(){ + * return this.parentNode; + * }) + * + * The 's parent () will be sorted instead + * of the itself. + * + * Credit: http://james.padolsey.com/javascript/sorting-elements-with-jquery/ + * + */ +jQuery.fn.sortElements = (function(){ + + var sort = [].sort; + + return function(comparator, getSortable) { + + getSortable = getSortable || function(){return this;}; + + var placements = this.map(function(){ + + var sortElement = getSortable.call(this), + parentNode = sortElement.parentNode, + + // Since the element itself will change position, we have + // to have some way of storing its original position in + // the DOM. The easiest way is to have a 'flag' node: + nextSibling = parentNode.insertBefore( + document.createTextNode(''), + sortElement.nextSibling + ); + + return function() { + + if (parentNode === this) { + throw new Error( + "You can't sort elements if any one is a descendant of another." + ); + } + + // Insert before flag: + parentNode.insertBefore(this, nextSibling); + // Remove flag: + parentNode.removeChild(nextSibling); + + }; + + }); + + return sort.call(this, comparator).each(function(i){ + placements[i].call(getSortable.call(this)); + }); + + }; + +})(); + +(function ($) { + Drupal.behaviors.features = { + attach: function(context, settings) { + // Features management form + $('table.features:not(.processed)', context).each(function() { + $(this).addClass('processed'); + + // Check the overridden status of each feature + Drupal.features.checkStatus(); + + // Add some nicer row hilighting when checkboxes change values + $('input', this).bind('change', function() { + if (!$(this).attr('checked')) { + $(this).parents('tr').removeClass('enabled').addClass('disabled'); + } + else { + $(this).parents('tr').addClass('enabled').removeClass('disabled'); + } + }); + }); + + // Export form component selector + $('form.features-export-form select.features-select-components:not(.processed)', context).each(function() { + $(this) + .addClass('processed') + .change(function() { + var target = $(this).val(); + $('div.features-select').hide(); + $('div.features-select-' + target).show(); + return false; + }).trigger('change'); + }); + + // Export form machine-readable JS + $('.feature-name:not(.processed)', context).each(function() { + $('.feature-name') + .addClass('processed') + .after('  '); + if ($('.feature-module-name').val() === $('.feature-name').val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_') || $('.feature-module-name').val() === '') { + $('.feature-module-name').parents('.form-item').hide(); + $('.feature-name').bind('keyup change', function() { + var machine = $(this).val().toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/_+/g, '_'); + if (machine !== '_' && machine !== '') { + $('.feature-module-name').val(machine); + $('.feature-module-name-suffix').empty().append(' Machine name: ' + machine + ' [').append($(''+ Drupal.t('Edit') +'').click(function() { + $('.feature-module-name').parents('.form-item').show(); + $('.feature-module-name-suffix').hide(); + $('.feature-name').unbind('keyup'); + return false; + })).append(']'); + } + else { + $('.feature-module-name').val(machine); + $('.feature-module-name-suffix').text(''); + } + }); + $('.feature-name').keyup(); + } + }); + + //View info dialog + var infoDialog = $('#features-info-file'); + if (infoDialog.length != 0) { + infoDialog.dialog({ + autoOpen: false, + modal: true, + draggable: false, + resizable: false, + width: 600, + height: 480 + }); + } + + if ((Drupal.settings.features != undefined) && (Drupal.settings.features.info != undefined)) { + $('#features-info-file textarea').val(Drupal.settings.features.info); + $('#features-info-file').dialog('open'); + //To be reset by the button click ajax + Drupal.settings.features.info = undefined; + } + + // mark any conflicts with a class + if ((Drupal.settings.features != undefined) && (Drupal.settings.features.conflicts != undefined)) { + for (var moduleName in Drupal.settings.features.conflicts) { + moduleConflicts = Drupal.settings.features.conflicts[moduleName]; + $('#features-export-wrapper input[type=checkbox]', context).each(function() { + if (!$(this).hasClass('features-checkall')) { + var key = $(this).attr('name'); + var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/); + var component = matches[1]; + var item = matches[4]; + if ((component in moduleConflicts) && (moduleConflicts[component].indexOf(item) != -1)) { + $(this).parent().addClass('features-conflict'); + } + } + }); + } + } + + function _checkAll(value) { + if (value) { + $('#features-export-wrapper .component-select input[type=checkbox]:visible', context).each(function() { + var move_id = $(this).attr('id'); + $(this).click(); + $('#'+ move_id).attr('checked', 'checked'); + }); + } + else { + $('#features-export-wrapper .component-added input[type=checkbox]:visible', context).each(function() { + var move_id = $(this).attr('id'); + $('#'+ move_id).removeAttr('checked'); + $(this).click(); + $('#'+ move_id).removeAttr('checked'); + }); + } + } + + function moveCheckbox(item, section, value) { + var curParent = item; + if ($(item).hasClass('form-type-checkbox')) { + item = $(item).children('input[type=checkbox]'); + } + else { + curParent = $(item).parents('.form-type-checkbox'); + } + var newParent = $(curParent).parents('.features-export-parent').find('.form-checkboxes.component-'+section); + $(curParent).detach(); + $(curParent).appendTo(newParent); + var list = ['select', 'added', 'detected', 'included']; + for (i in list) { + $(curParent).removeClass('component-' + list[i]); + $(item).removeClass('component-' + list[i]); + } + $(curParent).addClass('component-'+section); + $(item).addClass('component-'+section); + if (value) { + $(item).attr('checked', 'checked'); + } + else { + $(item).removeAttr('checked') + } + $(newParent).parent().removeClass('features-export-empty'); + + // re-sort new list of checkboxes based on labels + $(newParent).find('label').sortElements( + function(a, b){ + return $(a).text() > $(b).text() ? 1 : -1; + }, + function(){ + return this.parentNode; + } + ); + } + + // provide timer for auto-refresh trigger + var timeoutID = 0; + var inTimeout = 0; + function _triggerTimeout() { + timeoutID = 0; + _updateDetected(); + } + function _resetTimeout() { + inTimeout++; + // if timeout is already active, reset it + if (timeoutID != 0) { + window.clearTimeout(timeoutID); + if (inTimeout > 0) inTimeout--; + } + timeoutID = window.setTimeout(_triggerTimeout, 500); + } + + function _updateDetected() { + var autodetect = $('#features-autodetect input[type=checkbox]'); + if ((autodetect.length > 0) && (!autodetect.is(':checked'))) return; + // query the server for a list of components/items in the feature and update + // the auto-detected items + var items = []; // will contain a list of selected items exported to feature + var components = {}; // contains object of component names that have checked items + $('#features-export-wrapper input[type=checkbox]:checked', context).each(function() { + if (!$(this).hasClass('features-checkall')) { + var key = $(this).attr('name'); + var matches = key.match(/^([^\[]+)(\[.+\])?\[(.+)\]\[(.+)\]$/); + components[matches[1]] = matches[1]; + if (!$(this).hasClass('component-detected')) { + items.push(key); + } + } + }); + var featureName = $('#edit-module-name').val(); + if (featureName == '') { + featureName = '*'; + } + var url = Drupal.settings.basePath + 'features/ajaxcallback/' + featureName; + var excluded = Drupal.settings.features.excluded; + var postData = {'items': items, 'excluded': excluded}; + jQuery.post(url, postData, function(data) { + if (inTimeout > 0) inTimeout--; + // if we have triggered another timeout then don't update with old results + if (inTimeout == 0) { + // data is an object keyed by component listing the exports of the feature + for (var component in data) { + var itemList = data[component]; + $('#features-export-wrapper .component-' + component + ' input[type=checkbox]', context).each(function() { + var key = $(this).attr('value'); + // first remove any auto-detected items that are no longer in component + if ($(this).hasClass('component-detected')) { + if (!(key in itemList)) { + moveCheckbox(this, 'select', false) + } + } + // next, add any new auto-detected items + else if ($(this).hasClass('component-select')) { + if (key in itemList) { + moveCheckbox(this, 'detected', itemList[key]); + $(this).parent().show(); // make sure it's not hidden from filter + } + } + }); + } + // loop over all selected components and check for any that have been completely removed + for (var component in components) { + if ((data == null) || !(component in data)) { + $('#features-export-wrapper .component-' + component + ' input[type=checkbox].component-detected', context).each(function() { + moveCheckbox(this, 'select', false); + }); + } + } + } + }, "json"); + } + + // Handle component selection UI + $('#features-export-wrapper input[type=checkbox]', context).click(function() { + _resetTimeout(); + if ($(this).hasClass('component-select')) { + moveCheckbox(this, 'added', true); + } + else if ($(this).hasClass('component-included')) { + moveCheckbox(this, 'added', false); + } + else if ($(this).hasClass('component-added')) { + if ($(this).is(':checked')) { + moveCheckbox(this, 'included', true); + } + else { + moveCheckbox(this, 'select', false); + } + } + }); + + // Handle select/unselect all + $('#features-filter .features-checkall', context).click(function() { + if ($(this).attr('checked')) { + _checkAll(true); + $(this).next().html(Drupal.t('Deselect all')); + } + else { + _checkAll(false); + $(this).next().html(Drupal.t('Select all')); + } + _resetTimeout(); + }); + + // Handle filtering + + // provide timer for auto-refresh trigger + var filterTimeoutID = 0; + var inFilterTimeout = 0; + function _triggerFilterTimeout() { + filterTimeoutID = 0; + _updateFilter(); + } + function _resetFilterTimeout() { + inFilterTimeout++; + // if timeout is already active, reset it + if (filterTimeoutID != 0) { + window.clearTimeout(filterTimeoutID); + if (inFilterTimeout > 0) inFilterTimeout--; + } + filterTimeoutID = window.setTimeout(_triggerFilterTimeout, 200); + } + function _updateFilter() { + var filter = $('#features-filter input').val(); + var regex = new RegExp(filter, 'i'); + // collapse fieldsets + var newState = {}; + var currentState = {}; + $('#features-export-wrapper fieldset.features-export-component', context).each(function() { + // expand parent fieldset + var section = $(this).attr('id'); + currentState[section] = !($(this).hasClass('collapsed')); + if (!(section in newState)) { + newState[section] = false; + } + + $(this).find('div.component-select label').each(function() { + if (filter == '') { + if (currentState[section]) { + Drupal.toggleFieldset($('#'+section)); + currentState[section] = false; + } + $(this).parent().show(); + } + else if ($(this).text().match(regex)) { + $(this).parent().show(); + newState[section] = true; + } + else { + $(this).parent().hide(); + } + }); + }); + for (section in newState) { + if (currentState[section] != newState[section]) { + Drupal.toggleFieldset($('#'+section)); + } + } + } + $('#features-filter input', context).bind("input", function() { + _resetFilterTimeout(); + }); + $('#features-filter .features-filter-clear', context).click(function() { + $('#features-filter input').val(''); + _updateFilter(); + }); + + // show the filter bar + $('#features-filter', context).removeClass('element-invisible'); + } + } + + + Drupal.features = { + 'checkStatus': function() { + $('table.features tbody tr').not('.processed').filter(':first').each(function() { + var elem = $(this); + $(elem).addClass('processed'); + var uri = $(this).find('a.admin-check').attr('href'); + if (uri) { + $.get(uri, [], function(data) { + $(elem).find('.admin-loading').hide(); + switch (data.storage) { + case 3: + $(elem).find('.admin-rebuilding').show(); + break; + case 2: + $(elem).find('.admin-needs-review').show(); + break; + case 1: + $(elem).find('.admin-overridden').show(); + break; + default: + $(elem).find('.admin-default').show(); + break; + } + Drupal.features.checkStatus(); + }, 'json'); + } + else { + Drupal.features.checkStatus(); + } + }); + } + }; + + +})(jQuery); + + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/features.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/features.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1095 @@ + 'Features', + 'description' => 'Manage features.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_admin_form'), + 'type' => MENU_NORMAL_ITEM, + 'file' => 'features.admin.inc', + ); + $items['admin/structure/features/cleanup'] = array( + 'title' => 'Cleanup', + 'description' => 'Clear cache after enabling/disabling a feature.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_cleanup_form', 4), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + 'weight' => 1, + ); + $items['admin/structure/features/manage'] = array( + 'title' => 'Manage', + 'description' => 'Enable and disable features.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_admin_form'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'file' => 'features.admin.inc', + ); + $items['admin/structure/features/create'] = array( + 'title' => 'Create feature', + 'description' => 'Create a new feature.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_export_form'), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_LOCAL_TASK, + 'file' => "features.admin.inc", + 'weight' => 10, + ); + $items['admin/structure/features/settings'] = array( + 'title' => 'Settings', + 'description' => 'Adjust settings for using features module.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_settings_form'), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_LOCAL_TASK, + 'file' => "features.admin.inc", + 'weight' => 11, + ); + + + $items['admin/structure/features/%feature'] = array( + 'title callback' => 'features_get_feature_title', + 'title arguments' => array(3), + 'description' => 'Display components of a feature.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_admin_components', 3), + 'load arguments' => array(3, TRUE), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + ); + $items['admin/structure/features/%feature/view'] = array( + 'title' => 'View', + 'description' => 'Display components of a feature.', + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items['admin/structure/features/%feature/recreate'] = array( + 'title' => 'Recreate', + 'description' => 'Recreate an existing feature.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('features_export_form', 3), + 'load arguments' => array(3, TRUE), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_LOCAL_TASK, + 'file' => "features.admin.inc", + 'weight' => 11, + ); + if (module_exists('diff')) { + $items['admin/structure/features/%feature/diff'] = array( + 'title' => 'Review overrides', + 'description' => 'Compare default and current feature.', + 'page callback' => 'features_feature_diff', + 'page arguments' => array(3, 5), + 'load arguments' => array(3, TRUE), + 'access callback' => 'features_access_override_actions', + 'access arguments' => array(3), + 'type' => MENU_LOCAL_TASK, + 'file' => 'features.admin.inc', + ); + } + $items['admin/structure/features/%feature/status'] = array( + 'title' => 'Status', + 'description' => 'Javascript status call back.', + 'page callback' => 'features_feature_status', + 'page arguments' => array(3), + 'load arguments' => array(3, TRUE), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + ); + $items['features/autocomplete/packages'] = array( + 'page callback' => 'features_autocomplete_packages', + 'access arguments' => array('administer features'), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + ); + $items['features/ajaxcallback/%'] = array( + 'title callback' => 'features_get_feature_components', + 'description' => 'Return components of a feature.', + 'page callback' => 'features_export_components_json', + 'page arguments' => array(2), + 'access callback' => 'user_access', + 'access arguments' => array('administer features'), + 'type' => MENU_CALLBACK, + 'file' => 'features.admin.inc', + ); + foreach ($items as $path => $item) { + if (!isset($item['access callback'])) { + $items[$path]['access callback'] = 'user_access'; + $items[$path]['access arguments'] = array('manage features'); + } + } + return $items; +} + +/** + * Implements hook_theme(). + */ +function features_theme() { + $base = array( + 'path' => drupal_get_path('module', 'features') . '/theme', + 'file' => 'theme.inc', + ); + + $items = array(); + $items['features_module_status'] = array( + 'variables' => array('module' => null, 'status' => null) + ) + $base; + + $items['features_components'] = array( + 'variables' => array('info' => null, 'sources' => null), + ) + $base; + + $items['features_component_key'] = $base; + $items['features_component_list'] = array( + 'variables' => array('components' => array(), 'source' => array(), 'conflicts' => array()), + ) + $base; + + $items['features_storage_link'] = array( + 'variables' => array('storage' => null, 'text' => null, 'path' => null, 'options' => array()), + ) + $base; + + $items['features_form_components'] = + $items['features_form_export'] = + $items['features_form_package'] = array( + 'render element' => 'form', + ) + $base; + + $items['features_form_buttons'] = array( + 'render element' => 'element', + ) + $base; + + + $items['features_admin_components'] = array( + 'render element' => 'form', + 'template' => 'features-admin-components', + ) + $base; + + return $items; +} + +/** + * Implements hook_flush_caches(). + */ +function features_flush_caches() { + if (variable_get('features_rebuild_on_flush', TRUE)) { + features_rebuild(); + // Don't flush the modules cache during installation, for performance reasons. + if (variable_get('install_task') == 'done') { + features_get_modules(NULL, TRUE); + } + } + return array(); +} + +/** + * Implements hook_form(). + */ +function features_form($node, $form_state) { + return node_content_form($node, $form_state); +} + +/** + * Implements hook_permission(). + */ +function features_permission() { + return array( + 'administer features' => array( + 'title' => t('Administer features'), + 'description' => t('Perform administration tasks on features.'), + 'restrict access' => TRUE, + ), + 'manage features' => array( + 'title' => t('Manage features'), + 'description' => t('View, enable and disable features.'), + 'restrict access' => TRUE, + ), + 'generate features' => array( + 'title' => t('Generate features'), + 'description' => t('Allow feature exports to be generated and written directly to site.'), + 'restrict access' => TRUE, + ), + ); +} + +/** + * Implements hook_help(). + */ +function features_help($path, $arg) { + switch ($path) { + case 'admin/help#features': + $output = file_get_contents(drupal_get_path('module', 'features') .'/README.txt'); + return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '
    '. check_plain($output) .'
    '; + case 'admin/build/features': + return '

    '. t('A "Feature" is a certain type of Drupal module which contains a package of configuration that, when enabled, provides a new set of functionality for your Drupal site. Enable features by selecting the checkboxes below and clicking the Save configuration button. If the configuration of the feature has been changed its "State" will be either "overridden" or "needs review", otherwise it will be "default", indicating that the configuration has not been changed. Click on the state to see more details about the feature and its components.') .'

    '; + } +} + +/** + * Implements hook_modules_disabled(). + */ +function features_modules_disabled($modules) { + // Go through all modules and gather features that can be disabled. + $items = array(); + foreach ($modules as $module) { + if ($feature = features_load_feature($module)) { + $items[$module] = array_keys($feature->info['features']); + } + } + + if (!empty($items)) { + _features_restore('disable', $items); + // Rebuild the list of features includes. + features_include(TRUE); + } +} + +/** + * Implements hook_modules_enabled(). + */ +function features_modules_enabled($modules) { + // Go through all modules and gather features that can be enabled. + $items = array(); + foreach ($modules as $module) { + if ($feature = features_load_feature($module)) { + $items[$module] = array_keys($feature->info['features']); + } + } + + if (!empty($items)) { + _features_restore('enable', $items); + // Rebuild the list of features includes. + features_include(TRUE); + } +} + +/** + * Load includes for any modules that implement the features API and + * load includes for those provided by features. + */ +function features_include($reset = FALSE) { + static $once; + if (!isset($once) || $reset) { + $once = TRUE; + + // Features provides integration on behalf of these modules. + // The features include provides handling for the feature dependencies. + // Note that ctools is placed last because it implements hooks "dynamically" for other modules. + $modules = array('features', 'block', 'context', 'field', 'filter', 'image', 'locale', 'menu', 'node', 'taxonomy', 'user', 'views', 'ctools'); + + foreach (array_filter($modules, 'module_exists') as $module) { + module_load_include('inc', 'features', "includes/features.$module"); + } + + if (module_exists('ctools')) { + // Finally, add ctools eval'd implementations. + ctools_features_declare_functions($reset); + } + + // Clear static cache, since we've now included new implementers. + foreach (features_get_components(NULL, 'file', $reset) as $file) { + if (is_file(DRUPAL_ROOT . '/' . $file)) { + require_once DRUPAL_ROOT . '/' . $file; + } + } + } +} + +/** + * Load features includes for all components that require includes before + * collecting defaults. + */ +function features_include_defaults($components = NULL, $reset = FALSE) { + static $included = array(); + static $include_components; + + // Build an array of components that require inclusion: + // Views, CTools components and those using FEATURES_DEFAULTS_INCLUDED. + if (!isset($include_components) || $reset) { + $include_components = features_get_components(); + foreach ($include_components as $component => $info) { + if (!isset($info['api']) && (!isset($info['default_file']) || $info['default_file'] !== FEATURES_DEFAULTS_INCLUDED)) { + unset($include_components[$component]); + } + } + } + + // If components are specified, only include for the specified components. + if (isset($components)) { + $components = is_array($components) ? $components : array($components); + } + // Use all include components if none are explicitly specified. + else { + $components = array_keys($include_components); + } + foreach ($components as $component) { + if (isset($include_components[$component]) && (!isset($included[$component]) || $reset)) { + $info = $include_components[$component]; + // Inclusion of ctools components. + if (isset($info['api'], $info['module'], $info['current_version'])) { + ctools_include('plugins'); + ctools_plugin_api_include($info['module'], $info['api'], $info['current_version'], $info['current_version']); + } + // Inclusion of defaults for components using FEATURES_DEFAULTS_INCLUDED. + else { + $features = isset($features) ? $features : features_get_features(NULL, $reset); + foreach ($features as $feature) { + $filename = isset($info['default_file']) && $info['default_file'] == FEATURES_DEFAULTS_CUSTOM ? $info['default_filename'] : "features.{$component}"; + if (module_exists($feature->name) && isset($feature->info['features'][$component])) { + module_load_include('inc', $feature->name, "{$feature->name}.$filename"); + } + } + } + $included[$component] = TRUE; + } + } +} + +/** + * Feature object loader. DEPRECATED but included for backwards compatibility + */ +function feature_load($name, $reset = FALSE) { + return features_load_feature($name, $reset); +} + +/** + * Feature object loader. + */ +function features_load_feature($name, $reset = FALSE) { + // Use an alternative code path during installation, for better performance. + if (variable_get('install_task') != 'done') { + static $features; + + if (!isset($features[$name])) { + // Set defaults for module info. + $defaults = array( + 'dependencies' => array(), + 'description' => '', + 'package' => 'Other', + 'version' => NULL, + 'php' => DRUPAL_MINIMUM_PHP, + 'files' => array(), + 'bootstrap' => 0, + ); + $info = drupal_parse_info_file(drupal_get_path('module', $name) . '/' . $name . '.info'); + + $features[$name] = FALSE; + if (!empty($info['features']) && empty($info['hidden'])) { + // Build a fake file object with the data needed during installation. + $features[$name] = new stdClass; + $features[$name]->name = $name; + $features[$name]->filename = drupal_get_path('module', $name) . '/' . $name . '.module'; + $features[$name]->type = 'module'; + $features[$name]->info = $info + $defaults; + } + } + + return $features[$name]; + } + else { + return features_get_features($name, $reset); + } +} + +/** + * Return a module 'object' including .info information. + * + * @param $name + * The name of the module to retrieve information for. If ommitted, + * an array of all available modules will be returned. + * @param $reset + * Whether to reset the cache. + * + * @return + * If a module is request (and exists) a module object is returned. If no + * module is requested info for all modules is returned. + */ +function features_get_modules($name = NULL, $reset = FALSE) { + return features_get_info('module', $name, $reset); +} + +/** + * Returns the array of supported components. + * + * @see hook_features_api + * + * @param $component + * A specific type of component that supports features. + * @param $key + * A key that hook_features_api supports. + * + * @return An array of component labels keyed by the component names. + */ +function features_get_components($component = NULL, $key = NULL, $reset = FALSE) { + features_include(); + $components = &drupal_static(__FUNCTION__); + $component_by_key = &drupal_static(__FUNCTION__ . '_by_key'); + + if ($reset || !isset($components) || !isset($component_by_key)) { + $components = $component_by_key = array(); + if (!$reset && ($cache = cache_get('features_api'))) { + $components = $cache->data; + } + else { + $components = module_invoke_all('features_api'); + drupal_alter('features_api', $components); + cache_set('features_api', $components); + } + + foreach ($components as $component_type => $component_information) { + foreach ($component_information as $component_key => $component_value) { + $component_by_key[$component_key][$component_type] = $component_value; + } + } + } + + if ($key && $component) { + return !empty($components[$component][$key]) ? $components[$component][$key] : NULL; + } + elseif ($key) { + return !empty($component_by_key[$key]) ? $component_by_key[$key] : array(); + } + elseif ($component) { + return $components[$component]; + } + return $components; +} + +/** + * Returns components that are offered as an option on feature creation. + */ +function features_get_feature_components() { + return array_intersect_key(features_get_components(), array_filter(features_get_components(NULL, 'feature_source'))); +} + +/** + * Invoke a component callback. + */ +function features_invoke($component, $callback) { + $args = func_get_args(); + unset($args[0], $args[1]); + // Append the component name to the arguments. + $args[] = $component; + if ($function = features_hook($component, $callback)) { + return call_user_func_array($function, $args); + } +} + +/** + * Checks whether a component implements the given hook. + * + * @return + * The function implementing the hook, or FALSE. + */ +function features_hook($component, $hook, $reset = FALSE) { + // Determine the function callback base. + $base = features_get_components($component, 'base'); + $base = isset($base) ? $base : $component; + return function_exists($base . '_' . $hook) ? $base . '_' . $hook : FALSE; +} + +/** + * Enables and installs an array of modules, ignoring those + * already enabled & installed. Consider this a helper or + * extension to drupal_install_modules(). + * + * @param $modules + * An array of modules to install. + * @param $reset + * Clear the module info cache. + */ +function features_install_modules($modules) { + module_load_include('inc', 'features', 'features.export'); + $files = system_rebuild_module_data(); + + // Build maximal list of dependencies. + $install = array(); + foreach ($modules as $name) { + // Parse the dependency string into the module name and version information. + $parsed_name = drupal_parse_dependency($name); + $name = $parsed_name['name']; + if ($file = $files[$name]) { + $install[] = $name; + if (!empty($file->info['dependencies'])) { + $install = array_merge($install, _features_export_maximize_dependencies($file->info['dependencies'])); + } + } + } + + // Filter out enabled modules. + $enabled = array_filter($install, 'module_exists'); + $install = array_diff($install, $enabled); + + if (!empty($install)) { + // Make sure the install API is available. + $install = array_unique($install); + include_once DRUPAL_ROOT . '/' . './includes/install.inc'; + module_enable($install); + } +} + +/** + * Wrapper around features_get_info() that returns an array + * of module info objects that are features. + */ +function features_get_features($name = NULL, $reset = FALSE) { + return features_get_info('feature', $name, $reset); +} + +/** + * Helper for retrieving info from system table. + */ +function features_get_info($type = 'module', $name = NULL, $reset = FALSE) { + static $cache; + if (!isset($cache)) { + $cache = cache_get('features_module_info'); + } + if (empty($cache) || $reset) { + $data = array( + 'feature' => array(), + 'module' => array(), + ); + $ignored = variable_get('features_ignored_orphans', array()); + $files = system_rebuild_module_data(); + + foreach ($files as $row) { + // Avoid false-reported feature overrides for php = 5.2.4 line in .info file. + if (isset($row->info['php'])) { + unset($row->info['php']); + } + // If module is no longer enabled, remove it from the ignored orphans list. + if (in_array($row->name, $ignored, TRUE) && !$row->status) { + $key = array_search($row->name, $ignored, TRUE); + unset($ignored[$key]); + } + + if (!empty($row->info['features'])) { + // Fix css/js paths + if (!empty($row->info['stylesheets'])) { + foreach($row->info['stylesheets'] as $media => $css) { + $row->info['stylesheets'][$media] = array_keys($css); + } + } + if (!empty($row->info['scripts'])) { + $row->info['scripts'] = array_keys($row->info['scripts']); + } + // Rework the features array, to change the vocabulary permission + // features. + foreach ($row->info['features'] as $component => $features) { + if ($component == 'user_permission') { + foreach ($features as $key => $feature) { + // Export vocabulary permissions using the machine name, instead + // of vocabulary id. + _user_features_change_term_permission($feature); + $row->info['features'][$component][$key] = $feature; + } + } + } + $data['feature'][$row->name] = $row; + } + $data['module'][$row->name] = $row; + } + + // Sort features according to dependencies. + // @see install_profile_modules() + $required = array(); + $non_required = array(); + + $modules = array_keys($data['feature']); + foreach ($modules as $module) { + if ($files[$module]->requires) { + $modules = array_merge($modules, array_keys($files[$module]->requires)); + } + } + $modules = array_unique($modules); + foreach ($modules as $module) { + if (!empty($files[$module]->info['features'])) { + if (!empty($files[$module]->info['required'])) { + $required[$module] = $files[$module]->sort; + } + else { + $non_required[$module] = $files[$module]->sort; + } + } + } + arsort($required); + arsort($non_required); + + $sorted = array(); + foreach ($required + $non_required as $module => $weight) { + $sorted[$module] = $data['feature'][$module]; + } + $data['feature'] = $sorted; + + variable_set('features_ignored_orphans', $ignored); + cache_set("features_module_info", $data); + $cache = new stdClass(); + $cache->data = $data; + } + if (!empty($name)) { + return !empty($cache->data[$type][$name]) ? clone $cache->data[$type][$name] : array(); + } + return !empty($cache->data[$type]) ? $cache->data[$type] : array(); +} + +/** + * Generate an array of feature dependencies that have been orphaned. + */ +function features_get_orphans($reset = FALSE) { + static $orphans; + if (!isset($orphans) || $reset) { + module_load_include('inc', 'features', 'features.export'); + $orphans = array(); + + // Build a list of all dependencies for enabled and disabled features. + $dependencies = array('enabled' => array(), 'disabled' => array()); + $features = features_get_features(); + foreach ($features as $feature) { + $key = module_exists($feature->name) ? 'enabled' : 'disabled'; + if (!empty($feature->info['dependencies'])) { + $dependencies[$key] = array_merge($dependencies[$key], _features_export_maximize_dependencies($feature->info['dependencies'])); + } + } + $dependencies['enabled'] = array_unique($dependencies['enabled']); + $dependencies['disabled'] = array_unique($dependencies['disabled']); + + // Find the list of orphaned modules. + $orphaned = array_diff($dependencies['disabled'], $dependencies['enabled']); + $orphaned = array_intersect($orphaned, module_list(FALSE, FALSE)); + $orphaned = array_diff($orphaned, drupal_required_modules()); + $orphaned = array_diff($orphaned, array('features')); + + // Build final list of modules that can be disabled. + $modules = features_get_modules(NULL, TRUE); + $enabled = module_list(); + _module_build_dependencies($modules); + + foreach ($orphaned as $module) { + if (!empty($modules[$module]->required_by)) { + foreach ($modules[$module]->required_by as $module_name => $dependency) { + $modules[$module]->required_by[$module_name] = $dependency['name']; + } + // Determine whether any dependents are actually enabled. + $dependents = array_intersect($modules[$module]->required_by, $enabled); + if (empty($dependents)) { + $info = features_get_modules($module); + $orphans[$module] = $info; + } + } + } + } + return $orphans; +} + +/** + * Detect potential conflicts between any features that provide + * identical components. + */ +function features_get_conflicts($reset = FALSE) { + $conflicts = array(); + $component_info = features_get_components(); + $map = features_get_component_map(NULL, $reset); + + foreach ($map as $type => $components) { + // Only check conflicts for components we know about. + if (isset($component_info[$type])) { + foreach ($components as $component => $modules) { + if (isset($component_info[$type]['duplicates']) && $component_info[$type]['duplicates'] == FEATURES_DUPLICATES_ALLOWED) { + continue; + } + else if (count($modules) > 1) { + foreach ($modules as $module) { + if (!isset($conflicts[$module])) { + $conflicts[$module] = array(); + } + foreach ($modules as $m) { + if ($m != $module) { + $conflicts[$module][$m][$type][] = $component; + } + } + } + } + } + } + } + + return $conflicts; +} + +/** + * Provide a component to feature map. + */ +function features_get_component_map($key = NULL, $reset = FALSE) { + static $map; + if (!isset($map) || $reset) { + $map = array(); + $features = features_get_features(NULL, $reset); + foreach ($features as $feature) { + foreach ($feature->info['features'] as $type => $components) { + if (!isset($map[$type])) { + $map[$type] = array(); + } + foreach ($components as $component) { + $map[$type][$component][] = $feature->name; + } + } + } + } + if (isset($key)) { + return isset($map[$key]) ? $map[$key] : array(); + } + return $map; +} + +/** + * Simple wrapper returns the status of a module. + */ +function features_get_module_status($module) { + if (module_exists($module)) { + return FEATURES_MODULE_ENABLED; + } + else if (features_get_modules($module)) { + return FEATURES_MODULE_DISABLED; + } + else { + return FEATURES_MODULE_MISSING; + } +} + +/** + * Menu title callback. + */ +function features_get_feature_title($feature) { + return $feature->info['name']; +} + +/** + * Menu access callback for whether a user should be able to access + * override actions for a given feature. + */ +function features_access_override_actions($feature) { + if (user_access('administer features')) { + static $access = array(); + if (!isset($access[$feature->name])) { + // Set a value first. We may get called again from within features_detect_overrides(). + $access[$feature->name] = FALSE; + + features_include(); + module_load_include('inc', 'features', 'features.export'); + $access[$feature->name] = in_array(features_get_storage($feature->name), array(FEATURES_OVERRIDDEN, FEATURES_NEEDS_REVIEW)) && user_access('administer features'); + } + return $access[$feature->name]; + } + return FALSE; +} + +/** + * Implements hook_form_alter() for system_modules form(). + */ +function features_form_system_modules_alter(&$form) { + features_rebuild(); +} + +/** + * Restore the specified modules to the default state. + */ +function _features_restore($op, $items = array()) { + // Set this variable in $conf if having timeout issues during install/rebuild. + if (variable_get('features_restore_time_limit_' . $op, FALSE) !== FALSE) { + drupal_set_time_limit(variable_get('features_restore_time_limit_' . $op, FALSE)); + } + + module_load_include('inc', 'features', 'features.export'); + features_include(); + + switch ($op) { + case 'revert': + $restore_states = array(FEATURES_OVERRIDDEN, FEATURES_REBUILDABLE, FEATURES_NEEDS_REVIEW); + $restore_hook = 'features_revert'; + $log_action = 'Revert'; + break; + case 'rebuild': + $restore_states = array(FEATURES_REBUILDABLE); + $restore_hook = 'features_rebuild'; + $log_action = 'Rebuild'; + break; + case 'disable': + $restore_hook = 'features_disable_feature'; + $log_action = 'Disable'; + break; + case 'enable': + $restore_hook = 'features_enable_feature'; + $log_action = 'Enable'; + break; + } + + if (empty($items)) { + // Drush may execute a whole chain of commands that may trigger feature + // rebuilding multiple times during a single request. Make sure we do not + // rebuild the same cached list of modules over and over again by setting + // $reset to TRUE. + // Note: this may happen whenever more than one feature will be enabled + // in chain, for example also using features_install_modules(). + $states = features_get_component_states(array(), ($op == 'rebuild'), defined('DRUSH_BASE_PATH')); + foreach ($states as $module_name => $components) { + foreach ($components as $component => $state) { + if (in_array($state, $restore_states)) { + $items[$module_name][] = $component; + } + } + } + } + + foreach ($items as $module_name => $components) { + foreach ($components as $component) { + // Invoke pre hook + $pre_hook = 'pre_' . $restore_hook; + module_invoke($module_name, $pre_hook, $component); + + if (features_hook($component, $restore_hook)) { + // Set a semaphore to prevent other instances of the same script from running concurrently. + watchdog('features', '@actioning @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name)); + features_semaphore('set', $component); + features_invoke($component, $restore_hook, $module_name); + + // If the script completes, remove the semaphore and set the code signature. + features_semaphore('del', $component); + features_set_signature($module_name, $component); + watchdog('features', '@action completed for @module_name / @component.', array('@action' => $log_action, '@component' => $component, '@module_name' => $module_name)); + } + + // Invoke post hook + $post_hook = 'post_' . $restore_hook; + module_invoke($module_name, $post_hook, $component); + } + } +} + +/** + * Wrapper around _features_restore(). + */ +function features_revert($revert = array()) { + return _features_restore('revert', $revert); +} + +/** + * Wrapper around _features_restore(). + */ +function features_rebuild($rebuild = array()) { + return _features_restore('rebuild', $rebuild); +} + +/** + * Revert a single features module. + * + * @param string $module + * A features module machine name. This module must be a + * features module and enabled. + */ +function features_revert_module($module) { + if (($feature = feature_load($module, TRUE)) && module_exists($module)) { + $components = array(); + foreach (array_keys($feature->info['features']) as $component) { + if (features_hook($component, 'features_revert')) { + $components[] = $component; + } + } + features_revert(array($module => $components)); + } +} + +/** + * Utility functions ================================================== + */ + +/** + * Log a message, environment agnostic. + * + * @param $message + * The message to log. + * @param $severity + * The severity of the message: status, warning or error. + */ +function features_log($message, $severity = 'status') { + if (function_exists('drush_verify_cli')) { + $message = strip_tags($message); + if ($severity == 'status') { + $severity = 'ok'; + } + elseif ($severity == 'error') { + drush_set_error($message); + return; + } + drush_log($message, $severity); + return; + } + drupal_set_message($message, $severity, FALSE); +} + +/** + * Implements hook_hook_info(). + */ +function features_hook_info() { + $hooks = array( + 'features_api', + 'features_pipe_alter', + 'features_export_alter', + ); + return array_fill_keys($hooks, array('group' => 'features')); +} + +/** + * Change vocabularies permission, from vocab id to machine name and vice versa. + */ +function _user_features_change_term_permission(&$perm, $type = 'vid') { + // Export vocabulary permissions using the machine name, instead of vocabulary + // id. + if (strpos($perm, 'edit terms in ') !== FALSE || strpos($perm, 'delete terms in ') !== FALSE) { + preg_match("/(?<=\040)([^\s]+?)$/", trim($perm), $voc_id); + $vid = $voc_id[0]; + if (is_numeric($vid) && $type == 'vid') { + if (function_exists('taxonomy_vocabulary_load')) { + if ($voc = taxonomy_vocabulary_load($vid)) { + $perm = str_replace($vid, $voc->machine_name, $perm); + } + } + } + elseif ($type == 'machine_name') { + if ($voc = taxonomy_vocabulary_machine_name_load($vid)) { + $perm = str_replace($vid, $voc->vid, $perm); + } + } + } +} + +/** + * Recursively computes the difference of arrays with additional index check. + * + * This is a version of array_diff_assoc() that supports multidimensional + * arrays. + * + * @param array $array1 + * The array to compare from. + * @param array $array2 + * The array to compare to. + * + * @return array + * Returns an array containing all the values from array1 that are not present + * in array2. + */ +function features_array_diff_assoc_recursive(array $array1, array $array2) { + $difference = array(); + foreach ($array1 as $key => $value) { + if (is_array($value)) { + if (!isset($array2[$key]) || !is_array($array2[$key])) { + $difference[$key] = $value; + } + else { + $new_diff = features_array_diff_assoc_recursive($value, $array2[$key]); + if (!empty($new_diff)) { + $difference[$key] = $new_diff; + } + } + } + elseif (!isset($array2[$key]) || $array2[$key] != $value) { + $difference[$key] = $value; + } + } + return $difference; +} + +/** + * Returns an array of deprecated components + * Rather than deprecating the component directly, we look for other components + * that supersedes the component + * @param $components + * The array of components (component_info) from features_get_components typically. + */ +function features_get_deprecated($components = array()) { + if (empty($components)) { + $components = features_get_components(); + } + $deprecated = array(); + foreach ($components as $component => $component_info) { + if (!empty($component_info['supersedes'])) { + $deprecated[$component_info['supersedes']] = $component_info['supersedes']; + } + } + return $deprecated; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.block.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.block.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,40 @@ +conditions{$key}['values'])) { + foreach ($context->conditions{$key}['values'] as $item) { + // Special pipe for views + if ($key === 'views') { + $split = explode(':', $item); + $view_name = array_shift($split); + $pipe[$key][$view_name] = $view_name; + } + else { + $pipe[$key][$item] = $item; + } + } + } + } + // Reactions. + if (!empty($context->reactions['block']['blocks'])) { + foreach ($context->reactions['block']['blocks'] as $block) { + $block = (array) $block; + $bid = "{$block['module']}-{$block['delta']}"; + $pipe['block'][$bid] = $bid; + } + } + } + } + return $pipe; +} + +/** + * Implements hook_features_revert(). + * + * @param $module + * name of module to revert content for + */ +function context_features_revert($module = NULL) { + $return = ctools_component_features_revert('context', $module); + context_invalidate_cache(); + return $return; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.ctools.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.ctools.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,378 @@ + $info) { + $code = ''; + if (!function_exists("{$info['module']}_features_api")) { + $code .= 'function '. $info['module'] .'_features_api() { return ctools_component_features_api("'. $info['module'] .'"); }'; + } + + // ctools component with owner defined as "ctools" + if (!function_exists("{$component}_features_api") && $info['module'] === 'ctools') { + $code .= 'function '. $component .'_features_api() { return ctools_component_features_api("'. $component .'"); }'; + } + + if (!function_exists("{$component}_features_export")) { + $code .= 'function '. $component .'_features_export($data, &$export, $module_name = "") { return ctools_component_features_export("'. $component .'", $data, $export, $module_name); }'; + } + if (!function_exists("{$component}_features_export_options")) { + $code .= 'function '. $component .'_features_export_options() { return ctools_component_features_export_options("'. $component .'"); }'; + } + if (!function_exists("{$component}_features_export_render")) { + $code .= 'function '. $component .'_features_export_render($module, $data, $export = NULL) { return ctools_component_features_export_render("'. $component .'", $module, $data, $export); }'; + } + if (!function_exists("{$component}_features_revert")) { + $code .= 'function '. $component .'_features_revert($module) { return ctools_component_features_revert("'. $component .'", $module); }'; + } + eval($code); + } + } +} + +/** + * Implements hook_features_api(). + */ +function ctools_features_api() { + return array( + 'ctools' => array( + 'name' => 'CTools export API', + 'feature_source' => TRUE, + 'duplicates' => FEATURES_DUPLICATES_ALLOWED, + // CTools API integration does not include a default hook declaration as + // it is not a proper default hook. + // 'default_hook' => 'ctools_plugin_api', + ), + ); +} + +/** + * Implements hook_features_export(). + * Adds references to the ctools mothership hook, ctools_plugin_api(). + */ +function ctools_features_export($data, &$export, $module_name = '') { + // Add ctools dependency + $export['dependencies']['ctools'] = 'ctools'; + + // Add the actual ctools components which will need to be accounted for in + // hook_ctools_plugin_api(). The components are actually identified by a + // delimited list of values: `module_name:api:current_version` + foreach ($data as $component) { + if ($info = _ctools_features_get_info($component)) { + $identifier = "{$info['module']}:{$info['api']}:{$info['current_version']}"; + $export['features']['ctools'][$identifier] = $identifier; + } + } + + return array(); +} + +/** + * Implements hook_features_export_render(). + * Adds the ctools mothership hook, ctools_plugin_api(). + */ +function ctools_features_export_render($module, $data) { + $component_exports = array(); + foreach ($data as $component) { + $code = array(); + if ($info = _ctools_features_get_info($component)) { + // For background on why we change the output for hook_views_api() + // see http://drupal.org/node/1459120. + if ($info['module'] == 'views') { + $code[] = ' return array("api" => "3.0");'; + } + else { + $code[] = ' if ($module == "'. $info['module'] .'" && $api == "'. $info['api'] .'") {'; + $code[] = ' return array("version" => "'. $info['current_version'] .'");'; + $code[] = ' }'; + } + } + ctools_include('plugins'); + $plugin_api_hook_name = ctools_plugin_api_get_hook($info['module'], $info['api']); + + if (key_exists($plugin_api_hook_name, $component_exports)) { + $component_exports[$plugin_api_hook_name]['code'] .= "\n" . implode("\n", $code); + } + else { + $component_exports[$plugin_api_hook_name] = array( + 'code' => implode("\n", $code), + 'args' => '$module = NULL, $api = NULL', + ); + } + } + + return $component_exports; + +} + +/** + * Master implementation of hook_features_api() for all ctools components. + * + * Note that this master hook does not use $component like the others, but uses the + * component module's namespace instead. + */ +function ctools_component_features_api($module_name) { + $api = array(); + foreach (_ctools_features_get_info() as $component => $info) { + // if module owner is set to "ctools" we need to compare the component + if ($info['module'] == $module_name || ($info['module'] === 'ctools' && $component == $module_name) ) { + $api[$component] = $info; + } + } + return $api; +} + +/** + * Master implementation of hook_features_export_options() for all ctools components. + */ +function ctools_component_features_export_options($component) { + $options = array(); + + ctools_include('export'); + $schema = ctools_export_get_schema($component); + if ($schema && $schema['export']['bulk export']) { + if (!empty($schema['export']['list callback']) && function_exists($schema['export']['list callback'])) { + $options = $schema['export']['list callback'](); + } + else { + $options = _ctools_features_export_default_list($component, $schema); + } + } + asort($options); + return $options; +} + +/** + * Master implementation of hook_features_export() for all ctools components. + */ +function ctools_component_features_export($component, $data, &$export, $module_name = '') { + // Add the actual implementing module as a dependency + $info = _ctools_features_get_info(); + if ($module_name !== $info[$component]['module']) { + $export['dependencies'][$info[$component]['module']] = $info[$component]['module']; + } + + // Add the components + foreach ($data as $object_name) { + if ($object = _ctools_features_export_crud_load($component, $object_name)) { + // If this object is provided as a default by a different module, don't + // export and add that module as a dependency instead. + if (!empty($object->export_module) && $object->export_module !== $module_name) { + $export['dependencies'][$object->export_module] = $object->export_module; + if (isset($export['features'][$component][$object_name])) { + unset($export['features'][$component][$object_name]); + } + } + // Otherwise, add the component. + else { + $export['features'][$component][$object_name] = $object_name; + } + } + } + + // Let CTools handle API integration for this component. + return array('ctools' => array($component)); +} + +/** + * Master implementation of hook_features_export_render() for all ctools components. + */ +function ctools_component_features_export_render($component, $module, $data) { + // Reset the export display static to prevent clashes. + drupal_static_reset('panels_export_display'); + + ctools_include('export'); + $schema = ctools_export_get_schema($component); + + if (function_exists($schema['export']['to hook code callback'])) { + $export = $schema['export']['to hook code callback']($data, $module); + $code = explode("{\n", $export); + array_shift($code); + $code = explode('}', implode($code, "{\n")); + array_pop($code); + $code = implode('}', $code); + } + else { + $code = ' $export = array();'."\n\n"; + foreach ($data as $object_name) { + if ($object = _ctools_features_export_crud_load($component, $object_name)) { + $identifier = $schema['export']['identifier']; + $code .= _ctools_features_export_crud_export($component, $object, ' '); + $code .= " \$export[" . ctools_var_export($object_name) . "] = \${$identifier};\n\n"; + } + } + $code .= ' return $export;'; + } + + return array($schema['export']['default hook'] => $code); +} + +/** + * Master implementation of hook_features_revert() for all ctools components. + */ +function ctools_component_features_revert($component, $module) { + if ($objects = features_get_default($component, $module)) { + foreach ($objects as $name => $object) { + // Some things (like views) do not use the machine name as key + // and need to be loaded explicitly in order to be deleted. + $object = ctools_export_crud_load($component, $name); + if ($object && ($object->export_type & EXPORT_IN_DATABASE)) { + _ctools_features_export_crud_delete($component, $object); + } + } + } +} + +/** + * Helper function to return various ctools information for components. + */ +function _ctools_features_get_info($identifier = NULL, $reset = FALSE) { + static $components; + if (!isset($components) || $reset) { + $components = array(); + $modules = features_get_info(); + ctools_include('export'); + drupal_static('ctools_export_get_schemas', NULL, $reset); + foreach (ctools_export_get_schemas_by_module() as $module => $schemas) { + foreach ($schemas as $table => $schema) { + if ($schema['export']['bulk export']) { + // Let the API owner take precedence as the owning module. + $api_module = isset($schema['export']['api']['owner']) ? $schema['export']['api']['owner'] : $module; + $components[$table] = array( + 'name' => isset($modules[$api_module]->info['name']) ? $modules[$api_module]->info['name'] : $api_module, + 'default_hook' => $schema['export']['default hook'], + 'default_file' => FEATURES_DEFAULTS_CUSTOM, + 'module' => $api_module, + 'feature_source' => TRUE, + ); + if (isset($schema['export']['api'])) { + $components[$table] += array( + 'api' => $schema['export']['api']['api'], + 'default_filename' => $schema['export']['api']['api'], + 'current_version' => $schema['export']['api']['current_version'], + ); + } + } + } + } + } + + // Return information specific to a particular component. + if (isset($identifier)) { + // Identified by the table name. + if (isset($components[$identifier])) { + return $components[$identifier]; + } + // New API identifier. Allows non-exportables related CTools APIs to be + // supported by an explicit `module:api:current_version` key. + else if (substr_count($identifier, ':') === 2) { + list($module, $api, $current_version) = explode(':', $identifier); + // If a schema component matches the provided identifier, provide that + // information. This also ensures that the version number is up to date. + foreach ($components as $table => $info) { + if ($info['module'] == $module && $info['api'] == $api && $info['current_version'] >= $current_version) { + return $info; + } + } + // Fallback to just giving back what was provided to us. + return array('module' => $module, 'api' => $api, 'current_version' => $current_version); + } + return FALSE; + } + + return $components; +} + +/** + * Wrapper around ctools_export_crud_export() for < 1.7 compatibility. + */ +function _ctools_features_export_crud_export($table, $object, $indent = '') { + return ctools_api_version('1.7') ? ctools_export_crud_export($table, $object, $indent) : ctools_export_object($table, $object, $indent); +} + +/** + * Wrapper around ctools_export_crud_load() for < 1.7 compatibility. + */ +function _ctools_features_export_crud_load($table, $name) { + if (ctools_api_version('1.7')) { + return ctools_export_crud_load($table, $name); + } + elseif ($objects = ctools_export_load_object($table, 'names', array($name))) { + return array_shift($objects); + } + return FALSE; +} + +/** + * Wrapper around ctools_export_default_list() for < 1.7 compatibility. + */ +function _ctools_features_export_default_list($table, $schema) { + if (ctools_api_version('1.7')) { + return ctools_export_default_list($table, $schema); + } + elseif ($objects = ctools_export_load_object($table, 'all')) { + return drupal_map_assoc(array_keys($objects)); + } + return array(); +} + +/** + * Wrapper around ctools_export_crud_delete() for < 1.7 compatibility. + */ +function _ctools_features_export_crud_delete($table, $object) { + if (ctools_api_version('1.7')) { + ctools_export_crud_delete($table, $object); + } + else { + $schema = ctools_export_get_schema($table); + $export = $schema['export']; + db_query("DELETE FROM {{$table}} WHERE {$export['key']} = '%s'", $object->{$export['key']}); + } +} + +/** + * Implements hook_features_export_render() for page_manager. + */ +function page_manager_pages_features_export_render($module, $data) { + // Reset the export display static to prevent clashes. + drupal_static_reset('panels_export_display'); + + // Ensure that handlers have their code included before exporting. + page_manager_get_tasks(); + return ctools_component_features_export_render('page_manager_pages', $module, $data); +} + +/** + * Implements hook_features_revert() for page_manager. + */ +function page_manager_pages_features_revert($module) { + if ($pages = features_get_default('page_manager_pages', $module)) { + require_once drupal_get_path('module', 'ctools') . '/page_manager/plugins/tasks/page.inc'; + foreach ($pages as $page) { + page_manager_page_delete($page); + } + } +} + +/** + * Implements hook_features_pipe_COMPONENT_alter() for views_view. + */ +function views_features_pipe_views_view_alter(&$pipe, $data, $export) { + // @todo Remove this check before next stable release. + if (!function_exists('views_plugin_list')) { + return; + } + + $map = array_flip($data); + foreach (views_plugin_list() as $plugin) { + foreach ($plugin['views'] as $view_name) { + if (isset($map[$view_name])) { + $pipe['dependencies'][$plugin['module']] = $plugin['module']; + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.features.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.features.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,73 @@ + array( + 'name' => 'Dependencies', + 'feature_source' => TRUE, + 'duplicates' => FEATURES_DUPLICATES_ALLOWED, + ), + ); +} + +/** + * Implements hook_features_export_options(). + */ +function dependencies_features_export_options() { + // Excluded modules. + $excluded = drupal_required_modules(); + $options = array(); + foreach (features_get_modules() as $module_name => $info) { + if (!in_array($module_name, $excluded) && $info->status && !empty($info->info)) { + $options[$module_name] = $info->info['name']; + } + } + return $options; +} + +/** + * Implements hook_features_export(). + */ +function dependencies_features_export($data, &$export, $module_name = '') { + // Don't allow a module to depend upon itself. + if (!empty($data[$module_name])) { + unset($data[$module_name]); + } + + // Clean up existing dependencies and merge. + $export['dependencies'] = _features_export_minimize_dependencies($export['dependencies'], $module_name); + $export['dependencies'] = array_merge($data, $export['dependencies']); + $export['dependencies'] = array_unique($export['dependencies']); +} + +/** + * Implements hook_features_revert(). + */ +function dependencies_features_revert($module) { + dependencies_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + * Ensure that all of a feature's dependencies are enabled. + */ +function dependencies_features_rebuild($module) { + $feature = features_get_features($module); + if (!empty($feature->info['dependencies'])) { + $install = array(); + foreach ($feature->info['dependencies'] as $dependency) { + // Parse the dependency string into the module name and version information. + $parsed_dependency = drupal_parse_dependency($dependency); + $dependency = $parsed_dependency['name']; + if (!module_exists($dependency)) { + $install[] = $dependency; + } + } + if (!empty($install)) { + features_install_modules($install); + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.field.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.field.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,530 @@ + array( + // this is deprecated by field_base and field_instance + // but retained for compatibility with older exports + 'name' => t('Fields'), + 'default_hook' => 'field_default_fields', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => FALSE, + ), + 'field_base' => array( + 'name' => t('Field Bases'), + 'default_hook' => 'field_default_field_bases', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => TRUE, + 'supersedes' => 'field', + ), + 'field_instance' => array( + 'name' => t('Field Instances'), + 'default_hook' => 'field_default_field_instances', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => TRUE, + 'supersedes' => 'field', + ) + ); +} + +/** + * Implements hook_features_export_options(). + */ +function field_base_features_export_options() { + $options = array(); + $fields = field_info_fields(); + foreach ($fields as $field_name => $field) { + $options[$field_name] = $field_name; + } + return $options; +} + +/** + * Implements hook_features_export_options(). + */ +function field_instance_features_export_options() { + $options = array(); + foreach (field_info_fields() as $field_name => $field) { + foreach ($field['bundles'] as $entity_type => $bundles) { + foreach ($bundles as $bundle) { + $identifier = "{$entity_type}-{$bundle}-{$field_name}"; + $options[$identifier] = $identifier; + } + } + } + ksort($options); + return $options; +} + +/** + * Implements hook_features_export(). + */ +function field_base_features_export($data, &$export, $module_name = '') { + $pipe = array(); + $map = features_get_default_map('field_base'); + + // The field_default_field_bases() hook integration is provided by the + // features module so we need to add it as a dependency. + $export['dependencies']['features'] = 'features'; + + foreach ($data as $identifier) { + if ($base = features_field_base_load($identifier)) { + // If this field is already provided by another module, remove the field + // and add the other module as a dependency. + if (isset($map[$identifier]) && $map[$identifier] != $module_name) { + if (isset($export['features']['field_base'][$identifier])) { + unset($export['features']['field_base'][$identifier]); + } + $module = $map[$identifier]; + $export['dependencies'][$module] = $module; + } + // If the field has not yet been exported, add it + else { + $export['features']['field_base'][$identifier] = $identifier; + $export['dependencies'][$base['module']] = $base['module']; + if ($base['storage']['type'] != variable_get('field_storage_default', 'field_sql_storage')) { + $export['dependencies'][$base['storage']['module']] = $base['storage']['module']; + } + // If taxonomy field, add in the vocabulary + if ($base['type'] == 'taxonomy_term_reference' && !empty($base['settings']['allowed_values'])) { + foreach ($base['settings']['allowed_values'] as $allowed_values) { + if (!empty($allowed_values['vocabulary'])) { + $pipe['taxonomy'][] = $allowed_values['vocabulary']; + } + } + } + } + } + } + return $pipe; +} + +/** + * Implements hook_features_export(). + */ +function field_instance_features_export($data, &$export, $module_name = '') { + $pipe = array('field_base' => array()); + $map = features_get_default_map('field_instance'); + + // The field_default_field_instances() hook integration is provided by the + // features module so we need to add it as a dependency. + $export['dependencies']['features'] = 'features'; + + foreach ($data as $identifier) { + if ($instance = features_field_instance_load($identifier)) { + // If this field is already provided by another module, remove the field + // and add the other module as a dependency. + if (isset($map[$identifier]) && $map[$identifier] != $module_name) { + if (isset($export['features']['field_instance'][$identifier])) { + unset($export['features']['field_instance'][$identifier]); + } + $module = $map[$identifier]; + $export['dependencies'][$module] = $module; + } + // If the field has not yet been exported, add it + else { + $export['features']['field_instance'][$identifier] = $identifier; + $export['dependencies'][$instance['widget']['module']] = $instance['widget']['module']; + foreach ($instance['display'] as $key => $display) { + if (isset($display['module'])) { + $export['dependencies'][$display['module']] = $display['module']; + // @TODO: handle the pipe to image styles + } + } + $pipe['field_base'][] = $instance['field_name']; + } + } + } + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function field_base_features_export_render($module, $data, $export = NULL) { + $translatables = $code = array(); + $code[] = ' $field_bases = array();'; + $code[] = ''; + foreach ($data as $identifier) { + if ($field = features_field_base_load($identifier)) { + unset($field['columns']); + // unset($field['locked']); + // Only remove the 'storage' declaration if the field is using the default + // storage type. + if ($field['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) { + unset($field['storage']); + } + // If we still have a storage declaration here it means that a non-default + // storage type was altered into to the field definition. And noone would + // never need to change the 'details' key, so don't render it. + if (isset($field['storage']['details'])) { + unset($field['storage']['details']); + } + + _field_instance_features_export_sort($field); + $field_export = features_var_export($field, ' '); + $field_identifier = features_var_export($identifier); + $code[] = " // Exported field_base: {$field_identifier}"; + $code[] = " \$field_bases[{$field_identifier}] = {$field_export};"; + $code[] = ""; + } + } + $code[] = ' return $field_bases;'; + $code = implode("\n", $code); + return array('field_default_field_bases' => $code); +} + +/** + * Implements hook_features_export_render(). + */ +function field_instance_features_export_render($module, $data, $export = NULL) { + $translatables = $code = array(); + + $code[] = ' $field_instances = array();'; + $code[] = ''; + + foreach ($data as $identifier) { + if ($instance = features_field_instance_load($identifier)) { + _field_instance_features_export_sort($instance); + $field_export = features_var_export($instance, ' '); + $instance_identifier = features_var_export($identifier); + $code[] = " // Exported field_instance: {$instance_identifier}"; + $code[] = " \$field_instances[{$instance_identifier}] = {$field_export};"; + $code[] = ""; + + if (!empty($instance['label'])) { + $translatables[] = $instance['label']; + } + if (!empty($instance['description'])) { + $translatables[] = $instance['description']; + } + } + } + if (!empty($translatables)) { + $code[] = features_translatables_export($translatables, ' '); + } + $code[] = ' return $field_instances;'; + $code = implode("\n", $code); + return array('field_default_field_instances' => $code); +} + +// Helper to enforce consistency in field export arrays. +function _field_instance_features_export_sort(&$field, $sort = TRUE) { + + // Some arrays are not sorted to preserve order (for example allowed_values). + static $sort_blacklist = array( + 'allowed_values', + 'format_handlers', + ); + + if ($sort) { + uksort($field, 'strnatcmp'); + } + foreach ($field as $k => $v) { + if (is_array($v)) { + _field_instance_features_export_sort($field[$k], !in_array($k, $sort_blacklist)); + } + } +} + +/** + * Implements hook_features_revert(). + */ +function field_base_features_revert($module) { + field_base_features_rebuild($module); +} + +/** + * Implements hook_features_revert(). + */ +function field_instance_features_revert($module) { + field_instance_features_rebuild($module); +} + +/** + * Implements of hook_features_rebuild(). + * Rebuilds fields from code defaults. + */ +function field_base_features_rebuild($module) { + if ($fields = features_get_default('field_base', $module)) { + field_info_cache_clear(); + + // Load all the existing field bases up-front so that we don't + // have to rebuild the cache all the time. + $existing_fields = field_info_fields(); + + foreach ($fields as $field) { + // Create or update field. + if (isset($existing_fields[$field['field_name']])) { + $existing_field = $existing_fields[$field['field_name']]; + if ($field + $existing_field != $existing_field) { + field_update_field($field); + } + } + else { + field_create_field($field); + $existing_fields[$field['field_name']] = $field; + } + variable_set('menu_rebuild_needed', TRUE); + } + } +} + +/** + * Implements of hook_features_rebuild(). + * Rebuilds field instances from code defaults. + */ +function field_instance_features_rebuild($module) { + if ($instances = features_get_default('field_instance', $module)) { + field_info_cache_clear(); + + // Load all the existing instances up-front so that we don't + // have to rebuild the cache all the time. + $existing_instances = field_info_instances(); + + foreach ($instances as $field_instance) { + // If the field base information does not exist yet, cancel out. + if (!field_info_field($field_instance['field_name'])) { + continue; + } + + // Create or update field instance. + if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) { + $existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']]; + if ($field_instance + $existing_instance != $existing_instance) { + try { + field_update_instance($field_instance); + } + catch (FieldException $e) { + watchdog('features', 'Attempt to update field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR); + } + } + } + else { + try { + field_create_instance($field_instance); + } + catch (FieldException $e) { + watchdog('features', 'Attempt to create field instance %label (in %entity entity type %bundle bundle) failed: %message', array('%label' => $field_instance['field_name'], '%entity' => $field_instance['entity_type'], '%bundle' => $field_instance['bundle'], '%message' => $e->getMessage()), WATCHDOG_ERROR); + } + $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']] = $field_instance; + } + } + + if ($instances) { + variable_set('menu_rebuild_needed', TRUE); + } + } +} + +/** + * Load a field base configuration by a field_name identifier. + */ +function features_field_base_load($field_name) { + if ($field_info = field_info_field($field_name)) { + unset($field_info['id']); + unset($field_info['bundles']); + return $field_info; + } + return FALSE; +} + +/** + * Load a field's instance configuration by an entity_type-bundle-field_name + * identifier. + */ +function features_field_instance_load($identifier) { + list($entity_type, $bundle, $field_name) = explode('-', $identifier); + if ($instance_info = field_info_instance($entity_type, $field_name, $bundle)) { + unset($instance_info['id']); + unset($instance_info['field_id']); + return $instance_info; + } + return FALSE; +} + +/* ----- DEPRECATED FIELD EXPORT ----- + * keep this code for backward compatibility with older exports + * until v3.x + */ + +/** + * Implements hook_features_export_options(). + */ +function field_features_export_options() { + $options = array(); + $instances = field_info_instances(); + foreach ($instances as $entity_type => $bundles) { + foreach ($bundles as $bundle => $fields) { + foreach ($fields as $field) { + $identifier = "{$entity_type}-{$bundle}-{$field['field_name']}"; + $options[$identifier] = $identifier; + } + } + } + return $options; +} + +/** + * Implements hook_features_export(). + */ +function field_features_export($data, &$export, $module_name = '') { + $pipe = array(); + // Convert 'field' to 'field_instance' on features-update. + $pipe['field_instance'] = $data; + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function field_features_export_render($module, $data, $export = NULL) { + $translatables = $code = array(); + + $code[] = ' $fields = array();'; + $code[] = ''; + foreach ($data as $identifier) { + if ($field = features_field_load($identifier)) { + unset($field['field_config']['columns']); + // Only remove the 'storage' declaration if the field is using the default + // storage type. + if ($field['field_config']['storage']['type'] == variable_get('field_storage_default', 'field_sql_storage')) { + unset($field['field_config']['storage']); + } + // If we still have a storage declaration here it means that a non-default + // storage type was altered into to the field definition. And noone would + // never need to change the 'details' key, so don't render it. + if (isset($field['field_config']['storage']['details'])) { + unset($field['field_config']['storage']['details']); + } + + _field_features_export_sort($field); + $field_export = features_var_export($field, ' '); + $field_identifier = features_var_export($identifier); + $code[] = " // Exported field: {$field_identifier}."; + $code[] = " \$fields[{$field_identifier}] = {$field_export};"; + $code[] = ""; + + // Add label and description to translatables array. + if (!empty($field['field_instance']['label'])) { + $translatables[] = $field['field_instance']['label']; + } + if (!empty($field['field_instance']['description'])) { + $translatables[] = $field['field_instance']['description']; + } + } + } + if (!empty($translatables)) { + $code[] = features_translatables_export($translatables, ' '); + } + $code[] = ' return $fields;'; + $code = implode("\n", $code); + return array('field_default_fields' => $code); +} + +// Helper to enforce consistency in field export arrays. +function _field_features_export_sort(&$field, $sort = TRUE) { + + // Some arrays are not sorted to preserve order (for example allowed_values). + static $sort_blacklist = array( + 'allowed_values', + 'format_handlers', + ); + + if ($sort) { + ksort($field); + } + foreach ($field as $k => $v) { + if (is_array($v)) { + _field_features_export_sort($field[$k], !in_array($k, $sort_blacklist)); + } + } +} + +/** + * Implements hook_features_revert(). + */ +function field_features_revert($module) { + field_features_rebuild($module); +} + +/** + * Implements of hook_features_rebuild(). + * Rebuilds fields from code defaults. + */ +function field_features_rebuild($module) { + if ($fields = features_get_default('field', $module)) { + field_info_cache_clear(); + + // Load all the existing fields and instance up-front so that we don't + // have to rebuild the cache all the time. + $existing_fields = field_info_fields(); + $existing_instances = field_info_instances(); + + foreach ($fields as $field) { + // Create or update field. + $field_config = $field['field_config']; + if (isset($existing_fields[$field_config['field_name']])) { + $existing_field = $existing_fields[$field_config['field_name']]; + if ($field_config + $existing_field != $existing_field) { + try { + field_update_field($field_config); + } + catch (FieldException $e) { + watchdog('features', 'Attempt to update field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR); + } + } + } + else { + try { + field_create_field($field_config); + } + catch (FieldException $e) { + watchdog('features', 'Attempt to create field %label failed: %message', array('%label' => $field_config['field_name'], '%message' => $e->getMessage()), WATCHDOG_ERROR); + } + $existing_fields[$field_config['field_name']] = $field_config; + } + + // Create or update field instance. + $field_instance = $field['field_instance']; + if (isset($existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']])) { + $existing_instance = $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']]; + if ($field_instance + $existing_instance != $existing_instance) { + field_update_instance($field_instance); + } + } + else { + field_create_instance($field_instance); + $existing_instances[$field_instance['entity_type']][$field_instance['bundle']][$field_instance['field_name']] = $field_instance; + } + } + + if ($fields) { + variable_set('menu_rebuild_needed', TRUE); + } + } +} + +/** + * Load a field's configuration and instance configuration by an + * entity_type-bundle-field_name identifier. + */ +function features_field_load($identifier) { + list($entity_type, $bundle, $field_name) = explode('-', $identifier); + $field_info = field_info_field($field_name); + $instance_info = field_info_instance($entity_type, $field_name, $bundle); + if ($field_info && $instance_info) { + unset($field_info['id']); + unset($field_info['bundles']); + unset($instance_info['id']); + unset($instance_info['field_id']); + return array( + 'field_config' => $field_info, + 'field_instance' => $instance_info, + ); + } + return FALSE; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.filter.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.filter.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,120 @@ + array( + 'name' => t('Text formats'), + 'default_hook' => 'filter_default_formats', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => TRUE + ), + ); +} + +/** + * Implements hook_features_export_options(). + */ +function filter_features_export_options() { + $options = array(); + foreach (filter_formats() as $format => $info) { + $options[$format] = $info->name; + } + return $options; +} + +/** + * Implements hook_features_export(). + */ +function filter_features_export($data, &$export, $module_name = '') { + // The filter_default_formats() hook integration is provided by the + // features module so we need to add it as a dependency. + $export['dependencies']['features'] = 'features'; + + $filter_info = filter_get_filters(); + foreach ($data as $name) { + if ($format = features_filter_format_load($name)) { + // Add format to exports + $export['features']['filter'][$format->format] = $format->format; + + // Iterate through filters and ensure each filter's module is included as a dependency + foreach (array_keys($format->filters) as $name) { + if (isset($filter_info[$name], $filter_info[$name]['module'])) { + $module = $filter_info[$name]['module']; + $export['dependencies'][$module] = $module; + } + } + } + } + + $pipe = array(); + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function filter_features_export_render($module, $data, $export = NULL) { + $code = array(); + $code[] = ' $formats = array();'; + $code[] = ''; + + foreach ($data as $name) { + if ($format = features_filter_format_load($name)) { + $format_export = features_var_export($format, ' '); + $format_identifier = features_var_export($format->format); + $code[] = " // Exported format: {$format->name}."; + $code[] = " \$formats[{$format_identifier}] = {$format_export};"; + $code[] = ""; + } + } + + $code[] = ' return $formats;'; + $code = implode("\n", $code); + return array('filter_default_formats' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function filter_features_revert($module) { + return filter_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + */ +function filter_features_rebuild($module) { + if ($defaults = features_get_default('filter', $module)) { + foreach ($defaults as $format) { + $format = (object) $format; + filter_format_save($format); + } + } +} + +/** + * Load a filter format by its name. + */ +function features_filter_format_load($name) { + // Use machine name for retrieving the format if available. + $query = db_select('filter_format'); + $query->fields('filter_format'); + $query->condition('format', $name); + + // Retrieve filters for the format and attach. + if ($format = $query->execute()->fetchObject()) { + $format->filters = array(); + foreach (filter_list_format($format->format) as $filter) { + if (!empty($filter->status)) { + $format->filters[$filter->name]['weight'] = $filter->weight; + $format->filters[$filter->name]['status'] = $filter->status; + $format->filters[$filter->name]['settings'] = $filter->settings; + } + } + return $format; + } + return FALSE; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.image.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.image.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,101 @@ + array( + 'name' => t('Image styles'), + 'feature_source' => TRUE, + 'default_hook' => 'image_default_styles', + 'alter_hook' => 'image_styles', + ) + ); +} + +/** + * Implements hook_features_export_options(). + */ +function image_features_export_options() { + $options = array(); + foreach (image_styles() as $name => $style) { + $options[$name] = $style['name']; + } + return $options; +} + +/** + * Implements hook_features_export(). + */ +function image_features_export($data, &$export, $module_name = '') { + $pipe = array(); + $map = features_get_default_map('image'); + foreach ($data as $style) { + $export['dependencies']['image'] = 'image'; + // If another module provides this style, add it as a dependency + if (isset($map[$style]) && $map[$style] != $module_name) { + $module = $map[$style]; + $export['dependencies'][$module] = $module; + } + // Otherwise, export the style + elseif (image_style_load($style)) { + $export['features']['image'][$style] = $style; + } + } + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function image_features_export_render($module_name, $data, $export = NULL) { + $code = array(); + $code[] = ' $styles = array();'; + $code[] = ''; + foreach ($data as $name) { + if ($style = image_style_load($name)) { + _image_features_style_sanitize($style); + $style_export = features_var_export($style, ' '); + $style_identifier = features_var_export($name); + $code[] = " // Exported image style: {$name}."; + $code[] = " \$styles[{$style_identifier}] = {$style_export};"; + $code[] = ""; + } + } + $code[] = ' return $styles;'; + $code = implode("\n", $code); + return array('image_default_styles' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function image_features_revert($module) { + if ($default_styles = features_get_default('image', $module)) { + foreach (array_keys($default_styles) as $default_style) { + if ($style = image_style_load($default_style)) { + if ($style['storage'] != IMAGE_STORAGE_DEFAULT) { + image_default_style_revert($style); + } + } + } + } +} + +/** + * Remove unnecessary keys for export. + */ +function _image_features_style_sanitize(&$style, $child = FALSE) { + $omit = $child ? array('isid', 'ieid', 'storage') : array('isid', 'ieid', 'storage', 'module'); + if (is_array($style)) { + foreach ($style as $k => $v) { + if (in_array($k, $omit, TRUE)) { + unset($style[$k]); + } + else if (is_array($v)) { + _image_features_style_sanitize($style[$k], TRUE); + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.locale.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.locale.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,162 @@ + array( + 'name' => t('Languages'), + 'default_hook' => 'locale_default_languages', + 'feature_source' => TRUE, + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ), + ); +} + +/** + * Implements hook_features_export_options(). + */ +function language_features_export_options() { + return locale_language_list('native', TRUE); +} + +/** + * Implements hook_features_export(). + */ +function language_features_export($data, &$export, $module_name = '') { + $export['dependencies']['features'] = 'features'; + $export['dependencies']['locale'] = 'locale'; + + $language_list = locale_language_list('native', TRUE); + + foreach ($data as $name) { + // Only export existing languages. + if (!empty($language_list[$name])) { + // Add language to exports. + $export['features']['language'][$name] = $name; + } + } + + // No pipe to return. + return array(); +} + +/** + * Implements hook_features_export_render(). + */ +function language_features_export_render($module, $data, $export = NULL) { + $code = array(); + $code[] = ' $languages = array();'; + $code[] = ''; + + $language_list = language_list(); + + foreach ($data as $name) { + // Only render existing languages. + if (!empty($language_list[$name])) { + + $var = (array) $language_list[$name]; + // Unset javascript hash + unset($var['javascript']); + + $lang_export = features_var_export($var, ' '); + $lang_identifier = features_var_export($name); + $code[] = " // Exported language: $name."; + $code[] = " \$languages[{$lang_identifier}] = {$lang_export};"; + } + } + + $code[] = ' return $languages;'; + $code = implode("\n", $code); + return array('locale_default_languages' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function language_features_revert($module) { + return language_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + */ +function language_features_rebuild($module) { + if ($defaults = features_get_default('language', $module)) { + foreach ($defaults as $key => $language) { + _features_language_save((object) $language); + } + + // Set correct language count. + $enabled_languages = db_select('languages') + ->condition('enabled', 1) + ->fields('languages') + ->execute() + ->rowCount(); + variable_set('language_count', $enabled_languages); + } +} + +/** + * Helper function to save the language to database. + * + * @see locale_languages_edit_form_submit() + */ +function _features_language_save($language) { + + $current_language = db_select('languages') + ->condition('language', $language->language) + ->fields('languages') + ->execute() + ->fetchAssoc(); + + // Set the default language when needed. + $default = language_default(); + + // Insert new language via api function. + if (empty($current_language)) { + locale_add_language($language->language, + $language->name, + $language->native, + $language->direction, + $language->domain, + $language->prefix, + $language->enabled, + ($language->language == $default->language)); + // Additional params, locale_add_language does not implement. + db_update('languages') + ->fields(array( + 'plurals' => empty($language->plurals) ? 0 : $language->plurals, + 'formula' => empty($language->formula) ? '' : $language->formula, + )) + ->condition('language', $language->language) + ->execute(); + } + // Update Existing language. + else { + // @TODO: get properties from schema. + $properties = array('language', 'name', 'native', 'direction', 'enabled', 'plurals', 'formula', 'domain', 'prefix', 'weight', 'javascript'); + // The javascript hash is not in the imported data but should be empty + if (!isset($language->javascript)) { + $language->javascript = ''; + } + + $fields = array_intersect_key((array) $language, array_flip($properties)); + db_update('languages') + ->fields($fields) + ->condition('language', $language->language) + ->execute(); + + // Set the default language when needed. + $default = language_default(); + if ($default->language == $language->language) { + variable_set('language_default', (object) $fields); + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.menu.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.menu.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,312 @@ + array( + 'name' => t('Menus'), + 'default_hook' => 'menu_default_menu_custom', + 'feature_source' => TRUE, + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ), + 'menu_links' => array( + 'name' => t('Menu links'), + 'default_hook' => 'menu_default_menu_links', + 'feature_source' => TRUE, + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ), + // DEPRECATED + 'menu' => array( + 'name' => t('Menu items'), + 'default_hook' => 'menu_default_items', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'feature_source' => FALSE, + ), + ); +} + +/** + * Implements hook_features_export(). + * DEPRECATED: This implementation simply migrates deprecated `menu` items + * to the `menu_links` type. + */ +function menu_features_export($data, &$export, $module_name = '') { + $pipe = array(); + foreach ($data as $path) { + $pipe['menu_links'][] = "features:{$path}"; + } + return $pipe; +} + +/** + * Implements hook_features_export_options(). + */ +function menu_custom_features_export_options() { + $options = array(); + $result = db_query("SELECT * FROM {menu_custom} ORDER BY title", array(), array('fetch' => PDO::FETCH_ASSOC)); + foreach ($result as $menu) { + $options[$menu['menu_name']] = $menu['title']; + } + return $options; +} + +/** + * Implements hook_features_export(). + */ +function menu_custom_features_export($data, &$export, $module_name = '') { + // Default hooks are provided by the feature module so we need to add + // it as a dependency. + $export['dependencies']['features'] = 'features'; + $export['dependencies']['menu'] = 'menu'; + + // Collect a menu to module map + $pipe = array(); + $map = features_get_default_map('menu_custom', 'menu_name'); + foreach ($data as $menu_name) { + // If this menu is provided by a different module, add it as a dependency. + if (isset($map[$menu_name]) && $map[$menu_name] != $module_name) { + $export['dependencies'][$map[$menu_name]] = $map[$menu_name]; + } + else { + $export['features']['menu_custom'][$menu_name] = $menu_name; + } + } + return $pipe; +} + +/** + * Implements hook_features_export_render() + */ +function menu_custom_features_export_render($module, $data) { + $code = array(); + $code[] = ' $menus = array();'; + $code[] = ''; + + $translatables = array(); + foreach ($data as $menu_name) { + $row = db_select('menu_custom') + ->fields('menu_custom') + ->condition('menu_name', $menu_name) + ->execute() + ->fetchAssoc(); + if ($row) { + $export = features_var_export($row, ' '); + $code[] = " // Exported menu: {$menu_name}."; + $code[] = " \$menus['{$menu_name}'] = {$export};"; + $translatables[] = $row['title']; + $translatables[] = $row['description']; + } + } + if (!empty($translatables)) { + $code[] = features_translatables_export($translatables, ' '); + } + + $code[] = ''; + $code[] = ' return $menus;'; + $code = implode("\n", $code); + return array('menu_default_menu_custom' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function menu_custom_features_revert($module) { + menu_custom_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + */ +function menu_custom_features_rebuild($module) { + if ($defaults = features_get_default('menu_custom', $module)) { + foreach ($defaults as $menu) { + menu_save($menu); + } + } +} + +/** + * Implements hook_features_export_options(). + */ +function menu_links_features_export_options() { + global $menu_admin; + // Need to set this to TRUE in order to get menu links that the + // current user may not have access to (i.e. user/login) + $menu_admin = TRUE; + $use_menus = array_intersect_key(menu_get_menus(), array_flip(array_filter(variable_get('features_admin_menu_links_menus', array_keys(menu_get_menus()))))); + $menu_links = menu_parent_options($use_menus, array('mlid' => 0)); + $options = array(); + foreach ($menu_links as $key => $name) { + list($menu_name, $mlid) = explode(':', $key, 2); + if ($mlid != 0) { + $link = menu_link_load($mlid); + $identifier = menu_links_features_identifier($link); + $options[$identifier] = "{$menu_name}: {$name}"; + } + } + $menu_admin = FALSE; + return $options; +} + +/** + * Callback for generating the menu link exportable identifier. + */ +function menu_links_features_identifier($link) { + return isset($link['menu_name'], $link['link_path']) ? "{$link['menu_name']}:{$link['link_path']}" : FALSE; +} + +/** + * Implements hook_features_export(). + */ +function menu_links_features_export($data, &$export, $module_name = '') { + // Default hooks are provided by the feature module so we need to add + // it as a dependency. + $export['dependencies']['features'] = 'features'; + $export['dependencies']['menu'] = 'menu'; + + // Collect a link to module map + $pipe = array(); + $map = features_get_default_map('menu_links', 'menu_links_features_identifier'); + foreach ($data as $identifier) { + if ($link = features_menu_link_load($identifier)) { + // If this link is provided by a different module, add it as a dependency. + if (isset($map[$identifier]) && $map[$identifier] != $module_name) { + $export['dependencies'][$map[$identifier]] = $map[$identifier]; + } + else { + $export['features']['menu_links'][$identifier] = $identifier; + } + // For now, exclude a variety of common menus from automatic export. + // They may still be explicitly included in a Feature if the builder + // chooses to do so. + if (!in_array($link['menu_name'], array('features', 'primary-links', 'secondary-links', 'navigation', 'admin', 'devel'))) { + $pipe['menu_custom'][] = $link['menu_name']; + } + } + } + return $pipe; +} + +/** + * Implements hook_features_export_render() + */ +function menu_links_features_export_render($module, $data) { + $code = array(); + $code[] = ' $menu_links = array();'; + $code[] = ''; + + $translatables = array(); + foreach ($data as $identifier) { + if ($link = features_menu_link_load($identifier)) { + // Replace plid with a parent path. + if (!empty($link['plid']) && $parent = menu_link_load($link['plid'])) { + $link['parent_path'] = $parent['link_path']; + } + unset($link['plid']); + unset($link['mlid']); + + $code[] = " // Exported menu link: {$identifier}"; + $code[] = " \$menu_links['{$identifier}'] = ". features_var_export($link, ' ') .";"; + $translatables[] = $link['link_title']; + } + } + if (!empty($translatables)) { + $code[] = features_translatables_export($translatables, ' '); + } + + $code[] = ''; + $code[] = ' return $menu_links;'; + $code = implode("\n", $code); + return array('menu_default_menu_links' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function menu_links_features_revert($module) { + menu_links_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + */ +function menu_links_features_rebuild($module) { + if ($menu_links = features_get_default('menu_links', $module)) { + menu_links_features_rebuild_ordered($menu_links); + } +} + +/** + * Generate a depth tree of all menu links. + */ +function menu_links_features_rebuild_ordered($menu_links, $reset = FALSE) { + static $ordered; + static $all_links; + if (!isset($ordered) || $reset) { + $ordered = array(); + $unordered = features_get_default('menu_links'); + + // Order all links by depth. + if ($unordered) { + do { + $current = count($unordered); + foreach ($unordered as $key => $link) { + $identifier = menu_links_features_identifier($link); + $parent = isset($link['parent_path']) ? "{$link['menu_name']}:{$link['parent_path']}" : ''; + if (empty($parent)) { + $ordered[$identifier] = 0; + $all_links[$identifier] = $link; + unset($unordered[$key]); + } + elseif (isset($ordered[$parent])) { + $ordered[$identifier] = $ordered[$parent] + 1; + $all_links[$identifier] = $link; + unset($unordered[$key]); + } + } + } while (count($unordered) < $current); + } + asort($ordered); + } + + // Ensure any default menu items that do not exist are created. + foreach (array_keys($ordered) as $identifier) { + $link = $all_links[$identifier]; + $existing = features_menu_link_load($identifier); + if (!$existing || in_array($link, $menu_links)) { + // Retrieve the mlid if this is an existing item. + if ($existing) { + $link['mlid'] = $existing['mlid']; + } + // Retrieve the plid for a parent link. + if (!empty($link['parent_path']) && $parent = features_menu_link_load("{$link['menu_name']}:{$link['parent_path']}")) { + $link['plid'] = $parent['mlid']; + } + else { + $link['plid'] = 0; + } + menu_link_save($link); + } + } +} + +/** + * Load a menu link by its menu_name:link_path identifier. + */ +function features_menu_link_load($identifier) { + list($menu_name, $link_path) = explode(':', $identifier, 2); + $link = db_select('menu_links') + ->fields('menu_links', array('menu_name', 'mlid', 'plid', 'link_path', 'router_path', 'link_title', 'options', 'module', 'hidden', 'external', 'has_children', 'expanded', 'weight')) + ->condition('menu_name', $menu_name) + ->condition('link_path', $link_path) + ->addTag('features_menu_link') + ->execute() + ->fetchAssoc(); + if ($link) { + $link['options'] = unserialize($link['options']); + return $link; + } + return FALSE; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.node.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.node.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,161 @@ + array( + 'name' => t('Content types'), + 'feature_source' => TRUE, + 'default_hook' => 'node_info', + ), + ); +} + +/** + * Implements hook_features_export_options(). + */ +function node_features_export_options() { + return node_type_get_names(); +} + +/** + * Implements hook_features_export. + */ +function node_features_export($data, &$export, $module_name = '') { + $pipe = array(); + $map = features_get_default_map('node'); + + foreach ($data as $type) { + // Poll node module to determine who provides the node type. + if ($info = node_type_get_type($type)) { + // If this node type is provided by a different module, add it as a dependency + if (isset($map[$type]) && $map[$type] != $module_name) { + $export['dependencies'][$map[$type]] = $map[$type]; + } + // Otherwise export the node type. + elseif (in_array($info->base, array('node_content', 'features'))) { + $export['features']['node'][$type] = $type; + $export['dependencies']['node'] = 'node'; + $export['dependencies']['features'] = 'features'; + } + + $fields = field_info_instances('node', $type); + foreach ($fields as $name => $field) { + $pipe['field_instance'][] = "node-{$field['bundle']}-{$field['field_name']}"; + } + } + } + + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function node_features_export_render($module, $data, $export = NULL) { + $elements = array( + 'name' => TRUE, + 'base' => FALSE, + 'description' => TRUE, + 'has_title' => FALSE, + 'title_label' => TRUE, + 'help' => TRUE, + ); + $output = array(); + $output[] = ' $items = array('; + foreach ($data as $type) { + if ($info = node_type_get_type($type)) { + // Force module name to be 'features' if set to 'node. If we leave as + // 'node' the content type will be assumed to be database-stored by + // the node module. + $info->base = ($info->base === 'node') ? 'features' : $info->base; + $output[] = " '{$type}' => array("; + foreach ($elements as $key => $t) { + if ($t) { + $text = str_replace("'", "\'", $info->$key); + $text = !empty($text) ? "t('{$text}')" : "''"; + $output[] = " '{$key}' => {$text},"; + } + else { + $output[] = " '{$key}' => '{$info->$key}',"; + } + } + $output[] = " ),"; + } + } + $output[] = ' );'; + $output[] = ' return $items;'; + $output = implode("\n", $output); + return array('node_info' => $output); +} + +/** + * Implements hook_features_revert(). + * + * @param $module + * name of module to revert content for + */ +function node_features_revert($module = NULL) { + if ($default_types = features_get_default('node', $module)) { + foreach ($default_types as $type_name => $type_info) { + // Delete node types + // We don't use node_type_delete() because we do not actually + // want to delete the node type (and invoke hook_node_type()). + // This can lead to bad consequences like CCK deleting field + // storage in the DB. + db_delete('node_type') + ->condition('type', $type_name) + ->execute(); + } + node_types_rebuild(); + menu_rebuild(); + } +} + +/** + * Implements hook_features_disable(). + * + * When a features module is disabled, modify any node types it provides so + * they can be deleted manually through the content types UI. + * + * @param $module + * Name of module that has been disabled. + */ +function node_features_disable($module) { + if ($default_types = features_get_default('node', $module)) { + foreach ($default_types as $type_name => $type_info) { + $type_info = node_type_load($type_name); + $type_info->module = 'node'; + $type_info->custom = 1; + $type_info->modified = 1; + $type_info->locked = 0; + node_type_save($type_info); + } + } +} + +/** + * Implements hook_features_enable(). + * + * When a features module is enabled, modify any node types it provides so + * they can no longer be deleted manually through the content types UI. + * + * @param $module + * Name of module that has been enabled. + */ +function node_features_enable($module) { + if ($default_types = features_get_default('node', $module)) { + foreach ($default_types as $type_name => $type_info) { + // Ensure the type exists. + if ($type_info = node_type_load($type_name)) { + $type_info->module = $module; + $type_info->custom = 0; + $type_info->modified = 0; + $type_info->locked = 1; + node_type_save($type_info); + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.taxonomy.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.taxonomy.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,105 @@ + array( + 'name' => t('Taxonomy'), + 'feature_source' => TRUE, + 'default_hook' => 'taxonomy_default_vocabularies', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ), + ); +} + +/** + * Implements hook_features_export_options(). + */ +function taxonomy_features_export_options() { + $vocabularies = array(); + foreach (taxonomy_get_vocabularies() as $vocabulary) { + $vocabularies[$vocabulary->machine_name] = $vocabulary->name; + } + return $vocabularies; +} + +/** + * Implements hook_features_export(). + * + * @todo Test adding existing dependencies. + */ +function taxonomy_features_export($data, &$export, $module_name = '') { + $pipe = array(); + + // taxonomy_default_vocabularies integration is provided by Features. + $export['dependencies']['features'] = 'features'; + $export['dependencies']['taxonomy'] = 'taxonomy'; + + // Add dependencies for each vocabulary. + $map = features_get_default_map('taxonomy'); + foreach ($data as $machine_name) { + if (isset($map[$machine_name]) && $map[$machine_name] != $module_name) { + $export['dependencies'][$map[$machine_name]] = $map[$machine_name]; + } + else { + $export['features']['taxonomy'][$machine_name] = $machine_name; + + $fields = field_info_instances('taxonomy_term', $machine_name); + foreach ($fields as $name => $field) { + $pipe['field'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}"; + $pipe['field_instance'][] = "taxonomy_term-{$field['bundle']}-{$field['field_name']}"; + } + } + } + return $pipe; +} + +/** + * Implements hook_features_export_render(). + */ +function taxonomy_features_export_render($module, $data) { + $vocabularies = taxonomy_get_vocabularies(); + $code = array(); + foreach ($data as $machine_name) { + foreach ($vocabularies as $vocabulary) { + if ($vocabulary->machine_name == $machine_name) { + // We don't want to break the entity cache, so we need to clone the + // vocabulary before unsetting the id. + $vocabulary = clone $vocabulary; + unset($vocabulary->vid); + $code[$machine_name] = $vocabulary; + } + } + } + $code = " return ". features_var_export($code, ' ') .";"; + return array('taxonomy_default_vocabularies' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function taxonomy_features_revert($module) { + taxonomy_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + * + * Rebuilds Taxonomy vocabularies from code defaults. + */ +function taxonomy_features_rebuild($module) { + if ($vocabularies = features_get_default('taxonomy', $module)) { + $existing = taxonomy_get_vocabularies(); + foreach ($vocabularies as $vocabulary) { + $vocabulary = (object) $vocabulary; + foreach ($existing as $existing_vocab) { + if ($existing_vocab->machine_name === $vocabulary->machine_name) { + $vocabulary->vid = $existing_vocab->vid; + } + } + taxonomy_vocabulary_save($vocabulary); + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/includes/features.user.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/includes/features.user.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,290 @@ + array( + 'name' => t('Roles'), + 'feature_source' => TRUE, + 'default_hook' => 'user_default_roles', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ), + 'user_permission' => array( + 'name' => t('Permissions'), + 'feature_source' => TRUE, + 'default_hook' => 'user_default_permissions', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + ), + ); +} + +/** + * Implements hook_features_export(). + */ +function user_permission_features_export($data, &$export, $module_name = '') { + $export['dependencies']['features'] = 'features'; + + // Ensure the modules that provide the given permissions are included as dependencies. + $map = user_permission_get_modules(); + foreach ($data as $perm) { + $perm_name = $perm; + // Export vocabulary permissions using the machine name, instead of + // vocabulary id. + _user_features_change_term_permission($perm_name, 'machine_name'); + if (isset($map[$perm_name])) { + $perm_module = $map[$perm_name]; + $export['dependencies'][$perm_module] = $perm_module; + $export['features']['user_permission'][$perm] = $perm; + } + } + + return array(); +} + +/** + * Implements hook_features_export_options(). + */ +function user_permission_features_export_options() { + $modules = array(); + $module_info = system_get_info('module'); + foreach (module_implements('permission') as $module) { + $modules[$module_info[$module]['name']] = $module; + } + ksort($modules); + + $options = array(); + foreach ($modules as $display_name => $module) { + if ($permissions = module_invoke($module, 'permission')) { + foreach ($permissions as $perm => $perm_item) { + // Export vocabulary permissions using the machine name, instead of + // vocabulary id. + _user_features_change_term_permission($perm); + $options[$perm] = strip_tags("{$display_name}: {$perm_item['title']}"); + } + } + } + return $options; +} + +/** + * Implements hook_features_export_render(). + */ +function user_permission_features_export_render($module, $data) { + $perm_modules = &drupal_static(__FUNCTION__ . '_perm_modules'); + if (!isset($perm_modules)) { + $perm_modules = user_permission_get_modules(); + } + + $code = array(); + $code[] = ' $permissions = array();'; + $code[] = ''; + + $permissions = _user_features_get_permissions(); + + foreach ($data as $perm_name) { + $permission = array(); + // Export vocabulary permissions using the machine name, instead of + // vocabulary id. + $perm = $perm_name; + _user_features_change_term_permission($perm_name, 'machine_name'); + $permission['name'] = $perm; + if (!empty($permissions[$perm_name])) { + sort($permissions[$perm_name]); + $permission['roles'] = drupal_map_assoc($permissions[$perm_name]); + } + else { + $permission['roles'] = array(); + } + if (isset($perm_modules[$perm_name])) { + $permission['module'] = $perm_modules[$perm_name]; + } + $perm_identifier = features_var_export($perm); + $perm_export = features_var_export($permission, ' '); + $code[] = " // Exported permission: {$perm_identifier}."; + $code[] = " \$permissions[{$perm_identifier}] = {$perm_export};"; + $code[] = ""; + } + + $code[] = ' return $permissions;'; + $code = implode("\n", $code); + return array('user_default_permissions' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function user_permission_features_revert($module) { + user_permission_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + * Iterate through default permissions and update the permissions map. + * + * @param $module + * The module whose default user permissions should be rebuilt. + */ +function user_permission_features_rebuild($module) { + if ($defaults = features_get_default('user_permission', $module)) { + // Make sure the list of available node types is up to date, especially when + // installing multiple features at once, for example from an install profile + // or via drush. + node_types_rebuild(); + + $modules = user_permission_get_modules(); + $roles = _user_features_get_roles(); + $permissions_by_role = _user_features_get_permissions(FALSE); + foreach ($defaults as $permission) { + $perm = $permission['name']; + _user_features_change_term_permission($perm, 'machine_name'); + if (empty($modules[$perm])) { + $args = array('!name' => $perm, '!module' => $module,); + $msg = t('Warning in features rebuild of !module. No module defines permission "!name".', $args); + drupal_set_message($msg, 'warning'); + continue; + } + // Export vocabulary permissions using the machine name, instead of + // vocabulary id. + foreach ($roles as $role) { + if (in_array($role, $permission['roles'])) { + $permissions_by_role[$role][$perm] = TRUE; + } + else { + $permissions_by_role[$role][$perm] = FALSE; + } + } + } + // Write the updated permissions. + foreach ($roles as $rid => $role) { + if (isset($permissions_by_role[$role])) { + user_role_change_permissions($rid, $permissions_by_role[$role]); + } + } + } +} + +/** + * Implements hook_features_export(). + */ +function user_role_features_export($data, &$export, $module_name = '') { + $export['dependencies']['features'] = 'features'; + $map = features_get_default_map('user_role', 'name'); + foreach ($data as $role) { + // Role is provided by another module. Add dependency. + if (isset($map[$role]) && $map[$role] != $module_name) { + $export['dependencies'][$map[$role]] = $map[$role]; + } + // Export. + elseif(user_role_load_by_name($role)) { + $export['features']['user_role'][$role] = $role; + } + } + return array(); +} + +/** + * Implements hook_features_export_options(). + */ +function user_role_features_export_options() { + return drupal_map_assoc(_user_features_get_roles(FALSE)); +} + +/** + * Implements hook_features_export_render(). + */ +function user_role_features_export_render($module, $data) { + $code = array(); + $code[] = ' $roles = array();'; + $code[] = ''; + + foreach ($data as $name) { + if ($role = user_role_load_by_name($name)) { + unset($role->rid); + $role_identifier = features_var_export($name); + $role_export = features_var_export($role , ' '); + $code[] = " // Exported role: {$name}."; + $code[] = " \$roles[{$role_identifier}] = {$role_export};"; + $code[] = ""; + } + } + + $code[] = ' return $roles;'; + $code = implode("\n", $code); + return array('user_default_roles' => $code); +} + +/** + * Implements hook_features_revert(). + */ +function user_role_features_revert($module) { + user_role_features_rebuild($module); +} + +/** + * Implements hook_features_rebuild(). + */ +function user_role_features_rebuild($module) { + if ($defaults = features_get_default('user_role', $module)) { + foreach ($defaults as $role) { + $role = (object) $role; + if ($existing = user_role_load_by_name($role->name)) { + $role->rid = $existing->rid; + } + user_role_save($role); + } + } +} + +/** + * Generate $rid => $role with role names untranslated. + */ +function _user_features_get_roles($builtin = TRUE) { + $roles = array(); + foreach (user_roles() as $rid => $name) { + switch ($rid) { + case DRUPAL_ANONYMOUS_RID: + if ($builtin) { + $roles[$rid] = 'anonymous user'; + } + break; + case DRUPAL_AUTHENTICATED_RID: + if ($builtin) { + $roles[$rid] = 'authenticated user'; + } + break; + default: + $roles[$rid] = $name; + break; + } + } + return $roles; +} + +/** + * Represent the current state of permissions as a perm to role name array map. + */ +function _user_features_get_permissions($by_role = TRUE) { + $map = user_permission_get_modules(); + $roles = _user_features_get_roles(); + $permissions = array(); + foreach (user_role_permissions($roles) as $rid => $role_permissions) { + if ($by_role) { + foreach (array_keys(array_filter($role_permissions)) as $permission) { + if (isset($map[$permission])) { + $permissions[$permission][] = $roles[$rid]; + } + } + } + else { + $permissions[$roles[$rid]] = array(); + foreach ($role_permissions as $permission => $status) { + if (isset($map[$permission])) { + $permissions[$roles[$rid]][$permission] = $status; + } + } + } + } + return $permissions; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,289 @@ + t('Component tests'), + 'description' => t('Run tests for components of Features.') , + 'group' => t('Features'), + ); + } + + /** + * Set up test. + */ + public function setUp() { + parent::setUp(array( + 'field', + 'filter', + 'image', + 'taxonomy', + 'views', + 'features', + 'features_test' + )); + + // Run a features rebuild to ensure our feature is fully installed. + features_rebuild(); + + $admin_user = $this->drupalCreateUser(array('administer features')); + $this->drupalLogin($admin_user); + } + + /** + * Run test. + */ + public function test() { + module_load_include('inc', 'features', 'features.export'); + + $components = array_filter(array( + 'field_instance' => 'field', + 'filter' => 'filter', + 'image' => 'image', + 'node' => 'node', + 'user_permission' => 'user', + 'views_view' => 'views', + ), 'module_exists'); + + foreach (array_keys($components) as $component) { + $callback = "_test_{$component}"; + + // Ensure that the component/default is properly available. + $object = $this->$callback('load'); + $this->assertTrue(!empty($object), t('@component present.', array('@component' => $component))); + + // Ensure that the component is defaulted. + $states = features_get_component_states(array('features_test'), FALSE, TRUE); + $this->assertTrue($states['features_test'][$component] === FEATURES_DEFAULT, t('@component state: Default.', array('@component' => $component))); + + // Override component and test that Features detects the override. + $this->$callback('override', $this); + $states = features_get_component_states(array('features_test'), FALSE, TRUE); + $this->assertTrue($states['features_test'][$component] === FEATURES_OVERRIDDEN, t('@component state: Overridden.', array('@component' => $component))); + } + + // Revert component and ensure that component has reverted. + // Do this in separate loops so we only have to run + // drupal_flush_all_caches() once. + foreach (array_keys($components) as $component) { + features_revert(array('features_test' => array($component))); + } + drupal_flush_all_caches(); + foreach (array_keys($components) as $component) { + // Reload so things like Views can clear it's cache + $this->$callback('load'); + $states = features_get_component_states(array('features_test'), FALSE, TRUE); + $this->assertTrue($states['features_test'][$component] === FEATURES_DEFAULT, t('@component reverted.', array('@component' => $component))); + } + } + + protected function _test_field_instance($op = 'load') { + switch ($op) { + case 'load': + return field_info_instance('node', 'field_features_test', 'features_test'); + case 'override': + $field_instance = field_info_instance('node', 'field_features_test', 'features_test'); + $field_instance['label'] = 'Foo bar'; + field_update_instance($field_instance); + break; + } + } + + protected function _test_filter($op = 'load') { + // So... relying on our own API functions to test is pretty lame. + // But these modules don't have APIs either. So might as well use + // the ones we've written for them... + features_include(); + switch ($op) { + case 'load': + return features_filter_format_load('features_test'); + case 'override': + $format = features_filter_format_load('features_test'); + unset($format->filters['filter_url']); + filter_format_save($format); + break; + } + } + + protected function _test_image($op = 'load') { + switch ($op) { + case 'load': + return image_style_load('features_test'); + case 'override': + $style = image_style_load('features_test'); + $style = image_style_save($style); + foreach ($style['effects'] as $effect) { + $effect['data']['width'] = '120'; + image_effect_save($effect); + } + break; + } + } + + protected function _test_node($op = 'load') { + switch ($op) { + case 'load': + return node_type_get_type('features_test'); + case 'override': + $type = node_type_get_type('features_test'); + $type->description = 'Foo bar baz.'; + $type->modified = TRUE; + node_type_save($type); + break; + } + } + + protected function _test_views_view($op = 'load') { + switch ($op) { + case 'load': + return views_get_view('features_test', TRUE); + case 'override': + $view = views_get_view('features_test', TRUE); + $view->set_display('default'); + $view->display_handler->override_option('title', 'Foo bar'); + $view->save(); + // Clear the load cache from above + views_get_view('features_test', TRUE); + break; + } + } + + protected function _test_user_permission($op = 'load') { + switch ($op) { + case 'load': + $permissions = user_role_permissions(array(DRUPAL_AUTHENTICATED_RID => 'authenticated user')); + return !empty($permissions[DRUPAL_AUTHENTICATED_RID]['create features_test content']); + case 'override': + user_role_change_permissions(DRUPAL_AUTHENTICATED_RID, array('create features_test content' => 0)); + break; + } + } +} + +/** + * Tests enabling of feature modules. + */ +class FeaturesEnableTestCase extends DrupalWebTestCase { + protected $profile = 'testing'; + + /** + * Test info. + */ + public static function getInfo() { + return array( + 'name' => t('Features enable tests'), + 'description' => t('Run tests for enabling of features.') , + 'group' => t('Features'), + ); + } + + + /** + * Run test for features_get_components on enable. + */ + public function testFeaturesGetComponents() { + + // Testing that features_get_components returns correct after enable. + $modules = array( + 'features', + 'taxonomy', + 'features_test', + ); + + // Make sure features_get_components is cached if features already enabled. + if (!module_exists('features')) { + drupal_load('module', 'features'); + } + features_get_components(); + + module_enable($modules); + + // Make sure correct information for enabled modules is now cached. + $components = features_get_components(); + $taxonomy_component_info = taxonomy_features_api(); + $this->assertTrue(!empty($components['taxonomy']) && $components['taxonomy'] == $taxonomy_component_info['taxonomy'], 'features_get_components returns correct taxonomy information on enable'); + + features_rebuild(); + $this->assertNotNull(taxonomy_vocabulary_machine_name_load('taxonomy_features_test'), 'Taxonomy vocabulary correctly enabled on enable.'); + } +} + + +/** + * Tests intergration of ctools for features. + */ +class FeaturesCtoolsIntegrationTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + /** + * Test info. + */ + public static function getInfo() { + return array( + 'name' => t('Features Chaos Tools integration'), + 'description' => t('Run tests for ctool integration of features.') , + 'group' => t('Features'), + ); + } + + /** + * Set up test. + */ + public function setUp() { + parent::setUp(array( + 'features', + 'ctools', + )); + } + + /** + * Run test. + */ + public function testModuleEnable() { + $try = array( + 'strongarm', + 'views', + ); + + // Trigger the first includes and the static to be set. + features_include(); + $function_ends = array( + 'features_export', + 'features_export_options', + 'features_export_render', + 'features_revert', + ); + foreach ($try as $module) { + $function = $module . '_features_api'; + $this->assertFalse(function_exists($function), 'Chaos tools functions for ' . $module . ' do not exist while it is disabled.'); + // Module enable will trigger declaring the new functions. + module_enable(array($module)); + } + + // CTools hooks only created when there is an actual feature exportable + // enabled. + module_enable(array('features_test')); + + foreach ($try as $module) { + if (module_exists($module)) { + $function_exists = function_exists($function); + if ($function_exists) { + foreach ($function() as $component_type => $component_info) { + foreach ($function_ends as $function_end) { + $function_exists = $function_exists && function_exists($component_type . '_' . $function_end); + } + } + } + $this->assertTrue($function_exists, 'Chaos tools functions for ' . $module . ' exist when it is enabled.'); + } + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.features.field_base.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.features.field_base.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,43 @@ + 1, + 'cardinality' => 1, + 'deleted' => 0, + 'entity_types' => array(), + 'field_name' => 'field_features_test', + 'foreign keys' => array( + 'format' => array( + 'columns' => array( + 'format' => 'format', + ), + 'table' => 'filter_format', + ), + ), + 'indexes' => array( + 'format' => array( + 0 => 'format', + ), + ), + 'locked' => 0, + 'module' => 'text', + 'settings' => array( + 'max_length' => 255, + ), + 'translatable' => 1, + 'type' => 'text', + ); + + return $field_bases; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.features.field_instance.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.features.field_instance.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,76 @@ + 'features_test', + 'default_value' => NULL, + 'deleted' => 0, + 'description' => '', + 'display' => array( + 'default' => array( + 'label' => 'above', + 'module' => 'text', + 'settings' => array(), + 'type' => 'text_default', + 'weight' => 0, + ), + 'full' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + 'print' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + 'rss' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + 'teaser' => array( + 'label' => 'above', + 'settings' => array(), + 'type' => 'hidden', + 'weight' => 0, + ), + ), + 'entity_type' => 'node', + 'field_name' => 'field_features_test', + 'label' => 'Test', + 'required' => 0, + 'settings' => array( + 'text_processing' => 0, + 'user_register_form' => FALSE, + ), + 'widget' => array( + 'active' => 1, + 'module' => 'text', + 'settings' => array( + 'size' => 60, + ), + 'type' => 'text_textfield', + 'weight' => -4, + ), + ); + + // Translatables + // Included for use with string extractors like potx. + t('Test'); + + return $field_instances; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.features.filter.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.features.filter.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,56 @@ + 'features_test', + 'name' => 'features_test', + 'cache' => 1, + 'status' => 1, + 'weight' => 0, + 'filters' => array( + 'filter_autop' => array( + 'weight' => 10, + 'status' => 1, + 'settings' => array(), + ), + 'filter_html' => array( + 'weight' => 10, + 'status' => 1, + 'settings' => array( + 'allowed_html' => '
      1. ', + 'filter_html_help' => 1, + 'filter_html_nofollow' => 0, + ), + ), + 'filter_htmlcorrector' => array( + 'weight' => 10, + 'status' => 1, + 'settings' => array(), + ), + 'filter_html_escape' => array( + 'weight' => 10, + 'status' => 1, + 'settings' => array(), + ), + 'filter_url' => array( + 'weight' => 10, + 'status' => 1, + 'settings' => array( + 'filter_url_length' => 72, + ), + ), + ), + ); + + return $formats; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.features.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.features.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,72 @@ + "1"); + } +} + +/** + * Implements hook_views_api(). + */ +function features_test_views_api() { + return array("api" => "3.0"); +} + +/** + * Implements hook_image_default_styles(). + */ +function features_test_image_default_styles() { + $styles = array(); + + // Exported image style: features_test. + $styles['features_test'] = array( + 'name' => 'features_test', + 'effects' => array( + 2 => array( + 'label' => 'Scale', + 'help' => 'Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.', + 'effect callback' => 'image_scale_effect', + 'dimensions callback' => 'image_scale_dimensions', + 'form callback' => 'image_scale_form', + 'summary theme' => 'image_scale_summary', + 'module' => 'image', + 'name' => 'image_scale', + 'data' => array( + 'width' => 100, + 'height' => 100, + 'upscale' => 0, + ), + 'weight' => 1, + ), + ), + 'label' => 'features_test', + ); + + return $styles; +} + +/** + * Implements hook_node_info(). + */ +function features_test_node_info() { + $items = array( + 'features_test' => array( + 'name' => t('Testing: Features'), + 'base' => 'node_content', + 'description' => t('Content type provided for Features tests.'), + 'has_title' => '1', + 'title_label' => t('Title'), + 'help' => '', + ), + ); + return $items; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.features.taxonomy.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.features.taxonomy.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,36 @@ + array( + 'name' => 'Taxonomy Features Test', + 'machine_name' => 'taxonomy_features_test', + 'description' => 'Taxonomy vocabulary', + 'hierarchy' => 0, + 'module' => 'taxonomy', + 'weight' => 0, + 'rdf_mapping' => array( + 'rdftype' => array( + 0 => 'skos:ConceptScheme', + ), + 'name' => array( + 'predicates' => array( + 0 => 'dc:title', + ), + ), + 'description' => array( + 'predicates' => array( + 0 => 'rdfs:comment', + ), + ), + ), + ), + ); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.features.user_permission.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.features.user_permission.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,24 @@ + 'create features_test content', + 'roles' => array( + 'anonymous user' => 'anonymous user', + 'authenticated user' => 'authenticated user', + ), + 'module' => 'node', + ); + + return $permissions; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,29 @@ +name = Features Tests +description = Test module for Features testing. +core = 7.x +package = Testing +php = 5.2.0 +dependencies[] = features +dependencies[] = image +dependencies[] = strongarm +dependencies[] = taxonomy +dependencies[] = views +features[ctools][] = strongarm:strongarm:1 +features[ctools][] = views:views_default:3.0 +features[features_api][] = api:2 +features[field_base][] = field_features_test +features[field_instance][] = node-features_test-field_features_test +features[filter][] = features_test +features[image][] = features_test +features[node][] = features_test +features[taxonomy][] = taxonomy_features_test +features[user_permission][] = create features_test content +features[views_view][] = features_test +hidden = 1 + +; Information added by drupal.org packaging script on 2013-08-26 +version = "7.x-2.0-rc3" +core = "7.x" +project = "features" +datestamp = "1377548845" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/tests/features_test/features_test.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/tests/features_test/features_test.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,3 @@ +name = 'features_test'; + $view->description = 'Test view provided by Features testing module.'; + $view->tag = 'testing'; + $view->base_table = 'node'; + $view->human_name = ''; + $view->core = 0; + $view->api_version = '3.0'; + $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */ + + /* Display: Defaults */ + $handler = $view->new_display('default', 'Defaults', 'default'); + $handler->display->display_options['title'] = 'Test'; + $handler->display->display_options['use_more_always'] = FALSE; + $handler->display->display_options['access']['type'] = 'none'; + $handler->display->display_options['cache']['type'] = 'none'; + $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['exposed_form']['type'] = 'basic'; + $handler->display->display_options['pager']['type'] = 'full'; + $handler->display->display_options['style_plugin'] = 'default'; + $handler->display->display_options['row_plugin'] = 'node'; + $export['features_test'] = $view; + + return $export; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/theme/features-admin-components.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/theme/features-admin-components.tpl.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,23 @@ + +
        +
        +
        +

        +
        + +
        +
        +
        +
        + + +
        $key)) ?>
        + + +
        + +
        +
        + +
        diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/features/theme/theme.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/features/theme/theme.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,331 @@ + $status) { + $rows[] = array( + array( + 'data' => isset($modules[$dependency]->info['name']) ? $modules[$dependency]->info['name'] : $dependency, + 'class' => 'component' + ), + theme('features_module_status', array('status' => $status)), + ); + } + $vars['dependencies'] = theme('table', array('header' => array(t('Dependency'), t('Status')), 'rows' => $rows)); + + // Components + $rows = array(); + $components = features_get_components(); + + // Display key for conflicting elements. + if (!empty($form['#conflicts'])) { + $vars['key'][] = array( + 'title' => theme('features_storage_link', array('storage' => FEATURES_CONFLICT, 'text' => t('Conflicts with another feature'))), + 'html' => TRUE, + ); + } + + if (!empty($form['#info']['features'])) { + foreach ($form['#info']['features'] as $component => $items) { + if (!empty($items)) { + $conflicts = array_key_exists($component, $form['#conflicts']) + ? $form['#conflicts'][$component] + : NULL; + + $header = $data = array(); + if (element_children($form['revert'])) { + $header[] = array( + 'data' => isset($form['revert'][$component]) ? drupal_render($form['revert'][$component]) : '', + 'header' => TRUE + ); + } + $header[] = array( + 'data' => isset($components[$component]['name']) ? $components[$component]['name'] : $component, + 'header' => TRUE + ); + $header[] = array( + 'data' => drupal_render($form['components'][$component]), + 'header' => TRUE + ); + $rows[] = $header; + + if (element_children($form['revert'])) { + $data[] = ''; + } + $data[] = array( + 'data' => theme('features_component_list', array('components' => $items, 'source' => $items, 'conflicts' => $conflicts)), + 'colspan' => 2, + 'class' => 'component' + ); + $rows[] = $data; + } + } + } + $vars['components'] = theme('table', array('header' => array(), 'rows' => $rows)); + + // Other elements + $vars['buttons'] = drupal_render($form['buttons']); + $vars['form'] = $form; +} + +/** + * Themes a module status display. + */ +function theme_features_module_status($vars) { + switch ($vars['status']) { + case FEATURES_MODULE_ENABLED: + $text_status = t('Enabled'); + $class = 'admin-enabled'; + break; + case FEATURES_MODULE_DISABLED: + $text_status = t('Disabled'); + $class = 'admin-disabled'; + break; + case FEATURES_MODULE_MISSING: + $text_status = t('Missing'); + $class = 'admin-missing'; + break; + case FEATURES_MODULE_CONFLICT: + $text_status = t('Enabled'); + $class = 'admin-conflict'; + break; + } + $text = !empty($vars['module']) ? $vars['module'] . ' (' . $text_status . ')' : $text_status; + return "$text"; +} + +/** + * Themes a module status display. + */ +function theme_features_storage_link($vars) { + $classes = array( + FEATURES_OVERRIDDEN => 'admin-overridden', + FEATURES_DEFAULT => 'admin-default', + FEATURES_NEEDS_REVIEW => 'admin-needs-review', + FEATURES_REBUILDING => 'admin-rebuilding', + FEATURES_REBUILDABLE => 'admin-rebuilding', + FEATURES_CONFLICT => 'admin-conflict', + FEATURES_DISABLED => 'admin-disabled', + FEATURES_CHECKING => 'admin-loading', + ); + $default_text = array( + FEATURES_OVERRIDDEN => t('Overridden'), + FEATURES_DEFAULT => t('Default'), + FEATURES_NEEDS_REVIEW => t('Needs review'), + FEATURES_REBUILDING => t('Rebuilding'), + FEATURES_REBUILDABLE => t('Rebuilding'), + FEATURES_CONFLICT => t('Conflict'), + FEATURES_DISABLED => t('Disabled'), + FEATURES_CHECKING => t('Checking...'), + ); + $text = isset($vars['text']) ? $vars['text'] : $default_text[$vars['storage']]; + if ($vars['path']) { + $vars['options']['attributes']['class'][] = $classes[$vars['storage']]; + $vars['options']['attributes']['class'][] = 'features-storage'; + return l($text, $vars['path'], $vars['options']); + } + else { + return "{$text}"; + } +} + +/** + * Theme function for displaying form buttons + */ +function theme_features_form_buttons(&$vars) { + drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); + + $output = drupal_render_children($vars['element']); + return !empty($output) ? "
        {$output}
        " : ''; +} + +/** + * Theme for features management form. + */ +function theme_features_form_package(&$vars) { + drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); + drupal_add_js(drupal_get_path('module', 'features') . '/features.js'); + + $output = ''; + + $header = array('', t('Feature'), t('Signature')); + if (isset($vars['form']['state'])) { + $header[] = t('State'); + } + if (isset($vars['form']['actions'])) { + $header[] = t('Actions'); + } + + $rows = array(); + foreach (element_children($vars['form']['status']) as $element) { + // Yank title & description fields off the form element for + // rendering in their own cells. + $name = "
        "; + $name .= "{$vars['form']['status'][$element]['#title']}"; + $name .= "
        {$vars['form']['status'][$element]['#description']}
        "; + $name .= "
        "; + unset($vars['form']['status'][$element]['#title']); + unset($vars['form']['status'][$element]['#description']); + + + // Determine row & cell classes + $class = $vars['form']['status'][$element]['#default_value'] ? 'enabled' : 'disabled'; + + $row = array(); + $row['status'] = array('data' => drupal_render($vars['form']['status'][$element]), 'class' => array('status')); + $row['name'] = array('data' => $name, 'class' => 'name'); + $row['sign'] = array('data' => drupal_render($vars['form']['sign'][$element]), 'class' => array('sign')); + + if (isset($vars['form']['state'])) { + $row['state'] = array('data' => drupal_render($vars['form']['state'][$element]), 'class' => array('state')); + } + if (isset($vars['form']['actions'])) { + $row['actions'] = array('data' => drupal_render($vars['form']['actions'][$element]), 'class' => array('actions')); + } + $rows[] = array('data' => $row, 'class' => array($class)); + } + + if (empty($rows)) { + $rows[] = array('', array('data' => t('No features available.'), 'colspan' => count($header))); + } + + $class = count($header) > 3 ? 'features features-admin' : 'features features-manage'; + $output .= theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'features-form-table', 'class' => array($class)))); + + // Prevent section from being rendered by drupal_render(). + + $output .= drupal_render($vars['form']['buttons']); + $output .= drupal_render_children($vars['form']); + return $output; +} + +/** + * Theme functions ==================================================== + */ + +/** + * Export selection / display for features export form. + */ +function theme_features_form_export(&$vars) { + drupal_add_css(drupal_get_path('module', 'features') . '/features.css'); + drupal_add_js(drupal_get_path('module', 'features') . '/features.js'); + + $output = ''; + $output .= "
        "; + $output .= "
        " . drupal_render($vars['form']['components']) . drupal_render($vars['form']['sources']) . "
        "; + $output .= "
        " . drupal_render($vars['form']['preview']) . drupal_render($vars['form']['features']) . "
        "; + $output .= "
        "; + $output .= drupal_render_children($vars['form']); + return $output; +} + +/** + * Theme a set of features export components. + */ +function theme_features_form_components(&$vars) { + $output = ''; + foreach (element_children($vars['form']) as $key) { + unset($vars['form'][$key]['#title']); + $output .= "
        " . drupal_render($vars['form'][$key]) . "
        "; + } + $output .= drupal_render_children($vars['form']); + return $output; +} + +/** + * Theme a set of features export components. + */ +function theme_features_components($vars) { + $info = $vars['info']; + $sources = $vars['sources']; + + $output = ''; + $rows = array(); + $components = features_get_components(); + if (!empty($info['features']) || !empty($info['dependencies']) || !empty($sources)) { + $export = array_unique(array_merge( + array_keys($info['features']), + array_keys($sources), + array('dependencies') + )); + foreach ($export as $component) { + if ($component === 'dependencies') { + $feature_items = isset($info[$component]) ? $info[$component] : array(); + } + else { + $feature_items = isset($info['features'][$component]) ? $info['features'][$component] : array(); + } + $source_items = isset($sources[$component]) ? $sources[$component] : array(); + if (!empty($feature_items) || !empty($source_items)) { + $rows[] = array(array( + 'data' => isset($components[$component]['name']) ? $components[$component]['name'] : $component, + 'header' => TRUE + )); + $rows[] = array(array( + 'data' => theme('features_component_list', array('components' => $feature_items, 'source' => $source_items)), + 'class' => 'component' + )); + } + } + $output .= theme('table', array('header' => array(), 'rows' => $rows)); + $output .= theme('features_component_key', array()); + } + return $output; +} + +/** + * Theme individual components in a component list. + */ +function theme_features_component_list($vars) { + $components = $vars['components']; + $source = $vars['source']; + $conflicts = $vars['conflicts']; + + $list = array(); + foreach ($components as $component) { + // If component is not in source list, it was autodetected + if (!in_array($component, $source)) { + $list[] = "". check_plain($component) .""; + } + elseif (is_array($conflicts) && in_array($component, $conflicts)) { + $list[] = "". check_plain($component) .""; + } + else { + $list[] = "". check_plain($component) .""; + } + } + foreach ($source as $component) { + // If a source component is no longer in the items, it was removed because + // it is provided by a dependency. + if (!in_array($component, $components)) { + $list[] = "". check_plain($component) .""; + } + } + return "". implode(' ', $list) .""; +} + +/** + * Provide a themed key for a component list. + */ +function theme_features_component_key($vars) { + $list = array(); + $list[] = "" . t('Normal') . ""; + $list[] = "" . t('Auto-detected') . ""; + $list[] = "" . t('Provided by dependency') . ""; + return "" . implode(' ', $list) . ""; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,36 @@ + +Field collection +----------------- +Provides a field collection field, to which any number of fields can be attached. + +Each field collection item is internally represented as an entity, which is +referenced via the field collection field in the host entity. While +conceptually field collections are treated as part of the host entity, each +field collection item may also be viewed and edited separately. + + + Usage + ------ + + * Add a field collection field to any entity, e.g. to a node. For that use the + the usual "Manage fields" interface provided by the "field ui" module of + Drupal, e.g. "Admin -> Structure-> Content types -> Article -> Manage fields". + + * Then go to "Admin -> Structure-> Field collection" to define some fields for + the created field collection. + + * By the default, the field collection is not shown during editing of the host + entity. However, some links for adding, editing or deleting field collection + items is shown when the host entity is viewed. + + * Widgets for embedding the form for creating field collections in the + host-entity can be provided by any module. In future the field collection + module might provide such widgets itself too. + + +Restrictions +------------- + + * As of now, the field collection field does not properly respect different + languages of the host entity. Thus, for now it is suggested to only use the + field for entities that are not translatable. \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field-collection-item.tpl.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field-collection-item.tpl.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,37 @@ + +
        > +
        > + +
        +
        diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.admin.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.admin.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,47 @@ + t('Operations'), 'colspan' => '2')); + $rows = array(); + foreach ($instances as $entity_type => $type_bundles) { + foreach ($type_bundles as $bundle => $bundle_instances) { + foreach ($bundle_instances as $field_name => $instance) { + $field = field_info_field($field_name); + if ($field['type'] == 'field_collection') { + $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); + $rows[$field_name]['class'] = $field['locked'] ? array('menu-disabled') : array(''); + + $rows[$field_name]['data'][0] = $field['locked'] ? t('@field_name (Locked)', array('@field_name' => $field_name)) : $field_name; + $rows[$field_name]['data'][1][] = l($bundles[$entity_type][$bundle]['label'], $admin_path . '/fields'); + } + } + } + } + foreach ($rows as $field_name => $cell) { + $rows[$field_name]['data'][1] = implode(', ', $cell['data'][1]); + + $field_name_url_str = strtr($field_name, array('_' => '-')); + $rows[$field_name]['data'][2] = l(t('manage fields'), 'admin/structure/field-collections/' . $field_name_url_str . '/fields'); + $rows[$field_name]['data'][3] = l(t('manage display'), 'admin/structure/field-collections/' . $field_name_url_str . '/display'); + } + if (empty($rows)) { + $output = t('No field collections have been defined yet. To do so attach a field collection field to any entity.'); + } + else { + // Sort rows by field name. + ksort($rows); + $output = theme('table', array('header' => $header, 'rows' => $rows)); + } + return $output; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.api.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,169 @@ +my_field) && empty($item->my_field)) { + $is_empty = TRUE; + } +} + +/** + * Acts on field collections being loaded from the database. + * + * This hook is invoked during field collection item loading, which is handled + * by entity_load(), via the EntityCRUDController. + * + * @param array $entities + * An array of field collection item entities being loaded, keyed by id. + * + * @see hook_entity_load() + */ +function hook_field_collection_item_load(array $entities) { + $result = db_query('SELECT pid, foo FROM {mytable} WHERE pid IN(:ids)', array(':ids' => array_keys($entities))); + foreach ($result as $record) { + $entities[$record->pid]->foo = $record->foo; + } +} + +/** + * Responds when a field collection item is inserted. + * + * This hook is invoked after the field collection item is inserted into the + * database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being inserted. + * + * @see hook_entity_insert() + */ +function hook_field_collection_item_insert(FieldCollectionItemEntity $field_collection_item) { + db_insert('mytable')->fields(array( + 'id' => entity_id('field_collection_item', $field_collection_item), + 'extra' => print_r($field_collection_item, TRUE), + ))->execute(); +} + +/** + * Acts on a field collection item being inserted or updated. + * + * This hook is invoked before the field collection item is saved to the database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being inserted or updated. + * + * @see hook_entity_presave() + */ +function hook_field_collection_item_presave(FieldCollectionItemEntity $field_collection_item) { + $field_collection_item->name = 'foo'; +} + +/** + * Responds to a field collection item being updated. + * + * This hook is invoked after the field collection item has been updated in the + * database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being updated. + * + * @see hook_entity_update() + */ +function hook_field_collection_item_update(FieldCollectionItemEntity $field_collection_item) { + db_update('mytable') + ->fields(array('extra' => print_r($field_collection_item, TRUE))) + ->condition('id', entity_id('field_collection_item', $field_collection_item)) + ->execute(); +} + +/** + * Responds to field collection item deletion. + * + * This hook is invoked after the field collection item has been removed from + * the database. + * + * @param FieldCollectionItemEntity $field_collection_item + * The field collection item that is being deleted. + * + * @see hook_entity_delete() + */ +function hook_field_collection_item_delete(FieldCollectionItemEntity $field_collection_item) { + db_delete('mytable') + ->condition('pid', entity_id('field_collection_item', $field_collection_item)) + ->execute(); +} + +/** + * Act on a field collection item that is being assembled before rendering. + * + * @param $field_collection_item + * The field collection item entity. + * @param $view_mode + * The view mode the field collection item is rendered in. + * @param $langcode + * The language code used for rendering. + * + * The module may add elements to $field_collection_item->content prior to + * rendering. The structure of $field_collection_item->content is a renderable + * array as expected by drupal_render(). + * + * @see hook_entity_prepare_view() + * @see hook_entity_view() + */ +function hook_field_collection_item_view($field_collection_item, $view_mode, $langcode) { + $field_collection_item->content['my_additional_field'] = array( + '#markup' => $additional_field, + '#weight' => 10, + '#theme' => 'mymodule_my_additional_field', + ); +} + +/** + * Alter the results of entity_view() for field collection items. + * + * This hook is called after the content has been assembled in a structured + * array and may be used for doing processing which requires that the complete + * field collection item content structure has been built. + * + * If the module wishes to act on the rendered HTML of the field collection item + * rather than the structured content array, it may use this hook to add a + * #post_render callback. See drupal_render() and theme() documentation + * respectively for details. + * + * @param $build + * A renderable array representing the field collection item content. + * + * @see hook_entity_view_alter() + */ +function hook_field_collection_item_view_alter($build) { + if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) { + // Change its weight. + $build['an_additional_field']['#weight'] = -10; + + // Add a #post_render callback to act on the rendered HTML of the entity. + $build['#post_render'][] = 'my_module_post_render'; + } +} + +/** + * @} + */ \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,16 @@ +name = Field collection +description = Provides a field collection field, to which any number of fields can be attached. +core = 7.x +dependencies[] = entity +files[] = field_collection.test +files[] = field_collection.info.inc +files[] = views/field_collection_handler_relationship.inc +configure = admin/structure/field-collections +package = Fields + +; Information added by drupal.org packaging script on 2012-12-25 +version = "7.x-1.0-beta5" +core = "7.x" +project = "field_collection" +datestamp = "1356475963" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.info.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.info.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,30 @@ + t('Host entity'), + 'type' => 'entity', + 'description' => t('The entity containing the field collection field.'), + 'getter callback' => 'field_collection_item_get_host_entity', + 'setter callback' => 'field_collection_item_set_host_entity', + 'required' => TRUE, + ); + + return $info; + } + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,215 @@ + 'Stores information about field collection items.', + 'fields' => array( + 'item_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique field collection item ID.', + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Default revision ID.', + ), + 'field_name' => array( + 'description' => 'The name of the field on the host entity embedding this entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'archived' => array( + 'description' => 'Boolean indicating whether the field collection item is archived.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('item_id'), + ); + $schema['field_collection_item_revision'] = array( + 'description' => 'Stores revision information about field collection items.', + 'fields' => array( + 'revision_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique revision ID.', + ), + 'item_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Field collection item ID.', + ), + ), + 'primary key' => array('revision_id'), + 'indexes' => array( + 'item_id' => array('item_id'), + ), + 'foreign keys' => array( + 'versioned_field_collection_item' => array( + 'table' => 'field_collection_item', + 'columns' => array('item_id' => 'item_id'), + ), + ), + ); + return $schema; +} + +/** + * Implements hook_field_schema(). + */ +function field_collection_field_schema($field) { + $columns = array( + 'value' => array( + 'type' => 'int', + 'not null' => FALSE, + 'description' => 'The field collection item id.', + ), + 'revision_id' => array( + 'type' => 'int', + 'not null' => FALSE, + 'description' => 'The field collection item revision id.', + ), + ); + return array( + 'columns' => $columns, + ); +} + +/** + * Update the administer field collection permission machine name. + */ +function field_collection_update_7000() { + db_update('role_permission') + ->fields(array('permission' => 'administer field collections')) + ->condition('permission', 'administer field-collections') + ->execute(); +} + +/** + * Add revision support. + */ +function field_collection_update_7001() { + + // Add revision_id column to field_collection_item table. + $revision_id_spec = array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Default revision ID.', + // Set default to 0 temporarily. + 'initial' => 0, + ); + db_add_field('field_collection_item', 'revision_id', $revision_id_spec); + + // Initialize the revision_id to be the same as the item_id. + db_update('field_collection_item') + ->expression('revision_id', 'item_id') + ->execute(); + + // Add the archived column + $archived_spec = array( + 'description' => 'Boolean indicating whether the field collection item is archived.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ); + db_add_field('field_collection_item', 'archived', $archived_spec); + + // Create the new table. It is important to explicitly define the schema here + // rather than use the hook_schema definition: http://drupal.org/node/150220. + $schema['field_collection_item_revision'] = array( + 'description' => 'Stores revision information about field collection items.', + 'fields' => array( + 'revision_id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'Primary Key: Unique revision ID.', + ), + 'item_id' => array( + 'type' => 'int', + 'not null' => TRUE, + 'description' => 'Field collection item ID.', + ), + ), + 'primary key' => array('revision_id'), + 'indexes' => array( + 'item_id' => array('item_id'), + ), + 'foreign keys' => array( + 'versioned_field_collection_item' => array( + 'table' => 'field_collection_item', + 'columns' => array('item_id' => 'item_id'), + ), + ), + ); + db_create_table('field_collection_item_revision', $schema['field_collection_item_revision']); + + // Fill the new table with the correct data. + $items = db_select('field_collection_item', 'fci') + ->fields('fci') + ->execute(); + foreach ($items as $item) { + // Update field_collection_item_revision table. + db_insert('field_collection_item_revision') + ->fields(array( + 'revision_id' => $item->item_id, + 'item_id' => $item->item_id, + )) + ->execute(); + } + + // Update the field_collection_field_schema columns for all tables. + foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { + $table_prefixes = array('field_data', 'field_revision'); + foreach ($table_prefixes as $table_prefix) { + + $table = sprintf('%s_%s', $table_prefix, $field_name); + $value_column = sprintf('%s_value', $field_name); + $revision_id_column = sprintf('%s_revision_id', $field_name); + + // Add a revision_id column. + $revision_id_spec['description'] = 'The field collection item revision id.'; + db_add_field($table, $revision_id_column, $revision_id_spec); + + // Initialize the revision_id to be the same as the item_id. + db_update($table) + ->expression($revision_id_column, $value_column) + ->execute(); + } + } + + // Need to get the system up-to-date so drupal_schema_fields_sql() will work. + $schema = drupal_get_schema('field_collection_item_revision', TRUE); +} + +/** + * Remove orphaned field collection item entities. + */ +function field_collection_update_7002() { + // Loop over all fields and delete any orphaned field collection items. + foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { + + $select = db_select('field_collection_item', 'fci') + ->fields('fci', array('item_id')) + ->condition('field_name', $field_name) + ->condition('archived', 0); + $select->leftJoin('field_data_' . $field_name, 'field', "field.{$field_name}_value = fci.item_id "); + $select->isNull('field.entity_id'); + $ids = $select->execute()->fetchCol(0); + + entity_delete_multiple('field_collection_item', $ids); + $count = count($ids); + drupal_set_message("Deleted $count orphaned field collection items."); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1864 @@ +' . t('About') . ''; + $output .= '

        ' . t('The field collection module provides a field, to which any number of fields can be attached. See the Field module help page for more information about fields.', array('@field-help' => url('admin/help/field'))) . '

        '; + return $output; + } +} + +/** + * Implements hook_entity_info(). + */ +function field_collection_entity_info() { + $return['field_collection_item'] = array( + 'label' => t('Field collection item'), + 'label callback' => 'entity_class_label', + 'uri callback' => 'entity_class_uri', + 'entity class' => 'FieldCollectionItemEntity', + 'controller class' => 'EntityAPIController', + 'base table' => 'field_collection_item', + 'revision table' => 'field_collection_item_revision', + 'fieldable' => TRUE, + // For integration with Redirect module. + // @see http://drupal.org/node/1263884 + 'redirect' => FALSE, + 'entity keys' => array( + 'id' => 'item_id', + 'revision' => 'revision_id', + 'bundle' => 'field_name', + ), + 'module' => 'field_collection', + 'view modes' => array( + 'full' => array( + 'label' => t('Full content'), + 'custom settings' => FALSE, + ), + ), + 'access callback' => 'field_collection_item_access', + 'metadata controller class' => 'FieldCollectionItemMetadataController' + ); + + // Add info about the bundles. We do not use field_info_fields() but directly + // use field_read_fields() as field_info_fields() requires built entity info + // to work. + foreach (field_read_fields(array('type' => 'field_collection')) as $field_name => $field) { + $return['field_collection_item']['bundles'][$field_name] = array( + 'label' => t('Field collection @field', array('@field' => $field_name)), + 'admin' => array( + 'path' => 'admin/structure/field-collections/%field_collection_field_name', + 'real path' => 'admin/structure/field-collections/' . strtr($field_name, array('_' => '-')), + 'bundle argument' => 3, + 'access arguments' => array('administer field collections'), + ), + ); + } + + return $return; +} + +/** + * Menu callback for loading the bundle names. + */ +function field_collection_field_name_load($arg) { + $field_name = strtr($arg, array('-' => '_')); + if (($field = field_info_field($field_name)) && $field['type'] == 'field_collection') { + return $field_name; + } +} + +/** + * Loads a field collection item. + * + * @return field_collection_item + * The field collection item entity or FALSE. + */ +function field_collection_item_load($item_id, $reset = FALSE) { + $result = field_collection_item_load_multiple(array($item_id), array(), $reset); + return $result ? reset($result) : FALSE; +} + +/** + * Loads a field collection revision. + * + * @param $revision_id + * The field collection revision ID. + */ +function field_collection_item_revision_load($revision_id) { + return entity_revision_load('field_collection_item', $revision_id); +} + +/** + * Loads field collection items. + * + * @return + * An array of field collection item entities. + */ +function field_collection_item_load_multiple($ids = array(), $conditions = array(), $reset = FALSE) { + return entity_load('field_collection_item', $ids, $conditions, $reset); +} + +/** + * Class for field_collection_item entities. + */ +class FieldCollectionItemEntity extends Entity { + + /** + * Field collection field info. + * + * @var array + */ + protected $fieldInfo; + + /** + * The host entity object. + * + * @var object + */ + protected $hostEntity; + + /** + * The host entity ID. + * + * @var integer + */ + protected $hostEntityId; + + /** + * The host entity revision ID if this is not the default revision. + * + * @var integer + */ + protected $hostEntityRevisionId; + + /** + * The host entity type. + * + * @var string + */ + protected $hostEntityType; + + /** + * The language under which the field collection item is stored. + * + * @var string + */ + protected $langcode = LANGUAGE_NONE; + + /** + * Entity ID. + * + * @var integer + */ + public $item_id; + + /** + * Field collection revision ID. + * + * @var integer + */ + public $revision_id; + + /** + * The name of the field-collection field this item is associated with. + * + * @var string + */ + public $field_name; + + /** + * Whether this revision is the default revision. + * + * @var bool + */ + public $default_revision = TRUE; + + /** + * Whether the field collection item is archived, i.e. not in use. + * + * @see FieldCollectionItemEntity::isInUse() + * @var bool + */ + public $archived = FALSE; + + /** + * Constructs the entity object. + */ + public function __construct(array $values = array(), $entityType = NULL) { + parent::__construct($values, 'field_collection_item'); + // Workaround issues http://drupal.org/node/1084268 and + // http://drupal.org/node/1264440: + // Check if the required property is set before checking for the field's + // type. If the property is not set, we are hitting a PDO or a core's bug. + // FIXME: Remove when #1264440 is fixed and the required PHP version is + // properly identified and documented in the module documentation. + if (isset($this->field_name)) { + // Ok, we have the field name property, we can proceed and check the field's type + $field_info = $this->fieldInfo(); + if (!$field_info || $field_info['type'] != 'field_collection') { + throw new Exception("Invalid field name given: {$this->field_name} is not a Field Collection field."); + } + } + } + + /** + * Provides info about the field on the host entity, which embeds this + * field collection item. + */ + public function fieldInfo() { + return field_info_field($this->field_name); + } + + /** + * Provides info of the field instance containing the reference to this + * field collection item. + */ + public function instanceInfo() { + if ($this->fetchHostDetails()) { + return field_info_instance($this->hostEntityType(), $this->field_name, $this->hostEntityBundle()); + } + } + + /** + * Returns the field instance label translated to interface language. + */ + public function translatedInstanceLabel($langcode = NULL) { + if ($info = $this->instanceInfo()) { + if (module_exists('i18n_field')) { + return i18n_string("field:{$this->field_name}:{$info['bundle']}:label", $info['label'], array('langcode' => $langcode)); + } + return $info['label']; + } + } + + /** + * Specifies the default label, which is picked up by label() by default. + */ + public function defaultLabel() { + // @todo make configurable. + if ($this->fetchHostDetails()) { + $field = $this->fieldInfo(); + $label = $this->translatedInstanceLabel(); + + if ($field['cardinality'] == 1) { + return $label; + } + elseif ($this->item_id) { + return t('!instance_label @count', array('!instance_label' => $label, '@count' => $this->delta() + 1)); + } + else { + return t('New !instance_label', array('!instance_label' => $label)); + } + } + return t('Unconnected field collection item'); + } + + /** + * Returns the path used to view the entity. + */ + public function path() { + if ($this->item_id) { + return field_collection_field_get_path($this->fieldInfo()) . '/' . $this->item_id; + } + } + + /** + * Returns the URI as returned by entity_uri(). + */ + public function defaultUri() { + return array( + 'path' => $this->path(), + ); + } + + /** + * Sets the host entity. Only possible during creation of a item. + * + * @param $create_link + * (optional) Whether a field-item linking the host entity to the field + * collection item should be created. + */ + public function setHostEntity($entity_type, $entity, $langcode = LANGUAGE_NONE, $create_link = TRUE) { + if (!empty($this->is_new)) { + $this->hostEntityType = $entity_type; + $this->hostEntity = $entity; + $this->langcode = $langcode; + + list($this->hostEntityId, $this->hostEntityRevisionId) = entity_extract_ids($this->hostEntityType, $this->hostEntity); + // If the host entity is not saved yet, set the id to FALSE. So + // fetchHostDetails() does not try to load the host entity details. + if (!isset($this->hostEntityId)) { + $this->hostEntityId = FALSE; + } + // We are create a new field collection for a non-default entity, thus + // set archived to TRUE. + if (!entity_revision_is_default($entity_type, $entity)) { + $this->hostEntityId = FALSE; + $this->archived = TRUE; + } + if ($create_link) { + $entity->{$this->field_name}[$this->langcode][] = array('entity' => $this); + } + } + else { + throw new Exception('The host entity may be set only during creation of a field collection item.'); + } + } + + /** + * Returns the host entity, which embeds this field collection item. + */ + public function hostEntity() { + if ($this->fetchHostDetails()) { + if (!isset($this->hostEntity) && $this->isInUse()) { + $this->hostEntity = entity_load_single($this->hostEntityType, $this->hostEntityId); + } + elseif (!isset($this->hostEntity) && $this->hostEntityRevisionId) { + $this->hostEntity = entity_revision_load($this->hostEntityType, $this->hostEntityRevisionId); + } + return $this->hostEntity; + } + } + + /** + * Returns the entity type of the host entity, which embeds this + * field collection item. + */ + public function hostEntityType() { + if ($this->fetchHostDetails()) { + return $this->hostEntityType; + } + } + + /** + * Returns the id of the host entity, which embeds this field collection item. + */ + public function hostEntityId() { + if ($this->fetchHostDetails()) { + if (!$this->hostEntityId && $this->hostEntityRevisionId) { + $this->hostEntityId = entity_id($this->hostEntityType, $this->hostEntity()); + } + return $this->hostEntityId; + } + } + + /** + * Returns the bundle of the host entity, which embeds this field collection + * item. + */ + public function hostEntityBundle() { + if ($entity = $this->hostEntity()) { + list($id, $rev_id, $bundle) = entity_extract_ids($this->hostEntityType, $entity); + return $bundle; + } + } + + protected function fetchHostDetails() { + if (!isset($this->hostEntityId)) { + if ($this->item_id) { + // For saved field collections, query the field data to determine the + // right host entity. + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fieldInfo(), 'revision_id', $this->revision_id); + if (!$this->isInUse()) { + $query->age(FIELD_LOAD_REVISION); + } + $result = $query->execute(); + list($this->hostEntityType, $data) = each($result); + + if ($this->isInUse()) { + $this->hostEntityId = $data ? key($data) : FALSE; + $this->hostEntityRevisionId = FALSE; + } + // If we are querying for revisions, we get the revision ID. + else { + $this->hostEntityId = FALSE; + $this->hostEntityRevisionId = $data ? key($data) : FALSE; + } + } + else { + // No host entity available yet. + $this->hostEntityId = FALSE; + } + } + return !empty($this->hostEntityId) || !empty($this->hostEntity) || !empty($this->hostEntityRevisionId); + } + + /** + * Determines the $delta of the reference pointing to this field collection + * item. + */ + public function delta() { + if (($entity = $this->hostEntity()) && isset($entity->{$this->field_name})) { + foreach ($entity->{$this->field_name} as $langcode => &$data) { + foreach ($data as $delta => $item) { + if (isset($item['value']) && $item['value'] == $this->item_id) { + $this->langcode = $langcode; + return $delta; + } + elseif (isset($item['entity']) && $item['entity'] === $this) { + $this->langcode = $langcode; + return $delta; + } + } + } + } + } + + /** + * Determines the language code under which the item is stored. + */ + public function langcode() { + if ($this->delta() != NULL) { + return $this->langcode; + } + } + + /** + * Determines whether this field collection item revision is in use. + * + * Field collection items may be contained in from non-default host entity + * revisions. If the field collection item does not appear in the default + * host entity revision, the item is actually not used by default and so + * marked as 'archived'. + * If the field collection item appears in the default revision of the host + * entity, the default revision of the field collection item is in use there + * and the collection is not marked as archived. + */ + public function isInUse() { + return $this->default_revision && !$this->archived; + } + + /** + * Save the field collection item. + * + * By default, always save the host entity, so modules are able to react + * upon changes to the content of the host and any 'last updated' dates of + * entities get updated. + * + * For creating an item a host entity has to be specified via setHostEntity() + * before this function is invoked. For the link between the entities to be + * fully established, the host entity object has to be updated to include a + * reference on this field collection item during saving. So do not skip + * saving the host for creating items. + * + * @param $skip_host_save + * (internal) If TRUE is passed, the host entity is not saved automatically + * and therefore no link is created between the host and the item or + * revision updates might be skipped. Use with care. + */ + public function save($skip_host_save = FALSE) { + // Make sure we have a host entity during creation. + if (!empty($this->is_new) && !(isset($this->hostEntityId) || isset($this->hostEntity) || isset($this->hostEntityRevisionId))) { + throw new Exception("Unable to create a field collection item without a given host entity."); + } + + // Only save directly if we are told to skip saving the host entity. Else, + // we always save via the host as saving the host might trigger saving + // field collection items anyway (e.g. if a new revision is created). + if ($skip_host_save) { + return entity_get_controller($this->entityType)->save($this); + } + else { + $host_entity = $this->hostEntity(); + if (!$host_entity) { + throw new Exception("Unable to save a field collection item without a valid reference to a host entity."); + } + // If this is creating a new revision, also do so for the host entity. + if (!empty($this->revision) || !empty($this->is_new_revision)) { + $host_entity->revision = TRUE; + if (!empty($this->default_revision)) { + entity_revision_set_default($this->hostEntityType, $host_entity); + } + } + // Set the host entity reference, so the item will be saved with the host. + // @see field_collection_field_presave() + $delta = $this->delta(); + if (isset($delta)) { + $host_entity->{$this->field_name}[$this->langcode][$delta] = array('entity' => $this); + } + else { + $host_entity->{$this->field_name}[$this->langcode][] = array('entity' => $this); + } + return entity_save($this->hostEntityType, $host_entity); + } + } + + /** + * Deletes the field collection item and the reference in the host entity. + */ + public function delete() { + parent::delete(); + $this->deleteHostEntityReference(); + } + + /** + * Deletes the host entity's reference of the field collection item. + */ + protected function deleteHostEntityReference() { + $delta = $this->delta(); + if ($this->item_id && isset($delta)) { + unset($this->hostEntity->{$this->field_name}[$this->langcode][$delta]); + entity_save($this->hostEntityType, $this->hostEntity); + } + } + + /** + * Intelligently delete a field collection item revision. + * + * If a host entity is revisioned with its field collection items, deleting + * a field collection item on the default revision of the host should not + * delete the collection item from archived revisions too. Instead, we delete + * the current default revision and archive the field collection. + * + * If no revisions are left or the host is not revisionable, the whole item + * is deleted. + */ + public function deleteRevision($skip_host_update = FALSE) { + if (!$this->revision_id) { + return; + } + $info = entity_get_info($this->hostEntityType()); + if (empty($info['entity keys']['revision']) || !$this->hostEntity()) { + return $this->delete(); + } + if (!$skip_host_update) { + // Just remove the item from the host, which cares about deleting the + // item (depending on whether the update creates a new revision). + $this->deleteHostEntityReference(); + } + elseif (!$this->isDefaultRevision()) { + entity_revision_delete('field_collection_item', $this->revision_id); + } + // If deleting the default revision, take care! + else { + $row = db_select('field_collection_item_revision', 'r') + ->fields('r') + ->condition('item_id', $this->item_id) + ->condition('revision_id', $this->revision_id, '<>') + ->execute() + ->fetchAssoc(); + // If no other revision is left, delete. Else archive the item. + if (!$row) { + $this->delete(); + } + else { + // Make the other revision the default revision and archive the item. + db_update('field_collection_item') + ->fields(array('archived' => 1, 'revision_id' => $row['revision_id'])) + ->condition('item_id', $this->item_id) + ->execute(); + entity_get_controller('field_collection_item')->resetCache(array($this->item_id)); + entity_revision_delete('field_collection_item', $this->revision_id); + } + } + } + + /** + * Export the field collection item. + * + * Since field collection entities are not directly exportable (i.e., do not + * have 'exportable' set to TRUE in hook_entity_info()) and since Features + * calls this method when exporting the field collection as a field attached + * to another entity, we return the export in the format expected by + * Features, rather than in the normal Entity::export() format. + */ + public function export($prefix = '') { + // Based on code in EntityDefaultFeaturesController::export_render(). + $export = "entity_import('" . $this->entityType() . "', '"; + $export .= addcslashes(parent::export(), '\\\''); + $export .= "')"; + return $export; + } + + /** + * Magic method to only serialize what's necessary. + */ + public function __sleep() { + $vars = get_object_vars($this); + unset($vars['entityInfo'], $vars['idKey'], $vars['nameKey'], $vars['statusKey']); + unset($vars['fieldInfo']); + // Also do not serialize the host entity, but only if it has already an id. + if ($this->hostEntity && ($this->hostEntityId || $this->hostEntityRevisionId)) { + unset($vars['hostEntity']); + } + + // Also key the returned array with the variable names so the method may + // be easily overridden and customized. + return drupal_map_assoc(array_keys($vars)); + } + + /** + * Magic method to invoke setUp() on unserialization. + * + * @todo: Remove this once it appears in a released entity API module version. + */ + public function __wakeup() { + $this->setUp(); + } +} + +/** + * Implements hook_menu(). + */ +function field_collection_menu() { + $items = array(); + if (module_exists('field_ui')) { + $items['admin/structure/field-collections'] = array( + 'title' => 'Field collections', + 'description' => 'Manage fields on field collections.', + 'page callback' => 'field_collections_overview', + 'access arguments' => array('administer field collections'), + 'type' => MENU_NORMAL_ITEM, + 'file' => 'field_collection.admin.inc', + ); + } + + // Add menu paths for viewing/editing/deleting field collection items. + foreach (field_info_fields() as $field) { + if ($field['type'] == 'field_collection') { + $path = field_collection_field_get_path($field); + $count = count(explode('/', $path)); + + $items[$path . '/%field_collection_item'] = array( + 'page callback' => 'field_collection_item_page_view', + 'page arguments' => array($count), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('view', $count), + 'file' => 'field_collection.pages.inc', + ); + $items[$path . '/%field_collection_item/view'] = array( + 'title' => 'View', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + ); + $items[$path . '/%field_collection_item/edit'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_collection_item_form', $count), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('update', $count), + 'title' => 'Edit', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE, + 'file' => 'field_collection.pages.inc', + ); + $items[$path . '/%field_collection_item/delete'] = array( + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_collection_item_delete_confirm', $count), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('delete', $count), + 'title' => 'Delete', + 'type' => MENU_LOCAL_TASK, + 'context' => MENU_CONTEXT_INLINE, + 'file' => 'field_collection.pages.inc', + ); + // Add entity type and the entity id as additional arguments. + $items[$path . '/add/%/%'] = array( + 'page callback' => 'field_collection_item_add', + 'page arguments' => array($field['field_name'], $count + 1, $count + 2), + // The pace callback takes care of checking access itself. + 'access callback' => TRUE, + 'file' => 'field_collection.pages.inc', + ); + // Add menu items for dealing with revisions. + $items[$path . '/%field_collection_item/revisions/%field_collection_item_revision'] = array( + 'page callback' => 'field_collection_item_page_view', + 'page arguments' => array($count + 2), + 'access callback' => 'field_collection_item_access', + 'access arguments' => array('view', $count + 2), + 'file' => 'field_collection.pages.inc', + ); + } + } + + $items['field_collection/ajax'] = array( + 'title' => 'Remove item callback', + 'page callback' => 'field_collection_remove_js', + 'delivery callback' => 'ajax_deliver', + 'access callback' => TRUE, + 'theme callback' => 'ajax_base_page_theme', + 'type' => MENU_CALLBACK, + 'file path' => 'includes', + 'file' => 'form.inc', + ); + + return $items; +} + +/** + * Implements hook_menu_alter() to fix the field collections admin UI tabs. + */ +function field_collection_menu_alter(&$items) { + if (module_exists('field_ui') && isset($items['admin/structure/field-collections/%field_collection_field_name/fields'])) { + // Make the fields task the default local task. + $items['admin/structure/field-collections/%field_collection_field_name'] = $items['admin/structure/field-collections/%field_collection_field_name/fields']; + $item = &$items['admin/structure/field-collections/%field_collection_field_name']; + $item['type'] = MENU_NORMAL_ITEM; + $item['title'] = 'Manage fields'; + $item['title callback'] = 'field_collection_admin_page_title'; + $item['title arguments'] = array(3); + + $items['admin/structure/field-collections/%field_collection_field_name/fields'] = array( + 'title' => 'Manage fields', + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => 1, + ); + } +} + +/** + * Menu title callback. + */ +function field_collection_admin_page_title($field_name) { + return t('Field collection @field_name', array('@field_name' => $field_name)); +} + +/** + * Implements hook_admin_paths(). + */ +function field_collection_admin_paths() { + if (variable_get('node_admin_theme')) { + return array( + 'field-collection/*/*/edit' => TRUE, + 'field-collection/*/*/delete' => TRUE, + 'field-collection/*/add/*/*' => TRUE, + ); + } +} + +/** + * Implements hook_permission(). + */ +function field_collection_permission() { + return array( + 'administer field collections' => array( + 'title' => t('Administer field collections'), + 'description' => t('Create and delete fields on field collections.'), + ), + ); +} + +/** + * Determines whether the given user has access to a field collection. + * + * @param $op + * The operation being performed. One of 'view', 'update', 'create', 'delete'. + * @param $item + * Optionally a field collection item. If nothing is given, access for all + * items is determined. + * @param $account + * The user to check for. Leave it to NULL to check for the global user. + * @return boolean + * Whether access is allowed or not. + */ +function field_collection_item_access($op, FieldCollectionItemEntity $item = NULL, $account = NULL) { + // We do not support editing field collection revisions that are not used at + // the hosts default revision as saving the host might result in a new default + // revision. + if (isset($item) && !$item->isInUse() && $op != 'view') { + return FALSE; + } + if (user_access('administer field collections', $account)) { + return TRUE; + } + if (!isset($item)) { + return FALSE; + } + $op = $op == 'view' ? 'view' : 'edit'; + // Access is determined by the entity and field containing the reference. + $field = field_info_field($item->field_name); + $entity_access = entity_access($op == 'view' ? 'view' : 'update', $item->hostEntityType(), $item->hostEntity(), $account); + return $entity_access && field_access($op, $field, $item->hostEntityType(), $item->hostEntity(), $account); +} + +/** + * Implements hook_theme(). + */ +function field_collection_theme() { + return array( + 'field_collection_item' => array( + 'render element' => 'elements', + 'template' => 'field-collection-item', + ), + 'field_collection_view' => array( + 'render element' => 'element', + ), + ); +} + +/** + * Implements hook_field_info(). + */ +function field_collection_field_info() { + return array( + 'field_collection' => array( + 'label' => t('Field collection'), + 'description' => t('This field stores references to embedded entities, which itself may contain any number of fields.'), + 'instance_settings' => array(), + 'default_widget' => 'field_collection_hidden', + 'default_formatter' => 'field_collection_view', + // As of now there is no UI for setting the path. + 'settings' => array( + 'path' => '', + 'hide_blank_items' => TRUE, + ), + // Add entity property info. + 'property_type' => 'field_collection_item', + 'property_callbacks' => array('field_collection_entity_metadata_property_callback'), + ), + ); +} + +/** + * Implements hook_field_instance_settings_form(). + */ +function field_collection_field_instance_settings_form($field, $instance) { + + $element['fieldset'] = array( + '#type' => 'fieldset', + '#title' => t('Default value'), + '#collapsible' => FALSE, + // As field_ui_default_value_widget() does, we change the #parents so that + // the value below is writing to $instance in the right location. + '#parents' => array('instance'), + ); + // Be sure to set the default value to NULL, e.g. to repair old fields + // that still have one. + $element['fieldset']['default_value'] = array( + '#type' => 'value', + '#value' => NULL, + ); + $element['fieldset']['content'] = array( + '#pre' => '

        ', + '#markup' => t('To specify a default value, configure it via the regular default value setting of each field that is part of the field collection. To do so, go to the Manage fields screen of the field collection.', array('!url' => url('admin/structure/field-collections/' . strtr($field['field_name'], array('_' => '-')) . '/fields'))), + '#suffix' => '

        ', + ); + return $element; +} + +/** + * Returns the base path to use for field collection items. + */ +function field_collection_field_get_path($field) { + if (empty($field['settings']['path'])) { + return 'field-collection/' . strtr($field['field_name'], array('_' => '-')); + } + return $field['settings']['path']; +} + +/** + * Implements hook_field_settings_form(). + */ +function field_collection_field_settings_form($field, $instance) { + + $form['hide_blank_items'] = array( + '#type' => 'checkbox', + '#title' => t('Hide blank items'), + '#default_value' => $field['settings']['hide_blank_items'], + '#description' => t("A blank item is always added to any multivalued field's form. If checked, any additional blank items are hidden except of the first item which is always shown."), + '#weight' => 10, + '#states' => array( + // Hide the setting if the cardinality is 1. + 'invisible' => array( + ':input[name="field[cardinality]"]' => array('value' => '1'), + ), + ), + ); + return $form; +} + +/** + * Implements hook_field_presave(). + * + * Support saving field collection items in @code $item['entity'] @endcode. This + * may be used to seamlessly create field collection items during host-entity + * creation or to save changes to the host entity and its collections at once. + */ +function field_collection_field_presave($host_entity_type, $host_entity, $field, $instance, $langcode, &$items) { + foreach ($items as &$item) { + // In case the entity has been changed / created, save it and set the id. + // If the host entity creates a new revision, save new item-revisions as + // well. + if (isset($item['entity']) || !empty($host_entity->revision)) { + + if ($entity = field_collection_field_get_entity($item)) { + + if (!empty($entity->is_new)) { + $entity->setHostEntity($host_entity_type, $host_entity, LANGUAGE_NONE, FALSE); + } + + // If the host entity is saved as new revision, do the same for the item. + if (!empty($host_entity->revision)) { + $entity->revision = TRUE; + $is_default = entity_revision_is_default($host_entity_type, $host_entity); + // If an entity type does not support saving non-default entities, + // assume it will be saved as default. + if (!isset($is_default) || $is_default) { + $entity->default_revision = TRUE; + $entity->archived = FALSE; + } + } + $entity->save(TRUE); + + $item = array( + 'value' => $entity->item_id, + 'revision_id' => $entity->revision_id, + ); + } + } + } +} + +/** + * Implements hook_field_update(). + * + * Care about removed field collection items. + */ +function field_collection_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { + $items_original = !empty($entity->original->{$field['field_name']}[$langcode]) ? $entity->original->{$field['field_name']}[$langcode] : array(); + $original_by_id = array_flip(field_collection_field_item_to_ids($items_original)); + + foreach ($items as $item) { + unset($original_by_id[$item['value']]); + } + + // If there are removed items, care about deleting the item entities. + if ($original_by_id) { + $ids = array_flip($original_by_id); + + // If we are creating a new revision, the old-items should be kept but get + // marked as archived now. + if (!empty($entity->revision)) { + db_update('field_collection_item') + ->fields(array('archived' => 1)) + ->condition('item_id', $ids, 'IN') + ->execute(); + } + else { + // Delete unused field collection items now. + foreach (field_collection_item_load_multiple($ids) as $item) { + $item->deleteRevision(TRUE); + } + } + } +} + +/** + * Implements hook_field_delete(). + */ +function field_collection_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { + // Also delete all embedded entities. + if ($ids = field_collection_field_item_to_ids($items)) { + // We filter out entities that are still being referenced by other + // host-entities. This should never be the case, but it might happened e.g. + // when modules cloned a node without knowing about field-collection. + $entity_info = entity_get_info($entity_type); + $entity_id_name = $entity_info['entity keys']['id']; + $field_column = key($field['columns']); + + foreach ($ids as $id_key => $id) { + $query = new EntityFieldQuery(); + $entities = $query + ->fieldCondition($field['field_name'], $field_column, $id) + ->execute(); + unset($entities[$entity_type][$entity->$entity_id_name]); + + if (!empty($entities[$entity_type])) { + // Filter this $id out. + unset($ids[$id_key]); + } + } + + entity_delete_multiple('field_collection_item', $ids); + } +} + +/** + * Implements hook_field_delete_revision(). + */ +function field_collection_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) { + foreach ($items as $item) { + if (!empty($item['revision_id'])) { + if ($entity = field_collection_item_revision_load($item['revision_id'])) { + $entity->deleteRevision(TRUE); + } + } + } +} + +/** + * Get an array of field collection item IDs stored in the given field items. + */ +function field_collection_field_item_to_ids($items) { + $ids = array(); + foreach ($items as $item) { + if (!empty($item['value'])) { + $ids[] = $item['value']; + } + } + return $ids; +} + +/** + * Implements hook_field_is_empty(). + */ +function field_collection_field_is_empty($item, $field) { + if (!empty($item['value'])) { + return FALSE; + } + elseif (isset($item['entity'])) { + return field_collection_item_is_empty($item['entity']); + } + return TRUE; +} + +/** + * Determines whether a field collection item entity is empty based on the collection-fields. + */ +function field_collection_item_is_empty(FieldCollectionItemEntity $item) { + $instances = field_info_instances('field_collection_item', $item->field_name); + $is_empty = TRUE; + + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + + // Determine the list of languages to iterate on. + $languages = field_available_languages('field_collection_item', $field); + + foreach ($languages as $langcode) { + if (!empty($item->{$field_name}[$langcode])) { + // If at least one collection-field is not empty; the + // field collection item is not empty. + foreach ($item->{$field_name}[$langcode] as $field_item) { + if (!module_invoke($field['module'], 'field_is_empty', $field_item, $field)) { + $is_empty = FALSE; + } + } + } + } + } + + // Allow other modules a chance to alter the value before returning. + drupal_alter('field_collection_is_empty', $is_empty, $item); + return $is_empty; +} + +/** + * Implements hook_field_formatter_info(). + */ +function field_collection_field_formatter_info() { + return array( + 'field_collection_list' => array( + 'label' => t('Links to field collection items'), + 'field types' => array('field_collection'), + 'settings' => array( + 'edit' => t('Edit'), + 'delete' => t('Delete'), + 'add' => t('Add'), + 'description' => TRUE, + ), + ), + 'field_collection_view' => array( + 'label' => t('Field collection items'), + 'field types' => array('field_collection'), + 'settings' => array( + 'edit' => t('Edit'), + 'delete' => t('Delete'), + 'add' => t('Add'), + 'description' => TRUE, + 'view_mode' => 'full', + ), + ), + 'field_collection_fields' => array( + 'label' => t('Fields only'), + 'field types' => array('field_collection'), + 'settings' => array( + 'view_mode' => 'full', + ), + ), + ); +} + +/** + * Implements hook_field_formatter_settings_form(). + */ +function field_collection_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + $elements = array(); + + if ($display['type'] != 'field_collection_fields') { + $elements['edit'] = array( + '#type' => 'textfield', + '#title' => t('Edit link title'), + '#default_value' => $settings['edit'], + '#description' => t('Leave the title empty, to hide the link.'), + ); + $elements['delete'] = array( + '#type' => 'textfield', + '#title' => t('Delete link title'), + '#default_value' => $settings['delete'], + '#description' => t('Leave the title empty, to hide the link.'), + ); + $elements['add'] = array( + '#type' => 'textfield', + '#title' => t('Add link title'), + '#default_value' => $settings['add'], + '#description' => t('Leave the title empty, to hide the link.'), + ); + $elements['description'] = array( + '#type' => 'checkbox', + '#title' => t('Show the field description beside the add link.'), + '#default_value' => $settings['description'], + '#description' => t('If enabled and the add link is shown, the field description is shown in front of the add link.'), + ); + } + + // Add a select form element for view_mode if viewing the rendered field_collection. + if ($display['type'] !== 'field_collection_list') { + + $entity_type = entity_get_info('field_collection_item'); + $options = array(); + foreach ($entity_type['view modes'] as $mode => $info) { + $options[$mode] = $info['label']; + } + + $elements['view_mode'] = array( + '#type' => 'select', + '#title' => t('View mode'), + '#options' => $options, + '#default_value' => $settings['view_mode'], + '#description' => t('Select the view mode'), + ); + } + + return $elements; +} + +/** + * Implements hook_field_formatter_settings_summary(). + */ +function field_collection_field_formatter_settings_summary($field, $instance, $view_mode) { + $display = $instance['display'][$view_mode]; + $settings = $display['settings']; + $output = array(); + + if ($display['type'] !== 'field_collection_fields') { + $links = array_filter(array_intersect_key($settings, array_flip(array('add', 'edit', 'delete')))); + if ($links) { + $output[] = t('Links: @links', array('@links' => check_plain(implode(', ', $links)))); + } + else { + $output[] = t('Links: none'); + } + } + + if ($display['type'] !== 'field_collection_list') { + $entity_type = entity_get_info('field_collection_item'); + if (!empty($entity_type['view modes'][$settings['view_mode']]['label'])) { + $output[] = t('View mode: @mode', array('@mode' => $entity_type['view modes'][$settings['view_mode']]['label'])); + } + } + + return implode('
        ', $output); +} + +/** + * Implements hook_field_formatter_view(). + */ +function field_collection_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $element = array(); + $settings = $display['settings']; + + switch ($display['type']) { + case 'field_collection_list': + + foreach ($items as $delta => $item) { + if ($field_collection = field_collection_field_get_entity($item)) { + $output = l($field_collection->label(), $field_collection->path()); + $links = array(); + foreach (array('edit', 'delete') as $op) { + if ($settings[$op] && field_collection_item_access($op == 'edit' ? 'update' : $op, $field_collection)) { + $title = entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_$op", $settings[$op]); + $links[] = l($title, $field_collection->path() . '/' . $op, array('query' => drupal_get_destination())); + } + } + if ($links) { + $output .= ' (' . implode('|', $links) . ')'; + } + $element[$delta] = array('#markup' => $output); + } + } + field_collection_field_formatter_links($element, $entity_type, $entity, $field, $instance, $langcode, $items, $display); + break; + + case 'field_collection_view': + + $element['#attached']['css'][] = drupal_get_path('module', 'field_collection') . '/field_collection.theme.css'; + $view_mode = !empty($display['settings']['view_mode']) ? $display['settings']['view_mode'] : 'full'; + foreach ($items as $delta => $item) { + if ($field_collection = field_collection_field_get_entity($item)) { + $element[$delta]['entity'] = $field_collection->view($view_mode); + $element[$delta]['#theme_wrappers'] = array('field_collection_view'); + $element[$delta]['#attributes']['class'][] = 'field-collection-view'; + $element[$delta]['#attributes']['class'][] = 'clearfix'; + $element[$delta]['#attributes']['class'][] = drupal_clean_css_identifier('view-mode-' . $view_mode); + + $links = array( + '#theme' => 'links__field_collection_view', + ); + $links['#attributes']['class'][] = 'field-collection-view-links'; + foreach (array('edit', 'delete') as $op) { + if ($settings[$op] && field_collection_item_access($op == 'edit' ? 'update' : $op, $field_collection)) { + $links['#links'][$op] = array( + 'title' => entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_$op", $settings[$op]), + 'href' => $field_collection->path() . '/' . $op, + 'query' => drupal_get_destination(), + ); + } + } + $element[$delta]['links'] = $links; + } + } + field_collection_field_formatter_links($element, $entity_type, $entity, $field, $instance, $langcode, $items, $display); + break; + + case 'field_collection_fields': + + $view_mode = !empty($display['settings']['view_mode']) ? $display['settings']['view_mode'] : 'full'; + foreach ($items as $delta => $item) { + if ($field_collection = field_collection_field_get_entity($item)) { + $element[$delta]['entity'] = $field_collection->view($view_mode); + } + } + break; + } + + return $element; +} + +/** + * Helper function to add links to a field collection field. + */ +function field_collection_field_formatter_links(&$element, $entity_type, $entity, $field, $instance, $langcode, $items, $display) { + $settings = $display['settings']; + + if ($settings['add'] && ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || count($items) < $field['cardinality'])) { + // Check whether the current is allowed to create a new item. + $field_collection_item = entity_create('field_collection_item', array('field_name' => $field['field_name'])); + $field_collection_item->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE); + + if (field_collection_item_access('create', $field_collection_item)) { + $path = field_collection_field_get_path($field); + list($id) = entity_extract_ids($entity_type, $entity); + $element['#suffix'] = ''; + if (!empty($settings['description'])) { + $element['#suffix'] .= '
        ' . field_filter_xss($instance['description']) . '
        '; + } + $title = entity_i18n_string("field:{$field['field_name']}:{$instance['bundle']}:setting_add", $settings['add']); + $add_path = $path . '/add/' . $entity_type . '/' . $id; + $element['#suffix'] .= ''; + } + } + // If there is no add link, add a special class to the last item. + if (empty($element['#suffix'])) { + $index = count(element_children($element)) - 1; + $element[$index]['#attributes']['class'][] = 'field-collection-view-final'; + } + + $element += array('#prefix' => '', '#suffix' => ''); + $element['#prefix'] .= '
        '; + $element['#suffix'] .= '
        '; + + return $element; +} + +/** + * Themes field collection items printed using the field_collection_view formatter. + */ +function theme_field_collection_view($variables) { + $element = $variables['element']; + return '' . $element['#children'] . ''; +} + +/** + * Implements hook_field_widget_info(). + */ +function field_collection_field_widget_info() { + return array( + 'field_collection_hidden' => array( + 'label' => t('Hidden'), + 'field types' => array('field_collection'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_NONE, + ), + ), + 'field_collection_embed' => array( + 'label' => t('Embedded'), + 'field types' => array('field_collection'), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_DEFAULT, + 'default value' => FIELD_BEHAVIOR_NONE, + ), + ), + ); +} + +/** + * Implements hook_field_widget_form(). + */ +function field_collection_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { + static $recursion = 0; + + switch ($instance['widget']['type']) { + case 'field_collection_hidden': + return $element; + + case 'field_collection_embed': + // If the field collection item form contains another field collection, + // we might ran into a recursive loop. Prevent that. + if ($recursion++ > 3) { + drupal_set_message(t('The field collection item form has not been embedded to avoid recursive loops.'), 'error'); + return $element; + } + $field_parents = $element['#field_parents']; + $field_name = $element['#field_name']; + $language = $element['#language']; + + // Nest the field collection item entity form in a dedicated parent space, + // by appending [field_name, langcode, delta] to the current parent space. + // That way the form values of the field collection item are separated. + $parents = array_merge($field_parents, array($field_name, $language, $delta)); + + $element += array( + '#element_validate' => array('field_collection_field_widget_embed_validate'), + '#parents' => $parents, + ); + + if ($field['cardinality'] == 1) { + $element['#type'] = 'fieldset'; + } + + $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state); + + if (!empty($field['settings']['hide_blank_items']) && $delta == $field_state['items_count'] && $delta > 0) { + // Do not add a blank item. Also see + // field_collection_field_attach_form() for correcting #max_delta. + $recursion--; + return FALSE; + } + elseif (!empty($field['settings']['hide_blank_items']) && $field_state['items_count'] == 0) { + // We show one item, so also specify that as item count. So when the + // add button is pressed the item count will be 2 and we show to items. + $field_state['items_count'] = 1; + } + + if (isset($field_state['entity'][$delta])) { + $field_collection_item = $field_state['entity'][$delta]; + } + else { + if (isset($items[$delta])) { + $field_collection_item = field_collection_field_get_entity($items[$delta], $field_name); + } + // Show an empty collection if we have no existing one or it does not + // load. + if (empty($field_collection_item)) { + $field_collection_item = entity_create('field_collection_item', array('field_name' => $field_name)); + } + + // Put our entity in the form state, so FAPI callbacks can access it. + $field_state['entity'][$delta] = $field_collection_item; + } + + field_form_set_state($field_parents, $field_name, $language, $form_state, $field_state); + field_attach_form('field_collection_item', $field_collection_item, $element, $form_state, $language); + + if (empty($element['#required'])) { + $element['#after_build'][] = 'field_collection_field_widget_embed_delay_required_validation'; + } + + if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) { + $element['remove_button'] = array( + '#delta' => $delta, + '#name' => implode('_', $parents) . '_remove_button', + '#type' => 'submit', + '#value' => t('Remove'), + '#validate' => array(), + '#submit' => array('field_collection_remove_submit'), + '#limit_validation_errors' => array(), + '#ajax' => array( + 'path' => 'field_collection/ajax', + 'effect' => 'fade', + ), + '#weight' => 1000, + ); + } + + $recursion--; + return $element; + } +} + +/** + * Implements hook_field_attach_form(). + * + * Corrects #max_delta when we hide the blank field collection item. + * + * @see field_add_more_js() + * @see field_collection_field_widget_form() + */ +function field_collection_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { + + foreach (field_info_instances($entity_type, $form['#bundle']) as $field_name => $instance) { + $field = field_info_field($field_name); + + if ($field['type'] == 'field_collection' && $field['settings']['hide_blank_items'] + && field_access('edit', $field, $entity_type) && $instance['widget']['type'] == 'field_collection_embed') { + + $element_langcode = $form[$field_name]['#language']; + if ($form[$field_name][$element_langcode]['#max_delta'] > 0) { + $form[$field_name][$element_langcode]['#max_delta']--; + } + } + } +} + +/** + * Page callback to handle AJAX for removing a field collection item. + * + * This is a direct page callback. The actual job of deleting the item is + * done in the submit handler for the button, so all we really need to + * do is process the form and then generate output. We generate this + * output by doing a replace command on the id of the entire form element. + */ +function field_collection_remove_js() { + // drupal_html_id() very helpfully ensures that all html IDS are unique + // on a page. Unfortunately what it doesn't realize is that the IDs + // we are generating are going to replace IDs that already exist, so + // this actually works against us. + if (isset($_POST['ajax_html_ids'])) { + unset($_POST['ajax_html_ids']); + } + + list($form, $form_state) = ajax_get_form(); + drupal_process_form($form['#form_id'], $form, $form_state); + + // Get the information on what we're removing. + $button = $form_state['triggering_element']; + // Go two levels up in the form, to the whole widget. + $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -3)); + // Now send back the proper AJAX command to replace it. + $return = array( + '#type' => 'ajax', + '#commands' => array( + ajax_command_replace('#' . $element['#id'], drupal_render($element)) + ), + ); + + // Because we're doing this ourselves, messages aren't automatic. We have + // to add them. + $messages = theme('status_messages'); + if ($messages) { + $return['#commands'][] = ajax_command_prepend('#' . $element['#id'], $messages); + } + + return $return; +} + +/** + * Submit callback to remove an item from the field UI multiple wrapper. + * + * When a remove button is submitted, we need to find the item that it + * referenced and delete it. Since field UI has the deltas as a straight + * unbroken array key, we have to renumber everything down. Since we do this + * we *also* need to move all the deltas around in the $form_state['values'] + * and $form_state['input'] so that user changed values follow. This is a bit + * of a complicated process. + */ +function field_collection_remove_submit($form, &$form_state) { + $button = $form_state['triggering_element']; + $delta = $button['#delta']; + + // Where in the form we'll find the parent element. + $address = array_slice($button['#array_parents'], 0, -2); + + // Go one level up in the form, to the widgets container. + $parent_element = drupal_array_get_nested_value($form, $address); + $field_name = $parent_element['#field_name']; + $langcode = $parent_element['#language']; + $parents = $parent_element['#field_parents']; + + $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); + + // Go ahead and renumber everything from our delta to the last + // item down one. This will overwrite the item being removed. + for ($i = $delta; $i <= $field_state['items_count']; $i++) { + $old_element_address = array_merge($address, array($i + 1)); + $new_element_address = array_merge($address, array($i)); + + $moving_element = drupal_array_get_nested_value($form, $old_element_address); + $moving_element_value = drupal_array_get_nested_value($form_state['values'], $old_element_address); + $moving_element_input = drupal_array_get_nested_value($form_state['input'], $old_element_address); + + // Tell the element where it's being moved to. + $moving_element['#parents'] = $new_element_address; + + // Move the element around. + form_set_value($moving_element, $moving_element_value, $form_state); + drupal_array_set_nested_value($form_state['input'], $moving_element['#parents'], $moving_element_input); + + // Move the entity in our saved state. + if (isset($field_state['entity'][$i + 1])) { + $field_state['entity'][$i] = $field_state['entity'][$i + 1]; + } + else { + unset($field_state['entity'][$i]); + } + } + + // Replace the deleted entity with an empty one. This helps to ensure that + // trying to add a new entity won't ressurect a deleted entity from the + // trash bin. + $count = count($field_state['entity']); + $field_state['entity'][$count] = entity_create('field_collection_item', array('field_name' => $field_name)); + + // Then remove the last item. But we must not go negative. + if ($field_state['items_count'] > 0) { + $field_state['items_count']--; + } + + // Fix the weights. Field UI lets the weights be in a range of + // (-1 * item_count) to (item_count). This means that when we remove one, + // the range shrinks; weights outside of that range then get set to + // the first item in the select by the browser, floating them to the top. + // We use a brute force method because we lost weights on both ends + // and if the user has moved things around, we have to cascade because + // if I have items weight weights 3 and 4, and I change 4 to 3 but leave + // the 3, the order of the two 3s now is undefined and may not match what + // the user had selected. + $input = drupal_array_get_nested_value($form_state['input'], $address); + // Sort by weight + uasort($input, '_field_sort_items_helper'); + + // Reweight everything in the correct order. + $weight = -1 * $field_state['items_count']; + foreach ($input as $key => $item) { + if ($item) { + $input[$key]['_weight'] = $weight++; + } + } + + drupal_array_set_nested_value($form_state['input'], $address, $input); + field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); + + $form_state['rebuild'] = TRUE; +} + +/** + * Gets a field collection item entity for a given field item. + * + * @param $field_name + * (optional) If given and there is no entity yet, a new entity object is + * created for the given item. + * + * @return + * The entity object or FALSE. + */ +function field_collection_field_get_entity(&$item, $field_name = NULL) { + if (isset($item['entity'])) { + return $item['entity']; + } + elseif (isset($item['value'])) { + // By default always load the default revision, so caches get used. + $entity = field_collection_item_load($item['value']); + if ($entity->revision_id != $item['revision_id']) { + // A non-default revision is a referenced, so load this one. + $entity = field_collection_item_revision_load($item['revision_id']); + } + return $entity; + } + elseif (!isset($item['entity']) && isset($field_name)) { + $item['entity'] = entity_create('field_collection_item', array('field_name' => $field_name)); + return $item['entity']; + } + return FALSE; +} + +/** + * FAPI #after_build of an individual field collection element to delay the validation of #required. + */ +function field_collection_field_widget_embed_delay_required_validation(&$element, &$form_state) { + // If the process_input flag is set, the form and its input is going to be + // validated. Prevent #required (sub)fields from throwing errors while + // their non-#required field collection item is empty. + if ($form_state['process_input']) { + _field_collection_collect_required_elements($element, $element['#field_collection_required_elements']); + } + return $element; +} + +function _field_collection_collect_required_elements(&$element, &$required_elements) { + // Recurse through all children. + foreach (element_children($element) as $key) { + if (isset($element[$key]) && $element[$key]) { + _field_collection_collect_required_elements($element[$key], $required_elements); + } + } + if (!empty($element['#required'])) { + $element['#required'] = FALSE; + $required_elements[] = &$element; + $element += array('#pre_render' => array()); + array_unshift($element['#pre_render'], 'field_collection_field_widget_render_required'); + } +} + +/** + * #pre_render callback that ensures the element is rendered as being required. + */ +function field_collection_field_widget_render_required($element) { + $element['#required'] = TRUE; + return $element; +} + +/** + * FAPI validation of an individual field collection element. + */ +function field_collection_field_widget_embed_validate($element, &$form_state, $complete_form) { + $instance = field_widget_instance($element, $form_state); + $field = field_widget_field($element, $form_state); + $field_parents = $element['#field_parents']; + $field_name = $element['#field_name']; + $language = $element['#language']; + + $field_state = field_form_get_state($field_parents, $field_name, $language, $form_state); + $field_collection_item = $field_state['entity'][$element['#delta']]; + + // Attach field API validation of the embedded form. + field_attach_form_validate('field_collection_item', $field_collection_item, $element, $form_state); + + // Now validate required elements if the entity is not empty. + if (!field_collection_item_is_empty($field_collection_item) && !empty($element['#field_collection_required_elements'])) { + foreach ($element['#field_collection_required_elements'] as &$elements) { + + // Copied from _form_validate(). + if (isset($elements['#needs_validation'])) { + $is_empty_multiple = (!count($elements['#value'])); + $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); + $is_empty_value = ($elements['#value'] === 0); + if ($is_empty_multiple || $is_empty_string || $is_empty_value) { + if (isset($elements['#title'])) { + form_error($elements, t('!name field is required.', array('!name' => $elements['#title']))); + } + else { + form_error($elements); + } + } + } + } + } + + // Only if the form is being submitted, finish the collection entity and + // prepare it for saving. + if ($form_state['submitted'] && !form_get_errors()) { + + field_attach_submit('field_collection_item', $field_collection_item, $element, $form_state); + + // Load initial form values into $item, so any other form values below the + // same parents are kept. + $item = drupal_array_get_nested_value($form_state['values'], $element['#parents']); + + // Set the _weight if it is a multiple field. + if (isset($element['_weight']) && ($field['cardinality'] > 1 || $field['cardinality'] == FIELD_CARDINALITY_UNLIMITED)) { + $item['_weight'] = $element['_weight']['#value']; + } + + // Put the field collection item in $item['entity'], so it is saved with + // the host entity via hook_field_presave() / field API if it is not empty. + // @see field_collection_field_presave() + $item['entity'] = $field_collection_item; + form_set_value($element, $item, $form_state); + } +} + +/** + * Implements hook_field_create_field(). + */ +function field_collection_field_create_field($field) { + if ($field['type'] == 'field_collection') { + field_attach_create_bundle('field_collection_item', $field['field_name']); + + // Clear caches. + entity_info_cache_clear(); + // Do not directly issue menu rebuilds here to avoid potentially multiple + // rebuilds. Instead, let menu_get_item() issue the rebuild on the next + // request. + variable_set('menu_rebuild_needed', TRUE); + } +} + +/** + * Implements hook_field_delete_field(). + */ +function field_collection_field_delete_field($field) { + if ($field['type'] == 'field_collection') { + // Notify field.module that field collection was deleted. + field_attach_delete_bundle('field_collection_item', $field['field_name']); + + // Clear caches. + entity_info_cache_clear(); + // Do not directly issue menu rebuilds here to avoid potentially multiple + // rebuilds. Instead, let menu_get_item() issue the rebuild on the next + // request. + variable_set('menu_rebuild_needed', TRUE); + } +} + +/** + * Implements hook_i18n_string_list_{textgroup}_alter(). + */ +function field_collection_i18n_string_list_field_alter(&$properties, $type, $instance) { + if ($type == 'field_instance') { + $field = field_info_field($instance['field_name']); + + if ($field['type'] == 'field_collection' && !empty($instance['display'])) { + + foreach ($instance['display'] as $view_mode => $display) { + if ($display['type'] != 'field_collection_fields') { + $display['settings'] += array('edit' => 'edit', 'delete' => 'delete', 'add' => 'add'); + + $properties['field'][$instance['field_name']][$instance['bundle']]['setting_edit'] = array( + 'title' => t('Edit link title'), + 'string' => $display['settings']['edit'], + ); + $properties['field'][$instance['field_name']][$instance['bundle']]['setting_delete'] = array( + 'title' => t('Delete link title'), + 'string' => $display['settings']['delete'], + ); + $properties['field'][$instance['field_name']][$instance['bundle']]['setting_add'] = array( + 'title' => t('Add link title'), + 'string' => $display['settings']['add'], + ); + } + } + } + } +} + +/** + * Implements hook_views_api(). + */ +function field_collection_views_api() { + return array( + 'api' => '3.0-alpha1', + 'path' => drupal_get_path('module', 'field_collection') . '/views', + ); +} + +/** + * Implements hook_features_pipe_component_alter() for fields. + */ +function field_collection_features_pipe_field_alter(&$pipe, $data, $export) { + // Add the fields of the field collection entity to the pipe. + foreach ($data as $identifier) { + if (($field = features_field_load($identifier)) && $field['field_config']['type'] == 'field_collection') { + $fields = field_info_instances('field_collection_item', $field['field_config']['field_name']); + foreach ($fields as $name => $field) { + $pipe['field'][] = "{$field['entity_type']}-{$field['bundle']}-{$field['field_name']}"; + } + } + } +} + +/** + * Callback for generating entity metadata property info for our field instances. + * + * @see field_collection_field_info() + */ +function field_collection_entity_metadata_property_callback(&$info, $entity_type, $field, $instance, $field_type) { + $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']]; + // Set the bundle as we know it is the name of the field. + $property['bundle'] = $field['field_name']; + $property['getter callback'] = 'field_collection_field_property_get'; +} + +/** + * Entity property info setter callback for the host entity property. + * + * As the property is of type entity, the value will be passed as a wrapped + * entity. + */ +function field_collection_item_set_host_entity($item, $property_name, $wrapper) { + if (empty($item->is_new)) { + throw new EntityMetadataWrapperException('The host entity may be set only during creation of a field collection item.'); + } + if (!isset($wrapper->{$item->field_name})) { + throw new EntityMetadataWrapperException('The specified entity has no such field collection field.'); + } + $item->setHostEntity($wrapper->type(), $wrapper->value()); +} + +/** + * Entity property info getter callback for the host entity property. + */ +function field_collection_item_get_host_entity($item) { + // As the property is defined as 'entity', we have to return a wrapped entity. + return entity_metadata_wrapper($item->hostEntityType(), $item->hostEntity()); +} + +/** + * Entity property info getter callback for the field collection items. + * + * Like entity_metadata_field_property_get(), but additionally supports getting + * not-yet saved collection items from @code $item['entity'] @endcode. + */ +function field_collection_field_property_get($entity, array $options, $name, $entity_type, $info) { + $field = field_info_field($name); + $langcode = field_language($entity_type, $entity, $name, isset($options['language']) ? $options['language']->language : NULL); + $values = array(); + if (isset($entity->{$name}[$langcode])) { + foreach ($entity->{$name}[$langcode] as $delta => $data) { + // Wrappers do not support multiple entity references being revisions or + // not yet saved entities. In the case of a single reference we can return + // the entity object though. + if ($field['cardinality'] == 1) { + $values[$delta] = field_collection_field_get_entity($data); + } + elseif (isset($data['value'])) { + $values[$delta] = $data['value']; + } + } + } + // For an empty single-valued field, we have to return NULL. + return $field['cardinality'] == 1 ? ($values ? reset($values) : NULL) : $values; +} + +/** + * Implements hook_devel_generate(). + */ +function field_collection_devel_generate($object, $field, $instance, $bundle) { + // Create a new field collection object and add fake data to its fields. + $field_collection = entity_create('field_collection_item', array('field_name' => $field['field_name'])); + $field_collection->language = $object->language; + $field_collection->setHostEntity($instance['entity_type'], $object, $object->language, FALSE); + + devel_generate_fields($field_collection, 'field_collection_item', $field['field_name']); + + $field_collection->save(TRUE); + + return array('value' => $field_collection->item_id); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.pages.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.pages.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,138 @@ +label()); + return $field_collection_item->view('full', NULL, TRUE); +} + +/** + * Form for editing a field collection item. + * @todo implement hook_forms(). + */ +function field_collection_item_form($form, &$form_state, $field_collection_item) { + if (!isset($field_collection_item->is_new)) { + drupal_set_title($field_collection_item->label()); + } + $form_state += array('field_collection_item' => $field_collection_item); + + // Hack: entity_form_field_validate() needs the bundle to be set. + // @todo: Fix core and remove the hack. + $form['field_name'] = array('#type' => 'value', '#value' => $field_collection_item->field_name); + + field_attach_form('field_collection_item', $field_collection_item, $form, $form_state); + + $form['actions'] = array('#type' => 'actions', '#weight' => 50); + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save'), + '#weight' => 5, + ); + return $form; +} + +/** + * Validation callback. + */ +function field_collection_item_form_validate($form, &$form_state) { + entity_form_field_validate('field_collection_item', $form, $form_state); +} + +/** + * Submit builder. Extracts the form values and updates the entity. + */ +function field_collection_item_form_submit_build_field_collection($form, $form_state) { + entity_form_submit_build_entity('field_collection_item', $form_state['field_collection_item'], $form, $form_state); + return $form_state['field_collection_item']; +} + +/** + * Submit callback that permanently saves the changes to the entity. + */ +function field_collection_item_form_submit($form, &$form_state) { + $field_collection_item = field_collection_item_form_submit_build_field_collection($form, $form_state); + $field_collection_item->save(); + drupal_set_message(t('The changes have been saved.')); + $form_state['redirect'] = $field_collection_item->path(); +} + +/** + * Form for deleting a field collection item. + */ +function field_collection_item_delete_confirm($form, &$form_state, $field_collection_item) { + $form_state += array('field_collection_item' => $field_collection_item); + return confirm_form($form, + t('Are you sure you want to delete %label?', array('%label' => $field_collection_item->label())), + $field_collection_item->path(), + t('This action cannot be undone.'), + t('Delete'), + t('Cancel') + ); +} + +/** + * Submit callback for deleting a field collection item. + */ +function field_collection_item_delete_confirm_submit($form, &$form_state) { + $field_collection_item = $form_state['field_collection_item']; + $field_collection_item->deleteRevision(); + drupal_set_message(t('%label has been deleted.', array('%label' => drupal_ucfirst($field_collection_item->label())))); + $form_state['redirect'] = ''; +} + +/** + * Add a new field collection item. + * + * @todo: Support optionally passing in the revision_id and langcode parameters. + */ +function field_collection_item_add($field_name, $entity_type, $entity_id, $revision_id = NULL, $langcode = NULL) { + $info = entity_get_info(); + if (!isset($info[$entity_type])) { + return MENU_NOT_FOUND; + } + $result = entity_load($entity_type, array($entity_id)); + $entity = reset($result); + if (!$entity) { + return MENU_NOT_FOUND; + } + // Ensure the given entity is of a bundle that has an instance of the field. + list($id, $rev_id, $bundle) = entity_extract_ids($entity_type, $entity); + $instance = field_info_instance($entity_type, $field_name, $bundle); + if (!$instance) { + return MENU_NOT_FOUND; + } + + // Check field cardinality. + $field = field_info_field($field_name); + $langcode = LANGUAGE_NONE; + if (!($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || !isset($entity->{$field_name}[$langcode]) || count($entity->{$field_name}[$langcode]) < $field['cardinality'])) { + drupal_set_message(t('Too many items.'), 'error'); + return ''; + } + + $field_collection_item = entity_create('field_collection_item', array('field_name' => $field_name)); + // Do not link the field collection item with the host entity at this point, + // as during the form-workflow we have multiple field collection item entity + // instances, which we don't want link all with the host. + // That way the link is going to be created when the item is saved. + $field_collection_item->setHostEntity($entity_type, $entity, LANGUAGE_NONE, FALSE); + + $title = ($field['cardinality'] == 1) ? $instance['label'] : t('Add new !instance_label', array('!instance_label' => $field_collection_item->translatedInstanceLabel())); + drupal_set_title($title); + + // Make sure the current user has access to create a field collection item. + if (!field_collection_item_access('create', $field_collection_item)) { + return MENU_ACCESS_DENIED; + } + return drupal_get_form('field_collection_item_form', $field_collection_item); +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,422 @@ + 'Field collection', + 'description' => 'Tests creating and using field collections.', + 'group' => 'Field types', + ); + } + + function setUp() { + parent::setUp('field_collection'); + + // Create a field_collection field to use for the tests. + $this->field_name = 'field_test_collection'; + $this->field = array('field_name' => $this->field_name, 'type' => 'field_collection', 'cardinality' => 4); + $this->field = field_create_field($this->field); + $this->field_id = $this->field['id']; + + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array(), + 'widget' => array( + 'type' => 'hidden', + 'label' => 'Test', + 'settings' => array(), + ), + ); + $this->instance = field_create_instance($this->instance); + } + + /** + * Helper for creating a new node with a field collection item. + */ + protected function createNodeWithFieldCollection() { + $node = $this->drupalCreateNode(array('type' => 'article')); + // Manually create a field_collection. + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + $entity->save(); + + return array($node, $entity); + } + + /** + * Tests CRUD. + */ + function testCRUD() { + list ($node, $entity) = $this->createNodeWithFieldCollection(); + $node = node_load($node->nid, NULL, TRUE); + $this->assertEqual($entity->item_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['value'], 'A field_collection has been successfully created and referenced.'); + $this->assertEqual($entity->revision_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id'], 'A field_collection has been successfully created and referenced.'); + + // Test adding an additional field_collection during node edit. + $entity2 = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $node->{$this->field_name}[LANGUAGE_NONE][] = array('entity' => $entity2); + node_save($node); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(!empty($entity2->item_id) && !empty($entity2->revision_id), 'Field_collection has been saved.'); + $this->assertEqual($entity->item_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['value'], 'Existing reference has been kept during update.'); + $this->assertEqual($entity->revision_id, $node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id'], 'Existing reference has been kept during update (revision).'); + $this->assertEqual($entity2->item_id, $node->{$this->field_name}[LANGUAGE_NONE][1]['value'], 'New field_collection has been properly referenced'); + $this->assertEqual($entity2->revision_id, $node->{$this->field_name}[LANGUAGE_NONE][1]['revision_id'], 'New field_collection has been properly referenced (revision)'); + + // Make sure deleting the field_collection removes the reference. + $entity2->delete(); + $node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(!isset($node->{$this->field_name}[LANGUAGE_NONE][1]), 'Reference correctly deleted.'); + + // Make sure field_collections are removed during deletion of the host. + node_delete($node->nid); + $this->assertTrue(entity_load('field_collection_item', FALSE) === array(), 'Field collections are deleted when the host is deleted.'); + + // Try deleting nodes with collections without any values. + $node = $this->drupalCreateNode(array('type' => 'article')); + node_delete($node->nid); + $this->assertTrue(node_load($node->nid, NULL, TRUE) == FALSE, 'Node without collection values deleted.'); + + // Test creating a field collection entity with a not-yet saved host entity. + $node = entity_create('node', array('type' => 'article')); + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + $entity->save(); + // Now the node should have been saved with the collection and the link + // should have been established. + $this->assertTrue(!empty($node->nid), 'Node has been saved with the collection.'); + $this->assertTrue(count($node->{$this->field_name}[LANGUAGE_NONE]) == 1 && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['value']) && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']), 'Link has been established.'); + + // Again, test creating a field collection with a not-yet saved host entity, + // but this time save both entities via the host. + $node = entity_create('node', array('type' => 'article')); + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + node_save($node); + $this->assertTrue(!empty($entity->item_id) && !empty($entity->revision_id), 'Collection has been saved with the host.'); + $this->assertTrue(count($node->{$this->field_name}[LANGUAGE_NONE]) == 1 && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['value']) && !empty($node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']), 'Link has been established.'); + + // Test Revisions. + list ($node, $item) = $this->createNodeWithFieldCollection(); + + // Test saving a new revision of a node. + $node->revision = TRUE; + node_save($node); + $item_updated = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $this->assertNotEqual($item->revision_id, $item_updated->revision_id, 'Creating a new host entity revision creates a new field collection revision.'); + + // Test saving the node without creating a new revision. + $item = $item_updated; + $node->revision = FALSE; + node_save($node); + $item_updated = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $this->assertEqual($item->revision_id, $item_updated->revision_id, 'Updating a new host entity without creating a new revision does not create a new field collection revision.'); + + // Create a new revision of the node, such we have a non default node and + // field collection revision. Then test using it. + $vid = $node->vid; + $item_revision_id = $item_updated->revision_id; + $node->revision = TRUE; + node_save($node); + + $item_updated = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $this->assertNotEqual($item_revision_id, $item_updated->revision_id, 'Creating a new host entity revision creates a new field collection revision.'); + $this->assertTrue($item_updated->isDefaultRevision(), 'Field collection of default host entity revision is default too.'); + $this->assertEqual($item_updated->hostEntityId(), $node->nid, 'Can access host entity ID of default field collection revision.'); + $this->assertEqual($item_updated->hostEntity()->vid, $node->vid, 'Loaded default host entity revision.'); + + $item = entity_revision_load('field_collection_item', $item_revision_id); + $this->assertFalse($item->isDefaultRevision(), 'Field collection of non-default host entity is non-default too.'); + $this->assertEqual($item->hostEntityId(), $node->nid, 'Can access host entity ID of non-default field collection revision.'); + $this->assertEqual($item->hostEntity()->vid, $vid, 'Loaded non-default host entity revision.'); + + // Delete the non-default revision and make sure the field collection item + // revision has been deleted too. + entity_revision_delete('node', $vid); + $this->assertFalse(entity_revision_load('node', $vid), 'Host entity revision deleted.'); + $this->assertFalse(entity_revision_load('field_collection_item', $item_revision_id), 'Field collection item revision deleted.'); + + // Test having archived field collections, i.e. collections referenced only + // in non-default revisions. + list ($node, $item) = $this->createNodeWithFieldCollection(); + // Create two revisions. + $node_vid = $node->vid; + $node->revision = TRUE; + node_save($node); + + $node_vid2 = $node->vid; + $node->revision = TRUE; + node_save($node); + + // Now delete the field collection item for the default revision. + $item = field_collection_item_load($node->{$this->field_name}[LANGUAGE_NONE][0]['value']); + $item_revision_id = $item->revision_id; + $item->deleteRevision(); + $node = node_load($node->nid); + $this->assertTrue(!isset($node->{$this->field_name}[LANGUAGE_NONE][0]), 'Field collection item revision removed from host.'); + $this->assertFalse(field_collection_item_revision_load($item->revision_id), 'Field collection item default revision deleted.'); + + $item = field_collection_item_load($item->item_id); + $this->assertNotEqual($item->revision_id, $item_revision_id, 'Field collection default revision has been updated.'); + $this->assertTrue($item->archived, 'Field collection item has been archived.'); + $this->assertFalse($item->isInUse(), 'Field collection item specified as not in use.'); + $this->assertTrue($item->isDefaultRevision(), 'Field collection of non-default host entity is default (but archived).'); + $this->assertEqual($item->hostEntityId(), $node->nid, 'Can access host entity ID of non-default field collection revision.'); + $this->assertEqual($item->hostEntity()->nid, $node->nid, 'Loaded non-default host entity revision.'); + + // Test deleting a revision of an archived field collection. + $node_revision2 = node_load($node->nid, $node_vid2); + $item = field_collection_item_revision_load($node_revision2->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']); + $item->deleteRevision(); + // There should be one revision left, so the item should still exist. + $item = field_collection_item_load($item->item_id); + $this->assertTrue($item->archived, 'Field collection item is still archived.'); + $this->assertFalse($item->isInUse(), 'Field collection item specified as not in use.'); + + // Test that deleting the first node revision deletes the whole field + // collection item as it contains its last revision. + node_revision_delete($node_vid); + $this->assertFalse(field_collection_item_load($item->item_id), 'Archived field collection deleted when last revision deleted.'); + + // Test that removing a field-collection item also deletes it. + list ($node, $item) = $this->createNodeWithFieldCollection(); + + $node->{$this->field_name}[LANGUAGE_NONE] = array(); + $node->revision = FALSE; + node_save($node); + $this->assertFalse(field_collection_item_load($item->item_id), 'Removed field collection item has been deleted.'); + + // Test removing a field-collection item while creating a new host revision. + list ($node, $item) = $this->createNodeWithFieldCollection(); + $node->{$this->field_name}[LANGUAGE_NONE] = array(); + $node->revision = TRUE; + node_save($node); + // Item should not be deleted but archived now. + $item = field_collection_item_load($item->item_id); + $this->assertTrue($item, 'Removed field collection item still exists.'); + $this->assertTrue($item->archived, 'Removed field collection item is archived.'); + } + + /** + * Make sure the basic UI and access checks are working. + */ + function testBasicUI() { + // Add a field to the collection. + $field = array( + 'field_name' => 'field_text', + 'type' => 'text', + 'cardinality' => 1, + 'translatable' => FALSE, + ); + field_create_field($field); + $instance = array( + 'entity_type' => 'field_collection_item', + 'field_name' => 'field_text', + 'bundle' => $this->field_name, + 'label' => 'Test text field', + 'widget' => array( + 'type' => 'text_textfield', + ), + ); + field_create_instance($instance); + + $user = $this->drupalCreateUser(); + $node = $this->drupalCreateNode(array('type' => 'article')); + + $this->drupalLogin($user); + // Make sure access is denied. + $path = 'field-collection/field-test-collection/add/node/' . $node->nid; + $this->drupalGet($path); + $this->assertText(t('Access denied'), 'Access has been denied.'); + + $user_privileged = $this->drupalCreateUser(array('access content', 'edit any article content')); + $this->drupalLogin($user_privileged); + $this->drupalGet("node/$node->nid"); + $this->assertLinkByHref($path, 0, 'Add link is shown.'); + $this->drupalGet($path); + $this->assertText(t('Test text field'), 'Add form is shown.'); + + $edit['field_text[und][0][value]'] = $this->randomName(); + $this->drupalPost($path, $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), 'Field collection saved.'); + + $this->assertText($edit['field_text[und][0][value]'], "Added field value is shown."); + + $edit['field_text[und][0][value]'] = $this->randomName(); + $this->drupalPost('field-collection/field-test-collection/1/edit', $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), 'Field collection saved.'); + $this->assertText($edit['field_text[und][0][value]'], "Field collection has been edited."); + + $this->drupalGet('field-collection/field-test-collection/1'); + $this->assertText($edit['field_text[und][0][value]'], "Field collection can be viewed."); + + // Add further 3 items, so we have reached 4 == maxium cardinality. + $this->drupalPost($path, $edit, t('Save')); + $this->drupalPost($path, $edit, t('Save')); + $this->drupalPost($path, $edit, t('Save')); + // Make sure adding doesn't work any more as we have restricted cardinality + // to 1. + $this->drupalGet($path); + $this->assertText(t('Too many items.'), 'Maxium cardinality has been reached.'); + + $this->drupalPost('field-collection/field-test-collection/1/delete', array(), t('Delete')); + $this->drupalGet($path); + // Add form is shown again. + $this->assertText(t('Test text field'), 'Field collection item has been deleted.'); + + // Test the viewing a revision. There should be no links to change it. + $vid = $node->vid; + $node = node_load($node->nid, NULL, TRUE); + $node->revision = TRUE; + node_save($node); + + $this->drupalGet("node/$node->nid/revisions/$vid/view"); + $this->assertResponse(403, 'Access to view revision denied'); + // Login in as admin and try again. + $user = $this->drupalCreateUser(array('administer nodes', 'bypass node access')); + $this->drupalLogin($user); + $this->drupalGet("node/$node->nid/revisions/$vid/view"); + $this->assertNoResponse(403, 'Access to view revision granted'); + + $this->assertNoLinkByHref($path, 'No links on revision view.'); + $this->assertNoLinkByHref('field-collection/field-test-collection/2/edit', 'No links on revision view.'); + $this->assertNoLinkByHref('field-collection/field-test-collection/2/delete', 'No links on revision view.'); + + $this->drupalGet("node/$node->nid/revisions"); + } +} + + +/** + * Test using field collection with Rules. + */ +class FieldCollectionRulesIntegrationTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Field collection Rules integration', + 'description' => 'Tests using field collections with rules.', + 'group' => 'Field types', + 'dependencies' => array('rules'), + ); + } + + function setUp() { + parent::setUp(array('field_collection', 'rules')); + variable_set('rules_debug_log', 1); + } + + protected function createFields($cardinality = 4) { + // Create a field_collection field to use for the tests. + $this->field_name = 'field_test_collection'; + $this->field = array('field_name' => $this->field_name, 'type' => 'field_collection', 'cardinality' => $cardinality); + $this->field = field_create_field($this->field); + $this->field_id = $this->field['id']; + + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array(), + 'widget' => array( + 'type' => 'hidden', + 'label' => 'Test', + 'settings' => array(), + ), + ); + $this->instance = field_create_instance($this->instance); + // Add a field to the collection. + $field = array( + 'field_name' => 'field_text', + 'type' => 'text', + 'cardinality' => 1, + 'translatable' => FALSE, + ); + field_create_field($field); + $instance = array( + 'entity_type' => 'field_collection_item', + 'field_name' => 'field_text', + 'bundle' => $this->field_name, + 'label' => 'Test text field', + 'widget' => array( + 'type' => 'text_textfield', + ), + ); + field_create_instance($instance); + } + + /** + * Test creation field collection items. + */ + function testCreation() { + $this->createFields(); + + $node = $this->drupalCreateNode(array('type' => 'article')); + // Create a field collection. + $action_set = rules_action_set(array('node' => array('type' => 'node', 'bundle' => 'article'))); + $action_set->action('entity_create', array( + 'type' => 'field_collection_item', + 'param_field_name' => $this->field_name, + 'param_host_entity:select' => 'node', + )); + $action_set->action('data_set', array('data:select' => 'entity-created:field-text', 'value' => 'foo')); + $action_set->execute($node); + + $node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(!empty($node->{$this->field_name}[LANGUAGE_NONE][0]['value']), 'A field_collection has been successfully created.'); + $this->assertTrue(!empty($node->{$this->field_name}[LANGUAGE_NONE][0]['revision_id']), 'A field_collection has been successfully created (revision).'); + + // Now try making use of the field collection in rules. + $action_set = rules_action_set(array('node' => array('type' => 'node', 'bundle' => 'article'))); + $action_set->action('drupal_message', array('message:select' => 'node:field-test-collection:0:field-text')); + $action_set->execute($node); + + $msg = drupal_get_messages(); + $this->assertEqual(array_pop($msg['status']), 'foo', 'Field collection can be used.'); + RulesLog::logger()->checkLog(); + } + + /** + * Test using field collection items via the host while they are being created. + */ + function testUsageDuringCreation() { + // Test using a single-cardinality field collection. + $this->createFields(1); + + $node = $this->drupalCreateNode(array('type' => 'article')); + $entity = entity_create('field_collection_item', array('field_name' => $this->field_name)); + $entity->setHostEntity('node', $node); + // Now the field collection is linked to the host, but not yet saved. + + // Test using the wrapper on it. + $wrapper = entity_metadata_wrapper('node', $node); + $wrapper->get($this->field_name)->field_text->set('foo'); + $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], 'foo', 'Field collection item used during creation via the wrapper.'); + + // Now test it via Rules, which should save our changes. + $set = rules_action_set(array('node' => array('type' => 'node', 'bundle' => 'article'))); + $set->action('data_set', array('data:select' => 'node:' . $this->field_name . ':field-text', 'value' => 'bar')); + $set->execute($node); + $this->assertEqual($entity->field_text[LANGUAGE_NONE][0]['value'], 'bar', 'Field collection item used during creation via Rules.'); + $this->assertTrue(!empty($entity->item_id) && !empty($entity->revision_id), 'Field collection item has been saved by Rules and the host entity.'); + RulesLog::logger()->checkLog(); + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/field_collection.theme.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/field_collection.theme.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,66 @@ +@CHARSET "UTF-8"; + +.field-collection-container { + border-bottom: 1px solid #D3D7D9; + margin-bottom: 1em; +} + +.field-collection-container .field-items .field-item { + margin-bottom: 10px; +} + +.field-collection-container .field-items .field-items .field-item { + margin-bottom: 0; +} + +.field-collection-view { + padding: 1em 0 0.3em 0; + margin: 0 1em 0 1em; + border-bottom: 1px dotted #D3D7D9; +} + +/* If there is no add link, don't show the final border. */ +.field-collection-view-final { + border-bottom: none; +} + +.field-collection-view .entity-field-collection-item { + float: left; +} + +.field-collection-view ul.field-collection-view-links { + float: right; + font-size: 0.821em; + list-style-type: none; + width: auto; + margin: 0 1em; + padding: 0; +} + +.field-collection-view .field-label { + width: 25%; +} + +.field-collection-view .content { + margin-top: 0; + width: 100%; +} + +.field-collection-view .entity-field-collection-item { + width: 100%; +} + +ul.field-collection-view-links li { + float: left; +} + +ul.field-collection-view-links li a { + margin-right: 1em; +} + +.field-collection-container ul.action-links-field-collection-add { + float: right; + padding: 0 0.5em 0 0; + margin: 0 0 1em 2em; + font-size: 0.821em; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/views/field_collection.views.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/views/field_collection.views.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,53 @@ + $table_data) { + foreach ($table_data as $field_name => $field_data) { + // Only operate on the "field_api_field_name"_value column. + if (strrpos($field_name, '_value') === (strlen($field_name) - strlen('_value'))) { + $data[$table_name][$field_name]['relationship'] = array( + 'handler' => 'field_collection_handler_relationship', + 'base' => 'field_collection_item', + 'base field' => 'item_id', + 'label' => t('field collection item from !field_name', array('!field_name' => $field['field_name'])), + 'field_name' => $field['field_name'], + ); + } + } + } + + foreach ($field['bundles'] as $entity_type => $bundles) { + $entity_info = entity_get_info($entity_type); + $pseudo_field_name = $field['field_name'] . '_' . $entity_type; + + list($label, $all_labels) = field_views_field_label($field['field_name']); + $entity = $entity_info['label']; + if ($entity == t('Node')) { + $entity = t('Content'); + } + + $data['field_collection_item'][$pseudo_field_name]['relationship'] = array( + 'title' => t('Entity with the @field (@field_name)', array('@entity' => $entity, '@field' => $label, '@field_name' => $field['field_name'])), + 'help' => t('Relate each @entity using @field.', array('@entity' => $entity, '@field' => $label)), + 'handler' => 'views_handler_relationship_entity_reverse', + 'field_name' => $field['field_name'], + 'field table' => _field_sql_storage_tablename($field), + 'field field' => $field['field_name'] . '_value', + 'base' => $entity_info['base table'], + 'base field' => $entity_info['entity keys']['id'], + 'label' => t('!field_name', array('!field_name' => $field['field_name'])), + ); + } + + return $data; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_collection/views/field_collection_handler_relationship.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_collection/views/field_collection_handler_relationship.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,58 @@ + -1); + + return $options; + } + + /** + * Add a delta selector for multiple fields. + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $field = field_info_field($this->definition['field_name']); + + // Only add the delta selector if the field is multiple. + if ($field['cardinality']) { + $max_delta = ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) ? 10 : $field['cardinality']; + + $options = array('-1' => t('All')); + for ($i = 0; $i < $max_delta; $i++) { + $options[$i] = $i + 1; + } + $form['delta'] = array( + '#type' => 'select', + '#options' => $options, + '#default_value' => $this->options['delta'], + '#title' => t('Delta'), + '#description' => t('The delta allows you to select which item in a multiple value field to key the relationship off of. Select "1" to use the first item, "2" for the second item, and so on. If you select "All", each item in the field will create a new row, which may appear to cause duplicates.'), + ); + } + } + + function ensure_my_table() { + $field = field_info_field($this->definition['field_name']); + + if (!isset($this->table_alias)) { + $join = $this->get_join(); + if ($this->options['delta'] != -1 && $field['cardinality']) { + $join->extra[] = array( + 'field' => 'delta', + 'value' => $this->options['delta'], + 'numeric' => TRUE, + ); + } + $this->table_alias = $this->query->add_table($this->table, $this->relationship, $join); + } + return $this->table_alias; + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/CHANGELOG.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/CHANGELOG.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,40 @@ +/* $Id*/ +CHANGELOG for field_group for Drupal 7 + +Field_group 7.x-1.x-dev + o Issue #1095316: Field Groups disappear when Content Type is renamed. + o Issue #1095316 by swentel: Support for Entity API. + o Issue #1095002 by animelion: Upgrading removes all existing field groups. + o Issue #1095130 by willvincent: Features export not working with rc2. + +Field_group 7.x-1.0-rc2 + o Ran through coder, minor. + o Issue #1033036 by Stalski, swentel: Create a field_group.api.php. + o Made the summary descriptions more human readable. + o Issue #1086450: Cannot see red star on some field groups even required fields are set to 1. + o #1072292 by shadow_jh, stalski: Using on user settings page but need to hid on registration page. + o #1092360 by dww: Move field_group_update_7000 functionality to hook_install(). + o #1061228 Rewrite the field_group_field_group_is_empty function. + o Added ID's to fieldgroups. + o Removed unused field_group.admin.inc + menu item. Required asterix moving to field_group setting. + o #1045526 by stalski: Make formatter options more user-friendly and logical. + o #1041880 by robertgarrigos: duplicated entries in field_group table. + o #1043834 by amsri: Field Group module just does not work with profiles 2. + +Field_group 7.x-1.0-rc1 + o #1006464 Change #groups to #fieldgroups because of name collapsing with form_process_fieldset + o #1024184 fix collapsible when mode is set to open + o #1020278 by mori: Update fails. + o #1020116 by mikegfx: Confusing verbage across group types. + o #1018012 by mikegfx: Adding required asterisk to group tabs that have required fields. + o #960916 fixed reference warnings. + o No label anymore with div>open. + o #969258 Added check for fields and extra_fields. + o #960916 Fixed notice on for reference on group in field_group_settings. + o #961106 Fixed notice on entity type and bundle check. + o #962072 by mori: Improve CSS for horizontal tabs & accordion. + o Changed Fieldgroup API: defaults and instance_settings are now merged. + o Changed save action so everything is gathered during form_state to + postpone saving until the save button is hit. + o Changed some important variable name, so it makes more sense and easier to read. + o Add basic crud functions. \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/LICENSE.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/README.txt Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,47 @@ + +History: + Field_group was written for Drupal 7. For drupal 6, the module is + located in the CCK module (http://drupal.org/project/cck). + As drupal core has a fields API drupal > 6, the field_group module + is considered a contribution. + +Description: + field_group is a module that will group a set of fields. In Drupal7, + with fields, one means all fields that come from fieldable entities. + You can add fieldgroups in several types with their own format settings. + field_group has API functions to add your own formatter and rendering for + it. + One of the biggest improvements to previous versions, is that fieldgroups + have unlimited nesting, better display control. + Note that field_group will only group fields, it can not be used to hide + certain fields since this a permission matter. + +Module project page: + http://drupal.org/project/field_group + +Documentation page: + http://drupal.org/node/1017838 + http://drupal.org/node/1017962 + +Available group types: + - Fieldsets + - Horizontal tabs + - Vertical tabs + - Accordions + - Divs + - Multipage steps: Note: This is only client side. + - HTML5 group type + - Html element + +To submit bug reports and feature suggestions, or to track changes: + http://drupal.org/project/issues/field_group + +-- MAINTAINERS -- + +stalski - http://drupal.org/user/322618 +swentel - http://drupal.org/user/107403 +zuuperman - http://drupal.org/user/361625 + +-- INSPIRATORS -- + +yched - http://drupal.org/user/39567 diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group-rtl.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group-rtl.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,7 @@ +/** + * Override the accordion default style for view_modes. + */ +form .ui-accordion h3, form .ui-accordion h3.ui-state-active { + padding-left: 0; + padding-right: 2em; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.api.php --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.api.php Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,460 @@ + array( + * 'fieldset' => array( + * // required, String with the name of the formatter type. + * 'label' => t('Fieldset'), + * // optional, String description of the formatter type. + * 'description' => t('This is field group that ...'), + * // required, Array of available formatter options. + * 'format_types' => array('open', 'collapsible', 'collapsed'), + * // required, String with default value of the style. + 'default_formatter' => 'collapsible', + * // optional, Array with key => default_value pairs. + * 'instance_settings' => array('key' => 'value'), + * ), + * ), + * 'display' => array( + * 'fieldset' => array( + * // required, String with the name of the formatter type. + * 'label' => t('Fieldset'), + * // optional, String description of the formatter type. + * 'description' => t('This is field group that ...'), + * // required, Array of available formatter options. + * 'format_types' => array('open', 'collapsible', 'collapsed'), + * // required, String with default value of the style. + 'default_formatter' => 'collapsible', + * // optional, Array with key => default_value pairs. + * 'instance_settings' => array('key' => 'value'), + * ), + * ), + * ), + * @endcode + * + * @return $formatters + * A collection of available formatting html controls for form + * and display overview type. + * + * @see field_group_field_group_formatter_info() + */ +function hook_field_group_formatter_info() { + return array( + 'form' => array( + 'fieldset' => array( + 'label' => t('Fieldset'), + 'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'), + 'format_types' => array('open', 'collapsible', 'collapsed'), + 'instance_settings' => array('classes' => ''), + 'default_formatter' => 'collapsible', + ), + ), + 'display' => array( + 'div' => array( + 'label' => t('Div'), + 'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'), + 'format_types' => array('open', 'collapsible', 'collapsed'), + 'instance_settings' => array('effect' => 'none', 'speed' => 'fast', 'classes' => ''), + 'default_formatter' => 'collapsible', + ), + ), + ); +} + +/** + * Implements hook_field_group_format_settings(). + * + * Defines configuration widget for the settings on a field group + * formatter. Eache formatter can have different elements and storage. + * + * @params Object $group The group object. + * @return Array $form The form element for the format settings. + */ +function hook_field_group_format_settings($group) { + // Add a wrapper for extra settings to use by others. + $form = array( + 'instance_settings' => array( + '#tree' => TRUE, + '#weight' => 2, + ), + ); + + $field_group_types = field_group_formatter_info(); + $mode = $group->mode == 'form' ? 'form' : 'display'; + $formatter = $field_group_types[$mode][$group->format_type]; + + // Add the required formatter type selector. + if (isset($formatter['format_types'])) { + $form['formatter'] = array( + '#title' => t('Fieldgroup settings'), + '#type' => 'select', + '#options' => drupal_map_assoc($formatter['format_types']), + '#default_value' => isset($group->format_settings['formatter']) ? $group->format_settings['formatter'] : $formatter['default_formatter'], + '#weight' => 1, + ); + } + if ($mode == 'form') { + $form['instance_settings']['required_fields'] = array( + '#type' => 'checkbox', + '#title' => t('Mark group for required fields.'), + '#default_value' => isset($group->format_settings['instance_settings']['required_fields']) ? $group->format_settings['instance_settings']['required_fields'] : (isset($formatter['instance_settings']['required_fields']) ? $formatter['instance_settings']['required_fields'] : ''), + '#weight' => 2, + ); + } + $form['instance_settings']['classes'] = array( + '#title' => t('Extra CSS classes'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : (isset($formatter['instance_settings']['classes']) ? $formatter['instance_settings']['classes'] : ''), + '#weight' => 3, + '#element_validate' => array('field_group_validate_css_class'), + ); + $form['instance_settings']['description'] = array( + '#title' => t('Description'), + '#type' => 'textarea', + '#default_value' => isset($group->format_settings['instance_settings']['description']) ? $group->format_settings['instance_settings']['description'] : (isset($formatter['instance_settings']['description']) ? $formatter['instance_settings']['description'] : ''), + '#weight' => 0, + ); + + // Add optional instance_settings. + switch ($group->format_type) { + case 'div': + $form['instance_settings']['effect'] = array( + '#title' => t('Effect'), + '#type' => 'select', + '#options' => array('none' => t('None'), 'blind' => t('Blind')), + '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'], + '#weight' => 2, + ); + $form['instance_settings']['speed'] = array( + '#title' => t('Speed'), + '#type' => 'select', + '#options' => array('none' => t('None'), 'slow' => t('Slow'), 'fast' => t('Fast')), + '#default_value' => isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : $formatter['instance_settings']['speed'], + '#weight' => 3, + ); + break; + case 'fieldset': + $form['instance_settings']['classes'] = array( + '#title' => t('Extra CSS classes'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : $formatter['instance_settings']['classes'], + '#weight' => 3, + '#element_validate' => array('field_group_validate_css_class'), + ); + break; + case 'tabs': + case 'htabs': + case 'accordion': + unset($form['instance_settings']['description']); + if (isset($form['instance_settings']['required_fields'])) { + unset($form['instance_settings']['required_fields']); + } + break; + case 'tab': + case 'htab': + case 'accordion-item': + default: + } + + return $form; +} + +/** + * Implements hook_field_group_pre_render(). + * + * This function gives you the oppertunity to create the given + * wrapper element that can contain the fields. + * In the example beneath, some variables are prepared and used when building the + * actual wrapper element. All elements in drupal fapi can be used. + * + * Note that at this point, the field group has no notion of the fields in it. + * + * There is also an alternative way of handling this. The default implementation + * within field_group calls "field_group_pre_render_". + * @see field_group_pre_render_fieldset. + * + * @param Array $elements by address. + * @param Object $group The Field group info. + */ +function hook_field_group_pre_render(& $element, $group, & $form) { + + // You can prepare some variables to use in the logic. + $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form'; + $id = $form['#entity_type'] . '_' . $form['#bundle'] . '_' . $view_mode . '_' . $group->group_name; + + // Each formatter type can have whole different set of element properties. + switch ($group->format_type) { + + // Normal or collapsible div. + case 'div': + $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none'; + $speed = isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : 'none'; + $add = array( + '#type' => 'markup', + '#weight' => $group->weight, + '#id' => $id, + ); + $classes .= " speed-$speed effect-$effect"; + if ($group->format_settings['formatter'] != 'open') { + $add['#prefix'] = '
        + ' . check_plain(t($group->label)) . ' +
        '; + } + else { + $add['#prefix'] = '
        '; + $add['#suffix'] = '
        '; + } + if (!empty($description)) { + $add['#prefix'] .= '
        ' . $description . '
        '; + } + $element += $add; + + if ($effect == 'blind') { + drupal_add_library('system', 'effects.blind'); + } + + break; + break; + } +} + +/** + * Implements hook_field_group_pre_render(). + * + * Function that fungates as last resort to alter the pre_render build. + */ +function hook_field_group_pre_render_alter(&$element, $group, & $form) { + + if ($group->format_type == 'htab') { + $element['#theme_wrappers'] = array('my_horizontal_tab'); + } + +} + +/** + * Implements hook_field_group_build_pre_render_alter(). + * + * Function that fungates as last resort where you can alter things. It is + * expected that when you need this function, you have most likely a very custom + * case or it is a fix that can be put in field_group core. + * + * @param Array $elements by address. + */ +function hook_field_group_build_pre_render_alter(& $element) { + + // Prepare variables. + $display = isset($element['#view_mode']); + $groups = array_keys($element['#groups']); + + // Example from field_group itself to unset empty elements. + if ($display) { + foreach (element_children($element) as $name) { + if (in_array($name, $groups)) { + if (field_group_field_group_is_empty($element[$name], $groups)) { + unset($element[$name]); + } + } + } + } + + // You might include additional javascript files and stylesheets. + $element['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.js'; + $element['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.css'; + +} + +/** + * Implements hook_field_group_format_summary(). + * + * Place to override or change default summary behavior. In most + * cases the implementation of field group itself will be enough. + * + * TODO It might be better to change this hook with already created summaries, + * giving the ability to alter or add it later on. + */ +function hook_field_group_format_summary($group) { + $output = ''; + // Create additional summary or change the default setting. + return $output; +} + +/** + * Implement hook_ctools_plugin_api(). + * This hook is needed to let ctools know about exportables. + * If you create field groups by using hook_field_group_info, you + * will need to include the ctools api hook as well. + */ +function hook_ctools_plugin_api($module, $api) { + if ($module == 'field_group' && $api == 'field_group') { + return array('version' => 1); + } +} + +/** + * Implements hook_field_group_info(). + * Don't forget to include the ctools hook to notify that + * your modules has field group exports. + * @see hook_ctools_plugin_api. + */ +function hook_field_group_info() { + +} + +/** + * Alter the field group definitions provided by other modules. + * + * @param array $groups + * Reference to an array of field group definition objects. + */ +function hook_field_group_info_alter(&$groups) { + if (!empty($groups['group_issue_metadata|node|project_issue|form'])) { + $groups['group_issue_metadata|node|project_issue|form']->data['children'][] = 'taxonomy_vocabulary_9'; + } +} + +/** + * Implements hook_field_group_update_field_group(). + * + * This hook is invoked by ctools export API. + * Note that this is used by ctools and the group could occasional be + * the group ID. + * + * @param $object $group + * The FieldGroup object. + */ +function hook_field_group_update_field_group($group) { + // Delete extra data depending on the group. +} + +/** + * Implements hook_field_group_delete_field_group(). + * + * This hook is invoked by ctools export API. + * + * @param $object $group + * The FieldGroup object. + */ +function hook_field_group_delete_field_group($group) { + // Delete extra data depending on the group. +} + +/** + * Implements hook_field_group_create_field_group(). + * + * This hook is invoked by ctools export API. + * + * @param $object $group + * The FieldGroup object. + */ +function hook_field_group_create_field_group($group) { + // Create extra data depending on the group. +} + + + +/** + * @} End of "addtogroup hooks". + */ + + + +/** + * @addtogroup utility functions + * @{ + */ + +/** + * Get the groups for a given entity type, bundle and view mode. + * + * @param String $entity_type + * The Entity type where field groups are requested. + * @param String $bundle + * The entity bundle for the field groups. + * @param String $view_mode + * The view mode scope for the field groups. + * + * @see field_group_read_groups() + * @see ctools_export_crud_load() + * @see ctools_export_crud_load_all() + * @see ctools_export_crud_delete() + * @see ctools_export_crud_save() + */ +function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode = NULL, $reset = FALSE) { + // This function caches the result and delegates to field_group_read_groups. +} + +/** + * Get the groups for the given parameters, uncached. + * + * @param Array $params + * The Entity type where field groups are requested. + * @param $enabled + * Return enabled or disabled groups.* + * + * @see field_group_info_groups() + * @see ctools_export_load_object() + */ +function field_group_read_groups($conditions = array(), $enabled = TRUE) { + // This function loads the requested groups through ctools export api. +} + +/** + * Hides field groups including children in a render array. + * + * @param array $element + * A render array. Can be a form, node, user, ... + * @param array $group_names + * An array of field group names that should be hidden. + */ +function field_group_hide_field_groups(&$element, $group_names) { +} + +/** + * @} End of "addtogroup utility functions". + */ + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,23 @@ +/* $Id: field_group.css,v 1.1.2.12 2010/12/22 22:22:35 stalski Exp $ */ + +/** + * Fix for fieldsets in vertical tabs. + * Note that this can only be hardcoded to the Seven theme + * where people who override this, are in trouble. + * This can be removed in next d7 release. + */ +.vertical-tabs fieldset.default-fallback, +div.field-group-tabs-wrapper div.field-type-image fieldset, +div.field-group-tabs-wrapper div.field-type-file fieldset, +div.field-group-tabs-wrapper div.field-type-datetime fieldset { + border: 1px solid #CCCCCC; + margin: 1em 0; + padding: 2.5em 0 0; + position: relative; +} + +div.field-group-tabs-wrapper div.field-type-image legend, +div.field-group-tabs-wrapper div.field-type-file legend, +div.field-group-tabs-wrapper div.field-type-datetime legend { + display: block; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.features.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.features.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,58 @@ + $group) { + if ($group->entity_type == $entity_type && $group->bundle == $bundle + && in_array($field_name, $group->data['children']) && in_array($group->identifier, $export['features']['field_group'])) { + if (isset($group->export_module) && $group->export_module != $module_name) { + $export['dependencies'][$group->export_module] = $group->export_module; + } + else { + $export['features']['field_group'][$group_id] = $group_id; + } + } + } + } + } + + // Add any parent field groups that haven't been selected. + if (!empty($export['features']['field_group'])) { + foreach ($export['features']['field_group'] as $id) { + $group = isset($field_groups[$id]) ? $field_groups[$id] : FALSE; + + if ($group && !empty($group->parent_name)) { + $parent_id = $group->parent_name . '|' . $group->entity_type . '|' . $group->bundle . '|' . $group->mode; + $parent_group = isset($field_groups[$parent_id]) ? $field_groups[$parent_id] : FALSE; + + if ($parent_group && !isset($export['features']['field_group'][$parent_id])) { + if (isset($parent_group->export_module) && $parent_group->export_module != $module_name) { + $export['dependencies'][$parent_group->export_module] = $parent_group->export_module; + } + else { + $export['features']['field_group'][$parent_id] = $parent_id; + } + } + } + } + if(empty($export['dependencies']['field_group'])) { + $export['dependencies']['field_group'] = 'field_group'; + } + } +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.field_ui.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.field_ui.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,14 @@ + +#field-overview tr.field-group .group-label, +#field-display-overview tr.field-group .group-label { + font-weight: bold; +} + +#field-overview tr.static-region, +#field-display-overview tr.static-region { + background-color: #ddd; +} + +#edit-refresh { + display:none; +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.field_ui.inc --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.field_ui.inc Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,1008 @@ +entity_type = $form['#entity_type']; + $params->bundle = $form['#bundle']; + $params->admin_path = _field_ui_bundle_admin_path($params->entity_type, $params->bundle); + $params->display_overview = $display_overview; + + if ($display_overview) { + $params->region_callback = 'field_group_display_overview_row_region'; + $params->mode = $form['#view_mode']; + } + else { + $params->region_callback = 'field_group_field_overview_row_region'; + $params->mode = 'form'; + } + + $params->groups = field_group_info_groups($params->entity_type, $params->bundle, $params->mode, TRUE); + + // Gather parenting data. + $params->parents = array(); + foreach ($params->groups as $name => $group) { + foreach ($group->children as $child) { + $params->parents[$child] = $name; + } + } + + return $params; +} + +/** + * Function to alter the fields overview and display overview screen. + */ +function field_group_field_ui_overview_form_alter(&$form, &$form_state, $display_overview = FALSE) { + + // Only start altering the form if we need to. + if (empty($form['#fields']) && empty($form['#extra'])) { + return; + } + + $params = field_group_field_ui_form_params($form, $display_overview); + + // Add some things to be able to preserve synced usage of field_ui. + if (!$display_overview) { + // This key is used to store the current updated field. + $form_state += array( + 'formatter_settings_edit' => NULL, + ); + // Add AJAX wrapper. + $form['fields']['#prefix'] = '
        '; + $form['fields']['#suffix'] = '
        '; + } + $form['#groups'] = array_keys($params->groups); + + $table = &$form['fields']; + + // Add a region for 'add_new' rows, but only when fields are + // available and thus regions. + if (isset($table['#regions'])) { + $table['#regions'] += array( + 'add_new' => array('title' => ' '), + ); + } + + // Extend available parenting options. + foreach ($params->groups as $name => $group) { + $table['#parent_options'][$name] = $group->label; + } + $table['#parent_options']['_add_new_group'] = t('Add new group'); + + // Update existing rows accordingly to the parents. + foreach (element_children($table) as $name) { + $table[$name]['parent_wrapper']['parent']['#options'] = $table['#parent_options']; + // Inherit the value of the parent when default value is empty. + if (empty($table[$name]['parent_wrapper']['parent']['#default_value'])) { + $table[$name]['parent_wrapper']['parent']['#default_value'] = isset($params->parents[$name]) ? $params->parents[$name] : ''; + } + } + + $formatter_options = field_group_field_formatter_options($display_overview ? 'display' : 'form'); + + $refresh_rows = isset($form_state['values']['refresh_rows']) ? $form_state['values']['refresh_rows'] : (isset($form_state['input']['refresh_rows']) ? $form_state['input']['refresh_rows'] : NULL); + + // Create the group rows and check actions. + foreach (array_keys($params->groups) as $name) { + + // Play around with form_state so we only need to hold things + // between requests, until the save button was hit. + if (isset($form_state['field_group'][$name])) { + $group = & $form_state['field_group'][$name]; + } + else { + $group = & $params->groups[$name]; + } + + // Check the currently selected formatter, and merge persisted values for + // formatter settings for the group. + // This needs to be done first, so all fields are updated before creating form elements. + if (isset($refresh_rows) && $refresh_rows == $name) { + $settings = isset($form_state['values']['fields'][$name]) ? $form_state['values']['fields'][$name] : (isset($form_state['input']['fields'][$name]) ? $form_state['input']['fields'][$name] : NULL); + if (array_key_exists('settings_edit', $settings)) { + //$group->format_type = $form_state['field_group'][$name]->format_type; + $group = $form_state['field_group'][$name]; + } + field_group_formatter_row_update($group, $settings); + } + + // Save the group when the configuration is submitted. + if (!empty($form_state['values'][$name . '_formatter_settings_update'])) { + field_group_formatter_settings_update($group, $form_state['values']['fields'][$name]); + } + // After all updates are finished, let the form_state know. + $form_state['field_group'][$name] = $group; + + $settings = field_group_format_settings_form($group); + + $id = strtr($name, '_', '-'); + $js_rows_data[$id] = array('type' => 'group', 'name' => $name); + // A group cannot be selected as its own parent. + $parent_options = $table['#parent_options']; + unset($parent_options[$name]); + $table[$name] = array( + '#attributes' => array('class' => array('draggable', 'field-group'), 'id' => $id), + '#row_type' => 'group', + '#region_callback' => $params->region_callback, + '#js_settings' => array('rowHandler' => 'group'), + 'human_name' => array( + '#markup' => check_plain(t($group->label)), + '#prefix' => '', + '#suffix' => '', + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $group->weight, + '#size' => 3, + '#attributes' => array('class' => array('field-weight')), + ), + 'parent_wrapper' => array( + 'parent' => array( + '#type' => 'select', + '#options' => $parent_options, + '#empty_value' => '', + '#default_value' => isset($params->parents[$name]) ? $params->parents[$name] : '', + '#attributes' => array('class' => array('field-parent')), + '#parents' => array('fields', $name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), + ), + ); + + $table[$name] += array( + 'group_name' => array( + '#markup' => check_plain($name), + ), + 'format' => array( + 'type' => array( + '#type' => 'select', + '#options' => $formatter_options, + '#default_value' => $group->format_type, + '#attributes' => array('class' => array('field-group-type')), + ), + ), + ); + + $base_button = array( + '#submit' => array('field_ui_display_overview_multistep_submit'), + '#ajax' => array( + 'callback' => 'field_ui_display_overview_multistep_js', + 'wrapper' => 'field-display-overview-wrapper', + 'effect' => 'fade', + ), + '#field_name' => $name, + ); + + if ($form_state['formatter_settings_edit'] == $name) { + $table[$name]['format']['#cell_attributes'] = array('colspan' => $display_overview ? 3 : 3); + $table[$name]['format']['format_settings'] = array( + '#type' => 'container', + '#attributes' => array('class' => array('field-formatter-settings-edit-form')), + '#parents' => array('fields', $name, 'format_settings'), + '#weight' => -5, + 'label' => array( + '#markup' => t('Field group format:') . ' ' . $group->format_type . '', + ), + // Create a settings form where hooks can pick in. + 'settings' => $settings, + 'actions' => array( + '#type' => 'actions', + 'save_settings' => $base_button + array( + '#type' => 'submit', + '#name' => $name . '_formatter_settings_update', + '#value' => t('Update'), + '#op' => 'update', + ), + 'cancel_settings' => $base_button + array( + '#type' => 'submit', + '#name' => $name . '_formatter_settings_cancel', + '#value' => t('Cancel'), + '#op' => 'cancel', + // Do not check errors for the 'Cancel' button. + '#limit_validation_errors' => array(), + ), + ), + ); + $table[$name]['#attributes']['class'][] = 'field-formatter-settings-editing'; + $table[$name]['format']['type']['#attributes']['class'] = array('element-invisible'); + } + else { + // After saving, the settings are updated here aswell. First we create + // the element for the table cell. + $table[$name]['settings_summary'] = array('#markup' => ''); + if (!empty($group->format_settings)) { + $table[$name]['settings_summary'] = field_group_format_settings_summary($name, $group); + } + // Add the configure button. + $table[$name]['settings_edit'] = $base_button + array( + '#type' => 'image_button', + '#name' => $name . '_group_settings_edit', + '#src' => 'misc/configure.png', + '#attributes' => array('class' => array('field-formatter-settings-edit'), 'alt' => t('Edit')), + '#op' => 'edit', + // Do not check errors for the 'Edit' button. + '#limit_validation_errors' => array(), + '#prefix' => '
        ', + '#suffix' => '
        ', + ); + if ($display_overview) { + $table[$name]['settings_edit']['#suffix'] .= l(t('delete'), $params->admin_path . '/groups/' . $name . '/delete/' . $params->mode); + } + } + + if (!$display_overview) { + $table[$name] += array( + 'delete' => array( + '#markup' => l(t('delete'), $params->admin_path . '/groups/' . $name . '/delete/form'), + ), + ); + } + } + + // Additional row: add new group. + $parent_options = $table['#parent_options']; + unset($parent_options['_add_new_group']); + $table['_add_new_group'] = field_group_add_row('_add_new_group', $parent_options, $params); + + $table['_add_new_group'] += array( + 'format' => array( + 'type' => array( + '#type' => 'select', + '#options' => $formatter_options, + '#default_value' => 'fieldset', + '#prefix' => '
         
        ', + ), + '#cell_attributes' => array('colspan' => 3), + ), + ); + + if (!$display_overview) { + // See field_ui.admin.inc for more details on refresh rows. + $form['refresh_rows'] = array('#type' => 'hidden'); + $form['refresh'] = array( + '#type' => 'submit', + '#value' => t('Refresh'), + '#op' => 'refresh_table', + '#submit' => array('field_ui_display_overview_multistep_submit'), + '#ajax' => array( + 'callback' => 'field_ui_display_overview_multistep_js', + 'wrapper' => 'field-display-overview-wrapper', + 'effect' => 'fade', + // The button stays hidden, so we hide the AJAX spinner too. Ad-hoc + // spinners will be added manually by the client-side script. + 'progress' => 'none', + ), + ); + } + + $form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css'; + $form['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.js'; + + $form['#validate'][] = 'field_group_field_overview_validate'; + $form['#submit'][] = 'field_group_field_overview_submit'; + + // Create the settings for fieldgroup as vertical tabs (merged with DS). + field_group_field_ui_create_vertical_tabs($form, $form_state, $params); + + // Show a warning if the user has not set up required containers + if ($form['#groups']) { + + $parent_requirements = array( + 'multipage' => array( + 'parent' => 'multipage-group', + 'message' => 'Each Multipage element needs to have a parent Multipage group element.', + ), + 'htab' => array( + 'parent' => 'htabs', + 'message' => 'Each Horizontal tab element needs to have a parent Horizontal tabs group element.', + ), + 'accordion-item' => array( + 'parent' => 'accordion', + 'message' => 'Each Accordion item element needs to have a parent Accordion group element.', + ), + ); + + // On display overview tabs need to be checked. + if ($display_overview) { + $parent_requirements['tab'] = array( + 'parent' => 'tabs', + 'message' => 'Each Vertical tab element needs to have a parent Vertical tabs group element.', + ); + } + + foreach ($form['#groups'] as $group_name) { + $group_check = field_group_load_field_group($group_name, $params->entity_type, $params->bundle, $params->mode); + if (isset($parent_requirements[$group_check->format_type])) { + if (!$group_check->parent_name || field_group_load_field_group($group_check->parent_name, $params->entity_type, $params->bundle, $params->mode)->format_type != $parent_requirements[$group_check->format_type]['parent']) { + drupal_set_message(t($parent_requirements[$group_check->format_type]['message']), 'warning', FALSE); + } + } + } + } +} + +/** + * Return an array of field_group_formatter options. + */ +function field_group_field_formatter_options($type) { + $options = &drupal_static(__FUNCTION__); + + if (!isset($options)) { + $options = array(); + $field_group_types = field_group_formatter_info(); + foreach ($field_group_types[$type] as $name => $field_group_type) { + $options[$name] = $field_group_type['label']; + } + } + return $options; +} + +/** + * Helper function to add a row in the overview forms. + */ +function field_group_add_row($name, $parent_options, $params) { + return array( + '#attributes' => array('class' => array('draggable', 'field-group', 'add-new')), + '#row_type' => 'add_new_group', + '#js_settings' => array('rowHandler' => 'group'), + '#region_callback' => $params->region_callback, + 'label' => array( + '#title_display' => 'invisible', + '#title' => t('Label for new group'), + '#type' => 'textfield', + '#size' => 15, + '#description' => t('Label'), + '#prefix' => '
        ' . t('Add new group') . '
        ', + '#suffix' => '
        ', + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => field_info_max_weight($params->entity_type, $params->bundle, $params->mode) + 3, + '#size' => 3, + '#title_display' => 'invisible', + '#title' => t('Weight for new group'), + '#attributes' => array('class' => array('field-weight')), + '#prefix' => '
         
        ', + ), + 'parent_wrapper' => array( + 'parent' => array( + '#title_display' => 'invisible', + '#title' => t('Parent for new group'), + '#type' => 'select', + '#options' => $parent_options, + '#empty_value' => '', + '#attributes' => array('class' => array('field-parent')), + '#prefix' => '
         
        ', + '#parents' => array('fields', $name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), + ), + 'group_name' => array( + '#type' => 'textfield', + '#title_display' => 'invisible', + '#title' => t('Machine name for new group'), + // This field should stay LTR even for RTL languages. + '#field_prefix' => 'group_', + '#field_suffix' => '‎', + '#attributes' => array('dir' => 'ltr'), + '#size' => 15, + '#description' => t('Group name (a-z, 0-9, _)'), + '#prefix' => '
         
        ', + '#cell_attributes' => array('colspan' => $params->display_overview ? 1 : 2), + ), + ); +} + +/** + * Creates a form for field_group formatters. + * @param Object $group The FieldGroup object. + */ +function field_group_format_settings_form(&$group) { + $form = array(); + $form['label'] = array( + '#type' => 'textfield', + '#title' => t('Field group label'), + '#default_value' => $group->label, + '#weight' => -5, + '#element_validate' => array('field_group_format_settings_label_validate'), + ); + $form += module_invoke_all('field_group_format_settings', $group); + $form['#validate'] = array('field_group_format_settings_form_validate'); + return $form; +} + +/** + * Validate the label. Label is required for fieldsets that are collapsible. + */ +function field_group_format_settings_label_validate($element, &$form_state) { + + $group = $form_state['values']['fields'][$element['#parents'][1]]; + $settings = $group['format_settings']['settings']; + $name = $form_state['formatter_settings_edit']; + $form_state['values']['fields'][$name]['settings_edit_form']['settings'] = $settings; + if ($group['format']['type'] == 'fieldset' && ($settings['formatter'] == 'collapsed' || $settings['formatter'] == 'collapsible') && empty($settings['label'])) { + form_error($element, t('The label is required when formatter is collapsible or collapsed')); + } + +} + +/** + * Update the row so that the group variables are updated. + * The rendering of the elements needs the updated defaults. + * @param Object $group + * @param array $settings + */ +function field_group_formatter_row_update(& $group, $settings) { + // if the row has changed formatter type, update the group object + if (!empty($settings['format']['type']) && $settings['format']['type'] != $group->format_type) { + $group->format_type = $settings['format']['type']; + field_group_formatter_settings_update($group, $settings); + } +} + +/** + * Update handler for field_group configuration settings. + * @param Object $group The group object + * @param Array $settings Configuration settings + */ +function field_group_formatter_settings_update(& $group, $settings) { + + // for format changes we load the defaults. + if (empty($settings['format_settings']['settings'])) { + $mode = $group->mode == 'form' ? 'form' : 'display'; + $group->format_settings = _field_group_get_default_formatter_settings($group->format_type, $mode); + } + else { + $group->format_type = $settings['format']['type']; + $group->label = $settings['format_settings']['settings']['label']; + $group->format_settings = $settings['format_settings']['settings']; + } +} + +/** + * Creates a summary for the field format configuration summary. + * @param String $group_name The name of the group + * @param Object $group The group object + * @return Array ready to be rendered. + */ +function field_group_format_settings_summary($group_name, $group) { + $summary = implode('
        ', module_invoke_all('field_group_format_summary', $group)); + return array( + '#markup' => '
        ' . $summary . '
        ', + '#cell_attributes' => array('class' => array('field-formatter-summary-cell')), + ); +} + +/** + * Returns the region to which a row in the 'Manage fields' screen belongs. + * @param Array $row A field or field_group row + * @return String the current region. + */ +function field_group_field_overview_row_region($row) { + switch ($row['#row_type']) { + case 'group': + return 'main'; + case 'add_new_group': + // If no input in 'label', assume the row has not been dragged out of the + // 'add new' section. + if (empty($row['label']['#value'])) { + return 'add_new'; + } + return 'main'; + } +} + +/** + * Returns the region to which a row in the 'Manage display' screen belongs. + * @param Array $row A field or field_group row + * @return String the current region. + */ +function field_group_display_overview_row_region($row) { + switch ($row['#row_type']) { + case 'group': + return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'visible'); + case 'add_new_group': + // If no input in 'label', assume the row has not been dragged out of the + // 'add new' section. + if (empty($row['label']['#value'])) { + return 'add_new'; + } + return ($row['format']['type']['#value'] == 'hidden' ? 'hidden' : 'visible'); + } +} + +/** + * Validate handler for the overview screens. + * @param Array $form The complete form. + * @param Array $form_state The state of the form. + */ +function field_group_field_overview_validate($form, &$form_state) { + $form_values = $form_state['values']['fields']; + $entity_type = $form['#entity_type']; + $bundle = $form['#bundle']; + $mode = (isset($form['#view_mode']) ? $form['#view_mode'] : 'form'); + + $group = $form_values['_add_new_group']; + + // Validate if any information was provided in the 'add new group' row. + if (array_filter(array($group['label'], $group['group_name']))) { + + // Missing group name. + if (!$group['group_name']) { + form_set_error('fields][_add_new_group][group_name', t('Add new group: you need to provide a group name.')); + } + // Group name validation. + else { + $group_name = $group['group_name']; + + // Add the 'group_' prefix. + if (drupal_substr($group_name, 0, 6) != 'group_') { + $group_name = 'group_' . $group_name; + form_set_value($form['fields']['_add_new_group']['group_name'], $group_name, $form_state); + } + + // Invalid group name. + if (!preg_match('!^group_[a-z0-9_]+$!', $group_name)) { + form_set_error('fields][_add_new_group][group_name', t('Add new group: the group name %group_name is invalid. The name must include only lowercase unaccentuated letters, numbers, and underscores.', array('%group_name' => $group_name))); + } + if (drupal_strlen($group_name) > 32) { + form_set_error('fields][_add_new_group][group_name', t("Add new group: the group name %group_name is too long. The name is limited to 32 characters, including the 'group_' prefix.", array('%group_name' => $group_name))); + } + + // Group name already exists. + if (field_group_exists($group_name, $entity_type, $bundle, $mode)) { + form_set_error('fields][_add_new_group][group_name', t('Add new group: the group name %group_name already exists.', array('%group_name' => $group_name))); + } + } + } +} + +/** + * Submit handler for the overview screens. + * @param Array $form The complete form. + * @param Array $form_state The state of the form. + */ +function field_group_field_overview_submit($form, &$form_state) { + + $form_values = $form_state['values']['fields']; + $entity_type = $form['#entity_type']; + $bundle = $form['#bundle']; + $mode = (isset($form['#view_mode']) ? $form['#view_mode'] : 'form'); + + // Collect children. + $children = array_fill_keys($form['#groups'], array()); + foreach ($form_values as $name => $value) { + if (!empty($value['parent'])) { + // Substitute newly added fields, in case they were dragged + // directly in a group. + if ($name == '_add_new_field' && isset($form_state['fields_added']['_add_new_field'])) { + $name = $form_state['fields_added']['_add_new_field']; + } + elseif ($name == '_add_existing_field' && isset($form_state['fields_added']['_add_existing_field'])) { + $name = $form_state['fields_added']['_add_existing_field']; + } + $children[$value['parent']][$name] = $name; + } + } + + // Prepare storage with ctools. + ctools_include('export'); + + // Create new group. + if (!empty($form_values['_add_new_group']['group_name'])) { + $values = $form_values['_add_new_group']; + + $field_group_types = field_group_formatter_info(); + $formatter = $field_group_types[($mode == 'form' ? 'form' : 'display')][$values['format']['type']]; + + $new_group = (object) array( + 'identifier' => $values['group_name'] . '|' . $entity_type . '|' . $bundle . '|' . $mode, + 'group_name' => $values['group_name'], + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'mode' => $mode, + 'children' => isset($children['_add_new_group']) ? array_keys($children['_add_new_group']) : array(), + 'parent_name' => $values['parent'], + 'weight' => $values['weight'], + 'label' => $values['label'], + 'format_type' => $values['format']['type'], + 'disabled' => FALSE, + ); + $new_group->format_settings = array('formatter' => isset($formatter['default_formatter']) ? $formatter['default_formatter'] : ''); + if (isset($formatter['instance_settings'])) { + $new_group->format_settings['instance_settings'] = $formatter['instance_settings']; + } + + $classes = _field_group_get_html_classes($new_group); + $new_group->format_settings['instance_settings']['classes'] = implode(' ', $classes->optional); + + // Save and enable it in ctools. + ctools_export_crud_save('field_group', $new_group); + ctools_export_crud_enable('field_group', $new_group->identifier); + + // Store new group information for any additional submit handlers. + $form_state['groups_added']['_add_new_group'] = $new_group->group_name; + drupal_set_message(t('New group %label successfully created.', array('%label' => $new_group->label))); + + // Replace the newly created group in the $children array, in case it was + // dragged directly in an existing field. + foreach (array_keys($children) as $parent) { + if (isset($children[$parent]['_add_new_group'])) { + unset($children[$parent]['_add_new_group']); + $children[$parent][$new_group->group_name] = $new_group->group_name; + } + } + } + + // Update existing groups. + $groups = field_group_info_groups($entity_type, $bundle, $mode, TRUE); + foreach ($form['#groups'] as $group_name) { + $group = $groups[$group_name]; + $group->label = $form_state['field_group'][$group_name]->label; + $group->children = array_keys($children[$group_name]); + $group->parent_name = $form_values[$group_name]['parent']; + $group->weight = $form_values[$group_name]['weight']; + + $old_format_type = $group->format_type; + $group->format_type = isset($form_values[$group_name]['format']['type']) ? $form_values[$group_name]['format']['type'] : 'visible'; + if (isset($form_state['field_group'][$group_name]->format_settings)) { + $group->format_settings = $form_state['field_group'][$group_name]->format_settings; + } + + // If the format type is changed, make sure we have all required format settings. + if ($group->format_type != $old_format_type) { + $mode = $group->mode == 'form' ? 'form' : 'display'; + $default_formatter_settings = _field_group_get_default_formatter_settings($group->format_type, $mode); + $group->format_settings += $default_formatter_settings; + $group->format_settings['instance_settings'] += $default_formatter_settings['instance_settings']; + } + + ctools_export_crud_save('field_group', $group); + } + + cache_clear_all('field_groups', 'cache_field'); +} + +/** + * Validate the entered css class from the submitted format settings. + * @param Array $element The validated element + * @param Array $form_state The state of the form. + */ +function field_group_validate_css_class($element, &$form_state) { + if (!empty($form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['classes']) && !preg_match('!^[A-Za-z0-9-_ ]+$!', $form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['classes'])) { + form_error($element, t('The css class must include only letters, numbers, underscores and dashes.')); + } +} + +/** + * Validate the entered id attribute from the submitted format settings. + * @param Array $element The validated element + * @param Array $form_state The state of the form. + */ +function field_group_validate_id($element, &$form_state) { + if (!empty($form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['id']) && !preg_match('!^[A-Za-z0-9-_]+$!', $form_state['values']['fields'][$form_state['formatter_settings_edit']]['format_settings']['settings']['instance_settings']['id'])) { + form_error($element, t('The id must include only letters, numbers, underscores and dashes.')); + } +} + +/** + * Implements hook_field_info_max_weight(). + */ +function field_group_field_info_max_weight($entity_type, $bundle, $context) { + $weights = array(); + foreach (field_group_info_groups($entity_type, $bundle, $context) as $group) { + $weights[] = $group->weight; + } + return $weights ? max($weights) : NULL; +} + +/** + * Menu callback; present a form for removing a group. + */ +function field_group_delete_form($form, &$form_state, $group, $view_mode = 'form') { + + $form['#group'] = $group; + $admin_path = _field_ui_bundle_admin_path($group->entity_type, $group->bundle); + if ($view_mode == 'form') { + $admin_path .= '/fields'; + } + else { + $admin_path .= '/display/' . $view_mode; + } + $form['#redirect'] = array($admin_path); + $output = confirm_form($form, + t('Are you sure you want to delete the group %group?', array('%group' => t($group->label))), + $admin_path, + t('This action cannot be undone.'), + t('Delete'), t('Cancel'), + 'confirm' + ); + return $output; +} + +/** + * Remove group from bundle. + * + * @todo we'll have to reset all view mode settings - that will be fun :) + */ +function field_group_delete_form_submit($form, &$form_state) { + + $group = $form['#group']; + $bundle = $group->bundle; + $entity_type = $group->entity_type; + $group->mode = $form_state['build_info']['args'][1]; + + $bundles = field_info_bundles(); + $bundle_label = $bundles[$entity_type][$bundle]['label']; + + ctools_include('export'); + field_group_group_export_delete($group, FALSE); + + drupal_set_message(t('The group %group has been deleted from the %type content type.', array('%group' => t($group->label), '%type' => $bundle_label))); + + // Redirect. + $form_state['redirect'] = $form['#redirect']; +} + +/** + * Menu callback; present a form for re-enabling a group. + */ +function field_group_enable_form($form, &$form_state, $group, $view_mode = 'form') { + + $form['#group'] = $group; + $admin_path = _field_ui_bundle_admin_path($group->entity_type, $group->bundle); + if ($view_mode == 'form') { + $admin_path .= '/fields'; + } + else { + $admin_path .= '/display/' . $view_mode; + } + $form['#redirect'] = array($admin_path); + $output = confirm_form($form, + t('Are you sure you want to enable the group %group?', array('%group' => t($group->label))), + $admin_path, + '', + t('Enable'), t('Cancel'), + 'confirm' + ); + return $output; +} + +/** + * Re-enable the group on a bundle. + */ +function field_group_enable_form_submit($form, &$form_state) { + + $group = $form['#group']; + $bundle = $group->bundle; + $entity_type = $group->entity_type; + $group->mode = $form_state['build_info']['args'][1]; + + $bundles = field_info_bundles(); + $bundle_label = $bundles[$entity_type][$bundle]['label']; + + ctools_include('export'); + ctools_export_crud_enable('field_group', $group->identifier); + + drupal_set_message(t('The group %group has been enabled on the %type content type.', array('%group' => t($group->label), '%type' => $bundle_label))); + + // Redirect. + $form_state['redirect'] = $form['#redirect']; +} + +/** + * Create vertical tabs. + */ +function field_group_field_ui_create_vertical_tabs(&$form, &$form_state, $params) { + + $form_state['field_group_params'] = $params; + + $entity_info = entity_get_info($params->entity_type); + $view_modes = array(); + if ($params->mode != 'default') { + $view_modes['default'] = t('Default'); + } + if ($params->mode != 'form') { + $view_modes['0'] = t('Form'); + } + foreach ($entity_info['view modes'] as $view_mode => $data) { + if ($data['custom settings'] && $params->mode != $view_mode) { + $view_modes[$view_mode] = $data['label']; + } + } + + // Add additional settings vertical tab. + if (!isset($form['additional_settings'])) { + $form['additional_settings'] = array( + '#type' => 'vertical_tabs', + '#theme_wrappers' => array('vertical_tabs'), + '#prefix' => '
        ', + '#suffix' => '
        ', + '#tree' => TRUE, + ); + $form['#attached']['js'][] = 'misc/form.js'; + $form['#attached']['js'][] = 'misc/collapse.js'; + } + + // Add extra guidelines for webmaster. + $form['additional_settings']['field_group'] = array( + '#type' => 'fieldset', + '#title' => t('Fieldgroups'), + '#description' => t('

        Fields can be dragged into groups with unlimited nesting. Each fieldgroup format comes with a configuration form, specific for that format type.
        Note that some formats come in pair. These types have a html wrapper to nest its fieldgroup children. E.g. Place accordion items into the accordion, vertical tabs in vertical tab group and horizontal tabs in the horizontal tab group. There is one exception to this rule, you can use a vertical tab without a wrapper when the additional settings tabs are available. E.g. node forms.

        '), + '#collapsible' => TRUE, + '#collapsed' => FALSE, + '#parents' => array('additional_settings'), + ); + $form['additional_settings']['field_group']['fieldgroup_clone'] = array( + '#title' => t('Select source view mode or form'), + '#description' => t('Clone fieldgroups from selected view mode to the current display'), + '#type' => 'select', + '#options' => $view_modes, + '#default_value' => 'none' + ); + $form['additional_settings']['field_group']['fieldgroup_submit'] = array( + '#type' => 'submit', + '#value' => t('Clone'), + '#validate' => array('field_group_field_ui_clone_field_groups_validate'), + '#submit' => array('field_group_field_ui_clone_field_groups') + ); + + $disabled_groups = field_group_read_groups(array(), FALSE); + + // Show disabled fieldgroups, and make it possible to enable them again. + if ($disabled_groups && isset($disabled_groups[$params->entity_type][$params->bundle][$params->mode])) { + $form['additional_settings']['disabled_field_groups'] = array( + + '#type' => 'fieldset', + + '#title' => t('Disabled fieldgroups'), + + '#collapsible' => TRUE, + + '#collapsed' => FALSE, + + '#parents' => array('additional_settings'), + + ); + $form['additional_settings']['disabled_field_groups']['overview'] = field_group_disabled_groups_overview($disabled_groups[$params->entity_type][$params->bundle][$params->mode], $entity_info, $params); + } + +} + +/** + * Validate handler to validate saving existing fieldgroups from one view mode or form to another. + */ +function field_group_field_ui_clone_field_groups_validate($form, &$form_state) { + + $source_mode = $form_state['#source_mode'] = $form_state['values']['additional_settings']['fieldgroup_clone'] == '0' ? 'form' : $form_state['values']['additional_settings']['fieldgroup_clone']; + $groups_to_clone = $form_state['#groups_to_clone'] = field_group_read_groups(array('bundle' => $form_state['field_group_params']->bundle, 'entity_type' => $form_state['field_group_params']->entity_type)); + + $form_state['#source_groups'] = array(); + if (!empty($groups_to_clone) && isset($groups_to_clone[$form_state['field_group_params']->entity_type], $groups_to_clone[$form_state['field_group_params']->entity_type][$form_state['field_group_params']->bundle], $groups_to_clone[$form_state['field_group_params']->entity_type][$form_state['field_group_params']->bundle][$source_mode])) { + $form_state['#source_groups'] = $groups_to_clone[$form_state['field_group_params']->entity_type][$form_state['field_group_params']->bundle][$source_mode]; + } + + // Check for types are not known in current mode. + if ($form_state['field_group_params']->mode != 'form') { + $non_existing_types = array('multipage', 'multipage-group'); + } + else { + $non_existing_types = array('div'); + } + + foreach ($form_state['#source_groups'] as $key => $group) { + if (in_array($group->format_type, $non_existing_types)) { + unset($form_state['#source_groups'][$key]); + drupal_set_message(t('Skipping @group because this type does not exist in current mode', array('@group' => $group->label)), 'warning'); + } + } + + if (empty($form_state['#source_groups'])) { + // Report error found with selection. + form_set_error('additional_settings][fieldgroup_clone', t('No field groups were found in selected view mode.')); + return; + } + +} + +/** + * Submit handler to save existing fieldgroups from one view mode or form to another. + */ +function field_group_field_ui_clone_field_groups($form, &$form_state) { + + $source_mode = $form_state['#source_mode']; + $groups_to_clone = $form_state['#groups_to_clone']; + + $fields = array_keys($form_state['values']['fields']); + if (!empty($form_state['#source_groups'])) { + + foreach ($form_state['#source_groups'] as $source_group) { + if (in_array($source_group->group_name, $fields)) { + drupal_set_message(t('Fieldgroup @group is not cloned since a group already exists with the same name.', array('@group' => $source_group->group_name)), 'warning'); + continue; + } + + // Recreate the identifier and reset the id. + $source_group->id = NULL; + $source_group->mode = $form_state['field_group_params']->mode; + $source_group->identifier = $source_group->group_name . '|' . $source_group->entity_type . '|' . $source_group->bundle . '|' . $form_state['field_group_params']->mode; + $source_group->disabled = FALSE; + $source_group->children = array(); + unset($source_group->export_type, $source_group->type, $source_group->table); + + // Save and enable it in ctools. + ctools_include('export'); + ctools_export_crud_save('field_group', $source_group); + ctools_export_crud_enable('field_group', $source_group->identifier); + + drupal_set_message(t('Fieldgroup @group cloned successfully.', array('@group' => $source_group->group_name))); + + } + } + +} + +/** + * Show an overview of all the disabled fieldgroups, and make it possible to activate them again. + * @param $disabled_groups Array with all disabled groups. + */ +function field_group_disabled_groups_overview($disabled_groups, $entity_info, $params) { + + $formatter_options = field_group_field_formatter_options($params->mode != 'form' ? 'display' : 'form'); + + $table = array( + '#theme' => 'table', + '#header' => array( + t('Label'), + t('Machine name'), + t('Field'), + t('Widget'), + t('Operations'), + ), + '#attributes' => array( + 'class' => array('field-ui-overview'), + ), + '#rows' => array(), + ); + + // Add all of the disabled groups as a row on the table. + foreach ($disabled_groups as $group) { + + $summary = field_group_format_settings_summary($group->group_name, $group); + + $row = array(); + $row[] = $group->label; + $row[] = $group->group_name; + $row[] = $formatter_options[$group->format_type]; + $row[] = render($summary); + $path = (isset($entity_info['bundles'][$params->bundle]['admin']['real path']) ? $entity_info['bundles'][$params->bundle]['admin']['real path'] : $entity_info['bundles'][$params->bundle]['admin']['path']); + $row[] = l(t('Enable'), $path . '/groups/' . $group->group_name . '/enable/' . $group->mode); + + $table['#rows'][] = $row; + } + + return $table; + +} +/** + * eof(). + */ diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.field_ui.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.field_ui.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,137 @@ + +(function($) { + +Drupal.behaviors.fieldUIFieldsOverview = { + attach: function (context, settings) { + $('table#field-overview', context).once('field-field-overview', function() { + Drupal.fieldUIOverview.attach(this, settings.fieldUIRowsData, Drupal.fieldUIFieldOverview); + }); + } +}; + +/** + * Row handlers for the 'Manage fields' screen. + */ +Drupal.fieldUIFieldOverview = Drupal.fieldUIFieldOverview || {}; + +Drupal.fieldUIFieldOverview.group = function(row, data) { + this.row = row; + this.name = data.name; + this.region = data.region; + this.tableDrag = data.tableDrag; + + // Attach change listener to the 'group format' select. + this.$formatSelect = $('select.field-group-type', row); + this.$formatSelect.change(Drupal.fieldUIOverview.onChange); + + return this; +}; + +Drupal.fieldUIFieldOverview.group.prototype = { + getRegion: function () { + return 'main'; + }, + + regionChange: function (region, recurse) { + return {}; + }, + + regionChangeFields: function (region, element, refreshRows) { + + // Create a new tabledrag rowObject, that will compute the group's child + // rows for us. + var tableDrag = element.tableDrag; + rowObject = new tableDrag.row(element.row, 'mouse', true); + // Skip the main row, we handled it above. + rowObject.group.shift(); + + // Let child rows handlers deal with the region change - without recursing + // on nested group rows, we are handling them all here. + $.each(rowObject.group, function() { + var childRow = this; + var childRowHandler = $(childRow).data('fieldUIRowHandler'); + $.extend(refreshRows, childRowHandler.regionChange(region, false)); + }); + } +}; + + +/** + * Row handlers for the 'Manage display' screen. + */ +Drupal.fieldUIDisplayOverview = Drupal.fieldUIDisplayOverview || {}; + +Drupal.fieldUIDisplayOverview.group = function(row, data) { + this.row = row; + this.name = data.name; + this.region = data.region; + this.tableDrag = data.tableDrag; + + // Attach change listener to the 'group format' select. + this.$formatSelect = $('select.field-group-type', row); + this.$formatSelect.change(Drupal.fieldUIOverview.onChange); + + return this; +}; + +Drupal.fieldUIDisplayOverview.group.prototype = { + getRegion: function () { + return (this.$formatSelect.val() == 'hidden') ? 'hidden' : 'visible'; + }, + + regionChange: function (region, recurse) { + + // Default recurse to true. + recurse = (recurse == undefined) || recurse; + + // When triggered by a row drag, the 'format' select needs to be adjusted to + // the new region. + var currentValue = this.$formatSelect.val(); + switch (region) { + case 'visible': + if (currentValue == 'hidden') { + // Restore the group format back to 'fieldset'. + var value = 'fieldset'; + } + break; + + default: + var value = 'hidden'; + break; + } + if (value != undefined) { + this.$formatSelect.val(value); + } + + var refreshRows = {}; + refreshRows[this.name] = this.$formatSelect.get(0); + + if (recurse) { + this.regionChangeFields(region, this, refreshRows); + } + + return refreshRows; + }, + + regionChangeFields: function (region, element, refreshRows) { + + // Create a new tabledrag rowObject, that will compute the group's child + // rows for us. + var tableDrag = element.tableDrag; + rowObject = new tableDrag.row(element.row, 'mouse', true); + // Skip the main row, we handled it above. + rowObject.group.shift(); + + // Let child rows handlers deal with the region change - without recursing + // on nested group rows, we are handling them all here. + $.each(rowObject.group, function() { + var childRow = this; + var childRowHandler = $(childRow).data('fieldUIRowHandler'); + $.extend(refreshRows, childRowHandler.regionChange(region, false)); + }); + + } + +}; + +})(jQuery); \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.info --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.info Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,18 @@ +name = Fieldgroup +description = Fieldgroup +package = Fields +dependencies[] = field +dependencies[] = ctools +core = 7.x +files[] = field_group.install +files[] = field_group.module +files[] = field_group.field_ui.inc +files[] = field_group.form.inc +files[] = field_group.features.inc +files[] = field_group.test +; Information added by drupal.org packaging script on 2013-08-29 +version = "7.x-1.2" +core = "7.x" +project = "field_group" +datestamp = "1377806209" + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.install Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,296 @@ + t('Table that contains field group entries and settings.'), + + // CTools export definitions. + 'export' => array( + 'key' => 'identifier', + 'identifier' => 'field_group', + 'default hook' => 'field_group_info', + 'save callback' => 'field_group_group_save', + 'delete callback' => 'field_group_group_export_delete', + 'can disable' => TRUE, + 'api' => array( + 'owner' => 'field_group', + 'api' => 'field_group', + 'minimum_version' => 1, + 'current_version' => 1, + ), + ), + + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'not null' => TRUE, + 'description' => 'The primary identifier for a group', + 'no export' => TRUE, + ), + 'identifier' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The unique string identifier for a group.', + ), + 'group_name' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The name of this group.', + ), + 'entity_type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'bundle' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '' + ), + 'mode' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '' + ), + // @todo 'parent_name' is redundant with the data in the 'children' + // entry, brings a risk of inconsistent data. This should be removed from + // the schema and pre-computed it if needed in field_group_get_groups(). + 'parent_name' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The parent name for a group', + ), + 'data' => array( + 'type' => 'blob', + 'size' => 'big', + 'not null' => TRUE, + 'serialize' => TRUE, + 'description' => 'Serialized data containing the group properties that do not warrant a dedicated column.', + ), + ), + 'primary key' => array('id'), + 'indexes' => array( + 'group_name' => array('group_name'), + ), + 'unique keys' => array( + 'identifier' => array('identifier'), + ), + ); + return $schema; +} + +/** + * Utility function: fetch all the field_group definitions from the database. + */ +function _field_group_install_read_groups() { + $groups = array(); + if (db_table_exists('content_group')) { + $query = db_select('content_group', 'cg', array('fetch' => PDO::FETCH_ASSOC)) + ->fields('cg') + // We only want non-multigroups. + ->condition('group_type', 'standard'); + foreach ($query->execute() as $record) { + $record['settings'] = unserialize($record['settings']); + $groups[$record['group_name'] . '-' . $record['type_name']] = $record; + } + foreach ($groups as $key => $group) { + $query2 = db_select('content_group_fields', 'cgf', array('fetch' => PDO::FETCH_ASSOC)) + ->fields('cgf') + ->condition('group_name', $group['group_name']); + foreach ($query2->execute() as $field) { + $groups[$field['group_name'] . '-' . $field['type_name']]['children'][] = $field; + } + } + } + return $groups; +} + +/** + * Implements of hook_install(). + * + * Because this is a new module in D7, hook_update_N() doesn't help D6 + * users who upgrade to run the migration path. So, we try that here as + * the module is being installed. + */ +function field_group_install() { + + $groups = _field_group_install_read_groups(); + module_load_include('module', 'field_group'); + + if (!empty($groups)) { + + module_load_include('module', 'ctools'); + ctools_include('export'); + + foreach ($groups as $group) { + + $group = (object) $group; + + $new = new stdClass(); + $new->group_name = $group->group_name; + $new->entity_type = 'node'; + $new->bundle = $group->type_name; + $new->label = $group->label; + $new->parent_name = ''; + $new->children = array(); + foreach ($group->children as $child) { + $new->children[] = $child['field_name']; + } + + // The form. + $new->id = NULL; + $new->weight = $group->weight; + $new->mode = 'form'; + $new->format_type = 'fieldset'; + $new->format_settings = array( + 'formatter' => preg_match("/fieldset/", $group->settings['form']['style']) ? 'collapsible' : 'collapsed', + 'instance_settings' => array(), + ); + $new->identifier = $new->group_name . '|' . $new->entity_type . '|' . $new->bundle . '|' . $new->mode; + ctools_export_crud_save('field_group', $new); + + // The full node. + $new->id = NULL; + $new->weight = $group->weight; + $new->mode = 'default'; + $new->format_type = $group->settings['display']['full']['format']; + $new->format_settings = array( + 'formatter' => 'collapsible', + 'instance_settings' => array(), + ); + $new->identifier = $new->group_name . '|' . $new->entity_type . '|' . $new->bundle . '|' . $new->mode; + ctools_export_crud_save('field_group', $new); + + // The teaser node. + $new->id = NULL; + $new->weight = $group->weight; + $new->mode = 'teaser'; + $new->format_type = $group->settings['display']['teaser']['format']; + $new->format_settings = array( + 'formatter' => 'collapsible', + 'instance_settings' => array(), + ); + $new->identifier = $new->group_name . '|' . $new->entity_type . '|' . $new->bundle . '|' . $new->mode; + ctools_export_crud_save('field_group', $new); + + } + + } + + // Set weight to 1. + db_update('system') + ->fields(array('weight' => 1)) + ->condition('name', 'field_group') + ->execute(); + +} + +/** + * Update hook on the field_group table to add an unique identifier. + */ +function field_group_update_7001() { + + if (!db_field_exists('field_group', 'identifier')) { + // Add the new string identifier field for ctools. + db_add_field('field_group', 'identifier', array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + 'description' => 'The unique string identifier for a group.', + )); + + module_load_include('module', 'field_group'); + _field_group_recreate_identifiers(); + + } + + db_update('system') + ->fields(array('weight' => 1)) + ->condition('name', 'field_group') + ->execute(); + +} + +/** + * Update hook to clear cache for new changes to take effect. + */ +function field_group_update_7002() { + + module_load_include('module', 'field_group'); + + // This hook is called to satify people with older version of field_group. + // This will recreate all identifiers for the field_groups known in database. + // At the moment, we only trigger field_groups that are stored in the database, where + // we should maybe get all field_groups as ctools has registered them. + // See http://drupal.org/node/1169146. + // See http://drupal.org/node/1018550. + _field_group_recreate_identifiers(); + +} + +/** + * Update hook to recreate identifiers. + * @see function field_group_update_7002. + */ +function field_group_update_7003() { + + module_load_include('module', 'field_group'); + _field_group_recreate_identifiers(); + +} + +/** + * Update hook to make sure identifier is set as unique key. + */ +function field_group_update_7004() { + db_drop_unique_key('field_group', 'identifier'); + db_add_unique_key('field_group', 'identifier', array('identifier')); +} + +/** + * Checks all existing groups and removes optional HTML classes + * while adding them as extra classes. + */ +function field_group_update_7005() { + + // Migrate the field groups so they have a unique identifier. + $result = db_select('field_group', 'fg') + ->fields('fg') + ->execute(); + $rows = array(); + foreach($result as $row) { + //$row->identifier = $row->group_name . '|' . $row->entity_type . '|' . $row->bundle . '|' . $row->mode; + $row->data = unserialize($row->data); + $classes = explode(" ", $row->data['format_settings']['instance_settings']['classes']); + $optional_classes = array(str_replace("_", "-", $row->group_name), 'field-group-' . $row->data['format_type']); + foreach ($optional_classes as $optional_class) { + if (!in_array($optional_class, $classes)) { + $classes[] = $optional_class; + } + } + $row->data['format_settings']['instance_settings']['classes'] = implode(" ", $classes); + $rows[] = $row; + } + foreach ($rows as $row) { + drupal_write_record('field_group', $row, array('id')); + } + +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,224 @@ + +(function($) { + +/** + * Drupal FieldGroup object. + */ +Drupal.FieldGroup = Drupal.FieldGroup || {}; +Drupal.FieldGroup.Effects = Drupal.FieldGroup.Effects || {}; +Drupal.FieldGroup.groupWithfocus = null; + +Drupal.FieldGroup.setGroupWithfocus = function(element) { + element.css({display: 'block'}); + Drupal.FieldGroup.groupWithfocus = element; +} + +/** + * Implements Drupal.FieldGroup.processHook(). + */ +Drupal.FieldGroup.Effects.processFieldset = { + execute: function (context, settings, type) { + if (type == 'form') { + // Add required fields mark to any fieldsets containing required fields + $('fieldset.fieldset', context).once('fieldgroup-effects', function(i) { + if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) { + $('legend span.fieldset-legend', $(this)).eq(0).append(' ').append($('.form-required').eq(0).clone()); + } + if ($('.error', $(this)).length) { + $('legend span.fieldset-legend', $(this)).eq(0).addClass('error'); + Drupal.FieldGroup.setGroupWithfocus($(this)); + } + }); + } + } +} + +/** + * Implements Drupal.FieldGroup.processHook(). + */ +Drupal.FieldGroup.Effects.processAccordion = { + execute: function (context, settings, type) { + $('div.field-group-accordion-wrapper', context).once('fieldgroup-effects', function () { + var wrapper = $(this); + + wrapper.accordion({ + autoHeight: false, + active: '.field-group-accordion-active', + collapsible: true, + changestart: function(event, ui) { + if ($(this).hasClass('effect-none')) { + ui.options.animated = false; + } + else { + ui.options.animated = 'slide'; + } + } + }); + + if (type == 'form') { + + var $firstErrorItem = false; + + // Add required fields mark to any element containing required fields + wrapper.find('div.field-group-accordion-item').each(function(i) { + + if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) { + $('h3.ui-accordion-header a').eq(i).append(' ').append($('.form-required').eq(0).clone()); + } + if ($('.error', $(this)).length) { + // Save first error item, for focussing it. + if (!$firstErrorItem) { + $firstErrorItem = $(this).parent().accordion("activate" , i); + } + $('h3.ui-accordion-header').eq(i).addClass('error'); + } + }); + + // Save first error item, for focussing it. + if (!$firstErrorItem) { + $('.ui-accordion-content-active', $firstErrorItem).css({height: 'auto', width: 'auto', display: 'block'}); + } + + } + }); + } +} + +/** + * Implements Drupal.FieldGroup.processHook(). + */ +Drupal.FieldGroup.Effects.processHtabs = { + execute: function (context, settings, type) { + if (type == 'form') { + // Add required fields mark to any element containing required fields + $('fieldset.horizontal-tabs-pane', context).once('fieldgroup-effects', function(i) { + if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) { + $(this).data('horizontalTab').link.find('strong:first').after($('.form-required').eq(0).clone()).after(' '); + } + if ($('.error', $(this)).length) { + $(this).data('horizontalTab').link.parent().addClass('error'); + Drupal.FieldGroup.setGroupWithfocus($(this)); + $(this).data('horizontalTab').focus(); + } + }); + } + } +} + +/** + * Implements Drupal.FieldGroup.processHook(). + */ +Drupal.FieldGroup.Effects.processTabs = { + execute: function (context, settings, type) { + if (type == 'form') { + // Add required fields mark to any fieldsets containing required fields + $('fieldset.vertical-tabs-pane', context).once('fieldgroup-effects', function(i) { + if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) { + $(this).data('verticalTab').link.find('strong:first').after($('.form-required').eq(0).clone()).after(' '); + } + if ($('.error', $(this)).length) { + $(this).data('verticalTab').link.parent().addClass('error'); + Drupal.FieldGroup.setGroupWithfocus($(this)); + $(this).data('verticalTab').focus(); + } + }); + } + } +} + +/** + * Implements Drupal.FieldGroup.processHook(). + * + * TODO clean this up meaning check if this is really + * necessary. + */ +Drupal.FieldGroup.Effects.processDiv = { + execute: function (context, settings, type) { + + $('div.collapsible', context).once('fieldgroup-effects', function() { + var $wrapper = $(this); + + // Turn the legend into a clickable link, but retain span.field-group-format-toggler + // for CSS positioning. + + var $toggler = $('span.field-group-format-toggler:first', $wrapper); + var $link = $(''); + $link.prepend($toggler.contents()); + + // Add required field markers if needed + if ($(this).is('.required-fields') && $(this).find('.form-required').length > 0) { + $link.append(' ').append($('.form-required').eq(0).clone()); + } + + $link.appendTo($toggler); + + // .wrapInner() does not retain bound events. + $link.click(function () { + var wrapper = $wrapper.get(0); + // Don't animate multiple times. + if (!wrapper.animating) { + wrapper.animating = true; + var speed = $wrapper.hasClass('speed-fast') ? 300 : 1000; + if ($wrapper.hasClass('effect-none') && $wrapper.hasClass('speed-none')) { + $('> .field-group-format-wrapper', wrapper).toggle(); + } + else if ($wrapper.hasClass('effect-blind')) { + $('> .field-group-format-wrapper', wrapper).toggle('blind', {}, speed); + } + else { + $('> .field-group-format-wrapper', wrapper).toggle(speed); + } + wrapper.animating = false; + } + $wrapper.toggleClass('collapsed'); + return false; + }); + + }); + } +}; + +/** + * Behaviors. + */ +Drupal.behaviors.fieldGroup = { + attach: function (context, settings) { + if (settings.field_group == undefined) { + return; + } + + // Execute all of them. + $.each(Drupal.FieldGroup.Effects, function (func) { + // We check for a wrapper function in Drupal.field_group as + // alternative for dynamic string function calls. + var type = func.toLowerCase().replace("process", ""); + if (settings.field_group[type] != undefined && $.isFunction(this.execute)) { + this.execute(context, settings, settings.field_group[type]); + } + }); + + // Fixes css for fieldgroups under vertical tabs. + $('.fieldset-wrapper .fieldset > legend').css({display: 'block'}); + $('.vertical-tabs fieldset.fieldset').addClass('default-fallback'); + + + // Add a new ID to each fieldset. + $('.group-wrapper fieldset').each(function() { + // Tats bad, but we have to keep the actual id to prevent layouts to break. + var fieldgorupID = 'field_group-' + $(this).attr('id') + ' ' + $(this).attr('id'); + $(this).attr('id', fieldgorupID); + }) + // Set the hash in url to remember last userselection. + $('.group-wrapper ul li').each(function() { + var fieldGroupNavigationListIndex = $(this).index(); + $(this).children('a').click(function() { + var fieldset = $('.group-wrapper fieldset').get(fieldGroupNavigationListIndex); + // Grab the first id, holding the wanted hashurl. + var hashUrl = $(fieldset).attr('id').replace(/^field_group-/, '').split(' ')[0]; + window.location.hash = hashUrl; + }); + }); + } +}; + +})(jQuery); \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.module --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.module Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,2126 @@ + $entity_info) { + if (isset($entity_info['fieldable']) && $entity_info['fieldable']) { + foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { + if (isset($bundle_info['admin'])) { + // Extract path information from the bundle. + $path = $bundle_info['admin']['path']; + // Different bundles can appear on the same path (e.g. %node_type and + // %comment_node_type). To allow field_group_menu_load() to extract the + // actual bundle object from the translated menu router path + // arguments, we need to identify the argument position of the bundle + // name string ('bundle argument') and pass that position to the menu + // loader. The position needs to be casted into a string; otherwise it + // would be replaced with the bundle name string. + if (isset($bundle_info['admin']['bundle argument'])) { + $bundle_arg = $bundle_info['admin']['bundle argument']; + $bundle_pos = (string) $bundle_arg; + } + else { + $bundle_arg = $bundle_name; + $bundle_pos = '0'; + } + + // This is the position of the %field_group_menu placeholder in the + // items below. + $group_position = count(explode('/', $path)) + 1; + + // Extract access information, providing defaults. + $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments'))); + $access += array( + 'access callback' => 'user_access', + 'access arguments' => array('administer site configuration'), + ); + + $items["$path/groups/%field_group_menu/delete"] = array( + 'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'), + 'title' => 'Delete', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_group_delete_form', $group_position), + 'type' => MENU_CALLBACK, + 'file' => 'field_group.field_ui.inc', + ) + $access; + + $items["$path/groups/%field_group_menu/enable"] = array( + 'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'), + 'title' => 'Enable', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('field_group_enable_form', $group_position), + 'type' => MENU_CALLBACK, + 'file' => 'field_group.field_ui.inc', + ) + $access; + + } + } + } + } + + return $items; +} + +/** + * Implements hook_permission(). + */ +function field_group_permission() { + return array( + 'administer fieldgroups' => array( + 'title' => t('Administer fieldgroups'), + 'description' => t('Display the administration for fieldgroups.'), + ), + ); +} + +/** + * Menu Wildcard loader function to load group definitions. + * + * @param $group_name + * The name of the group, as contained in the path. + * @param $entity_type + * The name of the entity. + * @param $bundle_name + * The name of the bundle, as contained in the path. + * @param $bundle_pos + * The position of $bundle_name in $map. + * @param $map + * The translated menu router path argument map. + */ +function field_group_menu_load($group_name, $entity_type, $bundle_name, $bundle_pos, $map) { + + if ($bundle_pos > 0) { + $bundle = $map[$bundle_pos]; + $bundle_name = field_extract_bundle($entity_type, $bundle); + } + + $args = func_get_args(); + $args_pop = array_pop($args); + $mode = array_pop($args_pop); + + $group = field_group_load_field_group($group_name, $entity_type, $bundle_name, $mode); + + return empty($group) ? FALSE : $group; +} + +/** + * Loads a group definition. + * + * @param $group_name + * The name of the group. + * @param $entity_type + * The name of the entity. + * @param $bundle_name + * The name of the bundle. + * @param $mode + * The view mode to load. + */ +function field_group_load_field_group($group_name, $entity_type, $bundle_name, $mode) { + + ctools_include('export'); + $objects = ctools_export_load_object('field_group', 'conditions', array( + 'group_name' => $group_name, + 'entity_type' => $entity_type, + 'bundle' => $bundle_name, + 'mode' => $mode, + )); + $object = array_shift($objects); + + if ($object && isset($object->data)) { + return field_group_unpack($object); + } + + return $object; +} + +/** + * Implements hook_ctools_plugin_api(). + */ +function field_group_ctools_plugin_api($owner, $api) { + if ($owner == 'field_group' && $api == 'field_group') { + return array('version' => 1); + } +} + +/** + * Implements hook_theme(). + */ +function field_group_theme() { + return array( + 'horizontal_tabs' => array( + 'render element' => 'element', + ), + 'multipage' => array( + 'render element' => 'element', + ), + 'multipage_pane' => array( + 'render element' => 'element', + ), + ); +} + +/** + * Implements hook_theme_registry_alter(). + */ +function field_group_theme_registry_alter(&$theme_registry) { + + // Inject field_group_build_entity_groups in all entity theming functions. + $entity_info = entity_get_info(); + $entities = array(); + foreach ($entity_info as $entity => $info) { + if (isset($entity_info[$entity]['fieldable']) && $entity_info[$entity]['fieldable']) { + // User uses user_profile for theming. + if ($entity == 'user') $entity = 'user_profile'; + $entities[] = $entity; + } + } + + // Support for File Entity. + if (isset($theme_registry['file_entity'])) { + $entities[] = 'file_entity'; + } + + // Support for Entity API. + if (isset($theme_registry['entity'])) { + $entities[] = 'entity'; + } + + foreach ($entities as $entity) { + if (isset($theme_registry[$entity])) { + $theme_registry[$entity]['preprocess functions'][] = 'field_group_build_entity_groups'; + // DS support, make sure it comes after field_group. + if ($key = array_search('ds_entity_variables', $theme_registry[$entity]['preprocess functions'])) { + unset($theme_registry[$entity]['preprocess functions'][$key]); + $theme_registry[$entity]['preprocess functions'][] = 'ds_entity_variables'; + } + } + } + +} + +/** + * Implements hook_field_attach_delete_bundle(). + * + * @param String $entity_type + * @param String $bundle + */ +function field_group_field_attach_delete_bundle($entity_type, $bundle) { + + ctools_include('export'); + $list = field_group_read_groups(array('bundle' => $bundle, 'entity_type' => $entity_type)); + + // Delete the entity's entry from field_group of all entities. + // We fetch the field groups first to assign the removal task to ctools. + if (isset($list[$entity_type], $list[$entity_type][$bundle])) { + foreach ($list[$entity_type][$bundle] as $group_mode => $groups) { + foreach ($groups as $group) { + ctools_export_crud_delete('field_group', $group); + } + } + } + +} + +/** + * Implements hook_field_attach_form(). + */ +function field_group_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) { + $form['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.field_ui.css'; + field_group_attach_groups($form, 'form', $form_state); + $form['#pre_render'][] = 'field_group_form_pre_render'; +} + +/** + * Implements hook_form_FORM_ID_alter(). + * Using hook_form_field_ui_field_overview_form_alter. + */ +function field_group_form_field_ui_field_overview_form_alter(&$form, &$form_state) { + form_load_include($form_state, 'inc', 'field_group', 'field_group.field_ui'); + field_group_field_ui_overview_form_alter($form, $form_state); +} + +/** + * Implements hook_form_FORM_ID_alter(). + * Using hook_form_field_ui_display_overview_form_alter. + */ +function field_group_form_field_ui_display_overview_form_alter(&$form, &$form_state) { + form_load_include($form_state, 'inc', 'field_group', 'field_group.field_ui'); + field_group_field_ui_overview_form_alter($form, $form_state, TRUE); +} + +/** + * Implements hook_field_attach_view_alter(). + */ +function field_group_field_attach_view_alter(&$element, $context) { + // Check whether the view mode uses custom display settings or the 'default' mode. + $actual_mode = 'default'; + if (isset($element['#entity_type']) && isset($element['#bundle'])) { + $view_mode_settings = field_view_mode_settings($element['#entity_type'], $element['#bundle']); + $view_mode = $context['view_mode']; + $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default'); + field_group_attach_groups($element, $actual_mode); + } +} + +/** + * Implements hook_field_group_formatter_info(). + */ +function field_group_field_group_formatter_info() { + + return array( + 'form' => array( + 'html-element' => array( + 'label' => t('HTML element'), + 'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'), + 'instance_settings' => array('element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1), + ), + 'div' => array( + 'label' => t('Div'), + 'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'), + 'format_types' => array('open', 'collapsible', 'collapsed'), + 'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => '', 'required_fields' => 1, 'id' => ''), + 'default_formatter' => 'open', + ), + 'html5' => array( + 'label' => t('HTML5'), + 'description' => t('This fieldgroup renders the inner content in a semantic HTML5 wrapper'), + 'instance_settings' => array('wrapper' => '', 'classes' => ''), + ), + 'fieldset' => array( + 'label' => t('Fieldset'), + 'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'), + 'format_types' => array('open', 'collapsible', 'collapsed'), + 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1), + 'default_formatter' => 'collapsible', + ), + 'tabs' => array( + 'label' => t('Vertical tabs group'), + 'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'), + 'instance_settings' => array('classes' => ''), + ), + 'tab' => array( + 'label' => t('Vertical tab'), + 'description' => t('This fieldgroup renders the content in a fieldset, part of vertical tabs group.'), + 'format_types' => array('open', 'closed'), + 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1), + 'default_formatter' => 'closed', + ), + 'htabs' => array( + 'label' => t('Horizontal tabs group'), + 'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'), + 'instance_settings' => array('classes' => ''), + ), + 'htab' => array( + 'label' => t('Horizontal tab'), + 'format_types' => array('open', 'closed'), + 'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'), + 'default_formatter' => 'closed', + 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1, 'id' => ''), + ), + 'multipage-group' => array( + 'label' => t('Multipage group'), + 'description' => t('This fieldgroup renders groups on separate pages.'), + 'instance_settings' => array('classes' => '', 'page_header' => 3, 'move_additional' => 1, 'page_counter' => 1, 'move_button' => 0), + ), + 'multipage' => array( + 'label' => t('Multipage'), + 'format_types' => array('start', 'no-start'), + 'description' => t('This fieldgroup renders the content in a page.'), + 'default_formatter' => 'no-start', + 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1), + ), + 'accordion' => array( + 'label' => t('Accordion group'), + 'description' => t('This fieldgroup renders child groups as jQuery accordion.'), + 'instance_settings' => array('effect' => 'none', 'classes' => ''), + ), + 'accordion-item' => array( + 'label' => t('Accordion item'), + 'format_types' => array('open', 'closed'), + 'description' => t('This fieldgroup renders the content in a div, part of accordion group.'), + 'default_formatter' => 'closed', + 'instance_settings' => array('description' => '', 'classes' => '', 'required_fields' => 1), + ), + ), + 'display' => array( + 'html-element' => array( + 'label' => t('HTML element'), + 'description' => t('This fieldgroup renders the inner content in a HTML element with classes and attributes.'), + 'instance_settings' => array('element' => 'div', 'classes' => '', 'attributes' => '', 'required_fields' => 1), + ), + 'div' => array( + 'label' => t('Div'), + 'description' => t('This fieldgroup renders the inner content in a simple div with the title as legend.'), + 'format_types' => array('open', 'collapsible', 'collapsed'), + 'instance_settings' => array('description' => '', 'show_label' => 1, 'label_element' => 'h3', 'effect' => 'none', 'speed' => 'fast', 'classes' => ''), + 'default_formatter' => 'collapsible', + ), + 'html5' => array( + 'label' => t('HTML5'), + 'description' => t('This fieldgroup renders the inner content in a semantic HTML5 wrapper'), + 'instance_settings' => array('wrapper' => '', 'classes' => ''), + ), + 'fieldset' => array( + 'label' => t('Fieldset'), + 'description' => t('This fieldgroup renders the inner content in a fieldset with the title as legend.'), + 'format_types' => array('open', 'collapsible', 'collapsed'), + 'instance_settings' => array('description' => '', 'classes' => ''), + 'default_formatter' => 'collapsible', + ), + 'tabs' => array( + 'label' => t('Vertical tabs group'), + 'description' => t('This fieldgroup renders child groups in its own vertical tabs wrapper.'), + 'instance_settings' => array('classes' => ''), + ), + 'tab' => array( + 'label' => t('Vertical tab'), + 'description' => t('This fieldgroup renders the content in a fieldset, part of vertical tabs group.'), + 'format_types' => array('open', 'closed'), + 'instance_settings' => array('description' => '', 'classes' => ''), + 'default_formatter' => 'closed', + ), + 'htabs' => array( + 'label' => t('Horizontal tabs group'), + 'description' => t('This fieldgroup renders child groups in its own horizontal tabs wrapper.'), + 'instance_settings' => array('classes' => ''), + ), + 'htab' => array( + 'label' => t('Horizontal tab item'), + 'format_types' => array('open', 'closed'), + 'description' => t('This fieldgroup renders the content in a fieldset, part of horizontal tabs group.'), + 'instance_settings' => array('description' => '', 'classes' => '', 'id' => ''), + 'default_formatter' => 'closed', + ), + 'accordion' => array( + 'label' => t('Accordion group'), + 'description' => t('This fieldgroup renders child groups as jQuery accordion.'), + 'instance_settings' => array('description' => '', 'classes' => '', 'effect' => 'bounceslide'), + ), + 'accordion-item' => array( + 'label' => t('Accordion item'), + 'format_types' => array('open', 'closed'), + 'description' => t('This fieldgroup renders the content in a div, part of accordion group.'), + 'instance_settings' => array('classes' => ''), + 'default_formatter' => 'closed', + ), + ), + ); +} + +/** + * Implements hook_field_group_format_settings(). + * If the group has no format settings, default ones will be added. + * @params Object $group The group object. + * @return Array $form The form element for the format settings. + */ +function field_group_field_group_format_settings($group) { + // Add a wrapper for extra settings to use by others. + $form = array( + 'instance_settings' => array( + '#tree' => TRUE, + '#weight' => 2, + ), + ); + + $field_group_types = field_group_formatter_info(); + $mode = $group->mode == 'form' ? 'form' : 'display'; + $formatter = $field_group_types[$mode][$group->format_type]; + + // Add the required formatter type selector. + if (isset($formatter['format_types'])) { + $form['formatter'] = array( + '#title' => t('Fieldgroup settings'), + '#type' => 'select', + '#options' => drupal_map_assoc($formatter['format_types']), + '#default_value' => isset($group->format_settings['formatter']) ? $group->format_settings['formatter'] : $formatter['default_formatter'], + '#weight' => 1, + ); + } + + if (isset($formatter['instance_settings']['required_fields']) && $mode == 'form') { + $form['instance_settings']['required_fields'] = array( + '#type' => 'checkbox', + '#title' => t('Mark group for required fields.'), + '#default_value' => isset($group->format_settings['instance_settings']['required_fields']) ? $group->format_settings['instance_settings']['required_fields'] : (isset($formatter['instance_settings']['required_fields']) ? $formatter['instance_settings']['required_fields'] : ''), + '#weight' => 2, + ); + } + + if (isset($formatter['instance_settings']['id'])) { + $form['instance_settings']['id'] = array( + '#title' => t('ID'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['id']) ? $group->format_settings['instance_settings']['id'] : (isset($formatter['instance_settings']['id']) ? $formatter['instance_settings']['id'] : ''), + '#weight' => 3, + '#element_validate' => array('field_group_validate_id'), + ); + } + if (isset($formatter['instance_settings']['classes'])) { + $form['instance_settings']['classes'] = array( + '#title' => t('Extra CSS classes'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['classes']) ? $group->format_settings['instance_settings']['classes'] : (isset($formatter['instance_settings']['classes']) ? $formatter['instance_settings']['classes'] : ''), + '#weight' => 4, + '#element_validate' => array('field_group_validate_css_class'), + ); + } + + if (isset($formatter['instance_settings']['description'])) { + $form['instance_settings']['description'] = array( + '#title' => t('Description'), + '#type' => 'textarea', + '#default_value' => isset($group->format_settings['instance_settings']['description']) ? $group->format_settings['instance_settings']['description'] : (isset($formatter['instance_settings']['description']) ? $formatter['instance_settings']['description'] : ''), + '#weight' => 0, + ); + } + + // Add optional instance_settings. + switch ($group->format_type) { + case 'html-element': + $form['instance_settings']['element'] = array( + '#title' => t('Element'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['element']) ? $group->format_settings['instance_settings']['element'] : $formatter['instance_settings']['element'], + '#description' => t('E.g. div, section, aside etc.'), + '#weight' => 2, + ); + + $form['instance_settings']['attributes'] = array( + '#title' => t('Attributes'), + '#type' => 'textfield', + '#default_value' => isset($group->format_settings['instance_settings']['attributes']) ? $group->format_settings['instance_settings']['attributes'] : $formatter['instance_settings']['attributes'], + '#description' => t('E.g. name="anchor"'), + '#weight' => 4, + ); + break; + case 'div': + $form['instance_settings']['show_label'] = array( + '#title' => t('Show label'), + '#type' => 'select', + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#default_value' => isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : $formatter['instance_settings']['show_label'], + '#weight' => 2, + ); + $form['instance_settings']['label_element'] = array( + '#title' => t('Label element'), + '#type' => 'select', + '#options' => array('h2' => t('Header 2'), 'h3' => t('Header 3')), + '#default_value' => isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : $formatter['instance_settings']['label_element'], + '#weight' => 2, + ); + $form['instance_settings']['effect'] = array( + '#title' => t('Effect'), + '#type' => 'select', + '#options' => array('none' => t('None'), 'blind' => t('Blind')), + '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'], + '#weight' => 2, + ); + $form['instance_settings']['speed'] = array( + '#title' => t('Speed'), + '#type' => 'select', + '#options' => array('none' => t('None'), 'slow' => t('Slow'), 'fast' => t('Fast')), + '#default_value' => isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : $formatter['instance_settings']['speed'], + '#weight' => 3, + ); + break; + case 'html5': + $form['instance_settings']['wrapper'] = array( + '#title' => t('HTML5 wrapper'), + '#type' => 'select', + '#options' => array('section' => t('Section'), 'article' => t('Article'), 'header' => t('Header'), 'footer' => t('Footer'), 'aside' => t('Aside')), + '#default_value' => isset($group->format_settings['instance_settings']['wrapper']) ? $group->format_settings['instance_settings']['wrapper'] : 'section', + ); + break; + case 'fieldset': + break; + case 'multipage-group': + $form['instance_settings']['page_header'] = array( + '#title' => t('Format page title'), + '#type' => 'select', + '#options' => array(0 => t('None'), 1 => t('Label only'), 2 => t('Step 1 of 10'), 3 => t('Step 1 of 10 [Label]'),), + '#default_value' => isset($group->format_settings['instance_settings']['page_header']) ? $group->format_settings['instance_settings']['page_header'] : $formatter['instance_settings']['page_header'], + '#weight' => 1, + ); + $form['instance_settings']['page_counter'] = array( + '#title' => t('Add a page counter at the bottom'), + '#type' => 'select', + '#options' => array(0 => t('No'), 1 => t('Format 1 / 10'), 2 => t('The count number only')), + '#default_value' => isset($group->format_settings['instance_settings']['page_counter']) ? $group->format_settings['instance_settings']['page_counter'] : $formatter['instance_settings']['page_counter'], + '#weight' => 2, + ); + $form['instance_settings']['move_button'] = array( + '#title' => t('Move submit button to last multipage'), + '#type' => 'select', + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#default_value' => isset($group->format_settings['instance_settings']['move_button']) ? $group->format_settings['instance_settings']['move_button'] : $formatter['instance_settings']['move_button'], + '#weight' => 3, + ); + $form['instance_settings']['move_additional'] = array( + '#title' => t('Move additional settings to last multipage (if available)'), + '#type' => 'select', + '#options' => array(0 => t('No'), 1 => t('Yes')), + '#default_value' => isset($group->format_settings['instance_settings']['move_additional']) ? $group->format_settings['instance_settings']['move_additional'] : $formatter['instance_settings']['move_additional'], + '#weight' => 4, + ); + case 'tabs': + case 'htabs': + break; + case 'accordion': + $form['instance_settings']['effect'] = array( + '#title' => t('Effect'), + '#type' => 'select', + '#options' => array('none' => t('None'), 'bounceslide' => t('Bounce slide')), + '#default_value' => isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : $formatter['instance_settings']['effect'], + '#weight' => 2, + ); + break; + case 'multipage': + break; + case 'tab': + case 'htab': + case 'accordion-item': + default: + } + + return $form; +} + +/** + * Helper function to prepare basic variables needed for most formatters. + * + * Called in field_group_field_group_pre_render(), but can also be called in + * other implementations of hook_field_group_pre_render(). + */ +function field_group_pre_render_prepare(&$group) { + + $classes = _field_group_get_html_classes($group); + + $group->classes = implode(' ', $classes->required); + $group->description = isset($group->format_settings['instance_settings']['description']) ? filter_xss_admin(t($group->format_settings['instance_settings']['description'])) : ''; + +} + +/** + * Implements hook_field_group_pre_render(). + * + * @param Array $elements by address. + * @param Object $group The Field group info. + */ +function field_group_field_group_pre_render(& $element, &$group, & $form) { + + field_group_pre_render_prepare($group); + + $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form'; + + // Add all field_group format types to the js settings. + $form['#attached']['js'][] = array( + 'data' => array('field_group' => array($group->format_type => $view_mode)), + 'type' => 'setting', + ); + $form['#attached']['js'][] = 'misc/form.js'; + $form['#attached']['js'][] = 'misc/collapse.js'; + + if (isset($group->format_settings['instance_settings']['id']) && !empty($group->format_settings['instance_settings']['id'])) { + $element['#id'] = $group->format_settings['instance_settings']['id']; + } + else { + $element['#id'] = $form['#entity_type'] . '_' . $form['#bundle'] . '_' . $view_mode . '_' . $group->group_name; + } + + $element['#weight'] = $group->weight; + + // Call the pre render function for the format type. + $function = "field_group_pre_render_" . str_replace("-", "_", $group->format_type); + if (function_exists($function)) { + $function($element, $group, $form); + } + +} + +/** + * Implements field_group_pre_render_. + * Format type: Fieldset. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_fieldset(&$element, $group, &$form) { + + $element += array( + '#type' => 'fieldset', + '#title' => check_plain(t($group->label)), + '#collapsible' => $group->collapsible, + '#collapsed' => $group->collapsed, + '#pre_render' => array(), + '#attributes' => array('class' => explode(' ', $group->classes)), + '#description' => $group->description, + ); + $element['#attached']['js'][] = 'misc/form.js'; + $element['#attached']['js'][] = 'misc/collapse.js'; + +} + +/** + * Implements field_group_pre_render_. + * Format type: HTML element. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_html_element(&$element, $group, &$form) { + $html_element = isset($group->format_settings['instance_settings']['element']) ? $group->format_settings['instance_settings']['element'] : 'div'; + $attributes = isset($group->format_settings['instance_settings']['attributes']) ? ' ' . $group->format_settings['instance_settings']['attributes'] : ''; + $group->classes = trim($group->classes); + + // This regex split the attributes string so that we can pass that + // later to drupal_attributes(). + preg_match_all('/([^\s=]+)="([^"]+)"/', $attributes, $matches); + + $element_attributes = array(); + // Put the attribute and the value together. + foreach ($matches[1] as $key => $attribute) { + $element_attributes[$attribute] = $matches[2][$key]; + } + + // Add the classes to the attributes array. + if (!isset($element_attributes['class']) && $group->classes) { + $element_attributes['class'] = $group->classes; + } + elseif (isset($element_attributes['class']) && $group->classes) { + $element_attributes['class'] .= ' ' . $group->classes; + } + + $attributes = drupal_attributes($element_attributes); + + $element['#prefix'] = '<' . $html_element . $attributes . '>'; + $element['#suffix'] = ''; +} + +/** + * Implements field_group_pre_render_. + * Format type: Div. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_div(&$element, $group, &$form) { + + $show_label = isset($group->format_settings['instance_settings']['show_label']) ? $group->format_settings['instance_settings']['show_label'] : 0; + $label_element = isset($group->format_settings['instance_settings']['label_element']) ? $group->format_settings['instance_settings']['label_element'] : 'h2'; + $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none'; + + $element['#type'] = 'markup'; + if ($group->format_settings['formatter'] != 'open') { + $element['#prefix'] = '
        + <' . $label_element . '>' . check_plain(t($group->label)) . ' +
        '; + $element['#suffix'] = '
        '; + } + else { + $class_attribute = ''; + if (!empty($group->classes)) { + $class_attribute = 'class = "' . $group->classes . '"'; + } + $element['#prefix'] = '
        '; + if ($show_label) { + $element['#prefix'] .= '<' . $label_element . '>' . check_plain(t($group->label)) . ''; + } + $element['#suffix'] = '
        '; + } + if (!empty($group->description)) { + $element['#prefix'] .= '
        ' . $group->description . '
        '; + } + + if ($effect == 'blind') { + drupal_add_library('system', 'effects.blind'); + } + +} + +/** + * Implements field_group_pre_render_. + * Format type: HTML5. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_html5(&$element, $group, &$form) { + $element += array( + '#type' => 'markup', + '#prefix' => '<' . $group->format_settings['instance_settings']['wrapper'] . ' id="' . $element['#id'] . '" class="' . $group->classes . '">', + '#suffix' => 'format_settings['instance_settings']['wrapper'] . '>', + ); +} + +/** + * Implements field_group_pre_render_. + * Format type: Accordion. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_accordion(&$element, $group, &$form) { + + // Add the jQuery UI accordion. + drupal_add_library('system', 'ui.accordion'); + + $element += array( + '#type' => 'markup', + '#prefix' => '
        ', + '#suffix' => '
        ', + ); +} + +/** + * Implements field_group_pre_render_. + * Format type: Accordion item. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_accordion_item(&$element, $group, &$form) { + + $element += array( + '#type' => 'markup', + '#prefix' => '

        ' . check_plain(t($group->label)) . '

        +
        ', + '#suffix' => '
        ', + //'#attributes' => array('class' => array($group->format_type)), + ); + if (!empty($group->description)) { + $element['#prefix'] .= '
        ' . $group->description . '
        '; + } + drupal_add_library('system', 'ui.accordion'); + +} + +/** + * Implements field_group_pre_render_. + * Format type: Horizontal tabs group. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_htabs(&$element, $group, &$form) { + + $element += array( + '#type' => 'horizontal_tabs', + '#title' => check_plain(t($group->label)), + '#theme_wrappers' => array('horizontal_tabs'), + '#prefix' => '
        ', + '#suffix' => '
        ', + ); + + // By default vertical_tabs don't have titles but you can override it in the theme. + if (!empty($group->label)) { + $element['#title'] = check_plain($group->label); + } + +} + +/** + * Implements field_group_pre_render_. + * Format type: Horizontal tab. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_htab(&$element, $group, &$form) { + + $element += array( + '#type' => 'fieldset', + '#title' => check_plain(t($group->label)), + '#collapsible' => $group->collapsible, + '#collapsed' => $group->collapsed, + '#attributes' => array('class' => explode(" ", $group->classes)), + '#group' => $group->parent_name, + // very important. Cannot be added on the form! + '#parents' => array($group->parent_name), + '#description' => $group->description, + ); + +} + +/** + * Implements field_group_pre_render_. + * Format type: Multipage group. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_multipage_group(&$element, &$group, &$form) { + + $multipage_element = array( + '#type' => 'multipage', + '#theme_wrappers' => array('multipage'), + '#prefix' => '
        ', + '#suffix' => '
        ', + ); + + $element += $multipage_element; + + $move_additional = isset($group->format_settings['instance_settings']['move_additional']) ? ($group->format_settings['instance_settings']['move_additional'] && isset($form['additional_settings'])) : isset($form['additional_settings']); + $move_button = isset($group->format_settings['instance_settings']['move_button']) ? $group->format_settings['instance_settings']['move_button'] : 0; + + drupal_add_js(array( + 'field_group' => array( + 'multipage_move_submit' => (bool) $move_button, + 'multipage_move_additional' => (bool) $move_additional + ) + ), 'setting'); + +} + +/** + * Implements field_group_pre_render_. + * Format type: Multipage. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_multipage(&$element, $group, &$form) { + + $group->classes .= $group->format_settings['formatter'] == 'start' ? ' multipage-open' : ' multipage-closed'; + $element += array( + '#type' => 'multipage_pane', + '#title' => check_plain(t($group->label)), + '#collapsible' => $group->collapsible, + '#collapsed' => $group->collapsed, + '#attributes' => array('class' => explode(" ", $group->classes)), + '#group' => $group->parent_name, + '#group_object' => $group, + '#parent_group_object' => $form['#groups'][$group->parent_name], + // very important. Cannot be added on the form! + '#parents' => array($group->parent_name), + '#description' => $group->description, + ); + +} + +/** + * Implements field_group_pre_render_. + * Format type: Vertical tabs wrapper. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_tabs(&$element, $group, &$form) { + + $element += array( + '#type' => 'vertical_tabs', + '#theme_wrappers' => array('vertical_tabs'), + '#prefix' => '
        ', + '#suffix' => '
        ', + ); + + // By default vertical_tabs don't have titles but you can override it in the theme. + if (!empty($group->label)) { + $element['#title'] = check_plain($group->label); + } + + $element[$group->group_name . '__active_tab'] = array( + '#type' => 'hidden', + '#default_value' => '', + '#attributes' => array('class' => array('vertical-tabs-active-tab')), + ); + $form['#attached']['library'][] = array('system', 'drupal.form'); + $form['#attached']['library'][] = array('system', 'drupal.collapse'); + +} + +/** + * Implements field_group_pre_render_. + * Format type: Vertical tab. + * + * @param $element The field group form element. + * @param $group The Field group object prepared for pre_render. + * @param $form The root element or form. + */ +function field_group_pre_render_tab(&$element, $group, &$form) { + + $view_mode = isset($form['#view_mode']) ? $form['#view_mode'] : 'form'; + + // Could be it never runs through htab. + $form['#attached']['js'][] = array( + 'data' => array('field_group' => array('tabs' => $view_mode)), + 'type' => 'setting', + ); + + $add = array( + '#type' => 'fieldset', + '#id' => 'edit-' . $group->group_name, + '#title' => check_plain(t($group->label)), + '#collapsible' => $group->collapsible, + '#collapsed' => $group->collapsed, + '#attributes' => array('class' => explode(" ", $group->classes)), + '#description' => $group->description, + ); + + // Front-end and back-end on configuration will lead + // to vertical tabs nested in a separate vertical group. + if ($view_mode != 'form') { + $add['#group'] = empty($group->parent_name) ? 'additional_settings' : $group->parent_name; + $add['#parents'] = array($add['#group']); + $element += $add; + } + // Form fieldgroups which are nested into a vertical tab group + // are handled a little different. + elseif (!empty($group->parent_name)) { + $add['#group'] = $group->parent_name; + $element += $add; + } + // Forms "can" have additional settins. We'll try to locate it first, if not + // exists, field_group will create a parallel additional settings entry. + else { + // Create the fieldgroup element. + $add['#parents'] = array('additional_settings'); + $add['#group'] = 'additional_settings'; + $add['#weight'] = -30 + $group->weight; // hardcoded to bring our extra additional vtabs on top. + + // Check if the additional_settings exist for this type of form. + if (isset($form['additional_settings']['group']['#groups']['additional_settings'])) { + + // Merge fieldgroups with the core additional settings. + $form['additional_settings']['group']['#groups']['additional_settings'][$group->group_name] = $add; + $form['additional_settings']['group']['#groups'][$group->group_name] = array('#group_exists' => TRUE); + // Nest the fields inside the appropriate structure. + foreach (element_children($element) as $fieldname) { + $form['additional_settings']['group']['#groups']['additional_settings'][$group->group_name][$fieldname] = &$element[$fieldname]; + unset($element[$fieldname]); + } + } + // Assumption the wrapper is in the root. This could be done by field_group itself + // in previous loop of tabs in same wrapper or even some other contrib / custom module. + else { + if (!isset($form['additional_settings']['#type'])) { + $form['additional_settings'] = array( + '#type' => 'vertical_tabs', + '#weight' => $group->weight, + '#theme_wrappers' => array('vertical_tabs'), + '#prefix' => '
        ', + '#suffix' => '
        ', + ); + $form['#attached']['js'][] = 'misc/form.js'; + $form['#attached']['js'][] = 'misc/collapse.js'; + } + $form['additional_settings'][$group->group_name] = $add; + // Nest the fields inside the appropriate structure. + foreach (element_children($element) as $fieldname) { + $form['additional_settings'][$group->group_name][$fieldname] = &$element[$fieldname]; + unset($element[$fieldname]); + } + } + } + +} + +/** + * Implements hook_field_group_build_pre_render_alter(). + * @param Array $elements by address. + */ +function field_group_field_group_build_pre_render_alter(& $element) { + + // Someone is doing a node view, in a node view. Reset content. + // TODO Check if this breaks something else. + if (isset($element['#node']->content) && count($element['#node']->content) > 0) { + $element['#node']->content = array(); + } + + $display = isset($element['#view_mode']); + $groups = array_keys($element['#groups']); + + // Dish the fieldgroups with no fields for non-forms. + if ($display) { + field_group_remove_empty_display_groups($element, $groups); + } + else { + // Fix the problem on forms with additional settings. + field_group_remove_empty_form_groups('form', $element, $groups, $element['#groups'], $element['#entity_type']); + } + + // Add the default field_group javascript and stylesheet. + $element['#attached']['js'][] = drupal_get_path('module', 'field_group') . '/field_group.js'; + $element['#attached']['css'][] = drupal_get_path('module', 'field_group') . '/field_group.css'; + + // Move additional settings to the last multipage pane if configured that way. + // Note that multipages MUST be in the root of the form. + foreach (element_children($element) as $name) { + if (isset($element[$name]['#type']) && $element[$name]['#type'] == 'multipage' && isset($element['additional_settings'])) { + $parent_group = $element['#groups'][$name]; + $move_additional = isset($parent_group->format_settings['instance_settings']['move_additional']) ? $parent_group->format_settings['instance_settings']['move_additional'] : 1; + $last_pane = NULL; + foreach (element_children($element[$name], TRUE) as $pane) { + $last_pane = $pane; + } + $element[$name][$last_pane]['additional_settings'] = $element['additional_settings']; + unset($element['additional_settings']); + } + } + +} + +/** + * Remove empty groups on forms. + * + * @param String $parent_name + * The name of the element. + * @param array $element + * The element to check the empty state. + * @param array $groups + * Array of group objects. + */ +function field_group_remove_empty_form_groups($name, & $element, $groups, &$form_groups, $entity) { + + $exceptions = array('user__account', 'comment__author'); + + $children = element_children($element); + + $hasChildren = FALSE; + if (count($children)) { + foreach ($children as $childname) { + if (in_array($childname, $groups)) { + field_group_remove_empty_form_groups($childname, $element[$childname], $groups, $form_groups, $entity); + } + $exception = $entity . '__' . $childname; + $hasChildren = $hasChildren ? TRUE : (isset($element[$childname]['#type']) || in_array($exception, $exceptions)); + } + } + + if (!$hasChildren) { + + // Remove empty elements from the #groups. + if (empty($element) && isset($form_groups[$name]) && !is_array($form_groups[$name])) { + foreach ($form_groups as $group_name => $group) { + if (isset($group->children)) { + $group_children = array_flip($group->children); + if (isset($group_children[$name])) { + unset($form_groups[$group_name]->children[$group_children[$name]]); + } + } + } + } + + $element['#access'] = FALSE; + + } + +} + +/** + * Remove empty groups on entity display. + * @param array $element + * The element to check the empty state. + * @param array $groups + * Array of group objects. + */ +function field_group_remove_empty_display_groups(& $element, $groups) { + + $empty_child = TRUE; + $empty_group = TRUE; + + // Loop through the children for current element. + foreach (element_children($element) as $name) { + + // Descend if the child is a group. + if (in_array($name, $groups)) { + $empty_child = field_group_remove_empty_display_groups($element[$name], $groups); + if (!$empty_child) { + $empty_group = FALSE; + } + } + // Child is a field, the element is not empty. + elseif (!empty($element[$name])) { + $empty_group = FALSE; + } + + } + + // Reset an empty group. + if ($empty_group) { + $element = NULL; + } + + return $empty_group; + +} + +/** + * Implements hook_field_group_format_summary(). + */ +function field_group_field_group_format_summary($group) { + + $group_form = module_invoke_all('field_group_format_settings', $group); + + $output = ''; + if (isset($group->format_settings['formatter'])) { + $output .= '' . $group->format_type . ' ' . $group->format_settings['formatter'] . ''; + } + if (isset($group->format_settings['instance_settings'])) { + $last = end($group->format_settings['instance_settings']); + $output .= '
        '; + foreach ($group->format_settings['instance_settings'] as $key => $value) { + if (empty($value)) { + continue; + } + + $output .= '' . $key . ' '; + + if (isset($group_form['instance_settings'], $group_form['instance_settings'][$key]['#options'])) { + if (is_array($value)) { + $value = implode(array_filter($value), ', '); + } + else { + $value = $group_form['instance_settings'][$key]['#options'][$value]; + } + } + + // Shorten the string. + if (drupal_strlen($value) > 38) { + $value = truncate_utf8($value, 50, TRUE, TRUE); + } + // If still numeric, handle it as yes or no. + elseif (is_numeric($value)) { + $value = $value == '1' ? t('yes') : t('no'); + } + $output .= check_plain($value); + $output .= $last == $value ? ' ' : '
        '; + } + } + return $output; +} + +/** + * Implements hook_element_info(). + */ +function field_group_element_info() { + $types['horizontal_tabs'] = array( + '#theme_wrappers' => array('horizontal_tabs'), + '#default_tab' => '', + '#process' => array('form_process_horizontal_tabs'), + ); + $types['multipage'] = array( + '#theme_wrappers' => array('multipage'), + '#default_tab' => '', + '#process' => array('form_process_multipage'), + ); + $types['multipage_pane'] = array( + '#value' => NULL, + '#process' => array('form_process_fieldset', 'ajax_process_form'), + '#pre_render' => array('form_pre_render_fieldset'), + '#theme_wrappers' => array('multipage_pane'), + ); + return $types; +} + +/** + * Implements hook_library(). + */ +function field_group_library() { + + $path = drupal_get_path('module', 'field_group'); + // Horizontal Tabs. + $libraries['horizontal-tabs'] = array( + 'title' => 'Horizontal Tabs', + 'website' => 'http://drupal.org/node/323112', + 'version' => '1.0', + 'js' => array( + $path . '/horizontal-tabs/horizontal-tabs.js' => array(), + ), + 'css' => array( + $path . '/horizontal-tabs/horizontal-tabs.css' => array(), + ), + ); + // Multipage Tabs. + $libraries['multipage'] = array( + 'title' => 'Multipage', + 'website' => 'http://drupal.org/node/323112', + 'version' => '1.0', + 'js' => array( + $path . '/multipage/multipage.js' => array(), + ), + 'css' => array( + $path . '/multipage/multipage.css' => array(), + ), + ); + + return $libraries; +} + +/** + * Implements hook_field_extra_fields(). + */ +function field_group_field_extra_fields() { + $extra = array(); + + $extra['user']['user'] = array('form' => array()); + + // User picture field to integrate with user module. + if (variable_get('user_pictures', 0)) { + $extra['user']['user']['form']['picture'] = array( + 'label' => t('Picture'), + 'description' => t('User picture'), + 'weight' => 5, + ); + } + + // Field to itegrate with overlay module. + if (module_exists('overlay')) { + $extra['user']['user']['form']['overlay_control'] = array( + 'label' => t('Administrative overlay'), + 'description' => t('Administrative overlay'), + 'weight' => 5, + ); + } + + // Field to itegrate with contact module. + if (module_exists('contact')) { + $extra['user']['user']['form']['contact'] = array( + 'label' => t('Contact'), + 'description' => t('Contact user element'), + 'weight' => 5, + ); + } + + // Field to integrate with the locale module. + if (module_exists('locale')) { + $extra['user']['user']['form']['locale'] = array( + 'label' => t('Language settings'), + 'description' => t('Language settings for the user account.'), + 'weight' => 5, + ); + } + + // Field to integrate with the wysiwyg module on user settings. + if (module_exists('wysiwyg')) { + $extra['user']['user']['form']['wysiwyg'] = array( + 'label' => t('Wysiwyg status'), + 'description' => t('Text formats enabled for rich-text editing'), + 'weight' => 5, + ); + } + + return $extra; +} + +/** + * Implements hook_field_attach_rename_bundle(). + */ +function field_group_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { + db_query('UPDATE {field_group} SET bundle = :bundle WHERE bundle = :old_bundle AND entity_type = :entity_type', array( + ':bundle' => $bundle_new, + ':old_bundle' => $bundle_old, + ':entity_type' => $entity_type, + )); +} + +/** + * Creates a group formatted as horizontal tabs. + * This function will never be callable from within field_group rendering. Other + * modules using #type horizontal_tabs will have the benefit of this processor. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + * @param $form_state + * The $form_state array for the form this horizontal tab widget belongs to. + * @return + * The processed element. + */ +function form_process_horizontal_tabs($element, &$form_state) { + // Inject a new fieldset as child, so that form_process_fieldset() processes + // this fieldset like any other fieldset. + $element['group'] = array( + '#type' => 'fieldset', + '#theme_wrappers' => array(), + '#parents' => $element['#parents'], + ); + + // The JavaScript stores the currently selected tab in this hidden + // field so that the active tab can be restored the next time the + // form is rendered, e.g. on preview pages or when form validation + // fails. + $name = implode('__', $element['#parents']); + if (isset($form_state['values'][$name . '__active_tab'])) { + $element['#default_tab'] = $form_state['values'][$name . '__active_tab']; + } + $element[$name . '__active_tab'] = array( + '#type' => 'hidden', + '#default_value' => $element['#default_tab'], + '#attributes' => array('class' => array('horizontal-tabs-active-tab')), + ); + + return $element; +} + +/** + * Returns HTML for an element's children fieldsets as horizontal tabs. + * + * @param $variables + * An associative array containing: + * - element: An associative array containing the properties and children of the + * fieldset. Properties used: #children. + * + * @ingroup themeable + */ +function theme_horizontal_tabs($variables) { + $element = $variables['element']; + // Add required JavaScript and Stylesheet. + drupal_add_library('field_group', 'horizontal-tabs'); + + $output = '

        ' . (!empty($element['#title']) ? $element['#title'] : t('Horizontal Tabs')) . '

        '; + $output .= '
        ' . $element['#children'] . '
        '; + + return $output; +} + +/** + * Creates a group formatted as multipage. + * This function will never be callable from within field_group rendering. Other + * modules using #type multipage will have the benefit of this processor. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + * @param $form_state + * The $form_state array for the form this multipage tab widget belongs to. + * @return + * The processed element. + */ +function form_process_multipage($element, &$form_state) { + // Inject a new fieldset as child, so that form_process_fieldset() processes + // this fieldset like any other fieldset. + $element['group'] = array( + '#type' => 'fieldset', + '#theme_wrappers' => array(), + '#parents' => $element['#parents'], + ); + + // The JavaScript stores the currently selected tab in this hidden + // field so that the active control can be restored the next time the + // form is rendered, e.g. on preview pages or when form validation + // fails. + $name = implode('__', $element['#parents']); + if (isset($form_state['values'][$name . '__active_control'])) { + $element['#default_tab'] = $form_state['values'][$name . '__active_control']; + } + $element[$name . '__active_control'] = array( + '#type' => 'hidden', + '#default_value' => $element['#default_control'], + '#attributes' => array('class' => array('multipage-active-control')), + ); + + return $element; +} + +/** + * Returns HTML for an element's children fieldsets as multipage. + * + * @param $variables + * An associative array containing: + * - element: An associative array containing the properties and children of the + * fieldset. Properties used: #children. + * + * @ingroup themeable + */ +function theme_multipage($variables) { + $element = $variables['element']; + // Add required JavaScript and Stylesheet. + drupal_add_library('field_group', 'multipage'); + + $output = '

        ' . (!empty($element['#title']) ? $element['#title'] : t('Multipage')) . '

        '; + + $output .= '
        '; + $output .= $element['#children']; + $output .= '
        '; + + return $output; +} + +/** + * Returns HTML for multipage pane. + * + * @param $variables + * An associative array containing: + * - element: An associative array containing the properties and children of the + * fieldset. Properties used: #children. + * + * @ingroup themeable + */ +function theme_multipage_pane($variables) { + + $element = $variables['element']; + $group = $variables['element']['#group_object']; + $parent_group = $variables['element']['#parent_group_object']; + + static $multipages; + if (!isset($multipages[$group->parent_name])) { + $multipages = array($group->parent_name => 0); + } + $multipages[$parent_group->group_name]++; + + // Create a page title from the label. + $page_header = isset($parent_group->format_settings['instance_settings']['page_header']) ? $parent_group->format_settings['instance_settings']['page_header'] : 3; + switch ($page_header) { + case 1: + $title = $element['#title']; + break; + case 2: + $title = t('Step %count of %total', array('%count' => $multipages[$parent_group->group_name], '%total' => count($parent_group->children))); + break; + case 3: + $title = t('Step %count of %total !label', array('%count' => $multipages[$parent_group->group_name], '%total' => count($parent_group->children), '!label' => $element['#title'])); + break; + case 0: + default: + $title = ''; + break; + } + + element_set_attributes($element, array('id')); + _form_set_class($element, array('form-wrapper')); + + $output = ''; + if (!empty($element['#title'])) { + // Always wrap fieldset legends in a SPAN for CSS positioning. + $output .= '

        ' . $title . '

        '; + } + $output .= '
        '; + if (!empty($element['#description'])) { + $output .= '
        ' . $element['#description'] . '
        '; + } + $output .= $element['#children']; + if (isset($element['#value'])) { + $output .= $element['#value']; + } + + // Add a page counter if needed. + // counter array(0 => t('No'), 1 => t('Format 1 / 10'), 2 => t('The count number only')); + $page_counter_format = isset($parent_group->format_settings['instance_settings']['page_counter']) ? $parent_group->format_settings['instance_settings']['page_counter'] : 1; + $multipage_element['#page_counter_rendered'] = ''; + if ($page_counter_format == 1) { + $output .= t('%count / %total', array('%count' => $multipages[$parent_group->group_name], '%total' => count($parent_group->children))); + } + elseif ($page_counter_format == 2) { + $output .= t('%count', array('%count' => $multipages[$parent_group->group_name])); + } + + $output .= '
        '; + $output .= "\n"; + + return $output; + +} + +/** + * Get all groups. + * + * @param $entity_type + * The name of the entity. + * @param $bundle + * The name of the bundle. + * @param $view_mode + * The view mode. + * @param $reset. + * Whether to reset the cache or not. + */ +function field_group_info_groups($entity_type = NULL, $bundle = NULL, $view_mode = NULL, $reset = FALSE) { + static $groups = FALSE; + + if (!$groups || $reset) { + if (!$reset && $cached = cache_get('field_groups', 'cache_field')) { + $groups = $cached->data; + } + else { + drupal_static_reset('ctools_export_load_object'); + drupal_static_reset('ctools_export_load_object_all'); + $groups = field_group_read_groups(); + cache_set('field_groups', $groups, 'cache_field'); + } + } + + if (!isset($entity_type)) { + return $groups; + } + elseif (!isset($bundle) && isset($groups[$entity_type])) { + return $groups[$entity_type]; + } + elseif (!isset($view_mode) && isset($groups[$entity_type][$bundle])) { + return $groups[$entity_type][$bundle]; + } + elseif (isset($groups[$entity_type][$bundle][$view_mode])) { + return $groups[$entity_type][$bundle][$view_mode]; + } + return array(); +} + +/** + * Read all groups. + * + * @param $conditions + * Parameters for the query + * $name The name of the entity. + * $bundle The name of the bundle. + * $view_mode The view mode. + * @param $enabled + * Return enabled or disabled groups. + */ +function field_group_read_groups($conditions = array(), $enabled = TRUE) { + + $groups = array(); + ctools_include('export'); + + if (empty($conditions)) { + $records = ctools_export_load_object('field_group'); + } + else { + $records = ctools_export_load_object('field_group', 'conditions', $conditions); + } + + foreach ($records as $group) { + + // Return only enabled groups. + if ($enabled && isset($group->disabled) && $group->disabled) { + continue; + } + // Return only disabled groups. + elseif (!$enabled && (!isset($group->disabled) || !$group->disabled)) { + continue; + } + + $groups[$group->entity_type][$group->bundle][$group->mode][$group->group_name] = field_group_unpack($group); + + } + drupal_alter('field_group_info', $groups); + return $groups; + +} + +/** + * Utility function to recreate identifiers. + */ +function _field_group_recreate_identifiers() { + + // Migrate the field groups so they have a unique identifier. + $result = db_select('field_group', 'fg') + ->fields('fg') + ->execute(); + $rows = array(); + foreach($result as $row) { + $row->identifier = $row->group_name . '|' . $row->entity_type . '|' . $row->bundle . '|' . $row->mode; + $row->data = unserialize($row->data); + $rows[] = $row; + } + foreach ($rows as $row) { + drupal_write_record('field_group', $row, array('id')); + } + +} + +/** + * Checks if a field_group exists in required context. + * + * @param String $group_name + * The name of the group. + * @param String $entity_type + * The name of the entity. + * @param String $bundle + * The bundle for the entity. + * @param String $mode + * The view mode context the group will be rendered. + */ +function field_group_exists($group_name, $entity_type, $bundle, $mode) { + $groups = field_group_read_groups(); + return !empty($groups[$entity_type][$bundle][$mode][$group_name]); +} + +/** + * Unpacks a database row in a FieldGroup object. + * @param $packed_group + * Database result object with stored group data. + * @return $group + * Field group object. + */ +function field_group_unpack($packed_group) { + if (!isset($packed_group->data)) { + return $packed_group; + } + + // Extract unserialized data. + $group = clone $packed_group; + $data = $group->data; + unset($group->data); + $group->label = $data['label']; + $group->weight = $data['weight']; + $group->children = $data['children']; + $group->format_type = !empty($data['format_type']) ? $data['format_type'] : 'fieldset'; + if (isset($data['format_settings'])) { + $group->format_settings = $data['format_settings']; + } + + return $group; +} + +/** + * Packs a FieldGroup object into a database row. + * @param $group + * FieldGroup object. + * @return $record + * Database row object, ready to be inserted/update + */ +function field_group_pack($group) { + + $record = clone $group; + $record->data = array( + 'label' => $record->label, + 'weight' => $record->weight, + 'children' => $record->children, + 'format_type' => !empty($record->format_type) ? $record->format_type : 'fieldset', + ); + if (isset($record->format_settings)) { + $record->data['format_settings'] = $record->format_settings; + } + return $record; +} + +/** + * Delete a field group. + * This function is also called by ctools export when calls are + * made through ctools_export_crud_delete(). + * + * @param $group + * A group definition. + * @param $ctools_crud + * Is this function called by the ctools crud delete. + */ +function field_group_group_export_delete($group, $ctools_crud = TRUE) { + + $query = db_delete('field_group'); + + if (isset($group->identifier)) { + $query->condition('identifier', $group->identifier); + if (!$ctools_crud) { + ctools_export_crud_disable('field_group', $group->identifier); + } + } + elseif (isset($group->id)) { + $query->condition('id', $group->id); + } + + if (!empty($group->mode)) { + $query->condition('mode', $group->mode); + } + + $query->execute(); + + cache_clear_all('field_groups', 'cache_field'); + module_invoke_all('field_group_delete_field_group', $group); + +} + +/** + * field_group_group_save(). + * + * Saves a group definition. + * This function is called by ctools export when calls are made + * through ctools_export_crud_save(). + * + * @param $group + * A group definition. + */ +function field_group_group_save(& $group) { + + // Prepare the record. + $object = field_group_pack($group); + + if (isset($object->export_type) && $object->export_type & EXPORT_IN_DATABASE) { + // Existing record. + $update = array('id'); + module_invoke_all('field_group_update_field_group', $object); + } + else { + // New record. + $update = array(); + $object->export_type = EXPORT_IN_DATABASE; + module_invoke_all('field_group_create_field_group', $object); + } + + return drupal_write_record('field_group', $object, $update); + +} + +/** + * Function to retrieve all format possibilities for the fieldgroups. + */ +function field_group_formatter_info($display_overview = FALSE) { + $cache = &drupal_static(__FUNCTION__, array()); + if (empty($cache)) { + if ($cached = cache_get('field_group_formatter_info', 'cache_field')) { + $formatters = $cached->data; + } + else { + $formatters = array(); + $formatters += module_invoke_all('field_group_formatter_info'); + $hidden_region = array( + 'label' => '<' . t('Hidden') . '>', + 'description' => '', + 'format_types' => array(), + 'instance_settings' => array(), + 'default_formatter' => '', + ); + //$formatters['form']['hidden'] = $hidden_region; + $formatters['display']['hidden'] = $hidden_region; + cache_set('field_group_formatter_info', $formatters, 'cache_field'); + } + $cache = $formatters; + } + return $cache; +} + +/** + * Attach groups to the (form) build. + * + * @param Array $element + * The part of the form. + * @param String $view_mode + * The mode for the build. + * @param Array $form_state + * The optional form state when in view_mode = form context. + */ +function field_group_attach_groups(&$element, $view_mode, $form_state = array()) { + + $entity_type = $element['#entity_type']; + $bundle = $element['#bundle']; + + $element['#groups'] = field_group_info_groups($entity_type, $bundle, $view_mode); + $element['#fieldgroups'] = $element['#groups']; + + // Create a lookup array. + $group_children = array(); + foreach ($element['#groups'] as $group_name => $group) { + foreach ($group->children as $child) { + $group_children[$child] = $group_name; + } + } + $element['#group_children'] = $group_children; + +} + +/** + * Pre render callback for rendering groups. + * @see field_group_field_attach_form + * @param $element Form that is beïng rendered. + */ +function field_group_form_pre_render(&$element) { + return field_group_build_entity_groups($element, 'form'); +} + +/** + * Preprocess/ Pre-render callback. + * + * @see field_group_form_pre_render() + * @see field_group_theme_registry_alter + * @see field_group_fields_nest() + * @param $vars preprocess vars or form element + * @param $type The type of object beïng rendered + * @return $element Array with re-arranged fields in forms. + */ +function field_group_build_entity_groups(&$vars, $type) { + + if ($type == 'form') { + $element = &$vars; + $nest_vars = NULL; + } + else { + $element = &$vars['elements']; + $nest_vars = &$vars; + } + + // No groups on the entity. + if (empty($element['#groups'])) { + return $element; + } + + // Nest the fields in the corresponding field groups. + field_group_fields_nest($element, $nest_vars); + + // Allow others to alter the pre_rendered build. + drupal_alter('field_group_build_pre_render', $element); + + // Return the element on forms. + if ($type == 'form') { + return $element; + } + + // Put groups inside content if we are rendering an entity_view. + foreach ($element['#groups'] as $group) { + if (!empty($element[$group->group_name])) { + $vars['content'][$group->group_name] = $element[$group->group_name]; + } + } + + // New css / js can be attached. + drupal_process_attached($element); +} + +/** + * Recursive function to nest fields in the field groups. + * + * This function will take out all the elements in the form and + * place them in the correct container element, a fieldgroup. + * The current group element in the loop is passed recursively so we can + * stash fields and groups in it while we go deeper in the array. + * @param Array $element + * The current element to analyse for grouping. + * @param Array $vars + * Rendering vars from the entity beïng viewed. + */ +function field_group_fields_nest(&$element, &$vars = NULL) { + + // Create all groups and keep a flat list of references to these groups. + $group_references = array(); + foreach ($element['#groups'] as $group_name => $group) { + // Construct own weight, as some fields (for example preprocess fields) don't have weight set. + $element[$group_name] = array('#child_weight' => -20); + $group_references[$group_name] = &$element[$group_name]; + } + + // Loop through all form children looking for those that are supposed to be + // in groups, and insert placeholder element for the new group field in the + // correct location within the form structure. + $element_clone = array(); + foreach (element_children($element) as $child_name) { + $element_clone[$child_name] = $element[$child_name]; + // If this element is in a group, create the placeholder element. + if (isset($element['#group_children'][$child_name])) { + $element_clone[$element['#group_children'][$child_name]] = array(); + } + } + $element = array_merge($element_clone, $element); + + // Move all children to their parents. Use the flat list of references for + // direct access as we don't know where in the root_element hierarchy the + // parent currently is situated. + foreach ($element['#group_children'] as $child_name => $parent_name) { + + // Entity beïng viewed + if ($vars) { + // If not a group, check vars['content'] for empty field. + if (!isset($element['#groups'][$child_name]) && isset($vars['content'][$child_name])) { + $group_references[$parent_name][$child_name] = $vars['content'][$child_name]; + unset($vars['content'][$child_name]); + } + // If this is a group, we have to use a reference to keep the reference + // list intact (but if it is a field we don't mind). + else { + $group_references[$parent_name][$child_name] = &$element[$child_name]; + unset($element[$child_name]); + } + } + // Form beïng viewed + else { + + // Block denied fields (#access) before they are put in groups. + // Fields (not groups) that don't have children (like field_permissions) are removed + // in field_group_field_group_build_pre_render_alter. + if (isset($element[$child_name]) && (!isset($element[$child_name]['#access']) || $element[$child_name]['#access'])) { + // If this is a group, we have to use a reference to keep the reference + // list intact (but if it is a field we don't mind). + $group_references[$parent_name][$child_name] = &$element[$child_name]; + $group_references[$parent_name]['#weight'] = $element['#groups'][$parent_name]->weight; + } + + // The child has been copied to its parent: remove it from the root element. + unset($element[$child_name]); + } + + // Set correct weight. + //$group_references[$parent_name][$child_name]['#weight'] = $group_references[$parent_name]['#child_weight']; + //$group_references[$parent_name]['#child_weight']++; + } + + // Bring extra element wrappers to achieve a grouping of fields. + // This will mainly be prefix and suffix altering. + foreach ($element['#groups'] as $group_name => $group) { + field_group_pre_render($group_references[$group_name], $group, $element); + } + +} + +/** + * Function to pre render the field group element. + * + * @see field_group_fields_nest() + * + * @param $element Array of group element that needs to be created! + * @param $group Object with the group information. + * @param $form The form object itself. + */ +function field_group_pre_render(& $element, $group, & $form) { + + // Only run the pre_render function if the group has elements. + // $group->group_name + if ($element == array()) { + return; + } + + // Let modules define their wrapping element. + // Note that the group element has no properties, only elements. + foreach (module_implements('field_group_pre_render') as $module) { + $function = $module . '_field_group_pre_render'; + if (function_exists($function)) { + // The intention here is to have the opportunity to alter the + // elements, as defined in hook_field_group_formatter_info. + // Note, implement $element by reference! + $function($element, $group, $form); + } + } + + // Allow others to alter the pre_render. + drupal_alter('field_group_pre_render', $element, $group, $form); + +} + +/** + * Hides field groups including children in a render array. + * + * @param array $element + * A render array. Can be a form, node, user, ... + * @param array $group_names + * An array of field group names that should be hidden. + */ +function field_group_hide_field_groups(&$element, $group_names) { + foreach ($group_names as $group_name) { + if (isset($element['#fieldgroups'][$group_name]) && isset($element['#group_children'])) { + // Hide the field group. + $element['#fieldgroups'][$group_name]->format_type = 'hidden'; + // Hide the elements inside the field group. + $sub_groups = array(); + foreach (array_keys($element['#group_children'], $group_name) as $field_name) { + if (isset($element['#fieldgroups'][$field_name])) { + $sub_groups[] = $field_name; + } else { + $element[$field_name]['#access'] = FALSE; + } + } + field_group_hide_field_groups($element, $sub_groups); + } + } +} + +/** + * Calculates html classes for a group. + */ +function _field_group_get_html_classes(&$group) { + + if (isset($group->format_settings['formatter'])) { + $group->collapsible = in_array($group->format_settings['formatter'], array('collapsible', 'collapsed')); + // Open or closed horizontal or vertical tabs will be collapsible by default. + if ($group->format_type == 'tab' || $group->format_type == 'htab') { + $group->collapsible = TRUE; + } + $group->collapsed = in_array($group->format_settings['formatter'], array('collapsed', 'closed')); + } + + $classes = new stdClass(); + + // Prepare extra classes, required and optional ones. + $optional = array(str_replace('_', '-', $group->group_name)); + $required = array(); + if ($group->format_type == 'multipage') { + $required[] = 'field-group-' . $group->format_type; + } + else { + $optional[] = 'field-group-' . $group->format_type; + } + + if (isset($group->format_settings['formatter']) && $group->collapsible) { + $required[] = 'collapsible'; + if ($group->collapsed) { + $required[] = 'collapsed'; + } + } + + if (isset($group->format_settings['instance_settings'])) { + + // Add a required-fields class to trigger the js. + if (!empty($group->format_settings['instance_settings']['required_fields'])) { + $required[] = 'required-fields'; + } + + // Add user selected classes. + if (!empty($group->format_settings['instance_settings']['classes'])) { + $required[] = check_plain($group->format_settings['instance_settings']['classes']); + } + + // Extra required classes for div. + if ($group->format_type == 'div') { + if (!empty($group->format_settings['instance_settings']['effect'])) { + $effect = $group->format_settings['instance_settings']['effect']; + // Only add effect class when an effect was selected. + if ($effect != 'none') { + $speed = isset($group->format_settings['instance_settings']['speed']) ? $group->format_settings['instance_settings']['speed'] : 'none'; + $required[] = 'speed-' . $speed . 'effect-' . $effect; + } + } + } + + // Extra required classes for accordions. + elseif ($group->format_type == 'accordion') { + $required[] = 'field-group-' . $group->format_type . '-wrapper'; + $effect = isset($group->format_settings['instance_settings']['effect']) ? $group->format_settings['instance_settings']['effect'] : 'none'; + $required[] = 'effect-' . $effect; + } + + } + + $classes->required = $required; + $classes->optional = $optional; + + return $classes; +} + +/** + * Get the default formatter settings for a given formatter and a mode. + */ +function _field_group_get_default_formatter_settings($format_type, $mode) { + + $field_group_types = field_group_formatter_info(); + $mode = $format_type == 'form' ? 'form' : 'display'; + $formatter = $field_group_types[$mode][$format_type]; + + return array( + 'formatter' => isset($formatter['default_formatter']) ? $formatter['default_formatter'] : '', + 'instance_settings' => $formatter['instance_settings'] + ); +} + diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/field_group.test --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/field_group.test Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,488 @@ + 'UI tests', + 'description' => 'Test the field group UI.', + 'group' => 'Field group', + ); + } + + function setUp() { + parent::setUp('field_test', 'field_group'); + + // Create test user. + $admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'access administration pages', 'bypass node access')); + $this->drupalLogin($admin_user); + } + + /** + * Test the creation a group on the article content type. + */ + function createGroup() { + + // Create random group name. + $this->group_label = $this->randomName(8); + $this->group_name_input = drupal_strtolower($this->randomName(8)); + $this->group_name = 'group_' . $this->group_name_input; + + // Setup new group. + $group = array( + 'fields[_add_new_group][label]' => $this->group_label, + 'fields[_add_new_group][group_name]' => $this->group_name_input, + ); + + // Add new group on the 'Manage fields' page. + $this->drupalPost('admin/structure/types/manage/article/fields', $group, t('Save')); + + $this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.')); + + // Test if group is in the $groups array. + $groups = field_group_info_groups('node', 'article', 'form', TRUE); + $this->assertTrue(array_key_exists($this->group_name, $groups), t('Group found in groups array')); + + // Add new group on the 'Manage display' page. + $this->drupalPost('admin/structure/types/manage/article/display', $group, t('Save')); + $this->assertRaw(t('New group %label successfully created.', array('%label' => $this->group_label)), t('Group message displayed on screen.')); + + // Test if group is in the $groups array. + $groups = field_group_info_groups('node', 'article', 'default', TRUE); + $this->assertTrue(array_key_exists($this->group_name, $groups), t('Group found in groups array')); + } + + /** + * Delete a group. + */ + function deleteGroup() { + + $this->drupalPost('admin/structure/types/manage/article/groups/' . $this->group_name . '/delete/form', array(), t('Delete')); + $this->assertRaw(t('The group %label has been deleted from the %article content type.', array('%label' => $this->group_label, '%article' => 'Article')), t('Group removal message displayed on screen.')); + + // Test that group is not in the $groups array. + $groups = field_group_info_groups('node', 'article', 'form', TRUE); + $this->assertFalse(array_key_exists($this->group_name, $groups), t('Group not found in groups array while deleting')); + + $this->drupalPost('admin/structure/types/manage/article/groups/' . $this->group_name . '/delete/default', array(), t('Delete')); + $this->assertRaw(t('The group %label has been deleted from the %article content type.', array('%label' => $this->group_label, '%article' => 'Article')), t('Group removal message displayed on screen.')); + + // Test that group is not in the $groups array. + $groups = field_group_info_groups('node', 'article', 'default', TRUE); + $this->assertFalse(array_key_exists($this->group_name, $groups), t('Group not found in groups array while deleting')); + } + + /** + * General CRUD. + */ + function testCRUDGroup() { + $this->createGroup(); + $this->deleteGroup(); + } + + /** + * Nest a field underneath a group. + */ + function testNestField() { + + $this->createGroup(); + + $edit = array( + 'fields[field_image][parent]' => $this->group_name, + ); + $this->drupalPost('admin/structure/types/manage/article/fields', $edit, t('Save')); + $this->assertRaw(t('Your settings have been saved.'), t('Settings saved')); + + $groups = field_group_info_groups('node', 'article', 'form', TRUE); + $this->assertTrue(in_array('field_image', $groups[$this->group_name]->children), t('Image is a child of %group', array('%group' => $this->group_name))); + } + +} + +/** + * Group display tests + */ +class GroupDisplayTestCase extends DrupalWebTestCase { + + protected $node; + + public static function getInfo() { + return array( + 'name' => 'Display tests', + 'description' => 'Test the field group display.', + 'group' => 'Field group', + ); + } + + function setUp() { + + parent::setUp('field_test', 'field_group'); + + $node = new stdClass(); + $node->type = 'article'; + $node->title = $this->randomName(); + $node->status = 1; + + // Create test fields. + $test_fields = array('field_test', 'field_test_2'); + foreach ($test_fields as $field_name) { + + $field = array( + 'field_name' => $field_name, + 'type' => 'test_field', + 'cardinality' => 1, + ); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => $this->randomName(), + 'display' => array( + 'default' => array( + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $this->randomName(), + ), + ), + 'teaser' => array( + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $this->randomName(), + ), + ), + ), + ); + field_create_field($field); + field_create_instance($instance); + + $node->{$field_name}[LANGUAGE_NONE][0]['value'] = mt_rand(1, 127); + } + + node_save($node); + $this->node = $node; + } + + /** + * Create a new group. + * @param array $data + * Data for the field group. + */ + function createGroup($mode, array $data) { + + $group_name = 'group_' . drupal_strtolower($this->randomName(8)); + $identifier = $group_name . '|node|article|' . $mode; + + $field_group = new stdClass; + $field_group->disabled = FALSE; + $field_group->api_version = 1; + $field_group->identifier = $identifier; + $field_group->group_name = $group_name; + $field_group->entity_type = 'node'; + $field_group->bundle = 'article'; + $field_group->mode = $mode; + $field_group->parent_name = ''; + $field_group->children = $data['children']; + $field_group->data = $data; + drupal_write_record('field_group', $field_group); + ctools_export_crud_enable('field_group', $field_group->identifier); + + return $field_group; + } + + /** + * Test the div formatter. + */ + function testDiv() { + + $data = array( + 'label' => 'Wrapper', + 'weight' => '1', + 'children' => array( + 0 => 'field_test', + ), + 'format_type' => 'div', + 'format_settings' => array( + 'label' => 'Link', + 'instance_settings' => array( + 'required_fields' => 0, + 'id' => 'wrapper-id', + 'classes' => 'test-class', + 'description' => '', + 'show_label' => FALSE, + 'label_element' => 'h3', + 'effect' => 'none', + 'speed' => 'fast', + ), + 'formatter' => 'open', + ), + ); + $group = $this->createGroup('default', $data); + + $groups = field_group_info_groups('node', 'article', 'default', TRUE); + $this->drupalGet('node/' . $this->node->nid); + + // Test group ids and classes. + $this->assertRaw('id="wrapper-id"', t('Wrapper id set on wrapper div')); + $this->assertRaw('class="' . $group->group_name . ' test-class', t('Test class set on wrapper div') . 'class="' . $group->group_name . ' test-class'); + + // Test group label. + $this->assertNoRaw('

        ' . $data['label'] . '

        ', t('Label is not shown')); + + // Set show label to true. + $group->data['format_settings']['instance_settings']['show_label'] = TRUE; + drupal_write_record('field_group', $group, array('identifier')); + $groups = field_group_info_groups('node', 'article', 'form', TRUE); + $this->drupalGet('node/' . $this->node->nid); + $this->assertRaw('

        ' . $data['label'] . '

        ', t('Label is shown')); + } + + /** + * Test the horizontal tabs formatter. + */ + function testHorizontalTabs() { + + $data = array( + 'label' => 'Tab 1', + 'weight' => '1', + 'children' => array( + 0 => 'field_test', + ), + 'format_type' => 'htab', + 'format_settings' => array( + 'label' => 'Tab 1', + 'instance_settings' => array( + 'classes' => 'test-class', + 'description' => '', + ), + 'formatter' => 'open', + ), + ); + $first_tab = $this->createGroup('default', $data); + $first_tab_id = 'node_article_full_' . $first_tab->group_name; + + $data = array( + 'label' => 'Tab 2', + 'weight' => '1', + 'children' => array( + 0 => 'field_test_2', + ), + 'format_type' => 'htab', + 'format_settings' => array( + 'label' => 'Tab 1', + 'instance_settings' => array( + 'classes' => 'test-class-2', + 'description' => 'description of second tab', + ), + 'formatter' => 'closed', + ), + ); + $second_tab = $this->createGroup('default', $data); + $second_tab_id = 'node_article_full_' . $first_tab->group_name; + + $data = array( + 'label' => 'Tabs', + 'weight' => '1', + 'children' => array( + 0 => $first_tab->group_name, + 1 => $second_tab->group_name, + ), + 'format_type' => 'htabs', + 'format_settings' => array( + 'label' => 'Tab 1', + 'instance_settings' => array( + 'classes' => 'test-class-wrapper', + ), + ), + ); + $tabs = $this->createGroup('default', $data); + + $groups = field_group_info_groups('node', 'article', 'default', TRUE); + + $this->drupalGet('node/' . $this->node->nid); + + // Test properties. + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper')); + $this->assertFieldByXPath("//fieldset[contains(@class, 'test-class-2')]", NULL, t('Test class set on second tab')); + $this->assertRaw('
        description of second tab
        ', t('Description of tab is shown')); + $this->assertRaw('class="collapsible collapsed test-class-2', t('Second tab is default collapsed')); + + // Test if correctly nested + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$first_tab_id')]", NULL, 'First tab is displayed as child of the wrapper.'); + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$second_tab_id')]", NULL, 'Second tab is displayed as child of the wrapper.'); + + } + + /** + * Test the vertical tabs formatter. + */ + function testVerticalTabs() { + + $data = array( + 'label' => 'Tab 1', + 'weight' => '1', + 'children' => array( + 0 => 'field_test', + ), + 'format_type' => 'tab', + 'format_settings' => array( + 'label' => 'Tab 1', + 'instance_settings' => array( + 'classes' => 'test-class', + 'description' => '', + ), + 'formatter' => 'open', + ), + ); + $first_tab = $this->createGroup('default', $data); + $first_tab_id = 'node_article_full_' . $first_tab->group_name; + + $data = array( + 'label' => 'Tab 2', + 'weight' => '1', + 'children' => array( + 0 => 'field_test_2', + ), + 'format_type' => 'tab', + 'format_settings' => array( + 'label' => 'Tab 1', + 'instance_settings' => array( + 'classes' => 'test-class-2', + 'description' => 'description of second tab', + ), + 'formatter' => 'closed', + ), + ); + $second_tab = $this->createGroup('default', $data); + $second_tab_id = 'node_article_full_' . $first_tab->group_name; + + $data = array( + 'label' => 'Tabs', + 'weight' => '1', + 'children' => array( + 0 => $first_tab->group_name, + 1 => $second_tab->group_name, + ), + 'format_type' => 'tabs', + 'format_settings' => array( + 'label' => 'Tab 1', + 'instance_settings' => array( + 'classes' => 'test-class-wrapper', + ), + ), + ); + $tabs = $this->createGroup('default', $data); + + $groups = field_group_info_groups('node', 'article', 'default', TRUE); + + $this->drupalGet('node/' . $this->node->nid); + + // Test properties. + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper')); + $this->assertFieldByXPath("//fieldset[contains(@class, 'test-class-2')]", NULL, t('Test class set on second tab')); + $this->assertRaw('
        description of second tab
        ', t('Description of tab is shown')); + $this->assertRaw('class="collapsible collapsed test-class-2', t('Second tab is default collapsed')); + + // Test if correctly nested + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$first_tab_id')]", NULL, 'First tab is displayed as child of the wrapper.'); + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//fieldset[contains(@id, '$second_tab_id')]", NULL, 'Second tab is displayed as child of the wrapper.'); + } + + /** + * Test the accordion formatter. + */ + function testAccordion() { + + $data = array( + 'label' => 'Accordion item 1', + 'weight' => '1', + 'children' => array( + 0 => 'field_test', + ), + 'format_type' => 'accordion-item', + 'format_settings' => array( + 'label' => 'Accordion item 1', + 'instance_settings' => array( + 'classes' => 'test-class', + ), + 'formatter' => 'closed', + ), + ); + $first_item = $this->createGroup('default', $data); + $first_item_id = 'node_article_full_' . $first_item->group_name; + + $data = array( + 'label' => 'Accordion item 2', + 'weight' => '1', + 'children' => array( + 0 => 'field_test_2', + ), + 'format_type' => 'accordion-item', + 'format_settings' => array( + 'label' => 'Tab 2', + 'instance_settings' => array( + 'classes' => 'test-class-2', + ), + 'formatter' => 'open', + ), + ); + $second_item = $this->createGroup('default', $data); + $second_item_id = 'node_article_full_' . $second_item->group_name; + + $data = array( + 'label' => 'Accordion', + 'weight' => '1', + 'children' => array( + 0 => $first_item->group_name, + 1 => $second_item->group_name, + ), + 'format_type' => 'accordion', + 'format_settings' => array( + 'label' => 'Tab 1', + 'instance_settings' => array( + 'classes' => 'test-class-wrapper', + 'effect' => 'bounceslide' + ), + ), + ); + $accordion = $this->createGroup('default', $data); + + $groups = field_group_info_groups('node', 'article', 'default', TRUE); + + $this->drupalGet('node/' . $this->node->nid); + + // Test properties. + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]", NULL, t('Test class set on tabs wrapper')); + $this->assertFieldByXPath("//div[contains(@class, 'effect-bounceslide')]", NULL, t('Correct effect is set on the accordion')); + $this->assertFieldByXPath("//div[contains(@class, 'test-class')]", NULL, t('Accordion item with test-class is shown')); + $this->assertFieldByXPath("//div[contains(@class, 'test-class-2')]", NULL, t('Accordion item with test-class-2 is shown')); + $this->assertFieldByXPath("//h3[contains(@class, 'field-group-accordion-active')]", NULL, t('Accordion item 2 was set active')); + + // Test if correctly nested + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class')]", NULL, 'First item is displayed as child of the wrapper.'); + $this->assertFieldByXPath("//div[contains(@class, 'test-class-wrapper')]//div[contains(@class, 'test-class-2')]", NULL, 'Second item is displayed as child of the wrapper.'); + } + +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/horizontal-tabs/horizontal-tabs-rtl.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/horizontal-tabs/horizontal-tabs-rtl.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,15 @@ +div.horizontal-tabs { + margin: 0 0 1em 0; +} + +.horizontal-tabs ul.horizontal-tabs-list { + border-right: 0; + border-left: 1px solid #dedede; +} + +/* Layout of each tab */ +.horizontal-tabs ul.horizontal-tabs-list li { + border-right: 0; + border-left: 1px solid #ccc; + float: right; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/horizontal-tabs/horizontal-tabs.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/horizontal-tabs/horizontal-tabs.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,101 @@ +div.horizontal-tabs { + margin: 0 0 1em 0; /* LTR */ + padding: 0; + border: 1px solid #ccc; + position: relative; /* IE6/7 */ +} + +.horizontal-tabs ul.horizontal-tabs-list { + display: inline-block; + margin: 0; + border: 0; + padding: 0px; + position: relative; /* IE6 */ + list-style: none; + list-style-image: none; /* IE6 */ + background-color: #dedede; + border-right: 1px solid #dedede; /* LTR */ + width: 100%; + height: auto; + clear: both; +} + +.horizontal-tabs fieldset.horizontal-tabs-pane { + padding: 0 1em; + border: 0; +} + +fieldset.horizontal-tabs-pane > legend, +fieldset.vertical-tabs-pane fieldset.horizontal-tabs-pane > legend { + display: none; +} + +/* Layout of each tab */ +.horizontal-tabs ul.horizontal-tabs-list li { + background: #eee; + border-right: 1px solid #ccc; /* LTR */ + padding: 1px; + padding-top: 0; + margin: 0; + min-width: 5em; /* IE7 */ + float: left; /* LTR */ +} +.horizontal-tabs ul.horizontal-tabs-list li.selected { + background-color: #fff; + padding: 0 0 1px 0; +} +.horizontal-tabs ul.horizontal-tabs-list li a { + display: block; + text-decoration: none; + padding: 0.5em 0.6em; +} +.horizontal-tabs ul.horizontal-tabs-list li a:hover { + outline: none; + background-color: #ededdd; +} +.horizontal-tabs ul.horizontal-tabs-list li:hover, +.horizontal-tabs ul.horizontal-tabs-list li:focus { + background-color: #ddd; +} +.horizontal-tabs ul.horizontal-tabs-list :focus { + outline: none; +} +.horizontal-tabs ul.horizontal-tabs-list li a:focus strong, +.horizontal-tabs ul.horizontal-tabs-list li a:active strong, +.horizontal-tabs ul.horizontal-tabs-list li a:hover strong { + text-decoration: none; + outline: none; +} +.horizontal-tabs ul.horizontal-tabs-list li a, +.horizontal-tabs ul.horizontal-tabs-list li.selected a { + display: block; + text-decoration: none; + padding: 0.5em 0.6em 0.3em 0.6em; + position:relative; + top: 0px; +} +.horizontal-tabs ul.horizontal-tabs-list .selected strong { + color: #000; +} +.horizontal-tabs ul.horizontal-tabs-list .summary { + display: block; +} +.horizontal-tabs ul.horizontal-tabs ul.horizontal-tabs-list .summary { + line-height: normal; + margin-bottom: 0; +} + +/** + * tab content + */ +div.field-group-htabs-wrapper .field-group-format-wrapper { + clear: both; + padding: 0 0 0.6em; +} +/*hide*/ +.horizontal-tabs .horizontal-tab-hidden { + display: block; + position: absolute; + top: -100000px; + width: 100%; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/horizontal-tabs/horizontal-tabs.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/horizontal-tabs/horizontal-tabs.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,197 @@ +(function ($) { + +/** + * This script transforms a set of fieldsets into a stack of horizontal + * tabs. Another tab pane can be selected by clicking on the respective + * tab. + * + * Each tab may have a summary which can be updated by another + * script. For that to work, each fieldset has an associated + * 'horizontalTabCallback' (with jQuery.data() attached to the fieldset), + * which is called every time the user performs an update to a form + * element inside the tab pane. + */ +Drupal.behaviors.horizontalTabs = { + attach: function (context) { + $('.horizontal-tabs-panes', context).once('horizontal-tabs', function () { + var focusID = $(':hidden.horizontal-tabs-active-tab', this).val(); + var tab_focus; + + // Check if there are some fieldsets that can be converted to horizontal-tabs + var $fieldsets = $('> fieldset', this); + if ($fieldsets.length == 0) { + return; + } + + // Create the tab column. + var tab_list = $('
          '); + $(this).wrap('
          ').before(tab_list); + + // Transform each fieldset into a tab. + $fieldsets.each(function (i) { + var horizontal_tab = new Drupal.horizontalTab({ + title: $('> legend', this).text(), + fieldset: $(this) + }); + horizontal_tab.item.addClass('horizontal-tab-button-' + i); + tab_list.append(horizontal_tab.item); + $(this) + .removeClass('collapsible collapsed') + .addClass('horizontal-tabs-pane') + .data('horizontalTab', horizontal_tab); + if (this.id == focusID) { + tab_focus = $(this); + } + }); + + $('> li:first', tab_list).addClass('first'); + $('> li:last', tab_list).addClass('last'); + + if (!tab_focus) { + // If the current URL has a fragment and one of the tabs contains an + // element that matches the URL fragment, activate that tab. + if (window.location.hash && $(window.location.hash, this).length) { + tab_focus = $(window.location.hash, this).closest('.horizontal-tabs-pane'); + } + else { + tab_focus = $('> .horizontal-tabs-pane:first', this); + } + } + if (tab_focus.length) { + tab_focus.data('horizontalTab').focus(); + } + }); + } +}; + +/** + * The horizontal tab object represents a single tab within a tab group. + * + * @param settings + * An object with the following keys: + * - title: The name of the tab. + * - fieldset: The jQuery object of the fieldset that is the tab pane. + */ +Drupal.horizontalTab = function (settings) { + var self = this; + $.extend(this, settings, Drupal.theme('horizontalTab', settings)); + + this.link.click(function () { + self.focus(); + return false; + }); + + // Keyboard events added: + // Pressing the Enter key will open the tab pane. + this.link.keydown(function(event) { + if (event.keyCode == 13) { + self.focus(); + // Set focus on the first input field of the visible fieldset/tab pane. + $("fieldset.horizontal-tabs-pane :input:visible:enabled:first").focus(); + return false; + } + }); + + this.fieldset + .bind('summaryUpdated', function () { + self.updateSummary(); + }) + .trigger('summaryUpdated'); +}; + +Drupal.horizontalTab.prototype = { + /** + * Displays the tab's content pane. + */ + focus: function () { + this.fieldset + .removeClass('horizontal-tab-hidden') + .siblings('fieldset.horizontal-tabs-pane') + .each(function () { + var tab = $(this).data('horizontalTab'); + tab.fieldset.addClass('horizontal-tab-hidden'); + tab.item.removeClass('selected'); + }) + .end() + .siblings(':hidden.horizontal-tabs-active-tab') + .val(this.fieldset.attr('id')); + this.item.addClass('selected'); + // Mark the active tab for screen readers. + $('#active-horizontal-tab').remove(); + this.link.append('' + Drupal.t('(active tab)') + ''); + }, + + /** + * Updates the tab's summary. + */ + updateSummary: function () { + this.summary.html(this.fieldset.drupalGetSummary()); + }, + + /** + * Shows a horizontal tab pane. + */ + tabShow: function () { + // Display the tab. + this.item.removeClass('horizontal-tab-hidden'); + // Update .first marker for items. We need recurse from parent to retain the + // actual DOM element order as jQuery implements sortOrder, but not as public + // method. + this.item.parent().children('.horizontal-tab-button').removeClass('first') + .filter(':visible:first').addClass('first'); + // Display the fieldset. + this.fieldset.removeClass('horizontal-tab-hidden'); + // Focus this tab. + this.focus(); + return this; + }, + + /** + * Hides a horizontal tab pane. + */ + tabHide: function () { + // Hide this tab. + this.item.addClass('horizontal-tab-hidden'); + // Update .first marker for items. We need recurse from parent to retain the + // actual DOM element order as jQuery implements sortOrder, but not as public + // method. + this.item.parent().children('.horizontal-tab-button').removeClass('first') + .filter(':visible:first').addClass('first'); + // Hide the fieldset. + this.fieldset.addClass('horizontal-tab-hidden'); + // Focus the first visible tab (if there is one). + var $firstTab = this.fieldset.siblings('.horizontal-tabs-pane:not(.horizontal-tab-hidden):first'); + if ($firstTab.length) { + $firstTab.data('horizontalTab').focus(); + } + return this; + } +}; + +/** + * Theme function for a horizontal tab. + * + * @param settings + * An object with the following keys: + * - title: The name of the tab. + * @return + * This function has to return an object with at least these keys: + * - item: The root tab jQuery element + * - link: The anchor tag that acts as the clickable area of the tab + * (jQuery version) + * - summary: The jQuery element that contains the tab summary + */ +Drupal.theme.prototype.horizontalTab = function (settings) { + var tab = {}; + var idAttr = settings.fieldset.attr('id'); + + tab.item = $('
        • ') + .append(tab.link = $('') + .append(tab.title = $('').text(settings.title)) + .append(tab.summary = $('') + ) + ); + return tab; +}; + +})(jQuery); diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/multipage/multipage-rtl.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/multipage/multipage-rtl.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,13 @@ +.multipage-controls-list #edit-actions { + float: right !important; +} + +.multipage-button { + float: right !important; +} + +.multipage-counter{ + float: left !important; + margin-right: 0 !important; + margin-left: 5px !important; +} diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/multipage/multipage.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/multipage/multipage.css Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,128 @@ +.multipage-controls-list #edit-actions { + float: left; /* LTR */ +} + +.multipage-button { + margin-bottom: 1em; + margin-top: 0; + float: left; /* LTR */ + line-height: 36px; +} + +.multipage-button a { + padding-top: 10px; +} + +.multipage-counter { + float: right; /* LTR */ + margin-right: 5px; /* LTR */ + height: 0; + position: relative; + top: 1.8em; + line-height: 30px; + font: 12px arial,sans-serif; + font-weight: bold; + color:#666; +} + +a.multipage-link-previous { + font: 12px arial,sans-serif; + font-weight: bold; + color:#666; + -webkit-transition: color 218ms; + -moz-transition: color 218ms; + -o-transition: color 218ms; + transition: color 218ms; +} + +a.multipage-link-previous:hover { + text-decoration:none; + color: #333; +} + +.multipage-controls-list input.form-submit { + background:none; + border: none; + border-radius: 2px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border: 1px solid rgba(0, 0, 0, 0.1); + font: 12px arial,sans-serif; + font-weight: bold; + color: #666; + text-shadow: 0 1px 0 white; + padding: 7px 12px; + background: -webkit-gradient(linear,0% 40%,0% 70%,from(whiteSmoke),to(#F1F1F1)); + -o-transition: border-top-color 0.218s,border-right-color 0.218s,border-bottom-color 0.218s,border-left-color .218s; + -webkit-transition: border-color .218s; +} + +.multipage-controls-list input.form-submit:hover { + color:#333; + box-shadow: 0 1px 1px rgba(0,0,0,0.1); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.1); + border-color: #939393; +} + +.multipage-controls-list input.form-submit:active { + background: -webkit-gradient(linear,0% 40%,0% 70%,from(#F1F1F1),to(whiteSmoke)); +} + +.multipage-controls-list input#edit-submit { + background: #4D90FE; /* for non-css3 browsers */ + background-image: #4D90FE; /* for non-css3 browsers */ + background-image: -o-linear-gradient(top,#4d90fe,#4787ed); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4D90FE', endColorstr='#4787ED'); /* for IE */ + background: -webkit-gradient(linear, center top, center bottom, from(#4D90FE), to(#4787ED)); /* for webkit browsers */ + background: -moz-linear-gradient(center top, #4D90FE, #4787ED); /* for firefox 3.6+ */ + color: white; + text-shadow: none; + text-transform: uppercase; + min-width: 79px; +} + +.multipage-controls-list input#edit-submit:hover { + background-image: -moz-linear-gradient(top,#4d90fe,#357ae8); + background-image: -o-linear-gradient(top,#4d90fe,#357ae8); + background-image: -webkit-gradient(linear,left top,left bottom,from(#4d90fe),to(#357ae8)); + color: white; + text-shadow: none; + box-shadow: 0 1px 1px rgba(0,0,0,0.2); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.2); +} + +.multipage-controls-list input#edit-submit:active { + background: #4D90FE; + border-color: #2F5BB7; +} + +.multipage-controls-list input#edit-delete { + background-image: -moz-linear-gradient(top,#dd4b39,#d14836); + background-image: -o-linear-gradient(top,#dd4b39,#d14836); + background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#d14836)); + text-shadow: 0 1px rgba(0, 0, 0, 0.1); + border: 1px solid transparent; + color: white; + text-shadow: none; + +} + +.multipage-controls-list input#edit-delete:hover { + background-image: -moz-linear-gradient(top,#dd4b39,#c53727); + background-image: -o-linear-gradient(top,#dd4b39,#c53727); + background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#c53727)); + border: 1px solid #B0281A!important; + border-bottom: 1px solid #AF301F!important; + box-shadow: 0 1px 1px rgba(0,0,0,0.2); + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,0.2); + color: white; +} + +.multipage-controls-list input#edit-delete:active { + background-image: -moz-linear-gradient(top,#dd4b39,#b0281a); + background-image: -o-linear-gradient(top,#dd4b39,#b0281a); + background-image: -webkit-gradient(linear,left top,left bottom,from(#dd4b39),to(#b0281a)); + border: 1px solid #992A1b!important; + box-shadow: 0 1px 2px rgba(0,0,0,0.3); + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.3); +} \ No newline at end of file diff -r b28be78d8160 -r ce11bbd8f642 sites/all/modules/field_group/multipage/multipage.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sites/all/modules/field_group/multipage/multipage.js Thu Sep 19 10:38:44 2013 +0100 @@ -0,0 +1,268 @@ +(function ($) { + +/** + * This script transforms a set of wrappers into a stack of multipage pages. + * Another pane can be entered by clicking next/previous. + * + */ +Drupal.behaviors.MultiPage = { + attach: function (context) { + $('.multipage-panes', context).once('multipage', function () { + + var focusID = $(':hidden.multipage-active-control', this).val(); + var paneWithFocus; + + // Check if there are some wrappers that can be converted to multipages. + var $panes = $('> div.field-group-multipage', this); + var $form = $panes.parents('form'); + if ($panes.length == 0) { + return; + } + + // Create the next/previous controls. + var $controls; + + // Transform each div.multipage-pane into a multipage with controls. + $panes.each(function () { + + $controls = $('
          '); + $(this).append($controls); + + // Check if the submit button needs to move to the latest pane. + if (Drupal.settings.field_group.multipage_move_submit && $('.form-actions').length) { + $('.form-actions', $form).remove().appendTo($($controls, $panes.last())); + } + + var multipageControl = new Drupal.multipageControl({ + title: $('> .multipage-pane-title', this).text(), + wrapper: $(this), + has_next: $(this).next().length, + has_previous: $(this).prev().length + }); + + $controls.append(multipageControl.item); + $(this) + .addClass('multipage-pane') + .data('multipageControl', multipageControl); + + if (this.id == focusID) { + paneWithFocus = $(this); + } + + }); + + if (paneWithFocus === undefined) { + // If the current URL has a fragment and one of the tabs contains an + // element that matches the URL fragment, activate that tab. + if (window.location.hash && $(window.location.hash, this).length) { + paneWithFocus = $(window.location.hash, this).closest('.multipage-pane'); + } + else { + paneWithFocus = $('multipage-open', this).length ? $('multipage-open', this) : $('> .multipage-pane:first', this); + } + } + if (paneWithFocus !== undefined) { + paneWithFocus.data('multipageControl').focus(); + } + }); + } +}; + +/** + * The multipagePane object represents a single div as a page. + * + * @param settings + * An object with the following keys: + * - title: The name of the tab. + * - wrapper: The jQuery object of the
          that is the tab pane. + */ +Drupal.multipageControl = function (settings) { + var self = this; + var controls = Drupal.theme('multipage', settings); + $.extend(self, settings, controls); + + this.nextLink.click(function () { + self.nextPage(); + return false; + }); + + this.previousLink.click(function () { + self.previousPage(); + return false; + }); + +/* + // Keyboard events added: + // Pressing the Enter key will open the tab pane. + this.nextLink.keydown(function(event) { + if (event.keyCode == 13) { + self.focus(); + // Set focus on the first input field of the visible wrapper/tab pane. + $("div.multipage-pane :input:visible:enabled:first").focus(); + return false; + } + }); + + // Pressing the Enter key lets you leave the tab again. + this.wrapper.keydown(function(event) { + // Enter key should not trigger inside +tag, use the value of the name attribute in your URL, contained in the +edit array. For example, if the tag looks like this: + + + +then try this URL: + + http://www.example.com/node/add/content?edit[title]=Automatic filled in title + +CCK fields are a bit more complicated: + + + +The key is to put this in the edit[] array nested, like this: + + http://www.example.com/node/add/content?edit[field_office][0][node_name]=AL-235 + +Another example: + +