diff --git a/sites/all/modules/context/API.txt b/sites/all/modules/context/API.txt new file mode 100644 index 0000000000000000000000000000000000000000..8b4785284536df3877a08f1858e4b62d2e2b1985 --- /dev/null +++ b/sites/all/modules/context/API.txt @@ -0,0 +1,81 @@ + +Context 3.x API +--------------- +The following is an overview of using the Context API. + + +The context static cache +------------------------ +Context provides a centralized set of API functions for setting and retrieving a +static cache: + + // Set a static cache value at [my_namspace][mykey] + context_set('my_namespace', 'mykey', $value); + + // Retrieve a static cache value at [my_namespace][mykey] + context_get('my_namespace', 'mykey'); // $value + + // Boolean for whether there is a value at [my_namespace][mykey] + context_isset('my_namespace', 'mykey'); // TRUE + +These are used internally by context but may also be used by other modules. Just +do not use the namespace `context` unless you want to affect things that context +is up to. + + +Adding a condition or reaction plugin +------------------------------------- +Both context conditions and reactions utilize the CTools plugins API. In order +to add a new condition or reaction for your module, follow these steps: + +1. Implement `hook_context_plugins()` to define your plugins, classes, and class + hierarchy. + + function mymodule_context_plugins() { + $plugins = array(); + $plugins['mymodule_context_condition_bar'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'mymodule') .'/plugins', + 'file' => 'mymodule_context_condition_bar.inc', + 'class' => 'mymodule_context_condition_bar', + 'parent' => 'context_condition', + ), + ); + return $plugins; + } + +2. Implement `hook_context_registry()` to define your conditions and/or + reactions and map them to plugins. + + function mymodule_context_registry() { + return array( + 'conditions' => array( + 'bar' => array( + 'title' => t('Name of condition "bar"'), + 'plugin' => 'mymodule_context_condition_bar', + ), + ), + ); + } + +3. Write your condition or reaction plugin class. It's best to look at one of + the included plugins as a starting point. + +4. Add in a Drupal integration point for your plugin. A node page condition + plugin, for example, may be invoked from `hook_nodeapi()`. + + +Replacing or extending existing plugins +--------------------------------------- +You can replace a condition or reaction plugin with your own plugin class using +`hook_context_registry_alter()`: + + function mymodule_context_registry_alter(&$registry) { + if (!empty($registry['conditions']['node'])) { + $registry['conditions']['node']['plugin'] = 'mymodule_context_condition_customnode'; + } + } + +This entry would swap out the default node condition plugin for a custom one +provided by `mymodule`. Note that any replacement plugins must have an entry in +`hook_context_plugins()`. diff --git a/sites/all/modules/context/LICENSE.txt b/sites/all/modules/context/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/context/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. This General Public License +applies to most of the Free Software Foundation's software and to any other +program whose authors commit to using it. (Some other Free Software +Foundation software is covered by the GNU Library General Public License +instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this service if +you wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show +them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems +introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND + MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such +program or work, and a "work based on the Program" means either the +Program or any derivative work under copyright law: that is to say, a work +containing the Program or a portion of it, either verbatim or with +modifications and/or translated into another language. (Hereinafter, translation +is included without limitation in the term "modification".) Each licensee is +addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made +by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you +also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in +part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print such +an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you distribute +them as separate works. But when you distribute the same sections as part +of a whole which is a work based on the Program, the distribution of the +whole must be on the terms of this License, whose permissions for other +licensees extend to the entire whole, and thus to each and every part +regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to +work written entirely by you; rather, the intent is to exercise the right to +control the distribution of derivative or collective works based on the +Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the scope +of this License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above +on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for +noncommercial distribution and only if you received the program in object +code or executable form with such an offer, in accord with Subsection b +above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source code +means all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation and +installation of the executable. However, as a special exception, the source +code distributed need not include anything that is normally distributed (in +either source or binary form) with the major components (compiler, kernel, +and so on) of the operating system on which the executable runs, unless that +component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with the +object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense or distribute the Program is void, and will automatically +terminate your rights under this License. However, parties who have received +copies, or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the +Program (or any work based on the Program), you indicate your acceptance +of this License to do so, and all its terms and conditions for copying, +distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such +case, this License incorporates the limitation as if written in the body of this +License. + +9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that +version or of any later version published by the Free Software Foundation. If +the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make +exceptions for this. Our decision will be guided by the two goals of +preserving the free status of all derivatives of our free software and of +promoting the sharing and reuse of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT +PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR +AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR +ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE +LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES +SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN +IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/sites/all/modules/context/README.txt b/sites/all/modules/context/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..435e14360d2561d241e82851b3cf95708ec25d27 --- /dev/null +++ b/sites/all/modules/context/README.txt @@ -0,0 +1,97 @@ + +Current state of Context for Drupal 7 +------------------------------------- +Context for D7 is a straight port of Context 3.x from D6. There are no major +API changes and any exported contexts from D6 should be compatible with the D7 +version. You will need the latest CTools (as of Sept. 16 2010) from here: + +- http://github.com/sdboyer/ctools + +### Working + +- all conditions except node taxonomy condition +- all reactions +- context UI +- context layouts +- inline editor (with Admin 2.x for D7) + +### Expect API changes + +- node taxonomy condition to generic field condition for entities + + +Context 3.x for Drupal 7.x +-------------------------- +Context allows you to manage contextual conditions and reactions for +different portions of your site. You can think of each context as +representing a "section" of your site. For each context, you can choose +the conditions that trigger this context to be active and choose different +aspects of Drupal that should react to this active context. + +Think of conditions as a set of rules that are checked during page load +to see what context is active. Any reactions that are associated with +active contexts are then fired. + + +Installation +------------ +Context can be installed like any other Drupal module -- place it in +the modules directory for your site and enable it (and its requirement, +CTools) on the `admin/modules` page. + +You will probably also want to install Context UI which provides a way for +you to edit contexts through the Drupal admin interface. + + +Example +------- +You want to create a "pressroom" section of your site. You have a press +room view that displays press release nodes, but you also want to tie +a book with media resources tightly to this section. You would also +like a contact block you've made to appear whenever a user is in the +pressroom section. + +1. Add a new context on admin/structure/context +2. Under "Conditions", associate the pressroom nodetype, the pressroom view, + and the media kit book with the context. +3. Under "Reactions > Menu", choose the pressroom menu item to be set active. +4. Under "Reactions > Blocks", add the contact block to a region. +5. Save the context. + +For a more in-depth overview of the UI components, see the Context UI +`README.txt`. + + +Upgrading from Context 2.x for Drupal 6.x +----------------------------------------- +- Download latest Context 3.x and latest CTools release and place in modules + directory. Make sure to *remove* the existing Context 2.x directory before + unpacking Context 3.x. There are stale files in the 2.x branch that need to + be removed. +- Run `update.php` or `drush updatedb`. +- If your site contains contexts defined in code they will be overridden. + Re-export them to code again. If you are using any custom conditions or + reactions, you may need to upgrade or reconfigure them by hand. See `API.txt` + for instructions on adding and extending plugins in Context. + + +Hooks +----- +See `context.api.php` for the hooks made available by context and `API.txt` for +usage examples. + + +Maintainers +----------- + +- yhahn (Young Hahn) +- jmiccolis (Jeff Miccolis) +- Steven Jones + + +Contributors +------------ + +- alex_b (Alex Barth) +- dmitrig01 (Dmitri Gaskin) +- Pasqualle (Csuthy Bálint) diff --git a/sites/all/modules/context/context.api.php b/sites/all/modules/context/context.api.php new file mode 100644 index 0000000000000000000000000000000000000000..03f0563763592f233fe9196dbd2a7ff4d41ea80a --- /dev/null +++ b/sites/all/modules/context/context.api.php @@ -0,0 +1,106 @@ +<?php + +/** + * @file + * Hooks provided by Context. + */ + +/** + * CTools plugin API hook for Context. Note that a proper entry in + * hook_ctools_plugin_api() must exist for this hook to be called. + */ +function hook_context_plugins() { + $plugins = array(); + $plugins['foo_context_condition_bar'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'foo') .'/plugins', + 'file' => 'foo_context_condition_bar.inc', + 'class' => 'foo_context_condition_bar', + 'parent' => 'context_condition', + ), + ); + $plugins['foo_context_reaction_baz'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'foo') .'/plugins', + 'file' => 'foo_context_reaction_baz.inc', + 'class' => 'foo_context_reaction_baz', + 'parent' => 'context_reaction', + ), + ); + return $plugins; +} + +/** + * Registry hook for conditions & reactions. + * + * Each entry associates a condition or reaction with the CTools plugin to be + * used as its plugin class. + */ +function hook_context_registry() { + return array( + 'conditions' => array( + 'bar' => array( + 'title' => t('Name of condition "bar"'), + 'plugin' => 'foo_context_condition_bar', + ), + ), + 'reactions' => array( + 'baz' => array( + 'title' => t('Name of reaction "baz"'), + 'plugin' => 'foo_context_reaction_baz', + ), + ), + ); +} + +/** + * Alter the registry. + * + * Allows modules to alter the registry. Default plugins can be replaced by + * custom ones declared in hook_context_plugins(). + * + * @param $registry + * The registry, passed by reference. + */ +function hook_context_registry_alter(&$registry) { + if (isset($registry['reactions']['baz'])) { + $registry['reactions']['baz']['plugin'] = 'custom_context_reaction_baz'; + } +} + +/** + * Alter/add a condition to a node-related event. + * + * Allows modules to add one or more context condition plugin executions to a + * node view, form, etc. + * + * @param $node + * The node object. + * @param $op + * The node-related operation: 'node', 'form', 'comment'. + */ +function hook_context_node_condition_alter(&$node, $op) { + if ($plugin = context_get_plugin('condition', 'bar')) { + $plugin->execute($node, $op); + } +} + +/** + * Alter a context directly after it has been loaded. Allows modules to alter + * a context object's reactions. While you may alter conditions, this will + * generally have no effect as conditions are cached for performance and + * contexts are loaded after conditions are checked, not before. + * + * @param &$context + * The context object by reference. + */ +function hook_context_load_alter(&$context) { + if ($context->name === 'foo' && isset($context->reactions['block'])) { + $context->reactions['block']['blocks']['locale-0'] = array( + 'module' => 'locale', + 'delta' => '0', + 'region' => 'header', + 'weight' => '2', + ); + } +} diff --git a/sites/all/modules/context/context.core.inc b/sites/all/modules/context/context.core.inc new file mode 100644 index 0000000000000000000000000000000000000000..e6605a73552c9b824cbbdaae45db4d0c8a2d1e4d --- /dev/null +++ b/sites/all/modules/context/context.core.inc @@ -0,0 +1,345 @@ +<?php + +/** + * Implementation of hook_help(). + */ +function context_help($path, $arg) { + switch ($path) { + case 'admin/help#context': + $output = file_get_contents(drupal_get_path('module', 'context') .'/README.txt'); + return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>'. check_plain($output) .'</pre>'; + } +} + +/** + * Implementation of hook_theme(). + */ +function context_theme() { + $items = array(); + if (!module_exists('block')) { + $items['block'] = array( + 'render element' => 'elements', + 'template' => 'block', + 'path' => drupal_get_path('module', 'block'), + 'file' => 'block.module', + 'template' => 'block', + ); + } + $items['context_block_form'] = array( + 'render element' => 'form', + 'path' => drupal_get_path('module', 'context') . '/theme', + 'file' => 'context_reaction_block.theme.inc', + ); + $items['context_block_regions_form'] = array( + 'render element' => 'form', + 'path' => drupal_get_path('module', 'context') . '/theme', + 'file' => 'context_reaction_block.theme.inc', + ); + $items['context_block_editor'] = array( + 'render element' => 'form', + 'path' => drupal_get_path('module', 'context') . '/theme', + 'file' => 'context_reaction_block.theme.inc', + ); + $items['context_block_browser'] = array( + 'variables' => array('blocks' => array(), 'context' => array()), + 'path' => drupal_get_path('module', 'context') . '/theme', + 'template' => 'context-block-browser', + 'file' => 'context_reaction_block.theme.inc', + ); + $items['context_block_browser_item'] = array( + 'variables' => array('block' => array()), + 'path' => drupal_get_path('module', 'context') . '/theme', + 'template' => 'context-block-browser-item', + 'file' => 'context_reaction_block.theme.inc', + ); + $items['context_block_script_placeholder'] = array( + 'variables' => array('text' => NULL), + 'path' => drupal_get_path('module', 'context') . '/theme', + 'file' => 'context_reaction_block.theme.inc', + ); + return $items; +} + +/** + * Implementation of hook_theme_registry_alter(). + */ +function context_theme_registry_alter(&$theme_registry) { + // Push theme_page() through a context_preprocess to provide + // context-sensitive menus and variables. Ensure that + // context_preprocess_page() comes immediately after + // template_preprocess_page(). + $position = array_search('context_preprocess_page', $theme_registry['page']['preprocess functions']); + if ($position !== FALSE) { + unset($theme_registry['page']['preprocess functions'][$position]); + } + $position = array_search('template_preprocess_page', $theme_registry['page']['preprocess functions']); + $position = $position ? $position + 1 : 2; + array_splice($theme_registry['page']['preprocess functions'], $position, 0, 'context_preprocess_page'); +} + +/** + * Implementation of hook_ctools_render_alter(). + * Used to detect the presence of a page manager node view or node form. + */ +function context_ctools_render_alter($info, $page, $data) { + extract($data); + if ($page && in_array($task['name'], array('node_view', 'node_edit'), TRUE)) { + foreach ($contexts as $ctools_context) { + if ($ctools_context->type === 'node' && !empty($ctools_context->data)) { + context_node_condition($ctools_context->data, $task['name'] === 'node_view' ? 'view' : 'form'); + break; + } + } + } +} + +/** + * Implementation of hook_entity_prepare_view(). + */ +function context_entity_prepare_view($prepare, $entity_type) { + if ($entity_type === 'taxonomy_term' && count($prepare) === 1) { + $term = reset($prepare); + if ($term === menu_get_object('taxonomy_term', 2) && $plugin = context_get_plugin('condition', 'taxonomy_term')) { + $plugin->execute($term, 'view'); + } + } +} + +/** + * Implementation of hook_node_view(). + */ +function context_node_view($node, $view_mode) { + if ($view_mode === 'full') { + $object = menu_get_object(); + if (isset($object->nid) && $object->nid === $node->nid) { + context_node_condition($node, 'view'); + } + } +} + +/** + * Implementation of hook_form_alter(). + */ +function context_form_alter(&$form, $form_state, $form_id) { + // Prevent this from firing on admin pages... damn form driven apis... + if (!empty($form['#node_edit_form']) && arg(0) != 'admin') { + context_node_condition($form['#node'], 'form'); + } + // Clear out block info cache when an admin area form is submitted. + if (arg(0) === 'admin' && !empty($form_state['input']) && isset($form_state['method']) && $form_state['method'] === 'post') { + if ($plugin = context_get_plugin('reaction', 'block')) { + $plugin->rebuild_needed(TRUE); + } + } +} + +/** + * Centralized node condition call function for the ever increasing number of + * ways to get at a node view / node form. + */ +function context_node_condition(&$node, $op) { + if ($plugin = context_get_plugin('condition', 'node')) { + $plugin->execute($node, $op); + } + if (module_exists('taxonomy')) { + if ($plugin = context_get_plugin('condition', 'node_taxonomy')) { + $plugin->execute($node, $op); + } + } + if (module_exists('book')) { + if ($plugin = context_get_plugin('condition', 'book')) { + $plugin->execute($node, $op); + } + if ($plugin = context_get_plugin('condition', 'bookroot')) { + $plugin->execute($node, $op); + } + } + // Allow other plugins to easily be triggered on node-related events. + drupal_alter('context_node_condition', $node, $op); +} + +/** + * Implementation of hook_form_alter() for system_modules_form. + */ +function context_form_system_modules_form_alter(&$form, $form_state) { + context_invalidate_cache(); +} + +/** + * Implementation of hook_form_alter() for user_profile_form. + */ +function context_form_user_profile_form_alter(&$form, $form_state) { + if ($plugin = context_get_plugin('condition', 'user_page')) { + $plugin->execute($form['#user'], 'form'); + } +} + +/** + * Implementation of hook_form_alter() for user_register_form. + */ +function context_form_user_register_form(&$form, $form_state) { + if ($plugin = context_get_plugin('condition', 'user_page')) { + $plugin->execute($form['#user'], 'register'); + } +} + +/** + * Implementation of hook_form_alter() for comment_form. + */ +function context_form_comment_form_alter(&$form, $form_state) { + if ($nid = $form['nid']['#value']) { + $node = node_load($nid); + context_node_condition($node, 'comment'); + } +} + +/** + * Implementation of hook_views_pre_view(). + */ +function context_views_pre_view($view, $display) { + if ($plugin = context_get_plugin('condition', 'views')) { + $plugin->execute($view); + } + // Support Views overrides of specific entity paths. + if ($view->display_handler->has_path()) { + switch ($view->display_handler->get_option('path')) { + case 'taxonomy/term/%': + if (($term = taxonomy_term_load(arg(2))) && ($plugin = context_get_plugin('condition', 'taxonomy_term'))) { + $plugin->execute($term, 'view'); + } + break; + case 'node/%': + if ($node = node_load(arg(1))) { + context_node_condition($node, 'view'); + } + break; + case 'user/%': + if (($account = user_load(arg(1))) && ($plugin = context_get_plugin('condition', 'user_page'))) { + $plugin->execute($account, 'view'); + } + break; + } + } +} + +/** + * Implementation of hook_user(). + */ +function context_user_view($account, $view_mode) { + if ($view_mode === 'full' && $plugin = context_get_plugin('condition', 'user_page')) { + $plugin->execute($account, 'view'); + } +} + +/** + * Implements hook_page_build(). + */ +function context_page_build(&$page) { + module_invoke_all('context_page_condition'); + module_invoke_all('context_page_reaction'); + if ($plugin = context_get_plugin('reaction', 'block')) { + $plugin->execute($page); + } + + // See block_page_build. Clear static cache b/c in overlay form submissions + // hook_page_build can get called more than once per page load. + drupal_static_reset('context_reaction_block_list'); +} + +/** + * THEME FUNCTIONS & RELATED ========================================== + */ + +/** + * Generates an array of links (suitable for use with theme_links) + * to the node forms of types associated with current active contexts. + */ +function context_links($reset = FALSE) { + static $links; + if (!$links || $reset) { + $contexts = context_active_contexts(); + $active_types = array(); + $conditions = array('node', 'bookroot'); + foreach ($conditions as $condition) { + foreach ($contexts as $k => $v) { + if (!empty($v->conditions[$condition]['values'])) { + $active_types = array_merge($active_types, array_filter($v->conditions[$condition]['values'])); + } + } + } + + $links = array(); + if (!empty($active_types)) { + // Iterate over active contexts + foreach ($active_types as $type) { + $add_url = 'node/add/'. str_replace('_', '-', $type); + $item = menu_get_item($add_url); + if ($item && $item['access'] && strpos($_GET['q'], $add_url) !== 0) { + $links[$type] = array('title' => t('Add @type', array('@type' => node_type_get_name($type))), 'href' => $add_url); + } + } + } + drupal_alter('context_links', $links); + uasort($links, 'element_sort'); + } + return $links; +} + +/** + * Implementation of hook_context_page_condition(). + */ +function context_context_page_condition() { + if ($plugin = context_get_plugin('condition', 'menu')) { + $plugin->execute(); + } + if ($plugin = context_get_plugin('condition', 'sitewide')) { + $plugin->execute(1); + } + if ($plugin = context_get_plugin('condition', 'context')) { + $plugin->execute(); + } +} + +/** + * Implementation of hook_context_page_reaction(). + */ +function context_context_page_reaction() { + if ($plugin = context_get_plugin('reaction', 'breadcrumb')) { + $plugin->execute(); + } + if ($plugin = context_get_plugin('reaction', 'css_injector')) { + $plugin->execute(); + } + if ($plugin = context_get_plugin('reaction', 'debug')) { + $plugin->execute(); + } +} + +/** + * Implementation of hook_page_alter(). + */ +function context_preprocess_page(&$vars) { + if ($plugin = context_get_plugin('reaction', 'menu')) { + $plugin->execute($vars); + } + if ($plugin = context_get_plugin('reaction', 'theme')) { + $plugin->execute($vars); + } + /* + if ($context_links = context_links()) { + $vars['context_links'] = theme('links', $context_links); + } + else { + $vars['context_links'] = ''; + } + */ +} + +/** + * Implementation of hook_preprocess_html(). + */ +function context_preprocess_html(&$vars) { + if ($plugin = context_get_plugin('reaction', 'theme_html')) { + $plugin->execute($vars); + } +} diff --git a/sites/all/modules/context/context.info b/sites/all/modules/context/context.info new file mode 100644 index 0000000000000000000000000000000000000000..05240e65a8a04a0c6097b8fe4fe109483b212366 --- /dev/null +++ b/sites/all/modules/context/context.info @@ -0,0 +1,17 @@ +name = "Context" +dependencies[] = "ctools" +description = "Provide modules with a cache that lasts for a single page request." +package = "Context" +core = "7.x" + +files[] = context.module +files[] = tests/context.test +files[] = tests/context.conditions.test +files[] = tests/context.reactions.test + +; Information added by drupal.org packaging script on 2011-02-28 +version = "7.x-3.0-beta1" +core = "7.x" +project = "context" +datestamp = "1298925068" + diff --git a/sites/all/modules/context/context.install b/sites/all/modules/context/context.install new file mode 100644 index 0000000000000000000000000000000000000000..d9ded9c38353ad594867e528ee3d46ac244e6495 --- /dev/null +++ b/sites/all/modules/context/context.install @@ -0,0 +1,392 @@ +<?php + +/** + * Implementation of hook_install(). + */ +function context_install() { + // Nothing todo... +} + +/** + * Implementation of hook_uninstall(). + */ +function context_uninstall() { + drupal_uninstall_schema('context'); + variable_del('context_ui_show_empty_regions'); + variable_del('context_reaction_block_disable_core'); + variable_del('context_reaction_block_all_regions'); +} + +/** + * Implementation of hook_schema(). + */ +function context_schema() { + $schema = array(); + $schema['context'] = array( + 'description' => 'Storage for normal (user-defined) contexts.', + 'export' => array( + 'key' => 'name', + 'identifier' => 'context', + 'default hook' => 'context_default_contexts', // Function hook name. + 'status' => 'context_status', + 'api' => array( + 'owner' => 'context', + 'api' => 'context', // Base name for api include files. + 'minimum_version' => 3, + 'current_version' => 3, + ), + 'export callback' => 'context_export', + ), + 'fields' => array( + 'name' => array( + 'description' => 'The primary identifier for a context.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'description' => array( + 'description' => 'Description for this context.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'tag' => array( + 'description' => 'Tag for this context.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'conditions' => array( + 'description' => 'Serialized storage of all context condition settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + 'reactions' => array( + 'description' => 'Serialized storage of all context reaction settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + 'condition_mode' => array( + 'description' => 'Condition mode for this context.', + 'type' => 'int', + 'default' => 0, + ), + ), + 'primary key' => array('name'), + ); + return $schema; +} + +/** + * Update script for context that installs the context schema and migrates + * any existing context data from deprecated context_ui tables. + */ +function context_update_6001() { + $ret = array(); + + if (!db_table_exists('context')) { + drupal_install_schema('context'); + } + + if (db_table_exists('context_ui')) { + // Clear the schema cache and rebuild + drupal_get_schema(NULL, TRUE); + + // Migrate existing contexts to context table + $result = db_query("SELECT * FROM {context_ui}"); + while ($context = db_fetch_object($result)) { + // Load setters + $setter_result = db_query("SELECT * FROM {context_ui_setter} WHERE cid = %d", $context->cid); + while ($row = db_fetch_object($setter_result)) { + $context->{$row->type}[$row->id] = $row->id; + } + // Load getters + $getter_result = db_query("SELECT * FROM {context_ui_getter} WHERE cid = %d", $context->cid); + while ($row = db_fetch_object($getter_result)) { + $context->{$row->type} = unserialize($row->data); + } + // Load blocks + $block_result = db_query("SELECT module, delta, region, weight FROM {context_ui_block} WHERE cid = %d", $context->cid); + while ($block = db_fetch_object($block_result)) { + if (!isset($context->block)) { + $context->block = array(); + } + $block->bid = $block->module ."_". $block->delta; + $context->block[$block->bid] = $block; + } + // Clear out identifier + unset($context->cid); + context_save_context($context); + } + } + + module_enable(array('context_contrib')); + + return $ret; +} + +/** + * Update script for API change in path condition. + */ +function context_update_6002() { + define('CONTEXT_STORAGE_DEFAULT', 0); + define('CONTEXT_STORAGE_OVERRIDDEN', 1); + define('CONTEXT_STORAGE_NORMAL', 2); + + // Iterate through all DB-stored contexts and incorporate path + // wildcards into their path conditions. Any exported/default + // contexts will need to be updated by hand. + $contexts = context_enabled_contexts(); + foreach ($contexts as $context) { + if (($context->type == CONTEXT_STORAGE_NORMAL || $context->type == CONTEXT_STORAGE_OVERRIDDEN) && (!empty($context->path) && is_array($context->path))) { + $changed = FALSE; + foreach ($context->path as $k => $v) { + if ($v != '<front>' && strpos($v, '*') === FALSE) { + $changed = TRUE; + $context->path[$k] = "{$v}*"; + } + } + if ($changed) { + context_save_context($context); + } + } + } + return array(); +} + +/** + * Remove deprecated tables from context_ui. + */ +function context_update_6003() { + $ret = array(); + $tables = array('context_ui', 'context_ui_setter', 'context_ui_getter', 'context_ui_block'); + foreach ($tables as $table) { + if (db_table_exists($table)) { + db_drop_table($ret, $table); + } + } + return $ret; +} + +/** + * Update 6301: Update schema. + */ +function context_update_6301() { + // Install CTools. + drupal_install_modules(array('ctools')); + + $schema = array( + 'fields' => array( + 'name' => array( + 'description' => 'The primary identifier for a context.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'description' => array( + 'description' => 'Description for this context.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'tag' => array( + 'description' => 'Tag for this context.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'conditions' => array( + 'description' => 'Serialized storage of all context condition settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + 'reactions' => array( + 'description' => 'Serialized storage of all context reaction settings.', + 'type' => 'text', + 'serialize' => TRUE, + ), + ), + 'primary key' => array('name'), + ); + $ret = array(); + if (db_table_exists('context')) { + $result = db_query("SELECT * FROM {context}"); + + // Migrate old contexts into new table. + $contexts = array(); + while ($context = db_fetch_object($result)) { + $data = unserialize($context->data); + unset($context->data); + foreach ($data as $k => $v) { + $context->{$k} = $v; + } + $contexts["{$context->namespace}-{$context->attribute}-{$context->value}"] = $context; + } + + // Drop the existing context table and create one using the new schema. + db_drop_table($ret, 'context'); + db_create_table($ret, 'context', $schema); + + // Migrate objects. + context_migrate_api_3($ret, $contexts); + } + return $ret; +} + +/** + * Update 6302: Update old context exportables. This update script may be + * re-run at any time to update context 2 objects that have been exported. + */ +function context_update_6302() { + $contexts = array(); + // Invoke context 2 default hooks so that the contexts can be migrated. + foreach (module_invoke_all('context_default_contexts') as $context) { + $context = (object) $context; + if (!isset($context->api_version)) { + $contexts["{$context->namespace}-{$context->attribute}-{$context->value}"] = $context; + } + } + // Migrate objects. + $ret = array(); + context_migrate_api_3($ret, $contexts); + return $ret; +} + +/** + * Update 6303: Add field for context condition mode. + */ +function context_update_6303() { + $ret = array(); + $spec = array( + 'description' => 'Condition mode for this context.', + 'type' => 'int', + 'default' => 0, + ); + db_add_field($ret, 'context', 'condition_mode', $spec); + return $ret; +} + +/** + * Update 6304: Rename variable 'context_ui_show_empty_regions'. + */ +function context_update_6304() { + if (!db_result(db_query("SELECT name FROM {variable} WHERE name = 'context_reaction_block_all_regions'"))) { + db_query("UPDATE {variable} SET name = 'context_reaction_block_all_regions' WHERE name = 'context_ui_show_empty_regions'"); + return array(array('success' => TRUE, 'query' => 'Variable renamed successfully.')); + } + return array(); +} + +/** + * Update 7000: Handle adjustments to split of theme reaction to support D7 preprocess split between _page and _html + */ + +function context_update_7000() { + $updated = array(); + $contexts = context_load(NULL, TRUE); + foreach ($contexts as $c) { + // if the old data is in the old reaction and the new reaction hasn't been saved, migrate the old data to the new reaction + if (isset($c->reactions['theme']) && + isset($c->reactions['theme']['class']) && + !empty($c->reactions['theme']['class']) && + !isset($c->reactions['theme_html']) + ) { + $c->reactions['theme_html']['class'] = $c->reactions['theme']['class']; + context_save($c); + $updated[] = $c->name; + } + } + if (empty($updated)) { + $ret = t('No contexts requiring migration detected'); + } + else { + $ret = t('The following contexts had theme reaction data migrated: @names', array('@names' => join(', ', $updated))); + } + return $ret; +} + +/** + * Helper function to update context 2 objects to context 3. + */ +function context_migrate_api_3(&$ret, $contexts) { + foreach ($contexts as $context) { + if (!db_result(db_query("SELECT name FROM {context} WHERE name = '%s'", "{$context->namespace}-{$context->attribute}-{$context->value}"))) { + $new = array( + 'name' => "{$context->namespace}-{$context->attribute}-{$context->value}", + 'description' => isset($context->description) ? $context->description : '', + 'tag' => '', + 'conditions' => array(), + 'reactions' => array(), + ); + // Migration condition/reaction settings. + // Some have been renamed. Map them. + $conditions = array( + 'node' => 'node', + 'user' => 'user', + 'book' => 'book', + 'sitewide' => 'sitewide', + 'path' => 'path', + 'menu_trail' => 'menu', + 'views' => 'views', + 'nodequeue' => 'nodequeue' + ); + foreach ($conditions as $old_key => $new_key) { + if (isset($context->{$old_key})) { + $values = $context->{$old_key}; + $new['conditions'][$new_key] = array( + 'values' => is_array($values) ? $values : array($values), + 'options' => array() + ); + } + } + $reactions = array( + 'menu' => 'menu', + 'theme_section' => 'theme', + 'css_injector' => 'css_injector', + 'block' => 'block', + ); + foreach ($reactions as $old_key => $new_key) { + if (isset($context->{$old_key})) { + // Special treatment for blocks. + if ($old_key === 'block') { + foreach ($context->block as $block) { + $block = (array)$block; + $new['reactions']['block']['blocks'][$block['module'] .'-'. $block['delta']] = $block; + } + } + else { + $new['reactions'][$new_key] = $context->{$old_key}; + } + } + } + $new['conditions'] = serialize($new['conditions']); + $new['reactions'] = serialize($new['reactions']); + + // Update_sql does not escape strings properly. + db_query("INSERT INTO {context} (name,description,tag,conditions,reactions) VALUES ('%s', '%s', '%s', '%s', '%s')", $new['name'], $new['description'], $new['tag'], $new['conditions'], $new['reactions']); + + // Notify the user of any keys that were not migrated. + $known_keys = array_merge(array_keys($conditions), array_keys($reactions), array('cid', 'system', 'namespace', 'attribute', 'value', 'description')); + $unmigrated = array_diff(array_keys((array) $context), $known_keys); + if (!empty($unmigrated)) { + $unmigrated = implode(', ', $unmigrated); + $ret[] = array( + 'success' => TRUE, + 'query' => "Updated context: {$new['name']}. The following properties could not be migrated: {$unmigrated}." + ); + } + else { + $ret[] = array( + 'success' => TRUE, + 'query' => "Updated context: {$new['name']}." + ); + } + } + } +} diff --git a/sites/all/modules/context/context.module b/sites/all/modules/context/context.module new file mode 100644 index 0000000000000000000000000000000000000000..ea1ef3097c5c5a9345b9a429802ff6866cfe085b --- /dev/null +++ b/sites/all/modules/context/context.module @@ -0,0 +1,520 @@ +<?php + +require('context.core.inc'); + +define('CONTEXT_GET', 0); +define('CONTEXT_SET', 1); +define('CONTEXT_ISSET', 2); +define('CONTEXT_CLEAR', 3); + +define('CONTEXT_CONDITION_MODE_OR', 0); +define('CONTEXT_CONDITION_MODE_AND', 1); + +/** + * Master context function. Avoid calling this directly -- use one of the helper functions below. + * + * @param $op + * The operation to perform - handled by the context helper functions. Use them. + * @param $namespace + * A string to be used as the namespace for the context information. + * @param $attribute + * Usually a string to be used as a key to set/retrieve context information. An array can + * also be used when setting context to establish an entire context namespace at once. + * (At some point objects may also be accepted, but currently functionaliy isn't complete.) + * @param $value + * A value to set for the provided key. If omitted the value will be set to true. + * + * @return + * Either the requested value, or false if the operation fails. + */ +function context_context($op = CONTEXT_GET, $namespace = NULL, $attribute = NULL, $value = NULL) { + static $context; + $context = !$context ? array() : $context; + switch ($op) { + case CONTEXT_GET: + // return entire context + if (!$namespace) { + return $context; + } + // return entire space if set + else if (isset($context[(string) $namespace])) { + // return val of key from space + if (is_array($context[(string) $namespace]) && isset($context[(string) $namespace][(string) $attribute])) { + return $context[(string) $namespace][(string) $attribute]; + } + elseif (!$attribute) { + return $context[(string) $namespace]; + } + } + break; + case CONTEXT_SET: + // bail if invalid space is specified or context is already set + if (is_string($namespace) || is_int($namespace)) { + // initialize namespace if no key is specified + if (!$attribute) { + $context[(string) $namespace] = array(); + return TRUE; + } + // set to true if key is a usable identifier. otherwise, allow a key or object to be inserted + if ($value === NULL) { + if (is_string($attribute) || is_int($attribute)) { + $context[(string) $namespace][(string) $attribute] = TRUE; + return TRUE; + } + elseif (is_array($attribute) || is_object($attribute)) { + $context[(string) $namespace] = $attribute; + return TRUE; + } + } + // set value if key is valid + if ((is_string($attribute) || is_int($attribute)) && $value !== NULL) { + $context[$namespace][$attribute] = $value; + return TRUE; + } + } + break; + case CONTEXT_ISSET: + // return entire context + if (!$namespace) return FALSE; + if (!$attribute) { + // return entire space if set + return isset($context[$namespace]); + } + // return val of key from space + return isset($context[$namespace][$attribute]); + case CONTEXT_CLEAR: + $context = array(); + return TRUE; + } + return FALSE; +} + +/** + * Sets a context by namespace + attribute. + */ +function context_set($namespace, $attribute = NULL, $value = NULL) { + return context_context(CONTEXT_SET, $namespace, $attribute, $value); +} + +/** + * Retrieves a context by namespace + (optional) attribute. + */ +function context_get($namespace = NULL, $attribute = NULL) { + return context_context(CONTEXT_GET, $namespace, $attribute, NULL); +} + +/** + * Returns a boolean for whether a context namespace + attribute have been set. + */ +function context_isset($namespace = NULL, $attribute = NULL) { + return context_context(CONTEXT_ISSET, $namespace, $attribute, NULL); +} + +/** + * Deprecated context_exists() function. Retained for backwards + * compatibility -- please use context_isset() instead. + */ +function context_exists($namespace = NULL, $attribute = NULL) { + return context_context(CONTEXT_ISSET, $namespace, $attribute, NULL); +} + +/** + * Clears static context array() -- meant only for testing + */ +function context_clear() { + return context_context(CONTEXT_CLEAR); +} + +/** + * Implemented hooks ================================================== + */ + +/** + * Implementation of hook_ctools_plugin_type(). + */ +function context_ctools_plugin_type() { + return array( + 'plugins' => array( + 'cache' => TRUE, + 'use hooks' => TRUE, + 'classes' => array('handler'), + ), + ); +} + +/** + * Implementation of hook_context_plugins(). + * + * This is a ctools plugins hook. + */ +function context_context_plugins() { + module_load_include('inc', 'context', 'context.plugins'); + return _context_context_plugins(); +} + + +/** + * Implementation of hook_context_registry(). + */ +function context_context_registry() { + module_load_include('inc', 'context', 'context.plugins'); + return _context_context_registry(); +} + +/** + * Implementation of hook_init(). + */ +function context_init() { + if ($plugin = context_get_plugin('condition', 'path')) { + $plugin->execute(); + } + if ($plugin = context_get_plugin('condition', 'language')) { + global $language; + $plugin->execute($language->language); + } + if ($plugin = context_get_plugin('condition', 'user')) { + global $user; + $plugin->execute($user); + } +} + +/** + * Load & crud functions ============================================== + */ + +/** + * Context loader. + * + * @param $name + * The name for this context object. + * + * @return + * Returns a fully-loaded context definition. + */ +function context_load($name = NULL, $reset = FALSE) { + ctools_include('export'); + static $contexts; + static $altered; + if (!isset($contexts) || $reset) { + $contexts = $altered = array(); + if (!$reset && $contexts = context_cache_get('context')) { + // Nothing here. + } + else { + if ($reset) { + ctools_export_load_object_reset('context'); + } + $contexts = ctools_export_load_object('context', 'all'); + context_cache_set('context', $contexts); + } + } + if (isset($name)) { + // Allow other modules to alter the value just before it's returned. + if (isset($contexts[$name]) && !isset($altered[$name])) { + $altered[$name] = TRUE; + drupal_alter('context_load', $contexts[$name]); + } + return isset($contexts[$name]) ? $contexts[$name] : FALSE; + } + return $contexts; +} + +/** + * Inserts or updates a context object into the database. + * @TODO: should probably return the new cid on success -- make sure + * this doesn't break any checks elsewhere. + * + * @param $context + * The context object to be inserted. + * + * @return + * Returns true on success, false on failure. + */ +function context_save($context) { + $existing = context_load($context->name, TRUE); + if ($existing && ($existing->export_type & EXPORT_IN_DATABASE)) { + drupal_write_record('context', $context, 'name'); + } + else { + drupal_write_record('context', $context); + } + context_load(NULL, TRUE); + context_invalidate_cache(); + return TRUE; +} + +/** + * Deletes an existing context. + * + * @param $context + * The context object to be deleted. + * + * @return + * Returns true on success, false on failure. + */ +function context_delete($context) { + if (isset($context->name) && ($context->export_type & EXPORT_IN_DATABASE)) { + db_query("DELETE FROM {context} WHERE name = '%s'", $context->name); + context_invalidate_cache(); + return TRUE; + } + return FALSE; +} + +/** + * Exports the specified context. + */ +function context_export($context, $indent = '') { + $output = ctools_export_object('context', $context, $indent); + $translatables = array(); + foreach (array('description', 'tag') as $key) { + if (!empty($context->{$key})) { + $translatables[] = $context->{$key}; + } + } + $translatables = array_filter(array_unique($translatables)); + if (!empty($translatables)) { + $output .= "\n"; + $output .= "{$indent}// Translatables\n"; + $output .= "{$indent}// Included for use with string extractors like potx.\n"; + sort($translatables); + foreach ($translatables as $string) { + $output .= "{$indent}t(" . ctools_var_export($string) . ");\n"; + } + } + return $output; +} + +/** + * API FUNCTIONS ====================================================== + */ + +/** + * CTools list callback for bulk export. + */ +function context_context_list() { + $contexts = context_load(NULL, TRUE); + $list = array(); + foreach ($contexts as $context) { + $list[$context->name] = $context->name; + } + return $list; +} + +/** + * Wrapper around cache_get() to make it easier for context to pull different + * datastores from a single cache row. + */ +function context_cache_get($key, $reset = FALSE) { + static $cache; + if (!isset($cache) || $reset) { + $cache = cache_get('context', 'cache'); + $cache = $cache ? $cache->data : array(); + } + return !empty($cache[$key]) ? $cache[$key] : FALSE; +} + +/** + * Wrapper around cache_set() to make it easier for context to write different + * datastores to a single cache row. + */ +function context_cache_set($key, $value) { + $cache = cache_get('context', 'cache'); + $cache = $cache ? $cache->data : array(); + $cache[$key] = $value; + cache_set('context', $cache); +} + +/** + * Wrapper around context_load() that only returns enabled contexts. + */ +function context_enabled_contexts($reset = FALSE) { + $enabled = array(); + foreach (context_load(NULL, $reset) as $context) { + if (empty($context->disabled)) { + $enabled[$context->name] = $context; + } + } + return $enabled; +} + +/** + * Queue or activate contexts that have met the specified condition. + * + * @param $context + * The context object to queue or activate. + * @param $condition + * String. Name for the condition that has been met. + * @param $reset + * Reset flag for the queue static cache. + */ +function context_condition_met($context, $condition, $reset = FALSE) { + static $queue; + if (!isset($queue) || $reset) { + $queue = array(); + } + if (!context_isset('context', $context->name)) { + // Context is using AND mode. Queue it. + if (isset($context->condition_mode) && $context->condition_mode == CONTEXT_CONDITION_MODE_AND) { + $queue[$context->name][$condition] = $condition; + + // If all conditions have been met. set the context. + if (!array_diff(array_keys($context->conditions), $queue[$context->name])) { + context_set('context', $context->name, $context); + } + } + // Context is using OR mode. Set it. + else { + context_set('context', $context->name, $context); + } + } +} + +/** + * Loads any active contexts with associated reactions. This should be run + * at a late stage of the page load to ensure that relevant contexts have been set. + */ +function context_active_contexts() { + $contexts = context_get('context'); + return !empty($contexts) && is_array($contexts) ? $contexts : array(); +} + +/** + * Loads an associative array of conditions => context identifiers to allow + * contexts to be set by different conditions. + */ +function context_condition_map($reset = FALSE) { + static $condition_map; + if (!isset($condition_map) || $reset) { + if (!$reset && $cache = context_cache_get('condition_map')) { + $condition_map = $cache; + } + else { + foreach (array_keys(context_conditions()) as $condition) { + if ($plugin = context_get_plugin('condition', $condition)) { + foreach (context_enabled_contexts() as $context) { + $values = $plugin->fetch_from_context($context, 'values'); + foreach ($values as $value) { + if (!isset($condition_map[$condition][$value])) { + $condition_map[$condition][$value] = array(); + } + $condition_map[$condition][$value][] = $context->name; + } + } + } + } + context_cache_set('condition_map', $condition_map); + } + } + return $condition_map; +} + +/** + * Invalidates all context caches(). + * @TODO: Update to use a CTools API function for clearing plugin caches + * when/if it becomes available. + */ +function context_invalidate_cache() { + cache_clear_all('context', 'cache', TRUE); + cache_clear_all('plugins:context', 'cache', TRUE); +} + +/** + * Implementation of hook_flush_caches(). + */ +function context_flush_caches() { + context_invalidate_cache(); +} + +/** + * Recursive helper function to determine whether an array and its + * children are entirely empty. + */ +function context_empty($element) { + $empty = TRUE; + if (is_array($element)) { + foreach ($element as $child) { + $empty = $empty && context_empty($child); + } + } + else { + $empty = $empty && empty($element); + } + return $empty; +} + +/** + * Get a plugin handler. + */ +function context_get_plugin($type = 'condition', $key, $reset = FALSE) { + static $cache = array(); + if (!isset($cache[$type][$key]) || $reset) { + switch ($type) { + case 'condition': + $registry = context_conditions(); + break; + case 'reaction': + $registry = context_reactions(); + break; + } + if (isset($registry[$key], $registry[$key]['plugin'])) { + ctools_include('plugins'); + $info = $registry[$key]; + $plugins = ctools_get_plugins('context', 'plugins'); + if (isset($plugins[$info['plugin']]) && $class = ctools_plugin_get_class($plugins[$info['plugin']], 'handler')) { + // Check that class exists until CTools & registry issues are resolved. + if (class_exists($class)) { + $cache[$type][$key] = new $class($key, $info); + } + } + } + } + return isset($cache[$type][$key]) ? $cache[$type][$key] : FALSE; +} + +/** + * Get all context conditions. + */ +function context_conditions($reset = FALSE) { + return _context_registry('conditions', $reset); +} + +/** + * Get all context reactions. + */ +function context_reactions($reset = FALSE) { + return _context_registry('reactions', $reset); +} + +/** + * Retrieves & caches the context registry. + */ +function _context_registry($key = NULL, $reset = FALSE) { + static $registry; + if (!isset($registry) || $reset) { + if (!$reset && $cache = context_cache_get('registry')) { + $registry = $cache; + } + else { + $registry = module_invoke_all('context_registry'); + drupal_alter('context_registry', $registry); + context_cache_set('registry', $registry); + } + } + if (isset($key)) { + return isset($registry[$key]) ? $registry[$key] : array(); + } + return $registry; +} + +/** + * hook_block_view_alter - if the context editor block is on this page, + * ensure that all blocks have some content so that empty blocks are + * not dropped + */ +function context_block_view_alter(&$data, $block) { + $context_ui_editor_present = &drupal_static('context_ui_editor_present', FALSE); + if ($context_ui_editor_present && empty($data['content'])) { + $data['content']['#markup'] = "<div class='context-block-empty'>". t('This block appears empty when displayed on this page.') . "</div>"; + } +} diff --git a/sites/all/modules/context/context.plugins.inc b/sites/all/modules/context/context.plugins.inc new file mode 100644 index 0000000000000000000000000000000000000000..d5f18f737b58bba6154c51a075fa70f5dfdd2f0c --- /dev/null +++ b/sites/all/modules/context/context.plugins.inc @@ -0,0 +1,327 @@ +<?php + +/** + * Context registry. + */ +function _context_context_registry() { + $registry = array(); + $registry['conditions'] = array( + 'context' => array( + 'title' => t('Context'), + 'description' => t('Set this context on the basis of other active contexts. Put each context on a separate line. You can use the <code>*</code> character as a wildcard and <code>~</code> to exclude one or more contexts.'), + 'plugin' => 'context_condition_context', + ), + 'node' => array( + 'title' => t('Node type'), + 'description' => t('Set this context when viewing a node page or using the add/edit form of one of these content types.'), + 'plugin' => 'context_condition_node', + ), + 'sitewide' => array( + 'title' => t('Sitewide context'), + 'description' => t('Should this context always be set? If <strong>true</strong>, this context will be active across your entire site.'), + 'plugin' => 'context_condition_sitewide', + ), + 'path' => array( + 'title' => t('Path'), + 'description' => t('Set this context when any of the paths above match the page path. Put each path on a separate line. You can use the "*" character as a wildcard and <code>~</code> to exclude one or more paths. Use <front> for the site front page.'), + 'plugin' => 'context_condition_path', + ), + 'user' => array( + 'title' => t('User role'), + 'description' => t('Set this context when the current user has one of the selected role(s).'), + 'plugin' => 'context_condition_user', + ), + 'user_page' => array( + 'title' => t('User page'), + 'description' => t('Set this context when viewing a user page.'), + 'plugin' => 'context_condition_user_page', + ), + ); + if (module_exists('menu')) { + $registry['conditions']['menu'] = array( + 'title' => t('Menu'), + 'description' => t('Set this context when any of the selected menu items belong to the current active menu trail.'), + 'plugin' => 'context_condition_menu', + ); + } + if (module_exists('views')) { + $registry['conditions']['views'] = array( + 'title' => t('Views'), + 'description' => t('Set this context when displaying the page of one of these views.'), + 'plugin' => 'context_condition_views', + ); + } + if (module_exists('book')) { + $registry['conditions']['book'] = array( + 'title' => t('Book'), + 'description' => t('Set this context when a node in the selected book is viewed.'), + 'plugin' => 'context_condition_book', + ); + $registry['conditions']['bookroot'] = array( + 'title' => t('Book root'), + 'description' => t('Set this context when viewing a node whose root book is of the selected type.'), + 'plugin' => 'context_condition_bookroot', + ); + } + if (module_exists('locale')) { + $registry['conditions']['language'] = array( + 'title' => t('Language'), + 'description' => t('Set this context when viewing the site in the selected language.'), + 'plugin' => 'context_condition_language', + ); + } + if (module_exists('taxonomy')) { + $registry['conditions']['node_taxonomy'] = array( + 'title' => t('Taxonomy'), + 'description' => t('Set this context when viewing a node with the selected taxonomy terms.'), + 'plugin' => 'context_condition_node_taxonomy', + ); + $registry['conditions']['taxonomy_term'] = array( + 'title' => t('Taxonomy term'), + 'description' => t('Set this context when viewing a taxonomy term page.'), + 'plugin' => 'context_condition_taxonomy_term', + ); + } + $registry['reactions'] = array( + 'block' => array( + 'title' => t('Blocks'), + 'description' => t('Control block visibility using context.'), + 'plugin' => 'context_reaction_block', + ), + 'breadcrumb' => array( + 'title' => t('Breadcrumb'), + 'description' => t('Set the breadcrumb trail to the selected menu item.'), + 'plugin' => 'context_reaction_breadcrumb', + ), + 'theme' => array( + 'title' => t('Theme Page'), + 'description' => t('Control page theme variables using context.'), + 'plugin' => 'context_reaction_theme', + ), + 'theme_html' => array( + 'title' => t('Theme HTML'), + 'description' => t('Control HTML theme variables using context.'), + 'plugin' => 'context_reaction_theme_html', + ), + 'debug' => array( + 'title' => t('Debug'), + 'description' => t('Debug output reaction for SimpleTest.'), + 'plugin' => 'context_reaction_debug', + ), + ); + if (module_exists('menu')) { + $registry['reactions']['menu'] = array( + 'title' => t('Menu'), + 'description' => t('Control menu active class using context.'), + 'plugin' => 'context_reaction_menu', + ); + } + if (module_exists('css_injector')) { + $registry['reactions']['css_injector'] = array( + 'title' => t('CSS Injector'), + 'description' => t('Inject the selected css when this context is set.'), + 'plugin' => 'context_reaction_css_injector', + ); + } + return $registry; +} + +/** + * Context plugins. + */ +function _context_context_plugins() { + $plugins = array(); + + /** + * Conditions. + */ + $plugins['context_condition'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition.inc', + 'class' => 'context_condition', + ), + ); + $plugins['context_condition_context'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_context.inc', + 'class' => 'context_condition_context', + 'parent' => 'context_condition_path', + ), + ); + $plugins['context_condition_node'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_node.inc', + 'class' => 'context_condition_node', + 'parent' => 'context_condition', + ), + ); + $plugins['context_condition_sitewide'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_sitewide.inc', + 'class' => 'context_condition_sitewide', + 'parent' => 'context_condition', + ), + ); + $plugins['context_condition_path'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_path.inc', + 'class' => 'context_condition_path', + 'parent' => 'context_condition', + ), + ); + $plugins['context_condition_user'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_user.inc', + 'class' => 'context_condition_user', + 'parent' => 'context_condition', + ), + ); + $plugins['context_condition_user_page'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_user_page.inc', + 'class' => 'context_condition_user_page', + 'parent' => 'context_condition', + ), + ); + $plugins['context_condition_menu'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_menu.inc', + 'class' => 'context_condition_menu', + 'parent' => 'context_condition', + ), + ); + if (module_exists('taxonomy')) { + $plugins['context_condition_node_taxonomy'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_node_taxonomy.inc', + 'class' => 'context_condition_node_taxonomy', + 'parent' => 'context_condition_node', + ), + ); + $plugins['context_condition_taxonomy_term'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_taxonomy_term.inc', + 'class' => 'context_condition_taxonomy_term', + 'parent' => 'context_condition', + ), + ); + } + if (module_exists('locale')) { + $plugins['context_condition_language'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_language.inc', + 'class' => 'context_condition_language', + 'parent' => 'context_condition', + ), + ); + } + if (module_exists('book')) { + $plugins['context_condition_book'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_book.inc', + 'class' => 'context_condition_book', + 'parent' => 'context_condition', + ), + ); + $plugins['context_condition_bookroot'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_bookroot.inc', + 'class' => 'context_condition_bookroot', + 'parent' => 'context_condition_node', + ), + ); + } + if (module_exists('views')) { + $plugins['context_condition_views'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_condition_views.inc', + 'class' => 'context_condition_views', + 'parent' => 'context_condition', + ), + ); + } + + /** + * Reactions. + */ + $plugins['context_reaction'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction.inc', + 'class' => 'context_reaction', + ), + ); + $plugins['context_reaction_block'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction_block.inc', + 'class' => 'context_reaction_block', + 'parent' => 'context_reaction', + ), + ); + $plugins['context_reaction_breadcrumb'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction_breadcrumb.inc', + 'class' => 'context_reaction_breadcrumb', + 'parent' => 'context_reaction_menu', + ), + ); + $plugins['context_reaction_menu'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction_menu.inc', + 'class' => 'context_reaction_menu', + 'parent' => 'context_reaction', + ), + ); + $plugins['context_reaction_theme'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction_theme.inc', + 'class' => 'context_reaction_theme', + 'parent' => 'context_reaction', + ), + ); + $plugins['context_reaction_theme_html'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction_theme_html.inc', + 'class' => 'context_reaction_theme_html', + 'parent' => 'context_reaction_theme', + ), + ); + $plugins['context_reaction_debug'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction_debug.inc', + 'class' => 'context_reaction_debug', + 'parent' => 'context_reaction', + ), + ); + if (module_exists('css_injector')) { + $plugins['context_reaction_css_injector'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context') .'/plugins', + 'file' => 'context_reaction_css_injector.inc', + 'class' => 'context_reaction_css_injector', + 'parent' => 'context_reaction', + ), + ); + } + return $plugins; +} diff --git a/sites/all/modules/context/context_layouts/README.txt b/sites/all/modules/context/context_layouts/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..a0b3a046428cd926c68a6271d48e4ea54e1ddac8 --- /dev/null +++ b/sites/all/modules/context/context_layouts/README.txt @@ -0,0 +1,85 @@ + +Context layouts +--------------- +Context layouts provides a formalized way for themes to declare and switch +between page templates using Context. It is a continuation of an old Drupal +themer's trick to switch to something besides the standard `page.tpl.php` file +for a variety of special-case pages like the site frontpage, login page, admin +section, etc. + + +Requirements +------------ +In order to use context layouts, your site must meet a few conditions: + +- Context and Context layouts modules are enabled (`admin/modules`). +- You are using a theme which provides and has declared multiple layouts. (See + "Example themes" for themes you can try.) + + +Basic usage +----------- +Once you have layouts enabled, you can have a context trigger the usage of a +particular layout in either the admin interface (`admin/structure/context`) or +inline context editor. Different layouts may have fewer or greater regions than +the default page template, so adjust your blocks accordingly. + + +Supporting context layouts in your theme +---------------------------------------- +You can add layouts support to your theme by declaring additional layouts in +your theme's info file. Here is an example: + +`example.info` + + name = "Example" + description = "Example theme" + core = "6.x" + engine = "phptemplate" + + regions[left] = "Left sidebar" + regions[right] = "Right sidebar" + regions[content] = "Content" + regions[footer] = "Footer" + + ; Layout: Default + layouts[default][name] = "Default" + layouts[default][description] = "Simple two column page." + layouts[default][template] = "page" + layouts[default][regions][] = "content" + layouts[default][regions][] = "right" + + ; Layout: Columns + layouts[columns][name] = "3 columns" + layouts[columns][description] = "Three column page." + layouts[columns][stylesheet] = "layout-columns.css" + layouts[columns][template] = "layout-columns" + layouts[columns][regions][] = "left" + layouts[columns][regions][] = "content" + layouts[columns][regions][] = "right" + layouts[columns][regions][] = "footer" + +Each layout is declared under `layouts` with the key as the identifier that will +be used by context for this layout. You may use any reasonable machine name for +each layout, but note that `default` is special -- it will be the default layout +for your theme if no other layout is specified. + +The following keys can be declared for each layout: + +- `name`: The human readable name for this layout, shown in the admin UI. +- `description`: A short description of your layout, same as above. +- `stylesheet`: A stylesheet to be included with the layout. Optional. +- `template`: The name of the template file for this layout, without the + `.tpl.php` extension. +- `region`: An array of regions supported by this layout. Note that any of the + regions listed here **must also be declared** in the standard theme `regions` + array. + + +Example themes +-------------- +- Cube, a subtheme included with [Rubik][1] provides a variety of layouts. +- [Ginkgo][2] the default theme included with Open Atrium. + +[1]: http://github.com/developmentseed/rubik/downloads +[2]: http://github.com/developmentseed/ginkgo/downloads diff --git a/sites/all/modules/context/context_layouts/context_layouts.info b/sites/all/modules/context/context_layouts/context_layouts.info new file mode 100644 index 0000000000000000000000000000000000000000..b6246873cd99b48b32123a2b082378e5d2d14432 --- /dev/null +++ b/sites/all/modules/context/context_layouts/context_layouts.info @@ -0,0 +1,12 @@ +name = Context layouts +description = Allow theme layer to provide multiple region layouts and integrate with context. +dependencies[] = context +package = Context +core = 7.x + +; Information added by drupal.org packaging script on 2011-02-28 +version = "7.x-3.0-beta1" +core = "7.x" +project = "context" +datestamp = "1298925068" + diff --git a/sites/all/modules/context/context_layouts/context_layouts.module b/sites/all/modules/context/context_layouts/context_layouts.module new file mode 100644 index 0000000000000000000000000000000000000000..042576803fc522196afcf08f0379f49c94007174 --- /dev/null +++ b/sites/all/modules/context/context_layouts/context_layouts.module @@ -0,0 +1,128 @@ +<?php + +/** + * Implementation of hook_help(). + */ +function context_layouts_help($path, $arg) { + switch ($path) { + case 'admin/help#context_layouts': + $output = file_get_contents(drupal_get_path('module', 'context_layouts') .'/README.txt'); + return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>'. check_plain($output) .'</pre>'; + } +} + +/** + * Implementation of hook_context_plugins(). + * This is a ctools plugins hook. + */ +function context_layouts_context_plugins() { + return array( + 'context_layouts_reaction_block' => array( + 'handler' => array( + 'path' => drupal_get_path('module', 'context_layouts') .'/plugins', + 'file' => 'context_layouts_reaction_block.inc', + 'class' => 'context_layouts_reaction_block', + 'parent' => 'context_reaction_block', + ), + ), + ); +} + +/** + * Implementation of hook_context_registry_alter(). + */ +function context_layouts_context_registry_alter(&$registry) { + if (isset($registry['reactions']['block'])) { + $registry['reactions']['block']['plugin'] = 'context_layouts_reaction_block'; + } +} + +/** + * Implementation of hook_theme(). + * Declares each theme's layouts as a page template suggestion. + */ +function context_layouts_theme() { + $info = array(); + foreach (list_themes() as $theme) { + if (!empty($theme->status) && $layouts = context_layouts_get_layouts($theme->name)) { + foreach ($layouts as $layout) { + $info["page__context_layouts_{$theme->name}_{$layout['layout']}"] = array( + 'template' => $layout['template'], + 'path' => drupal_get_path('theme', $theme->name), + ); + } + } + } + return $info; +} + +/** + * Implementation of hook_context_page_reaction(). + */ +function context_layouts_context_page_reaction() { + $plugin = context_get_plugin('reaction', 'block'); + if ($plugin && method_exists($plugin, 'add_layout_stylesheet')) { + $plugin->add_layout_stylesheet(); + } +} + +/** + * Preprocessor for theme('page'). + */ +function context_layouts_preprocess_page(&$vars) { + $plugin = context_get_plugin('reaction', 'block'); + if ($plugin && method_exists($plugin, 'add_layout_template')) { + $plugin->add_layout_template($vars); + } +} + +/** + * Retrieve layouts for the specified theme. + */ +function context_layouts_get_layouts($theme = NULL, $reset = FALSE) { + static $layouts = array(); + $layouts = $reset ? array() : $layouts; + + global $theme_key; + $theme = isset($theme) ? $theme : $theme_key; + + if (!isset($layouts[$theme])) { + $info = system_get_info('theme', $theme); + $themes = array(); + + // Find all our ancestor themes that use layouts. + if (isset($info['base theme'])) { + while (!empty($info['base theme'])) { + $base_theme = $info['base theme']; + $info = system_get_info('theme', $base_theme); + $themes[$base_theme] = $info; + } + } + + // Assemble in inheritance order and add the theme on. + $themes = array_reverse($themes); + $themes[$theme] = system_get_info('theme', $theme); + + // Merge layout info into a single array. + foreach ($themes as $key => $info) { + if (!empty($info['layouts'])) { + foreach ($info['layouts'] as $layout => $layout_info) { + $layout_info['layout'] = str_replace('-', '_', $layout); + $layout_info['theme'] = $key; + $layouts[$theme][$layout] = $layout_info; + } + } + } + } + return isset($layouts[$theme]) ? $layouts[$theme] : FALSE; +} + +/** + * Get the active layout for the current page. + */ +function context_layouts_get_active_layout($info = TRUE) { + $plugin = context_get_plugin('reaction', 'block'); + if ($plugin && method_exists($plugin, 'get_active_layout')) { + return $plugin->get_active_layout($info); + } +} diff --git a/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.css b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.css new file mode 100644 index 0000000000000000000000000000000000000000..2abbcb6afa4c5203d81faae57a8e8870089038a2 --- /dev/null +++ b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.css @@ -0,0 +1,8 @@ +#admin-toolbar div.context-editor-block-layouts { + padding:0px 0px 9px; + border-bottom:1px solid #333; + margin:0px 0px 10px; + } + +#admin-toolbar div.context-editor-block-layouts div.form-item { display:inline; } +#admin-toolbar div.context-editor-block-layouts select { width:50%; } diff --git a/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.inc b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.inc new file mode 100644 index 0000000000000000000000000000000000000000..aa2d45d272de6e7a5a6714546be84377f549d88d --- /dev/null +++ b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.inc @@ -0,0 +1,205 @@ +<?php + +class context_layouts_reaction_block extends context_reaction_block { + /** + * Override of is_enabled_region(). + * Check that there is an active layout and it supports the given region. + */ + protected function is_enabled_region($region) { + $layout = $this->get_active_layout(); + if ($layout && isset($layout['regions']) && is_array($layout['regions'])) { + return in_array($region, $layout['regions'], TRUE) && parent::is_enabled_region($region); + } + return parent::is_enabled_region($region); + } + + /** + * Retrieve the first layout specified found by any active contexts. + */ + function get_active_layout($info = TRUE) { + $contexts = $this->get_contexts(); + $layouts = context_layouts_get_layouts(); + if (!empty($contexts) && !empty($layouts)) { + foreach ($contexts as $context) { + $values = $this->fetch_from_context($context); + if (isset($values['layout']) && isset($layouts[$values['layout']])) { + return $info ? $layouts[$values['layout']] : $values['layout']; + } + } + } + // Fallback to default layout if provided. + if (isset($layouts['default'])) { + return $info ? $layouts['default'] : 'default'; + } + return FALSE; + } + + /** + * Add the layout template to page vars. + */ + function add_layout_template(&$vars) { + if ($layout = $this->get_active_layout()) { + if (!empty($layout['template'])) { + global $theme; + $vars['theme_hook_suggestion'] = "page__context_layouts_{$theme}_{$layout['layout']}"; + } + } + } + + /** + * Add the layout stylesheet to the CSS. + */ + function add_layout_stylesheet() { + if ($layout = $this->get_active_layout()) { + if (!empty($layout['stylesheet'])) { + drupal_add_css(drupal_get_path('theme', $layout['theme']) .'/'. $layout['stylesheet']); + } + } + } + + /** + * Override of editor form. + */ + function editor_form($context) { + drupal_add_css(drupal_get_path('module', 'context_layouts') .'/plugins/context_layouts_reaction_block.css'); + + $form = parent::editor_form($context); + + if ($layouts = $this->get_layout_options()) { + $options = $this->fetch_from_context($context); + $form['layout'] = array( + // #tree *must* be true for our values to be nested correctly. + '#tree' => TRUE, + '#prefix' => '<div class="context-editor-block-layouts">', + '#suffix' => '</div>', + '#weight' => -100, + 'layout' => array( + '#title' => t('Layout'), + '#options' => $layouts, + '#type' => 'select', + '#weight' => -100, + '#default_value' => isset($options['layout']) ? $options['layout'] : NULL, + '#required' => FALSE, + '#empty_value' => 0, + '#empty_option' => '- '. t('Site default') .' -', + ), + 'update' => array( + '#value' => t('Change layout'), + '#type' => 'submit', + ), + ); + } + return $form; + } + + /** + * Override of editor form submit. + */ + function editor_form_submit(&$context, $values) { + // Someone has changed the layout, assume that the block values are not actually usable here. + if (isset($context->reactions['block']['layout']) && $context->reactions['block']['layout'] != $values['layout']['layout']) { + $options = $context->reactions['block']; + } + else { + $options = parent::editor_form_submit($context, $values); + } + + if (!empty($values['layout']['layout'])) { + $options['layout'] = $values['layout']['layout']; + } + else { + unset($options['layout']); + } + return $options; + } + + /** + * Override of options form. + */ + function options_form($context) { + $form = parent::options_form($context); + $options = $this->fetch_from_context($context); + + // Only alter the options form if the theme provides layouts. + $theme_key = variable_get('theme_default', 'garland'); + $layouts = $this->get_layout_options(); + if (!empty($layouts)) { + $form['layout'] = array( + '#title' => t('Layout'), + '#description' => t('Choose one of the layouts provided by the default theme.'), + '#options' => $layouts, + '#type' => 'select', + '#weight' => -100, + '#default_value' => !empty($options['layout']) ? $options['layout'] : NULL, + '#attributes' => array('class' => array('context-blockform-layout')), + '#required' => FALSE, + '#empty_value' => 0, + '#empty_option' => '- '. t('Site default') .' -', + ); + + // Add js. + // @TODO: Move this to a theme function or somewhere that will get called even + // if the form is using a cached version of itself (e.g. when validate fails). + drupal_add_js(drupal_get_path('module', 'context_layouts') .'/plugins/context_layouts_reaction_block.js'); + drupal_add_js(array('contextLayouts' => array('layouts' => $this->get_layout_regions())), 'setting'); + } + return $form; + } + + /** + * Override of submit handler. + */ + function options_form_submit($values) { + $options = parent::options_form_submit($values); + + // Only alter the options form if the theme provides layouts. + $theme_key = variable_get('theme_default', 'garland'); + $layouts = context_layouts_get_layouts($theme_key); + + // Check that this is a valid layout. + if (!empty($values['layout']) && isset($layouts[$values['layout']])) { + $layout = $values['layout']; + $options['layout'] = $layout; + + // Remove blocks that don't belong to regions in this layout. + if (isset($layouts[$layout]['regions'])) { + foreach ($options['blocks'] as $bid => $block) { + if (!in_array($block['region'], $layouts[$layout]['regions'])) { + unset($options['blocks'][$bid]); + } + } + } + } + return $options; + } + + /** + * Get layout options for the given theme. + */ + protected function get_layout_options($theme_key = NULL) { + $theme_key = !isset($theme_key) ? variable_get('theme_default', 'garland') : $theme_key; + $layouts = context_layouts_get_layouts($theme_key); + $layout_options = array(); + if (!empty($layouts)) { + foreach ($layouts as $layout => $info) { + $layout_options[$layout] = isset($info['name']) ? $info['name'] : $layout_options; + } + } + return $layout_options; + } + + /** + * Get a layout to region map for the given theme. + */ + protected function get_layout_regions($theme_key = NULL) { + $theme_key = !isset($theme_key) ? variable_get('theme_default', 'garland') : $theme_key; + $layouts = context_layouts_get_layouts($theme_key); + if (!empty($layouts)) { + $layout_regions = array(); + foreach ($layouts as $layout => $info) { + $layout_regions[$layout] = is_array($info['regions']) ? $info['regions'] : array(); + } + } + return $layout_regions; + } +} diff --git a/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.js b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.js new file mode 100644 index 0000000000000000000000000000000000000000..decc0690ad3b7e981f2a427263f0932af1325dfc --- /dev/null +++ b/sites/all/modules/context/context_layouts/plugins/context_layouts_reaction_block.js @@ -0,0 +1,28 @@ + +(function($) { + +Drupal.behaviors.contextLayoutsReactionBlock = {}; +Drupal.behaviors.contextLayoutsReactionBlock.attach = function(context) { + // ContextBlockForm: Init. + $('.context-blockform-layout:not(.contextLayoutsProcessed)').each(function() { + $(this).addClass('contextLayoutsProcessed'); + $(this).change(function() { + var layout = $(this).val(); + if (Drupal.settings.contextLayouts.layouts[layout]) { + $('#context-blockform td.blocks table').hide(); + $('#context-blockform td.blocks div.label').hide(); + for (var key in Drupal.settings.contextLayouts.layouts[layout]) { + var region = Drupal.settings.contextLayouts.layouts[layout][key]; + $('.context-blockform-regionlabel-'+region).show(); + $('#context-blockform-region-'+region).show(); + } + if (Drupal.contextBlockForm) { + Drupal.contextBlockForm.setState(); + } + } + }); + $(this).change(); + }); +}; + +})(jQuery); \ No newline at end of file diff --git a/sites/all/modules/context/context_ui/README.txt b/sites/all/modules/context/context_ui/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..3a31d3c6d84c2dbe1a023aeacb359bde665ea631 --- /dev/null +++ b/sites/all/modules/context/context_ui/README.txt @@ -0,0 +1,67 @@ + +Context UI +---------- +Context UI provides an administrative interface for managing and editing +Contexts. It is not necessary for the proper functioning of contexts once they +are built and can be turned off on most production sites. + + +Requirements +------------ +- Context, Context UI modules enabled (`admin/modules`) +- [jQuery UI 1.x][1] and [Admin 2.x][2] to use the inline context editor. + Optional. + + +Basic usage +----------- +As a site administrator you can manage your site's contexts at +`admin/structure/context`. The main page will show you a list of the contexts +on the site and give you some options for managing each context. + +When editing or adding a new context, you will be presented with a form to +manage some basic information about the context and then alter its conditions +and reactions. + +- `name`: The name of your context. This is the main identifier for your context + and cannot be changed after you've created it. +- `description`: A description or human-readable name for your context. This is + displayed in the inline editor if available instead of the name. +- `tag`: A category for organizing contexts in the administrative context + listing. Optional. + +**Conditions** + +When certain conditions are true, your context will be made active. You can +customize the conditions that trigger the activation of your context. + +- **Condition mode**: you can choose to have your context triggered if **ANY** + conditions are met or only active when **ALL** conditions are met. +- **Adding/removing conditions**: you can add or remove to the conditions on + your context using the conditions dropdown. +- **Individual settings**: most conditions provide a simple form for selecting + individual settings for that condition. For example, the node type condition + allows you to choose which node types activate the context. + +**Reactions** + +Whenever a particular context is active, all of its reactions will be run. +Like conditions, reactions can be added or removed and have settings that can +be configured. + + +Using the inline editor +----------------------- +If you have installed jQuery UI and Admin, you can enable the inline context +editor: + +1. As an administrative user go to `admin/settings/admin`. +2. Check the 'Context editor' block and save. +3. When viewing a page with one or more active contexts, you will see the + `Context editor` in the admin toolbar. +4. You can use the context editor to adjust the conditions under which each + context is active and alter its reactions. + + +[1]: http://drupal.org/project/jquery_ui +[2]: http://drupal.org/project/admin diff --git a/sites/all/modules/context/context_ui/context_ui.css b/sites/all/modules/context/context_ui/context_ui.css new file mode 100644 index 0000000000000000000000000000000000000000..4c2231e9eaf351ed5ff44653fb41c3e099aba592 --- /dev/null +++ b/sites/all/modules/context/context_ui/context_ui.css @@ -0,0 +1,173 @@ +/** + * Editor ============================================================= + */ +div.context-editor div.label { float:left; } +div.context-editor div.links { float:right; } + +div.context-editor div.context-editable { display:none; } +div.context-editor div.links a.done { display:none; } +div.context-editor li.context-editing a.edit { display:none; } +div.context-editor li.context-editing a.done { display:block; } + +/* Don't display form submission buttons initially or when editing */ +body.context-editing div.context-editor div.links { display:none; } +body.context-editing div.context-editor li.context-editing div.links { display:block; } + +div.context-editor div.buttons { display:none; } +form.edited div.context-editor div.buttons { display:block; } +body.context-editing form.edited div.context-editor div.buttons { display:none; } + +/** + * Styles for visual integration with admin. + */ +#admin-toolbar div.context-editor div.links a:hover { + background:#eee; + color:#000; + } + +#admin-toolbar div.context-editor div.links a { + float:left; + + padding:0px 10px; + margin-right:5px; + + background:#222; + font-size:11px; + -moz-border-radius:10px; + -webkit-border-radius:10px; + } + +div.context-editor div.context-editable { padding:5px 0px; } + +#admin-toolbar div.context-editor div.buttons { + padding:10px 0px; + text-align:center; + background:#111; + } + +/** + * Horizontal + */ +#admin-toolbar.horizontal form.edited div.context-editor div.buttons, +#admin-toolbar.horizontal div.context-editor div.item-list { + float:left; + clear:left; + width:30%; + } + +#admin-toolbar.horizontal div.context-editor div.contexts { + width:69.9%; + float:right; + } + +#admin-toolbar.horizontal div.context-editor div.context-editable { padding-left:20px } + +/** + * Admin listing page ================================================= + */ +table.context-admin td.tag { font-style:italic; } + +table.context-admin td.ctools-export-ui-name { + width:75%; + padding-left:20px; + } + +table.context-admin td.ctools-export-ui-operations { white-space:nowrap; } + +table.context-admin td.ctools-export-ui-storage { color:#999; } + +table.context-admin div.description { + padding-left:10px; + margin:0px; + } + +table.context-admin input.form-text { width:90%; } + +/** + * Context form ======================================================= + */ +.context-plugins { + position:relative; + margin:0px 0px 10px; + } + +.context-plugins .context-plugin-info { + padding:10px 10px 9px; + border-bottom:1px solid #e8e8e8; + } + +.context-plugins .context-plugin-info div.description { + margin:0px 0px 10px; + padding:0px; + } + +.context-plugins .context-plugin-selector { + width:25%; + background:#fff; + } + + .context-plugins .context-plugin-list .disabled { display:none; } + + .context-plugins .context-plugin-list ul { + margin:0px; + padding:0px; + } + + .context-plugins .context-plugin-list li { + list-style:none; + list-style-image:none; + background:transparent; + + padding:0px; + margin:0px; + border:0px; + } + + .context-plugins .context-plugin-list li a { + display:block; + position:relative; + padding:5px 10px 4px; + border-bottom:1px solid #e8e8e8; + } + + .context-plugins .context-plugin-list li a.active-form { + background:#f8f8f8; + color:#333; + font-weight:bold; + } + + .context-plugins .context-plugin-list li span.remove { + display:none; + position:absolute; + top:5px; + right:10px; + + font-size:9px; + font-weight:normal; + + -moz-border-radius:5px; + -webkit-border-radius:5px; + padding:0px 5px; + background:#fff; + } + + .context-plugins .context-plugin-list li a:hover span.remove { display:block; } + +.context-plugins .context-plugin-forms { + float:right; + width:75%; + background:#f8f8f8; + min-height:200px; + } + + .context-plugins .context-plugin-forms .context-plugin-form { + padding:10px; + display:none; + } + + .context-plugins .context-plugin-forms .active-form { display:block; } + + .context-plugins .context-plugin-form .form-checkboxes { + max-height:300px; + overflow:auto; + } diff --git a/sites/all/modules/context/context_ui/context_ui.info b/sites/all/modules/context/context_ui/context_ui.info new file mode 100644 index 0000000000000000000000000000000000000000..ea628b316350bf9b2e11dda46ae64a08999d2f42 --- /dev/null +++ b/sites/all/modules/context/context_ui/context_ui.info @@ -0,0 +1,15 @@ +name = Context UI +description = "Provides a simple UI for settings up a site structure using Context." +dependencies[] = context +package = Context +core = "7.x" + +files[] = context.module +files[] = tests/context_ui.test + +; Information added by drupal.org packaging script on 2011-02-28 +version = "7.x-3.0-beta1" +core = "7.x" +project = "context" +datestamp = "1298925068" + diff --git a/sites/all/modules/context/context_ui/context_ui.install b/sites/all/modules/context/context_ui/context_ui.install new file mode 100644 index 0000000000000000000000000000000000000000..8e22dc42ba48f175fe915c39126e383cbc72c000 --- /dev/null +++ b/sites/all/modules/context/context_ui/context_ui.install @@ -0,0 +1,9 @@ +<?php + +/** + * Update 6004: Placeholder update to ensure migrations from older versions + * in Aegir do not fail. + */ +function context_ui_update_6004() { + return array(); +} diff --git a/sites/all/modules/context/context_ui/context_ui.js b/sites/all/modules/context/context_ui/context_ui.js new file mode 100644 index 0000000000000000000000000000000000000000..e85407aba453c92bcc57859135fbee70b63a28c8 --- /dev/null +++ b/sites/all/modules/context/context_ui/context_ui.js @@ -0,0 +1,148 @@ +(function($) { + +/** + * Context plugin form. + */ +function DrupalContextPlugins(form) { + this.form = form; + + // Sync the form selector and state field with the list of plugins currently enabled. + this.setState = function() { + var state = []; + $('.context-plugin-list > li', this.form).each(function() { + var plugin = $(this).attr('class').split('context-plugin-')[1].split(' ')[0]; + if ($(this).is('.disabled')) { + $('.context-plugin-selector select option[value='+plugin+']', this.form).show(); + } + else { + state.push(plugin); + $('.context-plugin-selector select option[value='+plugin+']', this.form).hide(); + } + }); + // Set the hidden plugin list state. + $('.context-plugin-selector input.context-plugins-state', this.form).val(state.join(',')); + + // Reset the selector. + $('.context-plugin-selector select', this.form).val(0); + return this; + }; + + // Add a plugin to the list. + this.addPlugin = function(plugin) { + $('.context-plugin-list > li.context-plugin-'+plugin, this.form).removeClass('disabled'); + this.showForm(plugin).setState(); + return this; + }; + + // Remove a plugin from the list. + this.removePlugin = function(plugin) { + $('.context-plugin-list > li.context-plugin-'+plugin, this.form).addClass('disabled'); + this.hideForm(plugin).setState(); + return this; + }; + + // Show a plugin form. + this.showForm = function(plugin) { + $('.context-plugin-forms > .context-plugin-form.active-form', this.form).removeClass('active-form'); + $('.context-plugin-forms > .context-plugin-form-'+plugin, this.form).addClass('active-form'); + $('.context-plugin-list > li > a').removeClass('active-form'); + $('.context-plugin-list > li.context-plugin-'+plugin+' > a').addClass('active-form'); + return this; + }; + + // Show a plugin form. + this.hideForm = function(plugin) { + $('.context-plugin-forms > .context-plugin-form-'+plugin, this.form).removeClass('active-form'); + $('.context-plugin-list > li.context-plugin-'+plugin+' > a').removeClass('active-form'); + return this; + }; + + // Select handler. + $('.context-plugin-selector select', this.form).change(function() { + var plugins = $(this).parents('div.context-plugins').data('contextPlugins'); + if (plugins) { + var plugin = $(this).val(); + plugins.addPlugin(plugin); + } + }); + + // Show form handler. + $('.context-plugin-list > li > a', this.form).click(function() { + var plugins = $(this).parents('div.context-plugins').data('contextPlugins'); + if (plugins) { + var plugin = $(this).attr('href').split('#context-plugin-form-')[1]; + plugins.showForm(plugin); + } + return false; + }); + + // Remove handler. + $('.context-plugin-list span.remove', this.form).click(function() { + var plugins = $(this).parents('div.context-plugins').data('contextPlugins'); + if (plugins) { + var plugin = $(this).parent().attr('href').split('#context-plugin-form-')[1]; + plugins.removePlugin(plugin); + } + return false; + }); + + // Set the plugin states. + this.setState(); +} + +Drupal.behaviors.context_ui = { attach: function(context) { + // Initialize context plugin form. + $('form div.context-plugins:not(.context-ui-processed)').each(function() { + $(this).addClass('context-ui-processed'); + $(this).data('contextPlugins', new DrupalContextPlugins($(this))); + }); + + // Initialize context editor. + if (jQuery().pageEditor) { + $('form.context-editor:not(.context-ui-processed)') + .addClass('context-ui-processed') + .pageEditor() + .each(function() { + var editor = $(this); + var defaultContext = $('li.context-editable', this).attr('id').split('context-editable-trigger-')[1]; + $(this).data('defaultContext', defaultContext); + + // Attach start/end handlers to editable contexts. + $('li.context-editable a.edit', editor).click(function() { + var trigger = $(this).parents('li.context-editable').addClass('context-editing'); + var context = trigger.attr('id').split('context-editable-trigger-')[1]; + editor.pageEditor('start', context); + return false; + }); + $('li.context-editable a.done', editor).click(function() { + editor.pageEditor('end'); + return false; + }); + $(editor).submit(function() { + if (editor.pageEditor('isEditing')) { + editor.pageEditor('end'); + } + }); + + // Handler for start event. + editor.bind('start.pageEditor', function(event, context) { + // Fallback to first context if param is empty. + if (!context) { + context = $(this).data('defaultContext'); + $('li#context-editable-trigger-'+context, this).addClass('context-editing'); + } + $(document.body).addClass('context-editing'); + $('#context-editable-'+context, this).show(); + }); + + // Handler for end event. + editor.bind('end.pageEditor', function(event, context) { + $(document.body).removeClass('context-editing'); + $('div.contexts div.context-editable', this).hide(); + $('li.context-editable').removeClass('context-editing'); + $('form.context-editor').addClass('edited'); + }); + }); + } +}}; +})(jQuery); diff --git a/sites/all/modules/context/context_ui/context_ui.module b/sites/all/modules/context/context_ui/context_ui.module new file mode 100644 index 0000000000000000000000000000000000000000..5fcb32ccbc7bb014eedda2eaeabf13a8418c9eb0 --- /dev/null +++ b/sites/all/modules/context/context_ui/context_ui.module @@ -0,0 +1,273 @@ +<?php + +/** + * Implementation of hook_ctools_plugin_directory(). + */ +function context_ui_ctools_plugin_directory($module, $plugin) { + if ($module == 'ctools' && $plugin == 'export_ui') { + return 'export_ui'; + } +} + +/** + * Implementation of hook_theme(). + */ +function context_ui_theme() { + $items = array(); + $items['context_ui_form'] = array( + 'render element' => 'form', + 'path' => drupal_get_path('module', 'context_ui') .'/theme', + 'template' => 'context-ui-form', + 'file' => 'theme.inc', + ); + $items['context_ui_plugins'] = array( + 'render element' => 'form', + 'path' => drupal_get_path('module', 'context_ui') .'/theme', + 'template' => 'context-ui-plugins', + 'file' => 'theme.inc', + ); + $items['context_ui_editor'] = array( + 'render element' => 'form', + 'path' => drupal_get_path('module', 'context_ui') .'/theme', + 'template' => 'context-ui-editor', + 'file' => 'theme.inc', + ); + return $items; +} + +/** + * Implementation of hook_block_info(). + */ +function context_ui_block_info() { + $blocks = array(); + $blocks['editor'] = array('info' => t('Context editor'), 'admin' => TRUE); + if (module_exists('devel')) { + $blocks['devel'] = array('info' => t('Context inspector'), 'admin' => TRUE); + } + return $blocks; +} + +/** + * Implementation of hook_block_view(). + */ +function context_ui_block_view($delta = '') { + switch ($delta) { + case 'editor': + if (user_access('administer site configuration') && strpos($_GET['q'], 'admin/structure/context') === FALSE && $contexts = context_active_contexts()) { + return array( + 'subject' => t('Context editor'), + 'content' => drupal_get_form('context_ui_editor', $contexts), + ); + } + break; + case 'devel': + if (module_exists('devel') && $all = context_get()) { + return array( + 'subject' => t('Context inspector'), + 'content' => kdevel_print_object($all), + ); + } + break; + } +} + +/** + * Implementation of hook_menu(). + */ +function context_ui_menu() { + $items = array(); + $items['admin/structure/context/settings'] = array( + 'title' => 'Settings', + 'access callback' => 'user_access', + 'access arguments' => array('administer site configuration'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('context_ui_settings'), + 'type' => MENU_LOCAL_TASK, + 'weight' => 3, + ); + return $items; +} + +/** + * Implementation of hook_help(). + */ +function context_ui_help($path, $arg) { + switch ($path) { + case 'admin/help#context_ui': + $output = file_get_contents(drupal_get_path('module', 'context_ui') .'/README.txt'); + return module_exists('markdown') ? filter_xss_admin(module_invoke('markdown', 'filter', 'process', 0, -1, $output)) : '<pre>'. check_plain($output) .'</pre>'; + case 'admin/structure/context': + return '<p>'. t('Context allows you to manage contextual conditions and reactions for different portions of your site. You can think of each context as representing a "section" of your site. For each context, you can choose the conditions that trigger this context to be active and choose different aspects of Drupal that should react to this active context.') .'</p>'; + } +} + +/** + * Inline context editor form. + */ +function context_ui_editor($form, &$form_state, $contexts) { + $form = array( + '#attributes' => array('class' => 'context-editor'), + '#theme' => array('context_ui_editor'), + 'editables' => array(), + 'contexts' => array('#tree' => TRUE), + 'buttons' => array('#tree' => FALSE), + ); + + $items = array(); + $form_context = array(); + ksort($contexts); + + foreach ($contexts as $context) { + $edit = l(t('Edit'), $_GET['q'], array('fragment' => $context->name, 'attributes' => array('class' => array('edit')))); + $done = l(t('Done'), $_GET['q'], array('fragment' => $context->name, 'attributes' => array('class' => array('done')))); + $items[] = array( + 'data' => "<div class='label'>" . (empty($context->description) ? $context->name : check_plain($context->description)) ."</div><div class='links'>{$edit} {$done}</div>", + 'class' => array('context-editable clearfix'), + 'id' => "context-editable-trigger-{$context->name}", + ); + $form_context = array( + '#tree' => TRUE, + '#type' => module_exists('admin') ? 'admin_panes' : NULL, + 'context' => array('#type' => 'value', '#value' => $context), + ); + + // Edit context conditions. + foreach (array_keys(context_conditions()) as $condition) { + $plugin = context_get_plugin('condition', $condition); + if (method_exists($plugin, 'editor_form') && ($plugin_form = $plugin->editor_form($context))) { + $form_context['condition'][$condition] = $plugin_form; + } + } + if (count(element_children($form_context['condition']))) { + $form_context['condition']['#title'] = t('Conditions'); + $form_context['condition']['#description'] = t('This context is active when any of the selected conditions are true.'); + } + + // Edit context reactions. + foreach (array_keys(context_reactions()) as $reaction) { + $plugin = context_get_plugin('reaction', $reaction); + if (method_exists($plugin, 'editor_form') && ($plugin_form = $plugin->editor_form($context))) { + $form_context["reaction-{$reaction}"] = $plugin_form + array('#title' => $plugin->title); + } + } + + // Add to main form. + $form['contexts'][$context->name] = $form_context; + } + + // Display editable contexts in list. + $form['editables']['#markup'] = theme('item_list', array('items' => $items)); + + // Buttons. + $form['buttons']['save'] = array( + '#type' => 'submit', + '#value' => t('Save changes'), + '#submit' => array('context_ui_editor_submit'), + ); + $form['buttons']['cancel'] = array( + '#type' => 'submit', + '#value' => t('Cancel'), + '#submit' => array('context_ui_editor_cancel'), + ); + return $form; +} + +/** + * Values processor for context_ui_editor_submit(). + * Split out for reuse by overriding submit handlers. + */ +function context_ui_editor_process($values) { + $context = $values['context']; + foreach (array_keys(context_conditions()) as $condition) { + if (isset($values['condition'][$condition])) { + $plugin = context_get_plugin('condition', $condition); + if ($plugin && method_exists($plugin, 'editor_form_submit')) { + $context->conditions[$condition]['values'] = $plugin->editor_form_submit($context, $values['condition'][$condition]); + } + } + if (isset($context->conditions[$condition]) && context_empty($context->conditions[$condition]['values'])) { + unset($context->conditions[$condition]); + } + } + foreach (array_keys(context_reactions()) as $reaction) { + if (isset($values["reaction-{$reaction}"])) { + $plugin = context_get_plugin('reaction', $reaction); + if ($plugin && method_exists($plugin, 'editor_form_submit')) { + $context->reactions[$reaction] = $plugin->editor_form_submit($context, $values["reaction-{$reaction}"]); + } + } + if (isset($context->reactions[$reaction]) && context_empty($context->reactions[$reaction])) { + unset($context->reactions[$reaction]); + } + } + return $context; +} + +/** + * Save handler for context_block_editor(). + */ +function context_ui_editor_submit(&$form, &$form_state) { + foreach ($form_state['values']['contexts'] as $name => $values) { + $original_conditions = $values['context']->conditions; + $original_reactions = $values['context']->reactions; + $context = context_ui_editor_process($values); + if (($original_conditions !== $context->conditions) || ($original_reactions !== $context->reactions)) { + if (context_save($context)) { + drupal_set_message(t('Saved %title.', array( + '%title' => (!empty($context->description) ? $context->description : $context->name) + ))); + } + else { + drupal_set_message(t('Could not save context %title.', array('%title' => $context->name)), 'error'); + } + } + } + return; +} + +/** + * Cancel handler for context_block_editor(). + */ +function context_ui_editor_cancel(&$form, &$form_state) { + return; +} + +/** + * Settings form. + */ +function context_ui_settings($form, &$form_state) { + $form = array(); + foreach (context_conditions() as $condition => $info) { + if ($plugin = context_get_plugin('condition', $condition)) { + $settings_form = $plugin->settings_form(); + if ($settings_form) { + $form['conditions'][$reaction] = $settings_form; + $form['conditions'][$reaction]['#tree'] = FALSE; + $form['conditions'][$reaction]['#type'] = 'fieldset'; + $form['conditions'][$reaction]['#title'] = $info['title']; + } + } + } + foreach (context_reactions() as $reaction => $info) { + if ($plugin = context_get_plugin('reaction', $reaction)) { + $settings_form = $plugin->settings_form(); + if ($settings_form) { + $form['reactions'][$reaction] = $settings_form; + $form['reactions'][$reaction]['#tree'] = FALSE; + $form['reactions'][$reaction]['#type'] = 'fieldset'; + $form['reactions'][$reaction]['#title'] = $info['title']; + } + } + } + $form = system_settings_form($form); + $form['#submit'][] = 'context_ui_settings_submit'; + return $form; +} + +/** + * Extra submit handler for context_ui_settings. + * Mark the menu cache as needing a rebuild. + */ +function context_ui_settings_submit($form, &$form_state) { + variable_set('menu_rebuild_needed', TRUE); +} diff --git a/sites/all/modules/context/context_ui/export_ui/context.inc b/sites/all/modules/context/context_ui/export_ui/context.inc new file mode 100644 index 0000000000000000000000000000000000000000..968a88b5ba60c64c44fbdcba6aa92eead80b571e --- /dev/null +++ b/sites/all/modules/context/context_ui/export_ui/context.inc @@ -0,0 +1,23 @@ +<?php + +$plugin = array( + 'schema' => 'context', + 'menu' => array( + 'menu prefix' => 'admin/structure', + 'menu item' => 'context', + 'menu title' => 'Context', + 'menu description' => 'Associate menus, views, blocks, etc. with different contexts to structure your site.', + ), + 'title singular' => t('context'), + 'title singular proper' => t('Context'), + 'title plural' => t('contexts'), + 'title plural proper' => t('Contexts'), + 'form' => array( + 'settings' => 'context_ui_form', + 'submit' => 'context_ui_form_submit', + ), + 'handler' => array( + 'class' => 'context_export_ui', + 'parent' => 'ctools_export_ui', + ), +); diff --git a/sites/all/modules/context/context_ui/export_ui/context_export_ui.class.php b/sites/all/modules/context/context_ui/export_ui/context_export_ui.class.php new file mode 100644 index 0000000000000000000000000000000000000000..30b534ffdb1ba98f53cf98e5a59120664ef46bd7 --- /dev/null +++ b/sites/all/modules/context/context_ui/export_ui/context_export_ui.class.php @@ -0,0 +1,308 @@ +<?php + +/** + * CTools export UI extending class. Slightly customized for Context. + */ +class context_export_ui extends ctools_export_ui { + function list_form(&$form, &$form_state) { + parent::list_form($form, $form_state); + $form['top row']['submit'] = $form['bottom row']['submit']; + $form['top row']['reset'] = $form['bottom row']['reset']; + $form['bottom row']['#access'] = FALSE; + // Invalidate the context cache. + context_invalidate_cache(); + return; + } + + function list_css() { + ctools_add_css('export-ui-list'); + drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css"); + } + + function list_render(&$form_state) { + $table = array( + 'header' => $this->list_table_header(), + 'rows' => $this->rows, + 'attributes' => array( + 'class' => array('context-admin'), + 'id' => 'ctools-export-ui-list-items', + ), + ); + return theme('table', $table); + } + + function list_build_row($item, &$form_state, $operations) { + $name = $item->name; + + // Add a row for tags. + $tag = !empty($item->tag) ? $item->tag : t('< Untagged >'); + if (!isset($this->rows[$tag])) { + $this->rows[$tag]['data'] = array(); + $this->rows[$tag]['data'][] = array('data' => check_plain($tag), 'colspan' => 3, 'class' => array('tag')); + $this->sorts["{$tag}"] = $tag; + } + + // Build row for each context item. + $this->rows["{$tag}:{$name}"]['data'] = array(); + $this->rows["{$tag}:{$name}"]['class'] = !empty($item->disabled) ? array('ctools-export-ui-disabled') : array('ctools-export-ui-enabled'); + $this->rows["{$tag}:{$name}"]['data'][] = array( + 'data' => check_plain($name) . "<div class='description'>" . check_plain($item->description) . "</div>", + 'class' => array('ctools-export-ui-name') + ); + $this->rows["{$tag}:{$name}"]['data'][] = array( + 'data' => check_plain($item->type), + 'class' => array('ctools-export-ui-storage') + ); + $this->rows["{$tag}:{$name}"]['data'][] = array( + 'data' => theme('links', array( + 'links' => $operations, + 'attributes' => array('class' => array('links inline')) + )), + 'class' => array('ctools-export-ui-operations'), + ); + + // Sort by tag, name. + $this->sorts["{$tag}:{$name}"] = $tag . $name; + } + + /** + * Override of edit_form_submit(). + * Don't copy values from $form_state['values']. + */ + function edit_form_submit(&$form, &$form_state) { + if (!empty($this->plugin['form']['submit'])) { + $this->plugin['form']['submit']($form, $form_state); + } + } +} + + +/** + * Generates the omnibus context definition editing form. + * + * @param $form + * Form array to populate. + * @param $form_state + * Form state array + */ +function context_ui_form(&$form, &$form_state) { + $conditions = array_keys(context_conditions()); + sort($conditions); + $reactions = array_keys(context_reactions()); + sort($reactions); + + $context = $form_state['item']; + if (!empty($form_state['input'])) { + $context = _context_ui_rebuild_from_input($context, $form_state['input'], $conditions, $reactions); + } + + $form['#base'] = 'context_ui_form'; + $form['#theme'] = 'context_ui_form'; + + // Core context definition + $form['info']['#type'] = 'fieldset'; + $form['info']['#tree'] = FALSE; + + // Swap out name validator. Allow dashes. + if (isset($form['info']['name']['#element_validate'])) { + $form['info']['name']['#element_validate'] = array('context_ui_edit_name_validate'); + } + + $form['info']['tag'] = array( + '#title' => t('Tag'), + '#type' => 'textfield', + '#required' => FALSE, + '#maxlength' => 255, + '#default_value' => isset($context->tag) ? $context->tag : '', + '#description' => t('Example: <code>theme</code>') .'<br/>'. t('A tag to group this context with others.'), + ); + + $form['info']['description'] = array( + '#title' => t('Description'), + '#type' => 'textfield', + '#required' => FALSE, + '#maxlength' => 255, + '#default_value' => isset($context->description) ? $context->description: '', + '#description' => t('The description of this context definition.'), + ); + + // Condition mode + $form['condition_mode'] = array( + '#type' => 'checkbox', + '#default_value' => isset($context->condition_mode) ? $context->condition_mode : FALSE, + '#title' => t('Require all conditions'), + '#description' => t('If checked, all conditions must be met for this context to be active. Otherwise, the first condition that is met will activate this context.') + ); + + // Condition plugin forms + $form['conditions'] = array( + '#theme' => 'context_ui_plugins', + '#title' => t('Conditions'), + '#description' => t('Trigger the activation of this context'), + '#tree' => TRUE, + 'selector' => array( + '#type' => 'select', + '#options' => array(0 => '<'. t('Add a condition') .'>'), + '#default_value' => 0, + ), + 'state' => array( + '#attributes' => array('class' => array('context-plugins-state')), + '#type' => 'hidden', + ), + 'plugins' => array('#tree' => TRUE), + ); + foreach ($conditions as $condition) { + if ($plugin = context_get_plugin('condition', $condition)) { + $form['conditions']['plugins'][$condition] = array( + '#tree' => TRUE, + '#plugin' => $plugin, + '#context_enabled' => isset($context->conditions[$condition]), // This flag is used at the theme layer. + 'values' => $plugin->condition_form($context), + 'options' => $plugin->options_form($context), + ); + $form['conditions']['selector']['#options'][$condition] = $plugin->title; + } + } + + // Reaction plugin forms + $form['reactions'] = array( + '#theme' => 'context_ui_plugins', + '#title' => t('Reactions'), + '#description' => t('Actions to take when this context is active'), + '#tree' => TRUE, + 'selector' => array( + '#type' => 'select', + '#options' => array(0 => '<'. t('Add a reaction') .'>'), + '#default_value' => 0, + ), + 'state' => array( + '#attributes' => array('class' => array('context-plugins-state')), + '#type' => 'hidden', + ), + 'plugins' => array('#tree' => TRUE), + ); + foreach ($reactions as $reaction) { + if ($plugin = context_get_plugin('reaction', $reaction)) { + $form['reactions']['plugins'][$reaction] = $plugin->options_form($context) + array( + '#plugin' => $plugin, + '#context_enabled' => isset($context->reactions[$reaction]), // This flag is used at the theme layer. + ); + $form['reactions']['selector']['#options'][$reaction] = $plugin->title; + } + } +} + +/** + * Handle the complex job of rebuilding a Context from submission data in the case of a validation error. + * + * @param $context + * The context object to modify. + * @param $input + * A form submission values + * @param $conditions + * The full list of condition plugins + * @param $reactions + * The full list of reaction plugins + * + * @return + * A context object + */ +function _context_ui_rebuild_from_input($context, $input, $conditions, $reactions) { + $condition_defaults = array(); + foreach ($conditions as $condition) { + if ($plugin = context_get_plugin('condition', $condition)) { + $condition_defaults[$condition] = array( + 'values' => $plugin->condition_form($context), + 'options' => $plugin->options_form($context), + ); + } + } + $input['conditions']['plugins'] = array_merge($condition_defaults, $input['conditions']['plugins']); + + $reaction_defaults = array(); + foreach ($reactions as $reaction) { + if ($plugin = context_get_plugin('reaction', $reaction)) { + $reaction_defaults[$reaction] = $plugin->options_form($context); + } + } + $input['reactions']['plugins'] = array_merge($reaction_defaults, $input['reactions']['plugins']); + + return context_ui_form_process($context, $input, FALSE); +} + +/** + * Modifies a context object from submitted form values. + * + * @param $context + * The context object to modify. + * @param $form + * A form array with submitted values + * @param $submit + * A flag indicating if we are building a context on submit. If on + * submit, it will clear out conditions/reactions that are empty. + * + * @return + * A context object + */ +function context_ui_form_process($context, $form, $submit = TRUE) { + $context->name = isset($form['name']) ? $form['name'] : NULL; + $context->description = isset($form['description']) ? $form['description'] : NULL; + $context->tag = isset($form['tag']) ? $form['tag'] : NULL; + $context->condition_mode = isset($form['condition_mode']) ? $form['condition_mode'] : NULL; + $context->conditions = array(); + $context->reactions = array(); + if (!empty($form['conditions'])) { + $enabled = explode(',', $form['conditions']['state']); + foreach ($form['conditions']['plugins'] as $condition => $values) { + if (in_array($condition, $enabled, TRUE) && ($plugin = context_get_plugin('condition', $condition))) { + if (isset($values['values'])) { + $context->conditions[$condition]['values'] = $plugin->condition_form_submit($values['values']); + } + if (isset($values['options'])) { + $context->conditions[$condition]['options'] = $plugin->options_form_submit($values['options']); + } + if ($submit && context_empty($context->conditions[$condition]['values'])) { + unset($context->conditions[$condition]); + } + } + } + } + if (!empty($form['reactions'])) { + $enabled = explode(',', $form['reactions']['state']); + foreach ($form['reactions']['plugins'] as $reaction => $values) { + if (in_array($reaction, $enabled, TRUE) && ($plugin = context_get_plugin('reaction', $reaction))) { + if (isset($values)) { + $context->reactions[$reaction] = $plugin->options_form_submit($values); + } + if ($submit && context_empty($context->reactions[$reaction])) { + unset($context->reactions[$reaction]); + } + } + } + } + return $context; +} + +/** + * Submit handler for main context_ui form. + */ +function context_ui_form_submit($form, &$form_state) { + $form_state['item'] = context_ui_form_process($form_state['item'], $form_state['values']); +} + +/** + * Replacement for ctools_export_ui_edit_name_validate(). Allow dashes. + */ +function context_ui_edit_name_validate($element, &$form_state) { + $plugin = $form_state['plugin']; + // Check for string identifier sanity + if (!preg_match('!^[a-z0-9_-]+$!', $element['#value'])) { + form_error($element, t('The export id can only consist of lowercase letters, underscores, dashes, and numbers.')); + return; + } + + // Check for name collision + if (empty($form_state['item']->export_ui_allow_overwrite) && $exists = ctools_export_crud_load($plugin['schema'], $element['#value'])) { + form_error($element, t('A @plugin with this name already exists. Please choose another name or delete the existing item before creating a new one.', array('@plugin' => $plugin['title singular']))); + } +} diff --git a/sites/all/modules/context/context_ui/jquery.pageEditor.js b/sites/all/modules/context/context_ui/jquery.pageEditor.js new file mode 100644 index 0000000000000000000000000000000000000000..9b1392e5bbdda52b3992b038d30b28c392c7a5bc --- /dev/null +++ b/sites/all/modules/context/context_ui/jquery.pageEditor.js @@ -0,0 +1,38 @@ + +/** + * Generic pageEditor plugin. Allows an editor DOM object to trigger + * init, start, and end events. Implementors can check whether the + * editor is currently editing and bind handlers for the events triggered + * by the editor. + */ +(function($) { + $.fn.pageEditor = function(method, data) { + this.each(function() { + switch (method) { + case 'isEditing': + return this.editing; + case 'start': + if (!this.inited) { + this.inited = true; + $(this).trigger('init.pageEditor', data); + } + this.editing = true; + $(this).trigger('start.pageEditor', data); + break; + case 'end': + if (!this.inited) { + this.inited = true; + $(this).trigger('init.pageEditor', data); + } + this.editing = false; + $(this).trigger('end.pageEditor', data); + break; + default: + this.inited = false; + this.editing = false; + break; + } + }); + return this; + }; +})(jQuery); diff --git a/sites/all/modules/context/context_ui/json2.js b/sites/all/modules/context/context_ui/json2.js new file mode 100644 index 0000000000000000000000000000000000000000..d4f454114b33cbc2d62abbd703b8f65823cabb40 --- /dev/null +++ b/sites/all/modules/context/context_ui/json2.js @@ -0,0 +1 @@ +if(!this.JSON){this.JSON={}}(function(){function f(n){return n<10?'0'+n:n}if(typeof Date.prototype.toJSON!=='function'){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+'-'+f(this.getUTCMonth()+1)+'-'+f(this.getUTCDate())+'T'+f(this.getUTCHours())+':'+f(this.getUTCMinutes())+':'+f(this.getUTCSeconds())+'Z':null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==='string'?c:'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==='object'&&typeof value.toJSON==='function'){value=value.toJSON(key)}if(typeof rep==='function'){value=rep.call(holder,key,value)}switch(typeof value){case'string':return quote(value);case'number':return isFinite(value)?String(value):'null';case'boolean':case'null':return String(value);case'object':if(!value){return'null'}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==='[object Array]'){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||'null'}v=partial.length===0?'[]':gap?'[\n'+gap+partial.join(',\n'+gap)+'\n'+mind+']':'['+partial.join(',')+']';gap=mind;return v}if(rep&&typeof rep==='object'){length=rep.length;for(i=0;i<length;i+=1){k=rep[i];if(typeof k==='string'){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}else{for(k in value){if(Object.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?': ':':')+v)}}}}v=partial.length===0?'{}':gap?'{\n'+gap+partial.join(',\n'+gap)+'\n'+mind+'}':'{'+partial.join(',')+'}';gap=mind;return v}}if(typeof JSON.stringify!=='function'){JSON.stringify=function(value,replacer,space){var i;gap='';indent='';if(typeof space==='number'){for(i=0;i<space;i+=1){indent+=' '}}else if(typeof space==='string'){indent=space}rep=replacer;if(replacer&&typeof replacer!=='function'&&(typeof replacer!=='object'||typeof replacer.length!=='number')){throw new Error('JSON.stringify')}return str('',{'':value})}}if(typeof JSON.parse!=='function'){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==='object'){for(k in value){if(Object.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return'\\u'+('0000'+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,'@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']').replace(/(?:^|:|,)(?:\s*\[)+/g,''))){j=eval('('+text+')');return typeof reviver==='function'?walk({'':j},''):j}throw new SyntaxError('JSON.parse')}}}()); \ No newline at end of file diff --git a/sites/all/modules/context/context_ui/tests/context_ui.test b/sites/all/modules/context/context_ui/tests/context_ui.test new file mode 100644 index 0000000000000000000000000000000000000000..7330e51691af44071a55fc21a28d2a57392d65ba --- /dev/null +++ b/sites/all/modules/context/context_ui/tests/context_ui.test @@ -0,0 +1,56 @@ +<?php + +/** + * Functional Test for Context UI + * + * TODO Test if menu and blocks respond. + */ + +class ContextUiTestCase extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Context UI functional tests', + 'description' => 'Create and save a context.', + 'group' => 'Context UI', + ); + } + + function setUp() { + parent::setUp('ctools', 'context', 'context_ui', 'blog'); + + // Create and login user + $admin_user = $this->drupalCreateUser(array( + 'access administration pages', + 'administer site configuration', + 'access content', + 'create blog content' + )); + $this->drupalLogin($admin_user); + } + + function testCreateContext() { + // Create context + $context = new stdClass(); + $context->name = strtolower($this->randomName(15)); + $context->description = strtolower($this->randomName(15)); + $context->tag = strtolower($this->randomName(15)); + $this->context = $context; + + $this->drupalGet('admin'); + $this->drupalGet('admin/structure'); + $this->drupalGet('admin/structure/context'); + + $edit = array( + 'name' => $context->name, + 'description' => $context->description, + 'tag' => $context->tag, + 'conditions[plugins][node][values][page]' => 'blog', + 'reactions[plugins][menu]' => 'node/add/blog', + ); + $this->drupalPost('admin/structure/context/add', $edit, 'Save'); + $this->assertText($context->name . ' has been created.', 'Context saved.'); + + } +} diff --git a/sites/all/modules/context/context_ui/theme/context-ui-editor.tpl.php b/sites/all/modules/context/context_ui/theme/context-ui-editor.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..fb1e2ea04bf83384f2bc49f9cb0d37007766c5ef --- /dev/null +++ b/sites/all/modules/context/context_ui/theme/context-ui-editor.tpl.php @@ -0,0 +1,10 @@ +<div class='context-editor clearfix'> + <?php print drupal_render_children($form) ?> + <div class='contexts'> + <?php foreach (element_children($contexts) as $context): ?> + <div class='context-editable' id='context-editable-<?php print $context ?>'><?php print drupal_render($contexts[$context]) ?></div> + <?php endforeach; ?> + <?php print drupal_render($contexts) ?> + </div> + <div class='buttons'><?php print drupal_render($buttons) ?></div> +</div> diff --git a/sites/all/modules/context/context_ui/theme/context-ui-form.tpl.php b/sites/all/modules/context/context_ui/theme/context-ui-form.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..5a726fdcb48beed03486b42f49086710c3b056b6 --- /dev/null +++ b/sites/all/modules/context/context_ui/theme/context-ui-form.tpl.php @@ -0,0 +1,2 @@ +<?php print drupal_render_children($form) ?> +<div class='buttons'><?php print drupal_render($buttons) ?></div> diff --git a/sites/all/modules/context/context_ui/theme/context-ui-plugins.tpl.php b/sites/all/modules/context/context_ui/theme/context-ui-plugins.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..f2519bf7c264c07f9745b37fef9e8c853cc5e6e2 --- /dev/null +++ b/sites/all/modules/context/context_ui/theme/context-ui-plugins.tpl.php @@ -0,0 +1,22 @@ +<div class='context-plugins clearfix'> + <div class='context-plugin-forms'> + <?php foreach (element_children($form['plugins']) as $plugin): ?> + <div class='context-plugin-form context-plugin-form-<?php print $plugin ?>'> + <?php print drupal_render($form['plugins'][$plugin]) ?> + </div> + <?php endforeach; ?> + </div> + + <div class='context-plugin-selector'> + <div class='context-plugin-info'> + <h2 class='context-plugin-title'><?php print $title ?></h2> + <div class='description'><?php print $description ?></div> + <?php print drupal_render($form['selector']) ?> + <?php print drupal_render($form['state']) ?> + </div> + <?php print theme('links', array('links' => $plugins, 'attributes' => array('class' => array('context-plugin-list')))) ?> + </div> + + <?php print drupal_render_children($form) ?> + +</div> diff --git a/sites/all/modules/context/context_ui/theme/theme.inc b/sites/all/modules/context/context_ui/theme/theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..7d969e4175bcaf539f8bd622aea2d40a1a9fb8e6 --- /dev/null +++ b/sites/all/modules/context/context_ui/theme/theme.inc @@ -0,0 +1,54 @@ +<?php + +/** + * Preprocessor for theme('context_ui_editor'). + */ +function template_preprocess_context_ui_editor(&$vars) { + drupal_add_css(drupal_get_path('module', 'context_ui') . '/context_ui.css'); + drupal_add_js(drupal_get_path('module', 'context_ui') . '/context_ui.js'); + drupal_add_js(drupal_get_path('module', 'context_ui') . '/jquery.pageEditor.js'); + + $vars['contexts'] = $vars['form']['contexts']; + unset($vars['form']['contexts']); + + $vars['buttons'] = $vars['form']['buttons']; + unset($vars['form']['buttons']); +} + +/** + * Preprocessor for theme('context_ui_plugins'). + */ +function template_preprocess_context_ui_plugins(&$vars) { + drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css"); + drupal_add_js(drupal_get_path("module", "context_ui") ."/context_ui.js"); + drupal_add_js(drupal_get_path('module', 'context_ui') . '/jquery.pageEditor.js'); + + // Provide title & desc. + $vars['title'] = check_plain($vars['form']['#title']); + $vars['description'] = check_plain($vars['form']['#description']); + + // Build list of plugins. + $plugins = array(); + $vars['plugins'] = array(); + foreach (element_children($vars['form']['plugins']) as $plugin) { + $link = array( + 'title' => $vars['form']['plugins'][$plugin]['#plugin']->title . "<span class='remove'>". t('Remove') ."</span>", + 'href' => $_GET['q'], + 'html' => TRUE, + 'fragment' => "context-plugin-form-{$plugin}", + ); + $class = $vars['form']['plugins'][$plugin]['#context_enabled'] ? "context-plugin-{$plugin}" : "context-plugin-{$plugin} disabled"; + $vars['plugins'][$class] = $link; + } +} + +/** + * Preprocessor for theme('context_ui_form'). + */ +function template_preprocess_context_ui_form(&$vars) { + drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css"); + drupal_add_js(drupal_get_path("module", "context_ui") ."/context_ui.js"); + drupal_add_js(drupal_get_path('module', 'context_ui') . '/jquery.pageEditor.js'); + $vars['buttons'] = $vars['form']['buttons']; + unset($vars['form']['buttons']); +} diff --git a/sites/all/modules/context/plugins/context_condition.inc b/sites/all/modules/context/plugins/context_condition.inc new file mode 100644 index 0000000000000000000000000000000000000000..95951d9bbd0a088665dd5c85b747ac0759b43b14 --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition.inc @@ -0,0 +1,196 @@ +<?php + +/** + * Base class for a context condition. + */ +class context_condition { + var $plugin; + var $title; + var $description; + var $values = array(); + + /** + * Clone our references when we're being cloned. + * + * PHP 5.3 performs 'shallow' clones when clone()'ing objects, meaning that + * any objects or arrays referenced by this object will not be copied, the + * cloned object will just reference our objects/arrays instead. By iterating + * over our properties and serializing and unserializing them, we force PHP to + * copy them. + */ + function __clone() { + foreach ($this as $key => $val) { + if (is_object($val) || (is_array($val))) { + $this->{$key} = unserialize(serialize($val)); + } + } + } + + /** + * Constructor. Do not override. + */ + function __construct($plugin, $info) { + $this->plugin = $plugin; + $this->title = isset($info['title']) ? $info['title'] : $plugin; + $this->description = isset($info['description']) ? $info['description'] : ''; + } + + /** + * Condition values. + */ + function condition_values() { + return array(); + } + + /** + * Condition form. + */ + function condition_form($context) { + return array( + '#title' => $this->title, + '#description' => $this->description, + '#options' => $this->condition_values(), + '#type' => 'checkboxes', + '#default_value' => $this->fetch_from_context($context, 'values'), + ); + } + + /** + * Condition form submit handler. + */ + function condition_form_submit($values) { + ksort($values); + + // Editor forms are generally checkboxes -- do some checkbox processing. + return drupal_map_assoc(array_keys(array_filter($values))); + } + + /** + * Options form. Provide additional options for your condition. + */ + function options_form($context) { + return array(); + } + + /** + * Options form submit handler. + */ + function options_form_submit($values) { + return $values; + } + + /** + * Settings form. Provide variable settings for your condition. + */ + function settings_form() { + return array(); + } + + /** + * Context editor form for conditions. + */ + function editor_form($context = NULL) { + $form = array(); + if (!empty($this->values)) { + $options = $this->condition_values(); + foreach ($this->values as $value => $contexts) { + $label = "{$this->title}: "; + $label .= isset($options[$value]) ? trim($options[$value], ' -') : $value; + $form[$value] = array( + '#type' => 'checkbox', + '#title' => check_plain($label), + '#default_value' => empty($context->name) ? TRUE : in_array($context->name, $contexts, TRUE), + ); + } + } + return $form; + } + + /** + * Context editor form submit handler. + */ + function editor_form_submit(&$context, $values) { + // Merge existing values in from non-active conditions. + $existing = $this->fetch_from_context($context, 'values'); + $values += !empty($existing) ? $existing : array(); + + ksort($values); + + // Editor forms are generally checkboxes -- do some checkbox processing. + return drupal_map_assoc(array_keys(array_filter($values))); + } + + /** + * Public method that is called from hooks or other integration points with + * Drupal. Note that it is not implemented in the base class, allowing + * extending classes to change the function signature if necessary. + * + * function execute($value) { + * foreach ($this->get_contexts($value) as $context) { + * $this->condition_met($context, $value); + * } + * } + */ + + /** + * Marks a context as having met this particular condition. + */ + function condition_met($context, $value = NULL) { + if (isset($value)) { + $this->values[$value] = isset($this->values[$value]) ? $this->values[$value] : array(); + $this->values[$value][] = $context->name; + } + context_condition_met($context, $this->plugin); + } + + /** + * Check whether this condition is used by any contexts. Can be used to + * prevent expensive condition checks from being triggered when no contexts + * use this condition. + */ + function condition_used() { + $map = context_condition_map(); + return !empty($map[$this->plugin]); + } + + /** + * Retrieve all contexts with the condition value provided. + */ + function get_contexts($value = NULL) { + $map = context_condition_map(); + $map = isset($map[$this->plugin]) ? $map[$this->plugin] : array(); + + $contexts = array(); + if (isset($value) && (is_string($value) || is_numeric($value))) { + if (isset($map[$value]) && is_array($map[$value])) { + foreach ($map[$value] as $name) { + if (!isset($contexts[$name])) { + $context = context_load($name); + $contexts[$context->name] = $context; + } + } + } + } + else { + foreach ($map as $submap) { + foreach ($submap as $name) { + if (!isset($contexts[$name])) { + $context = context_load($name); + $contexts[$context->name] = $context; + } + } + } + } + return $contexts; + } + + /** + * Retrieve options from the context provided. + */ + function fetch_from_context($context, $key = NULL) { + if (isset($key)) { + return isset($context->conditions[$this->plugin][$key]) ? $context->conditions[$this->plugin][$key] : array(); + } + return isset($context->conditions[$this->plugin]) ? $context->conditions[$this->plugin] : array(); + } +} diff --git a/sites/all/modules/context/plugins/context_condition_book.inc b/sites/all/modules/context/plugins/context_condition_book.inc new file mode 100644 index 0000000000000000000000000000000000000000..24fe76903d594454cc0fd3446bad6a98e2bf0d31 --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_book.inc @@ -0,0 +1,22 @@ +<?php + +/** + * Expose book properties as a context condition. + */ +class context_condition_book extends context_condition { + function condition_values() { + $values = array(); + foreach (book_get_books() as $book) { + $values[$book['menu_name']] = check_plain($book['title']); + } + return $values; + } + + function execute($node, $op) { + if (isset($node->book, $node->book['menu_name'])) { + foreach ($this->get_contexts($node->book['menu_name']) as $context) { + $this->condition_met($context, $node->book['menu_name']); + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_bookroot.inc b/sites/all/modules/context/plugins/context_condition_bookroot.inc new file mode 100644 index 0000000000000000000000000000000000000000..200326ceb6291d4ba22bdc8e8d2af40a233eedb7 --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_bookroot.inc @@ -0,0 +1,19 @@ +<?php + +/** + * Set the context on the basis of the node type of page's book root. + */ +class context_condition_bookroot extends context_condition_node { + function execute($node, $op) { + if ($this->condition_used() && !empty($node->book['bid'])) { + $type = db_select('node') + ->fields('node', array('type')) + ->condition('nid', $node->book['nid']) + ->execute() + ->fetchField(); + $book = new stdClass(); + $book->type = $type; + parent::execute($book, $op); + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_context.inc b/sites/all/modules/context/plugins/context_condition_context.inc new file mode 100644 index 0000000000000000000000000000000000000000..b9140705b6687705e3feb183cbac10cc1dd030aa --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_context.inc @@ -0,0 +1,23 @@ +<?php + +/** + * Expose active contexts as a context condition. + */ +class context_condition_context extends context_condition_path { + function execute() { + if ($this->condition_used()) { + $active_contexts = array_keys(context_active_contexts()); + foreach ($this->get_contexts() as $context) { + if (!in_array($context->name, $active_contexts, TRUE) && $values = $this->fetch_from_context($context, 'values')) { + if ($this->match($active_contexts, $values)) { + $this->condition_met($context); + } + } + } + // If the list of active contexts has changed, we need to recurse. + if ($active_contexts != array_keys(context_active_contexts())) { + $this->execute(); + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_language.inc b/sites/all/modules/context/plugins/context_condition_language.inc new file mode 100644 index 0000000000000000000000000000000000000000..2d9cc43e0948d3f22adc917f9b0b1fe35e77ae0e --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_language.inc @@ -0,0 +1,16 @@ +<?php + +/** + * Expose current language as a context condition. + */ +class context_condition_language extends context_condition { + function condition_values() { + return module_exists('locale') ? locale_language_list() : array(); + } + + function execute($value) { + foreach ($this->get_contexts($value) as $context) { + $this->condition_met($context, $value); + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_menu.inc b/sites/all/modules/context/plugins/context_condition_menu.inc new file mode 100644 index 0000000000000000000000000000000000000000..cb58c54d0ec396e23d8beb89f92059724c9b6f6e --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_menu.inc @@ -0,0 +1,78 @@ +<?php + +/** + * Expose menu items as a context condition. + */ +class context_condition_menu extends context_condition { + /** + * Override of condition_values(). + */ + function condition_values() { + if (module_exists('menu')) { + $menus = menu_parent_options(menu_get_menus(), array('mlid' => 0)); + $root_menus = array(); + foreach ($menus as $key => $name) { + $id = explode(':', $key); + if ($id[1] == '0') { + $root_menus[$id[0]] = check_plain($name); + } + else { + $link = menu_link_load($id[1]); + $identifier = $link['link_path']; + $root_menu = $root_menus[$id[0]]; + while (isset($menus[$root_menu][$identifier])) { + $identifier .= "'"; + } + $menus[$root_menu][$identifier] = $name; + } + unset($menus[$key]); + } + array_unshift($menus, "-- ". t('None') ." --"); + } + else { + $menus = array(); + } + return $menus; + } + + /** + * Override of condition_form(). + * Use multiselect widget. + */ + function condition_form($context) { + $form = parent::condition_form($context); + $form['#type'] = 'select'; + $form['#multiple'] = TRUE; + return $form; + } + + /** + * Override of condition_form_submit(). + * Trim any identifier padding for non-unique path menu items. + */ + function condition_form_submit($values) { + // Trim any identifier padding for non-unique path menu items. + $values = parent::condition_form_submit($values); + $trimmed = array(); + foreach ($values as $key => $value) { + $trimmed[trim($key, "'")] = trim($value, "'"); + } + return $trimmed; + } + + /** + * Override of execute(). + */ + function execute() { + if ($this->condition_used()) { + $trail = menu_get_active_trail(); + foreach ($trail as $item) { + if (!empty($item['href'])) { + foreach ($this->get_contexts($item['href']) as $context) { + $this->condition_met($context, $item['href']); + } + } + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_node.inc b/sites/all/modules/context/plugins/context_condition_node.inc new file mode 100644 index 0000000000000000000000000000000000000000..6c2c534c21f35057e003c00671b92378cbec70aa --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_node.inc @@ -0,0 +1,62 @@ +<?php + +/** + * Trigger context on node view only. + */ +define('CONTEXT_NODE_VIEW', 0); + +/** + * Trigger context on node view and node form. + */ +define('CONTEXT_NODE_FORM', 1); + +/** + * Trigger context on node form only. + */ +define('CONTEXT_NODE_FORM_ONLY', 2); + +/** + * Expose node views/node forms of specific node types as a context condition. + */ +class context_condition_node extends context_condition { + function condition_values() { + $values = array(); + foreach (node_type_get_types() as $type) { + $values[$type->type] = check_plain($type->name); + } + return $values; + } + + function options_form($context) { + $defaults = $this->fetch_from_context($context, 'options'); + return array( + 'node_form' => array( + '#title' => t('Set on node form'), + '#type' => 'select', + '#options' => array( + CONTEXT_NODE_VIEW => t('No'), + CONTEXT_NODE_FORM => t('Yes'), + CONTEXT_NODE_FORM_ONLY => t('Only on node form') + ), + '#description' => t('Set this context on node forms'), + '#default_value' => isset($defaults['node_form']) ? $defaults['node_form'] : TRUE, + ), + ); + } + + function execute($node, $op) { + foreach ($this->get_contexts($node->type) as $context) { + // Check the node form option. + $options = $this->fetch_from_context($context, 'options'); + if ($op === 'form') { + $options = $this->fetch_from_context($context, 'options'); + if (!empty($options['node_form']) && in_array($options['node_form'], array(CONTEXT_NODE_FORM, CONTEXT_NODE_FORM_ONLY))) { + $this->condition_met($context, $node->type); + } + } + elseif (empty($options['node_form']) || $options['node_form'] != CONTEXT_NODE_FORM_ONLY) { + $this->condition_met($context, $node->type); + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_node_taxonomy.inc b/sites/all/modules/context/plugins/context_condition_node_taxonomy.inc new file mode 100644 index 0000000000000000000000000000000000000000..ec976943dda36920816261632489ac2a9f5c038d --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_node_taxonomy.inc @@ -0,0 +1,73 @@ +<?php + +/** + * Expose node taxonomy terms as a context condition. + */ +class context_condition_node_taxonomy extends context_condition_node { + function condition_values() { + $values = array(); + if (module_exists('taxonomy')) { + foreach (taxonomy_get_vocabularies() as $vocab) { + if (empty($vocab->tags)) { + foreach (taxonomy_get_tree($vocab->vid) as $term) { + $values[$term->tid] = check_plain($term->name); + } + } + } + } + return $values; + } + + function condition_form($context) { + $form = parent::condition_form($context); + $form['#type'] = 'select'; + $form['#size'] = 12; + $form['#multiple'] = TRUE; + $vocabularies = taxonomy_get_vocabularies(); + $options = array(); + foreach ($vocabularies as $vid => $vocabulary) { + $tree = taxonomy_get_tree($vid); + if ($tree && (count($tree) > 0)) { + $options[$vocabulary->name] = array(); + foreach ($tree as $term) { + $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name; + } + } + } + $form['#options'] = $options; + return $form; + } + + function execute($node, $op) { + // build a list of each taxonomy reference field belonging to the bundle for the current node + $fields = field_info_fields(); + $instance_fields = field_info_instances('node', $node->type); + $check_fields = array(); + foreach ($instance_fields as $key => $field_info) { + if ($fields[$key]['type'] == 'taxonomy_term_reference') { + $check_fields[] = $key; + } + } + + if ($this->condition_used() && !empty($check_fields)) { + foreach ($check_fields as $field) { + if (isset($node->{$field}[$node->language])) { + foreach ($node->{$field}[$node->language] as $term) { + foreach ($this->get_contexts($term['tid']) as $context) { + // Check the node form option. + if ($op === 'form') { + $options = $this->fetch_from_context($context, 'options'); + if (!empty($options['node_form'])) { + $this->condition_met($context, $term['tid']); + } + } + else { + $this->condition_met($context, $term['tid']); + } + } + } + } + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_path.inc b/sites/all/modules/context/plugins/context_condition_path.inc new file mode 100644 index 0000000000000000000000000000000000000000..0babde962fce4340b021e2b9259057389542f052 --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_path.inc @@ -0,0 +1,116 @@ +<?php + +/** + * Expose paths as a context condition. + */ +class context_condition_path extends context_condition { + /** + * Omit condition values. We will provide a custom input form for our conditions. + */ + function condition_values() { + return array(); + } + + /** + * Condition form. + */ + function condition_form($context) { + $form = parent::condition_form($context); + unset($form['#options']); + + $form['#type'] = 'textarea'; + $form['#default_value'] = implode("\n", $this->fetch_from_context($context, 'values')); + return $form; + } + + /** + * Condition form submit handler. + */ + function condition_form_submit($values) { + $parsed = array(); + $items = explode("\n", $values); + if (!empty($items)) { + foreach ($items as $v) { + $v = trim($v); + if (!empty($v)) { + $parsed[$v] = $v; + } + } + } + return $parsed; + } + + /** + * Execute. + */ + function execute() { + if ($this->condition_used()) { + // Include both the path alias and normal path for matching. + $current_path = array(drupal_get_path_alias($_GET['q'])); + if ($current_path != $_GET['q']) { + $current_path[] = $_GET['q']; + } + foreach ($this->get_contexts() as $context) { + $paths = $this->fetch_from_context($context, 'values'); + if ($this->match($current_path, $paths, TRUE)) { + $this->condition_met($context); + } + } + } + } + + /** + * Match the subject against a set of regex patterns. + * Similar to drupal_match_path() but also handles negation through the use + * of the ~ character. + * + * @param mixed $subject + * The subject string or an array of strings to be matched. + * @param array $patterns + * An array of patterns. Any patterns that begin with ~ are considered + * negative or excluded conditions. + * @param boolean $path + * Whether the given subject should be matched as a Drupal path. If TRUE, + * '<front>' will be replaced with the site frontpage when matching against + * $patterns. + */ + protected function match($subject, $patterns, $path = FALSE) { + static $regexps; + $match = FALSE; + $positives = $negatives = 0; + $subject = !is_array($subject) ? array($subject) : $subject; + foreach ($patterns as $pattern) { + if (strpos($pattern, '~') === 0) { + $negate = TRUE; + $negatives++; + } + else { + $negate = FALSE; + $positives++; + } + $pattern = ltrim($pattern, '~'); + if (!isset($regexps[$pattern])) { + if ($path) { + $regexps[$pattern] = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote($pattern, '/')) .')$/'; + } + else { + $regexps[$pattern] = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/'), array('|', '.*'), preg_quote($pattern, '/')) .')$/'; + } + } + foreach ($subject as $value) { + if (preg_match($regexps[$pattern], $value)) { + if ($negate) { + return FALSE; + } + $match = TRUE; + } + } + } + // If there are **only** negative conditions and we've gotten this far none + // we actually have a match. + if ($positives === 0 && $negatives) { + return TRUE; + } + return $match; + } +} diff --git a/sites/all/modules/context/plugins/context_condition_sitewide.inc b/sites/all/modules/context/plugins/context_condition_sitewide.inc new file mode 100644 index 0000000000000000000000000000000000000000..11e64c1d4646c41cf4dc16bf528a599fbdac6b4d --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_sitewide.inc @@ -0,0 +1,23 @@ +<?php + +/** + * Simple sitewide context, always present. + */ +class context_condition_sitewide extends context_condition { + function condition_values() { + return array(1 => t('Always active')); + } + + function editor_form($context = NULL) { + $form = parent::editor_form($context); + $form[1]['#title'] = t('Always active'); + $form['#weight'] = -10; + return $form; + } + + function execute($value) { + foreach ($this->get_contexts($value) as $context) { + $this->condition_met($context, $value); + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_taxonomy_term.inc b/sites/all/modules/context/plugins/context_condition_taxonomy_term.inc new file mode 100644 index 0000000000000000000000000000000000000000..e3e0478703449263d538ccf29ace3fbf83d17399 --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_taxonomy_term.inc @@ -0,0 +1,47 @@ +<?php + +/** + * Expose term views/term forms by vocabulary as a context condition. + */ +class context_condition_taxonomy_term extends context_condition { + function condition_values() { + $values = array(); + foreach (taxonomy_get_vocabularies() as $vocab) { + $values[$vocab->machine_name] = check_plain($vocab->name); + } + return $values; + } + + function options_form($context) { + $defaults = $this->fetch_from_context($context, 'options'); + return array( + 'term_form' => array( + '#title' => t('Set on term form'), + '#type' => 'select', + '#options' => array( + 0 => t('No'), + 1 => t('Yes'), + 2 => t('Only on term form') + ), + '#description' => t('Set this context on term forms'), + '#default_value' => isset($defaults['term_form']) ? $defaults['term_form'] : TRUE, + ), + ); + } + + function execute($term, $op) { + foreach ($this->get_contexts($term->vocabulary_machine_name) as $context) { + // Check the node form option. + $options = $this->fetch_from_context($context, 'options'); + if ($op === 'form') { + $options = $this->fetch_from_context($context, 'options'); + if (!empty($options['term_form']) && in_array($options['term_form'], array(1, 2))) { + $this->condition_met($context, $term->vocabulary_machine_name); + } + } + elseif (empty($options['term_form']) || $options['term_form'] != 2) { + $this->condition_met($context, $term->vocabulary_machine_name); + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_user.inc b/sites/all/modules/context/plugins/context_condition_user.inc new file mode 100644 index 0000000000000000000000000000000000000000..f7184f007d8c1ffd92bfcf8ce5938f9ad0e124b0 --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_user.inc @@ -0,0 +1,31 @@ +<?php + +/** + * Expose current user role as a context condition. + */ +class context_condition_user extends context_condition { + function condition_values() { + $values = array(); + foreach (user_roles() as $rid => $role_name) { + if ($rid == DRUPAL_ANONYMOUS_RID) { + $values['anonymous user'] = check_plain($role_name); + } + elseif ($rid == DRUPAL_AUTHENTICATED_RID) { + $values['authenticated user'] = check_plain($role_name); + } + else { + $values[$role_name] = check_plain($role_name); + } + } + return $values; + } + + function execute($account) { + $roles = $account->roles; + foreach ($roles as $rid => $role) { + foreach ($this->get_contexts($role) as $context) { + $this->condition_met($context, $role); + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_user_page.inc b/sites/all/modules/context/plugins/context_condition_user_page.inc new file mode 100644 index 0000000000000000000000000000000000000000..4e1596b884bd2a859fb5f8ee907ac11904191b13 --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_user_page.inc @@ -0,0 +1,59 @@ +<?php + +/** + * Expose user pages as a context condition. + */ +class context_condition_user_page extends context_condition { + function condition_values() { + $values = array(); + $values['view'] = t('User profile'); + $values['form'] = t('User account form'); + $values['register'] = t('Registration form'); + return $values; + } + + function options_form($context) { + $defaults = $this->fetch_from_context($context, 'options'); + return array( + 'mode' => array( + '#title' => t('Active for'), + '#type' => 'select', + '#options' => array( + 'all' => t('Any user'), + 'current' => t('Only the current user'), + 'other' => t('Only other users'), + ), + '#default_value' => isset($defaults['mode']) ? $defaults['mode'] : 'all', + ), + ); + } + + function execute($account, $op) { + global $user; + foreach ($this->get_contexts($op) as $context) { + if ($op === 'register') { + $this->condition_met($context); + } + else { + $options = $this->fetch_from_context($context, 'options'); + $mode = !empty($options['mode']) ? $options['mode'] : 'all'; + switch ($options['mode']) { + case 'current': + if ($account->uid == $user->uid) { + $this->condition_met($context); + } + break; + case 'other': + if ($account->uid != $user->uid) { + $this->condition_met($context); + } + break; + case 'all': + default: + $this->condition_met($context); + break; + } + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_condition_views.inc b/sites/all/modules/context/plugins/context_condition_views.inc new file mode 100644 index 0000000000000000000000000000000000000000..939a472c5b2506cf451a63046894b9f5ffe7f3db --- /dev/null +++ b/sites/all/modules/context/plugins/context_condition_views.inc @@ -0,0 +1,48 @@ +<?php + +class context_condition_views extends context_condition { + + /** + * Generate a list of database and module provided views. + */ + function condition_values() { + $enabled_views = array(); + + $views = views_get_all_views(); + ksort($views); + + foreach ($views as $view) { + if (!isset($views[$view->name]->disabled) || !$views[$view->name]->disabled) { + $enabled_views[$view->name] = check_plain($view->name); + + // Provide more granular options for each page display + $displays = array(); + foreach ($view->display as $id => $display) { + if ($display->display_plugin == 'page') { + $displays[$view->name .":". $id] = check_plain("-- {$display->display_title}"); + } + } + $enabled_views += $displays; + } + } + return $enabled_views; + } + + function execute($view) { + switch ($view->display_handler->display->display_plugin) { + case 'page': + case 'calendar': + // Set contexts for this view. + foreach ($this->get_contexts($view->name) as $context) { + $this->condition_met($context, $view->name); + } + // Set any contexts associated with the current display + if (!empty($view->current_display)) { + foreach ($this->get_contexts("{$view->name}:{$view->current_display}") as $context) { + $this->condition_met($context, "{$view->name}:{$view->current_display}"); + } + } + break; + } + } +} diff --git a/sites/all/modules/context/plugins/context_reaction.inc b/sites/all/modules/context/plugins/context_reaction.inc new file mode 100644 index 0000000000000000000000000000000000000000..5c291da1ea16898444259290d8029ef0b07d803c --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction.inc @@ -0,0 +1,85 @@ +<?php + +/** + * Base class for a context reaction. + */ +class context_reaction { + var $plugin; + var $title; + var $description; + + /** + * Clone our references when we're being cloned. + * + * PHP 5.3 performs 'shallow' clones when clone()'ing objects, meaning that + * any objects or arrays referenced by this object will not be copied, the + * cloned object will just reference our objects/arrays instead. By iterating + * over our properties and serializing and unserializing them, we force PHP to + * copy them. + */ + function __clone() { + foreach ($this as $key => $val) { + if (is_object($val) || (is_array($val))) { + $this->{$key} = unserialize(serialize($val)); + } + } + } + + /** + * Constructor. Do not override. + */ + function __construct($plugin, $info) { + $this->plugin = $plugin; + $this->title = isset($info['title']) ? $info['title'] : $plugin; + $this->description = isset($info['description']) ? $info['description'] : ''; + } + + function options_form($context) { + } + + /** + * Options form submit handler. + */ + function options_form_submit($values) { + return $values; + } + + /** + * Settings form. Provide variable settings for your reaction. + */ + function settings_form() { + return array(); + } + + /** + * Public method that is called from hooks or other integration points with + * Drupal. Note that it is not implemented in the base class, allowing + * extending classes to change the function signature if necessary. + * + * function execute($value) { + * foreach ($this->get_contexts($value) as $context) { + * $this->condition_met($context, $value); + * } + * } + */ + + /** + * Retrieve active contexts that have values for this reaction. + */ + function get_contexts() { + $contexts = array(); + foreach (context_active_contexts() as $context) { + if ($this->fetch_from_context($context)) { + $contexts[$context->name] = $context; + } + } + return $contexts; + } + + /** + * Retrieve options from the context provided. + */ + function fetch_from_context($context) { + return isset($context->reactions[$this->plugin]) ? $context->reactions[$this->plugin] : array(); + } +} diff --git a/sites/all/modules/context/plugins/context_reaction_block.css b/sites/all/modules/context/plugins/context_reaction_block.css new file mode 100644 index 0000000000000000000000000000000000000000..f1087bc32919c1764d932c0c5d30cd96adea13a2 --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_block.css @@ -0,0 +1,195 @@ +/** + * Script placeholder markup. + */ +.script-placeholder { + padding:100px 0px; + text-align:center; + } + +/** + * Browser + */ +div.context-block-browser div.category { display:none; } + +div.context-block-item, +div.context-block-browser div.draggable-placeholder, +#admin-toolbar div.context-block-browser div.context-block-item { + font-size:11px; + line-height:20px; + height:20px; + + text-shadow:#333 0px 1px 0px; + color:#fff; + + padding:5px 4px 4px 5px; + margin:0px 1px 1px 0px; + max-width:300px; + white-space:nowrap; + overflow:hidden; + + background:url(context_reaction_block.png) 0px -40px repeat-x; + position:relative; + + -moz-border-radius:5px; + -webkit-border-radius:5px; + -moz-user-select:none; + -webkit-user-select:none; + } + + div.context-block-item span.icon { + background:url(context_reaction_block.png) 0px -80px no-repeat; + display:block; + width:20px; + height:20px; + float:left; + margin-right:5px; + } + + div.context-block-loading { max-width:none; } + + div.context-block-loading span.icon { + background-position:-20px -80px; + float:none; + margin:0px auto; + } + + div.context-block-browser div.draggable-placeholder { padding:2px 1px 1px 2px; } + + #admin-toolbar.horizontal div.context-block-browser div.draggable-placeholder, + #admin-toolbar.horizontal div.context-block-browser div.context-block-item { + width:180px; + margin-right:1px; + padding-right:9px; + float:left; + } + +div.context-block-addable { cursor: move; } +div.context-block-added { display:none !important; } + +/** + * Inline editing elements ============================================ + */ +a.context-block-region { display:none; } +a.context-block { display:none !important; } + +body.context-editing div.context-block-region-empty a.context-block-region { + -moz-border-radius:5px; + -webkit-border-radius:5px; + background:#666; + color:#fff; + opacity:.25; + + display:block; + height:40px; + line-height:40px; + + text-align:center; + font-size:18px; + white-space:nowrap; + } + +body.context-editing .ui-sortable div.block { opacity:.25; } + +body.context-editing .ui-sortable div.draggable { + position:relative; + opacity:1; + } + +body.context-editing div.draggable-placeholder { + -moz-border-radius:5px; + -webkit-border-radius:5px; + + background:#fff; + border:3px dashed #666; + opacity:.2; + } + +body.context-editing div.draggable:hover a.context-block-remove, +body.context-editing div.draggable:hover a.context-block-handle { + background:url(context_reaction_block.png) no-repeat; + cursor:move; + display:block; + width:40px; + height:40px; + position:absolute; + right:35px; + top:-5px; + z-index:100; + } + +body.context-editing div.draggable:hover a.context-block-remove { + background-position:-40px 0px; + cursor:pointer; + right:-5px; + } + +div.context-block-hidden { display:none !important; } + +div.block div.context-block-empty { + text-align:center; + padding:10px; + opacity:.5; + background:#fff; + color:#666; + } + +/** + * Block visibility =================================================== + */ +#context-blockform div.context-blockform-selector { + height:20em; + overflow:auto; + } + +#context-blockform span.system-blocks { color:#999; } + +#context-blockform td.blocks, +#context-blockform td.selector { + border:1px solid #ddd; + padding:10px; + width:50%; + } + +#context-blockform td.blocks div.label, +#context-blockform td.blocks td, +#context-blockform td.blocks th { + background:#fff; + padding:5px 5px 4px; + border:0px; + border-bottom:1px solid #ddd; + } + + #context-blockform td.blocks div.label { background:#eee; } + #context-blockform td.blocks div.label a { float:right; } + +#context-ui-items #context-blockform { + font-size:11px; + line-height:15px; + } + +#context-ui-items #context-blockform div.form-checkboxes { + height:auto; + overflow:visible; + padding:0px; + margin:0px; + border:0px; + } + +#context-ui-items #context-blockform div.form-item { padding:0px; } + +#context-ui-items #context-blockform label { + background:#eee; + display:block; + padding:5px; + line-height:15px; + } + +#context-ui-items #context-blockform label.option { + background:#fff; + display:block; + border:0px; + } + +#context-blockform .tabledrag-toggle-weight-wrapper { + margin-bottom:0; + } diff --git a/sites/all/modules/context/plugins/context_reaction_block.inc b/sites/all/modules/context/plugins/context_reaction_block.inc new file mode 100644 index 0000000000000000000000000000000000000000..f505e1c810b94f3c2c258be91ef4b551ef099ca6 --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_block.inc @@ -0,0 +1,602 @@ +<?php + +/** + * Expose blocks as context reactions. + */ +class context_reaction_block extends context_reaction { + /** + * Options form. + */ + function options_form($context) { + // Rebuild the block info cache if necessary. + $this->get_blocks(NULL, NULL, $this->rebuild_needed()); + $this->rebuild_needed(FALSE); + + $theme_key = variable_get('theme_default', 'garland'); + $weight_delta = $this->max_block_weight(); + + $form = array( + '#tree' => TRUE, + '#theme' => 'context_block_form', + 'max_block_weight' => array( + '#value' => $weight_delta, + '#type' => 'value', + ), + 'state' => array( + '#type' => 'hidden', + '#attributes' => array('class' => 'context-blockform-state'), + ), + ); + + /** + * Selector. + */ + $modules = module_list(); + $form['selector'] = array( + '#type' => 'item', + '#tree' => TRUE, + '#prefix' => '<div class="context-blockform-selector">', + '#suffix' => '</div>', + ); + foreach ($this->get_blocks() as $block) { + if (!isset($form['selector'][$block->module])) { + $form['selector'][$block->module] = array( + '#type' => 'checkboxes', + '#title' => $modules[$block->module], + '#options' => array(), + ); + } + $form['selector'][$block->module]['#options'][$block->bid] = check_plain($block->info); + } + ksort($form['selector']); + + /** + * Regions. + */ + $form['blocks'] = array( + '#tree' => TRUE, + '#theme' => 'context_block_regions_form', + ); + foreach (system_region_list($theme_key, REGIONS_VISIBLE) as $region => $label) { + $form['blocks'][$region] = array( + '#type' => 'item', + '#title' => $label, + '#tree' => TRUE, + ); + foreach ($this->get_blocks($region, $context) as $block) { + if (!empty($block->context)) { + $form['blocks'][$region][$block->bid] = array( + '#value' => check_plain($block->info), + '#weight' => $block->weight, + '#type' => 'markup', + '#tree' => TRUE, + 'weight' => array('#type' => 'weight', '#delta' => $weight_delta, '#default_value' => $block->weight), + ); + } + } + } + return $form; + } + + /** + * Options form submit handler. + */ + function options_form_submit($values) { + $blocks = array(); + $block_info = $this->get_blocks(); + + // Retrieve blocks from submitted JSON string. + if (!empty($values['state'])) { + $edited = $this->json_decode($values['state']); + } + else { + $edited = array(); + } + + foreach ($edited as $region => $block_data) { + foreach ($block_data as $position => $data) { + if (isset($block_info[$data->bid])) { + $blocks[$data->bid] = array( + 'module' => $block_info[$data->bid]->module, + 'delta' => $block_info[$data->bid]->delta, + 'region' => $region, + 'weight' => $data->weight, + ); + } + } + } + return array('blocks' => $blocks); + } + + /** + * Context editor form for blocks. + */ + function editor_form($context) { + $form = array(); + drupal_add_library('system', 'ui.droppable'); + drupal_add_library('system', 'ui.sortable'); + drupal_add_js(drupal_get_path('module', 'context_ui') .'/json2.js'); + drupal_add_js(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.js'); + drupal_add_css(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.css'); + + // We might be called multiple times so use a static to ensure this is set just once. + static $once; + if (!isset($once)) { + $settings = array( + 'path' => url($_GET['q']), + 'params' => (object) array_diff_key($_GET, array('q' => '')), + 'scriptPlaceholder' => theme('context_block_script_placeholder', array('text' => '')), + ); + drupal_add_js(array('contextBlockEditor' => $settings), 'setting'); + $once = TRUE; + } + + $form['state'] = array( + '#type' => 'hidden', + '#attributes' => array('class' => array('context-block-editor-state')), + ); + $form['browser'] = array( + '#markup' => theme('context_block_browser', array( + 'blocks' => $this->get_blocks(NULL, NULL, $this->rebuild_needed()), + 'context' => $context + )), + ); + $this->rebuild_needed(FALSE); + return $form; + } + + /** + * Submit handler context editor form. + */ + function editor_form_submit(&$context, $values) { + $edited = !empty($values['state']) ? (array) $this->json_decode($values['state']) : array(); + + $options = array(); + + // Take the existing context values and remove blocks that belong affected regions. + $affected_regions = array_keys($edited); + if (!empty($context->reactions['block']['blocks'])) { + $options = $context->reactions['block']; + foreach ($options['blocks'] as $key => $block) { + if (in_array($block['region'], $affected_regions)) { + unset($options['blocks'][$key]); + } + } + } + + // Iterate through blocks and add in the ones that belong to the context. + foreach ($edited as $region => $blocks) { + foreach ($blocks as $weight => $block) { + if ($block->context === $context->name) { + $split = explode('-', $block->bid); + $options['blocks'][$block->bid] = array( + 'module' => array_shift($split), + 'delta' => implode('-', $split), + 'region' => $region, + 'weight' => $weight, + ); + } + } + } + + return $options; + } + + /** + * Settings form for variables. + */ + function settings_form() { + $form = array(); + $form['context_reaction_block_all_regions'] = array( + '#title' => t('Show all regions'), + '#type' => 'checkbox', + '#default_value' => variable_get('context_reaction_block_all_regions', FALSE), + '#description' => t('Show all regions including those that are empty. Enable if you are administering your site using the inline editor.') + ); + return $form; + } + + /** + * Execute. + */ + function execute(&$page) { + global $theme; + + // The theme system might not yet be initialized. We need $theme. + drupal_theme_initialize(); + + // If the context_block querystring param is set, switch to AJAX rendering. + // Note that we check the output buffer for any content to ensure that we + // are not in the middle of a PHP template render. + if (isset($_GET['context_block']) && !ob_get_contents()) { + return $this->render_ajax($_GET['context_block']); + } + + // Populate all block regions + $all_regions = system_region_list($theme); + + // Load all region content assigned via blocks. + foreach (array_keys($all_regions) as $region) { + if ($this->is_enabled_region($region)) { + $page[$region] = isset($page[$region]) ? array_merge($page[$region], $this->block_get_blocks_by_region($region)) : $this->block_get_blocks_by_region($region); + } + } + } + + /** + * Return a list of enabled regions for which blocks should be built. + * Split out into a separate method for easy overrides in extending classes. + */ + protected function is_enabled_region($region) { + global $theme; + $regions = array_keys(system_region_list($theme)); + return in_array($region, $regions, TRUE); + } + + /** + * Determine whether inline editing requirements are met and that the current + * user may edit. + */ + protected function is_editable_region($region, $reset = FALSE) { + // Check requirements. + // Generally speaking, it does not make sense to allow anonymous users to + // edit a context inline. Though it may be possible to save (and restore) + // an edited context to an anonymous user's cookie or session, it's an + // edge case and probably not something we want to encourage anyway. + static $requirements; + if (!isset($requirements) || $reset) { + global $user; + if ($user->uid) { + $requirements = TRUE; + drupal_add_library('system', 'ui.droppable'); + drupal_add_library('system', 'ui.sortable'); + drupal_add_js(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.js'); + drupal_add_css(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.css'); + } + else { + $requirements = FALSE; + } + } + // Check that this region is not locked by the theme. + global $theme; + $info = system_get_info('theme', $theme); + if ($info && isset($info['regions_locked']) && in_array($region, $info['regions_locked'])) { + return FALSE; + } + // Check that this region is not hidden + $visible = system_region_list($theme, REGIONS_VISIBLE); + return $requirements && $this->is_enabled_region($region) && isset($visible[$region]); + } + + /** + * Add markup for making a block editable. + */ + protected function editable_block($block) { + if (!empty($block->content['#markup']) || element_children($block->content)) { + $block->content = array( + 'content' => $block->content, + 'context' => array('#markup' => "<a id='context-block-{$block->module}-{$block->delta}' class='context-block editable edit-{$block->context}'></a>"), + ); + } + return $block; + } + + /** + * Add markup for making a region editable. + */ + protected function editable_region($region, $build) { + if ($this->is_editable_region($region) && (!empty($build) || variable_get('context_reaction_block_all_regions', FALSE))) { + global $theme; + $regions = system_region_list($theme); + $name = isset($regions[$region]) ? $regions[$region] : $region; + $build['context']['#markup'] = "<a class='context-block-region' id='context-block-region-{$region}'>{$name}</a>"; + } + return $build; + } + + /** + * Get a renderable array of a region containing all enabled blocks. + */ + function block_get_blocks_by_region($region) { + module_load_include('module', 'block', 'block'); + + $build = array(); + if ($list = $this->block_list($region)) { + $build = _block_get_renderable_array($list); + } + if ($this->is_editable_region($region)) { + $build = $this->editable_region($region, $build); + } + return $build; + } + + /** + * An alternative version of block_list() that provides any context enabled blocks. + */ + function block_list($region) { + module_load_include('module', 'block', 'block'); + + $context_blocks = &drupal_static('context_reaction_block_list');; + $contexts = context_active_contexts(); + if (!isset($context_blocks)) { + $info = $this->get_blocks(); + $context_blocks = array(); + foreach ($contexts as $context) { + $options = $this->fetch_from_context($context); + if (!empty($options['blocks'])) { + foreach ($options['blocks'] as $block) { + $block = (object) $block; + $block->context = $context->name; + $block->bid = "{$block->module}-{$block->delta}"; + $block->title = isset($info[$block->bid]->title) ? $info[$block->bid]->title : NULL; + $block->cache = isset($info[$block->bid]->cache) ? $info[$block->bid]->cache : DRUPAL_NO_CACHE; + $context_blocks[$block->region][$block->bid] = $block; + } + } + } + + $this->is_editable_check($context_blocks); + foreach ($context_blocks as $r => $blocks) { + $context_blocks[$r] = _block_render_blocks($blocks); + + // Make blocks editable if allowed. + if ($this->is_editable_region($r)) { + foreach ($context_blocks[$r] as $key => $block) { + $context_blocks[$r][$key] = $this->editable_block($block); + } + } + + // Sort blocks. + uasort($context_blocks[$r], array('context_reaction_block', 'block_sort')); + } + } + return isset($context_blocks[$region]) ? $context_blocks[$region] : array(); + } + + /** + * Determine if there is an active context editor block, and set a flag. We will set a flag so + * that we can make sure that blocks with empty content have some default content. This is + * needed so the save of the context inline editor does not remove the blocks with no content. + */ + function is_editable_check($context_blocks) { + $context_ui_editor_present = &drupal_static('context_ui_editor_present', FALSE); + foreach ($context_blocks as $r => $blocks) { + if (isset($blocks['context_ui-editor'])) { + $block = $blocks['context_ui-editor']; + // see if the editor is actually enabled, lifted from _block_render_blocks + if (!count(module_implements('node_grants')) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && ($cid = _block_get_cache_id($block)) && ($cache = cache_get($cid, 'cache_block'))) { + $array = $cache->data; + } + else { + $array = module_invoke($block->module, 'block_view', $block->delta); + drupal_alter(array('block_view', "block_view_{$block->module}_{$block->delta}"), $array, $block); + } + $context_ui_editor_present = !empty($array['content']); + break; + } + } + } + + /** + * Generate the safe weight range for a block being added to a region such that + * there are enough potential unique weights to support all blocks. + */ + protected function max_block_weight() { + $blocks = $this->get_blocks(); + $block_count = 0; + foreach ($blocks as $region => $block_list) { + $block_count += count($block_list); + } + // Add 2 to make sure there's space at either end of the block list + return round(($block_count + 2) / 2); + } + + /** + * Check or set whether a rebuild of the block info cache is needed. + */ + function rebuild_needed($set = NULL) { + if (isset($set) && $set != variable_get('context_block_rebuild_needed', FALSE)) { + variable_set('context_block_rebuild_needed', $set); + } + return (bool) variable_get('context_block_rebuild_needed', FALSE); + } + + /** + * Helper function to generate a list of blocks from a specified region. If provided a context object, + * will generate a full list of blocks for that region distinguishing between system blocks and + * context-provided blocks. + * + * @param $region + * The string identifier for a theme region. e.g. "left" + * @param $context + * A context object. + * + * @return + * A keyed (by "module_delta" convention) array of blocks. + */ + function get_blocks($region = NULL, $context = NULL, $reset = FALSE) { + static $block_info; + + if (!isset($block_info) || $reset) { + $block_info = array(); + if (!$reset) { + $block_info = context_cache_get('block_info'); + } + if (empty($block_info)) { + $block_info = array(); + foreach (module_implements('block_info') as $module) { + $module_blocks = module_invoke($module, 'block_info'); + if (!empty($module_blocks)) { + foreach ($module_blocks as $delta => $block) { + $block = (object) $block; + $block->module = $module; + $block->delta = $delta; + $block->bid = "{$block->module}-{$block->delta}"; + $block_info[$block->bid] = $block; + } + } + } + context_cache_set('block_info', $block_info); + } + // Allow other modules that may declare blocks dynamically to alter + // this list. + drupal_alter('context_block_info', $block_info); + + // Gather only region info from the database. + if (module_exists('block')) { + $theme_key = variable_get('theme_default', 'garland'); + $result = db_select('block') + ->fields('block', array('module','weight','delta','region', 'title')) + ->condition('theme', $theme_key) + ->execute(); + foreach ($result as $row) { + if (isset($block_info["{$row->module}-{$row->delta}"])) { + $block_info["{$row->module}-{$row->delta}"]->weight = $row->weight; + $block_info["{$row->module}-{$row->delta}"]->region = $row->region; + $block_info["{$row->module}-{$row->delta}"]->title = $row->title; + } + } + } + } + + $blocks = array(); + + // No region specified, provide all blocks. + if (!isset($region)) { + $blocks = $block_info; + } + // Region specified. + else { + foreach ($block_info as $bid => $block) { + if (isset($block->region) && $block->region == $region) { + $blocks[$bid] = $block; + } + } + } + + // Add context blocks if provided. + if (is_object($context) && $options = $this->fetch_from_context($context)) { + if (!empty($options['blocks'])) { + foreach ($options['blocks'] as $block) { + if ( + isset($block_info["{$block['module']}-{$block['delta']}"]) && // Block is valid. + (!isset($region) || (!empty($region) && $block['region'] == $region)) // No region specified or regions match. + ) { + $context_block = $block_info["{$block['module']}-{$block['delta']}"]; + $context_block->weight = $block['weight']; + $context_block->region = $block['region']; + $context_block->context = !empty($context->name) ? $context->name : 'tempname'; + $blocks[$context_block->bid] = $context_block; + } + } + } + uasort($blocks, array('context_reaction_block', 'block_sort')); + } + return $blocks; + } + + /** + * Sort callback. + */ + static function block_sort($a, $b) { + return ($a->weight - $b->weight); + } + + /** + * Compatibility wrapper around json_decode(). + */ + protected function json_decode($json, $assoc = FALSE) { + // Requires PHP 5.2. + if (function_exists('json_decode')) { + return json_decode($json, $assoc); + } + return context_reaction_block::_json_decode($json); + } + + /** + * From http://www.php.net/manual/en/function.json-decode.php#91216 + * with modifications for consistency with output of json_decode(). + * + * Original author: walidator.info 2009. + */ + static function _json_decode($json) { + $comment = false; + $out = '$x = '; + for ($i=0; $i < strlen($json); $i++) { + if (!$comment) { + switch ($json[$i]) { + case '{': + $out .= ' (object) array('; + break; + case '}': + $out .= ')'; + break; + case '[': + $out .= ' array('; + break; + case ']': + $out .= ')'; + break; + case ':'; + $out .= '=>'; + break; + default: + $out .= $json[$i]; + break; + } + } + else { + $out .= $json[$i]; + } + if ($json[$i] == '"') { + $comment = !$comment; + } + } + eval($out . ';'); + return $x; + } + + /** + * Block renderer for AJAX requests. Triggered when $_GET['context_block'] + * is set. See ->execute() for how this is called. + */ + function render_ajax($param) { + // Besure the page isn't a 404 or 403. + $headers = drupal_get_http_header(); + if (array_key_exists('status', $headers) && ($headers['status'] == "404 Not Found" || $headers['status'] == "403 Forbidden")) { + return; + } + // Set the header right away. This will inform any players in the stack + // that we are in the middle of responding to an AJAX request. + drupal_add_http_header('Content-Type', 'text/javascript; charset=utf-8'); + + if (strpos($param, ',') !== FALSE) { + list($bid, $context) = explode(',', $param); + list($module, $delta) = explode('-', $bid, 2); + + // Ensure $bid is valid. + $info = $this->get_blocks(); + if (isset($info[$bid])) { + module_load_include('module', 'block', 'block'); + $block = $info[$bid]; + $block->title = isset($block->title) ? $block->title : ''; + $block->context = $context; + $block->region = ''; + $rendered_blocks = _block_render_blocks(array($block)); // For E_STRICT warning + $block = array_shift($rendered_blocks); + if (empty($block->content['#markup'])) { + $block->content['#markup'] = "<div class='context-block-empty'>". t('This block appears empty when displayed on this page.') ."</div>"; + } + $block = $this->editable_block($block); + $renderable_block = _block_get_renderable_array(array($block)); // For E_STRICT warning + echo drupal_json_encode(array( + 'status' => 1, + 'block' => drupal_render($renderable_block), + )); + drupal_exit(); + } + } + echo drupal_json_encode(array('status' => 0)); + drupal_exit(); + } +} diff --git a/sites/all/modules/context/plugins/context_reaction_block.js b/sites/all/modules/context/plugins/context_reaction_block.js new file mode 100644 index 0000000000000000000000000000000000000000..6326afdbd96cad7d0055e6855f1cb4603d6041c1 --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_block.js @@ -0,0 +1,434 @@ +(function($){ +Drupal.behaviors.contextReactionBlock = {attach: function(context) { + $('form.context-editor:not(.context-block-processed)') + .addClass('context-block-processed') + .each(function() { + var id = $(this).attr('id'); + Drupal.contextBlockEditor = Drupal.contextBlockEditor || {}; + $(this).bind('init.pageEditor', function(event) { + Drupal.contextBlockEditor[id] = new DrupalContextBlockEditor($(this)); + }); + $(this).bind('start.pageEditor', function(event, context) { + // Fallback to first context if param is empty. + if (!context) { + context = $(this).data('defaultContext'); + } + Drupal.contextBlockEditor[id].editStart($(this), context); + }); + $(this).bind('end.pageEditor', function(event) { + Drupal.contextBlockEditor[id].editFinish(); + }); + }); + + // + // Admin Form ======================================================= + // + // ContextBlockForm: Init. + $('#context-blockform:not(.processed)').each(function() { + $(this).addClass('processed'); + Drupal.contextBlockForm = new DrupalContextBlockForm($(this)); + Drupal.contextBlockForm.setState(); + }); + + // ContextBlockForm: Attach block removal handlers. + // Lives in behaviors as it may be required for attachment to new DOM elements. + $('#context-blockform a.remove:not(.processed)').each(function() { + $(this).addClass('processed'); + $(this).click(function() { + $(this).parents('tr').eq(0).remove(); + Drupal.contextBlockForm.setState(); + return false; + }); + }); +}}; + +/** + * Context block form. Default form for editing context block reactions. + */ +DrupalContextBlockForm = function(blockForm) { + this.state = {}; + + this.setState = function() { + $('table.context-blockform-region', blockForm).each(function() { + var region = $(this).attr('id').split('context-blockform-region-')[1]; + var blocks = []; + $('tr', $(this)).each(function() { + var bid = $(this).attr('id'); + var weight = $(this).find('select').val(); + blocks.push({'bid' : bid, 'weight' : weight}); + }); + Drupal.contextBlockForm.state[region] = blocks; + }); + + // Serialize here and set form element value. + $('form input.context-blockform-state').val(JSON.stringify(this.state)); + + // Hide enabled blocks from selector that are used + $('table.context-blockform-region tr').each(function() { + var bid = $(this).attr('id'); + $('div.context-blockform-selector input[value='+bid+']').parents('div.form-item').eq(0).hide(); + }); + // Show blocks in selector that are unused + $('div.context-blockform-selector input').each(function() { + var bid = $(this).val(); + if ($('table.context-blockform-region tr#'+bid).size() === 0) { + $(this).parents('div.form-item').eq(0).show(); + } + }); + }; + + // make sure we update the state right before submits, this takes care of an + // apparent race condition between saving the state and the weights getting set + // by tabledrag + $('#ctools-export-ui-edit-item-form').submit(function() { Drupal.contextBlockForm.setState(); }); + + // Tabledrag + // Add additional handlers to update our blocks. + $.each(Drupal.settings.tableDrag, function(base) { + var table = $('#' + base + ':not(.processed)', blockForm); + if (table && table.is('.context-blockform-region')) { + table.addClass('processed'); + table.bind('mouseup', function(event) { + Drupal.contextBlockForm.setState(); + return; + }); + } + }); + + // Add blocks to a region + $('td.blocks a', blockForm).each(function() { + $(this).click(function() { + var region = $(this).attr('href').split('#')[1]; + var selected = $("div.context-blockform-selector input:checked"); + if (selected.size() > 0) { + selected.each(function() { + // create new block markup + var block = document.createElement('tr'); + var text = $(this).parents('div.form-item').eq(0).hide().children('label').text(); + var select = '<div class="form-item form-type-select"><select class="tabledrag-hide form-select">'; + var i; + for (i = -10; i < 10; ++i) { + select += '<option>' + i + '</option>'; + } + select += '</select></div>'; + $(block).attr('id', $(this).attr('value')).addClass('draggable'); + $(block).html("<td>"+ text + "</td><td>" + select + "</td><td><a href='' class='remove'>X</a></td>"); + + // add block item to region + var base = "context-blockform-region-"+ region; + Drupal.tableDrag[base].makeDraggable(block); + $('table#'+base).append(block); + if ($.cookie('Drupal.tableDrag.showWeight') == 1) { + $('table#'+base).find('.tabledrag-hide').css('display', ''); + $('table#'+base).find('.tabledrag-handle').css('display', 'none'); + } + else { + $('table#'+base).find('.tabledrag-hide').css('display', 'none'); + $('table#'+base).find('.tabledrag-handle').css('display', ''); + } + Drupal.attachBehaviors($('table#'+base)); + + Drupal.contextBlockForm.setState(); + $(this).removeAttr('checked'); + }); + } + return false; + }); + }); +}; + +/** + * Context block editor. AHAH editor for live block reaction editing. + */ +DrupalContextBlockEditor = function(editor) { + this.editor = editor; + this.state = {}; + this.blocks = {}; + this.regions = {}; + + // Category selector handler. + // Also set to "Choose a category" option as browsers can retain + // form values from previous page load. + $('select.context-block-browser-categories', editor).change(function() { + var category = $(this).val(); + var params = { + containment: 'document', + revert: true, + dropOnEmpty: true, + placeholder: 'draggable-placeholder', + forcePlaceholderSize: true, + helper: 'clone', + appendTo: 'body', + connectWith: ($.ui.version === '1.6') ? ['.ui-sortable'] : '.ui-sortable' + }; + $('div.category', editor).hide().sortable('destroy'); + $('div.category-'+category, editor).show().sortable(params); + }); + $('select.context-block-browser-categories', editor).val(0).change(); + + return this; +}; + +DrupalContextBlockEditor.prototype.initBlocks = function(blocks) { + var self = this; + this.blocks = blocks; + blocks.each(function() { + $(this).addClass('draggable'); + $(this).prepend($('<a class="context-block-handle"></a>')); + $(this).prepend($('<a class="context-block-remove"></a>').click(function() { + $(this).parents('div.block').eq(0).fadeOut('medium', function() { + $(this).remove(); + self.updateBlocks(); + }); + return false; + })); + }); +}; + +DrupalContextBlockEditor.prototype.initRegions = function(regions) { + this.regions = regions; +}; + +/** + * Update UI to match the current block states. + */ +DrupalContextBlockEditor.prototype.updateBlocks = function() { + var browser = $('div.context-block-browser'); + + // For all enabled blocks, mark corresponding addables as having been added. + $('div.block, div.admin-block').each(function() { + var bid = $(this).attr('id').split('block-')[1]; // Ugh. + $('#context-block-addable-'+bid, browser).draggable('disable').addClass('context-block-added').removeClass('context-block-addable'); + }); + // For all hidden addables with no corresponding blocks, mark as addable. + $('.context-block-item', browser).each(function() { + var bid = $(this).attr('id').split('context-block-addable-')[1]; + if ($('#block-'+bid).size() === 0) { + $(this).draggable('enable').removeClass('context-block-added').addClass('context-block-addable'); + } + }); + + // Mark empty regions. + $(this.regions).each(function() { + if ($('div.block:has(a.context-block)', this).size() > 0) { + $(this).removeClass('context-block-region-empty'); + } + else { + $(this).addClass('context-block-region-empty'); + } + }); +}; + +/** + * Live update a region. + */ +DrupalContextBlockEditor.prototype.updateRegion = function(event, ui, region, op) { + switch (op) { + case 'over': + $(region).removeClass('context-block-region-empty'); + break; + case 'out': + if ( + // jQuery UI 1.8 + $('div.draggable-placeholder', region).size() === 1 && + $('div.block:has(a.context-block)', region).size() == 0 + // jQuery UI 1.6 + // $('div.draggable-placeholder', region).size() === 0 && + // $('div.block:has(a.context-block)', region).size() == 1 && + // $('div.block:has(a.context-block)', region).attr('id') == ui.item.attr('id') + ) { + $(region).addClass('context-block-region-empty'); + } + break; + } +}; + +/** + * Remove script elements while dragging & dropping. + */ +DrupalContextBlockEditor.prototype.scriptFix = function(event, ui, editor, context) { + if ($('script', ui.item)) { + var placeholder = $(Drupal.settings.contextBlockEditor.scriptPlaceholder); + var label = $('div.handle label', ui.item).text(); + placeholder.children('strong').html(label); + $('script', ui.item).parent().empty().append(placeholder); + } +}; + +/** + * Add a block to a region through an AHAH load of the block contents. + */ +DrupalContextBlockEditor.prototype.addBlock = function(event, ui, editor, context) { + var self = this; + if (ui.item.is('.context-block-addable')) { + var bid = ui.item.attr('id').split('context-block-addable-')[1]; + + // Construct query params for our AJAX block request. + var params = Drupal.settings.contextBlockEditor.params; + params.context_block = bid + ',' + context; + + // Replace item with loading block. + var blockLoading = $('<div class="context-block-item context-block-loading"><span class="icon"></span></div>'); + ui.item.addClass('context-block-added'); + ui.item.after(blockLoading); + ui.sender.append(ui.item); + + $.getJSON(Drupal.settings.contextBlockEditor.path, params, function(data) { + if (data.status) { + var newBlock = $(data.block); + if ($('script', newBlock)) { + $('script', newBlock).remove(); + } + blockLoading.fadeOut(function() { + $(this).replaceWith(newBlock); + self.initBlocks(newBlock); + self.updateBlocks(); + Drupal.attachBehaviors(); + }); + } + else { + blockLoading.fadeOut(function() { $(this).remove(); }); + } + }); + } + else if (ui.item.is(':has(a.context-block)')) { + self.updateBlocks(); + } +}; + +/** + * Update form hidden field with JSON representation of current block visibility states. + */ +DrupalContextBlockEditor.prototype.setState = function() { + var self = this; + + $(this.regions).each(function() { + var region = $('a.context-block-region', this).attr('id').split('context-block-region-')[1]; + var blocks = []; + $('a.context-block', $(this)).each(function() { + if ($(this).attr('class').indexOf('edit-') != -1) { + var bid = $(this).attr('id').split('context-block-')[1]; + var context = $(this).attr('class').split('edit-')[1].split(' ')[0]; + context = context ? context : 0; + var block = {'bid': bid, 'context': context}; + blocks.push(block); + } + }); + self.state[region] = blocks; + }); + + // Serialize here and set form element value. + $('input.context-block-editor-state', this.editor).val(JSON.stringify(this.state)); +}; + +/** + * Disable text selection. + */ +DrupalContextBlockEditor.prototype.disableTextSelect = function() { + if ($.browser.safari) { + $('div.block:has(a.context-block):not(:has(input,textarea))').css('WebkitUserSelect','none'); + } + else if ($.browser.mozilla) { + $('div.block:has(a.context-block):not(:has(input,textarea))').css('MozUserSelect','none'); + } + else if ($.browser.msie) { + $('div.block:has(a.context-block):not(:has(input,textarea))').bind('selectstart.contextBlockEditor', function() { return false; }); + } + else { + $(this).bind('mousedown.contextBlockEditor', function() { return false; }); + } +}; + +/** + * Enable text selection. + */ +DrupalContextBlockEditor.prototype.enableTextSelect = function() { + if ($.browser.safari) { + $('*').css('WebkitUserSelect',''); + } + else if ($.browser.mozilla) { + $('*').css('MozUserSelect',''); + } + else if ($.browser.msie) { + $('*').unbind('selectstart.contextBlockEditor'); + } + else { + $(this).unbind('mousedown.contextBlockEditor'); + } +}; + +/** + * Start editing. Attach handlers, begin draggable/sortables. + */ +DrupalContextBlockEditor.prototype.editStart = function(editor, context) { + var self = this; + + // This is redundant to the start handler found in context_ui.js. + // However it's necessary that we trigger this class addition before + // we call .sortable() as the empty regions need to be visible. + $(document.body).addClass('context-editing'); + this.editor.addClass('context-editing'); + + this.disableTextSelect(); + this.initBlocks($('div.block:has(a.context-block.edit-'+context+')')); + this.initRegions($('a.context-block-region').parent()); + this.updateBlocks(); + + // First pass, enable sortables on all regions. + $(this.regions).each(function() { + var region = $(this); + var params = { + containment: 'document', + revert: true, + dropOnEmpty: true, + placeholder: 'draggable-placeholder', + forcePlaceholderSize: true, + items: '> div.block:has(a.context-block.editable)', + handle: 'a.context-block-handle', + start: function(event, ui) { self.scriptFix(event, ui, editor, context); }, + stop: function(event, ui) { self.addBlock(event, ui, editor, context); }, + receive: function(event, ui) { self.addBlock(event, ui, editor, context); }, + over: function(event, ui) { self.updateRegion(event, ui, region, 'over'); }, + out: function(event, ui) { self.updateRegion(event, ui, region, 'out'); } + }; + region.sortable(params); + }); + + // Second pass, hook up all regions via connectWith to each other. + $(this.regions).each(function() { + $(this).sortable('option', 'connectWith', ['.ui-sortable']); + }); + + // Terrible, terrible workaround for parentoffset issue in Safari. + // The proper fix for this issue has been committed to jQuery UI, but was + // not included in the 1.6 release. Therefore, we do a browser agent hack + // to ensure that Safari users are covered by the offset fix found here: + // http://dev.jqueryui.com/changeset/2073. + if ($.ui.version === '1.6' && $.browser.safari) { + $.browser.mozilla = true; + } +}; + +/** + * Finish editing. Remove handlers. + */ +DrupalContextBlockEditor.prototype.editFinish = function() { + this.editor.removeClass('context-editing'); + this.enableTextSelect(); + + // Remove UI elements. + $(this.blocks).each(function() { + $('a.context-block-handle, a.context-block-remove', this).remove(); + $(this).removeClass('draggable'); + }); + this.regions.sortable('destroy'); + + this.setState(); + + // Unhack the user agent. + if ($.ui.version === '1.6' && $.browser.safari) { + $.browser.mozilla = false; + } +}; + +})(jQuery); diff --git a/sites/all/modules/context/plugins/context_reaction_block.png b/sites/all/modules/context/plugins/context_reaction_block.png new file mode 100644 index 0000000000000000000000000000000000000000..50ad859c4125cdaa04bcc35330de748a8119c08c Binary files /dev/null and b/sites/all/modules/context/plugins/context_reaction_block.png differ diff --git a/sites/all/modules/context/plugins/context_reaction_block.svg b/sites/all/modules/context/plugins/context_reaction_block.svg new file mode 100644 index 0000000000000000000000000000000000000000..536254cd07072b4d4eada67d428b08287cb5bff7 --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_block.svg @@ -0,0 +1,256 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="80" + height="120" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + version="1.0" + sodipodi:docname="context_reaction_block.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + inkscape:export-filename="/home/devseed/kitrium/profiles/openatrium/modules/contrib/context/plugins/context_reaction_block.png" + inkscape:export-xdpi="90" + inkscape:export-ydpi="90"> + <defs + id="defs4"> + <linearGradient + inkscape:collect="always" + id="linearGradient3186"> + <stop + style="stop-color:#000000;stop-opacity:0.1254902" + offset="0" + id="stop3188" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop3190" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3191"> + <stop + style="stop-color:#ffffff;stop-opacity:1;" + offset="0" + id="stop3193" /> + <stop + style="stop-color:#c0c0c0;stop-opacity:1" + offset="1" + id="stop3195" /> + </linearGradient> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective10" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3197" + x1="10" + y1="10" + x2="10" + y2="30" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3199" + gradientUnits="userSpaceOnUse" + x1="10" + y1="10" + x2="10" + y2="30" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3176" + gradientUnits="userSpaceOnUse" + x1="18" + y1="81" + x2="18" + y2="99" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3186" + id="linearGradient3192" + x1="2" + y1="92.5" + x2="10" + y2="92.5" + gradientUnits="userSpaceOnUse" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3255" + gradientUnits="userSpaceOnUse" + x1="18" + y1="81" + x2="18" + y2="99" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3260" + gradientUnits="userSpaceOnUse" + x1="18" + y1="81" + x2="18" + y2="99" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3265" + gradientUnits="userSpaceOnUse" + x1="18" + y1="81" + x2="18" + y2="99" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3276" + gradientUnits="userSpaceOnUse" + x1="18" + y1="81" + x2="18" + y2="99" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3191" + id="linearGradient3284" + gradientUnits="userSpaceOnUse" + x1="18" + y1="81" + x2="18" + y2="99" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + gridtolerance="10000" + guidetolerance="10" + objecttolerance="10" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="31.144684" + inkscape:cy="24.681366" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false"> + <inkscape:grid + type="xygrid" + id="grid2383" + visible="true" + enabled="true" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <path + sodipodi:nodetypes="ccccsccccccccccssccsccc" + id="path3287" + d="M 30,84.5 C 27.8265,84.5 25.87799,85.506572 24.59375,87.0625 L 24.59375,87.09375 L 26,88.5 C 26.912944,87.291403 28.369734,86.5 30,86.5 C 32.044638,86.5 33.787894,87.740652 34.5625,89.5 L 31.5,89.5 L 35.5,93.5 L 39.5,89.5 L 39,89.5 L 36.6875,89.5 C 35.825628,86.615704 33.164199,84.5 30,84.5 z M 24.5,89.5 L 20.5,93.5 L 23.3125,93.5 C 24.174372,96.384296 26.835801,98.5 30,98.5 C 32.1735,98.5 34.12201,97.493428 35.40625,95.9375 C 35.411546,95.931083 35.400976,95.912686 35.40625,95.90625 L 34,94.5 C 33.087056,95.708597 31.630266,96.5 30,96.5 C 27.955362,96.5 26.212106,95.259348 25.4375,93.5 L 28.5,93.5 L 24.5,89.5 z" + style="opacity:1;fill:#000000;fill-opacity:0.50196078000000000;fill-rule:nonzero;stroke:none;stroke-width:1.25000000000000000;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;color:#000000" /> + <rect + style="opacity:1;fill:#404040;fill-opacity:0.75294118;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect2385" + width="80" + height="40" + x="0" + y="0" + rx="10" + ry="10" /> + <path + id="path3183" + d="M 20,12.5 L 17,15.5 L 19,15.5 L 19,20.5 L 14,20.5 L 14,18.5 L 11,21.5 L 14,24.5 L 14,22.5 L 19,22.5 L 19,27.5 L 17,27.5 L 20,30.5 L 23,27.5 L 21,27.5 L 21,22.5 L 26,22.5 L 26,24.5 L 29,21.5 L 26,18.5 L 26,20.5 L 21,20.5 L 21,15.5 L 23,15.5 L 20,12.5 z" + style="opacity:1;fill:#000000;fill-opacity:0.50196078;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + style="opacity:1;fill:url(#linearGradient3199);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="M 20,11 L 17,14 L 19,14 L 19,19 L 14,19 L 14,17 L 11,20 L 14,23 L 14,21 L 19,21 L 19,26 L 17,26 L 20,29 L 23,26 L 21,26 L 21,21 L 26,21 L 26,23 L 29,20 L 26,17 L 26,19 L 21,19 L 21,14 L 23,14 L 20,11 z" + id="path3161" /> + <path + id="path3185" + d="M 55.5,15.21875 C 55.372191,15.21875 55.254184,15.245816 55.15625,15.34375 L 53.84375,16.65625 C 53.647881,16.852119 53.647881,17.147881 53.84375,17.34375 L 58,21.5 L 53.84375,25.65625 C 53.647883,25.852118 53.647881,26.147881 53.84375,26.34375 L 55.15625,27.65625 C 55.352118,27.852119 55.647881,27.852119 55.84375,27.65625 L 60,23.5 L 64.15625,27.65625 C 64.352119,27.852119 64.64788,27.852119 64.84375,27.65625 L 66.15625,26.34375 C 66.352119,26.147881 66.352117,25.852118 66.15625,25.65625 L 62,21.5 L 66.15625,17.34375 C 66.352119,17.147882 66.352119,16.852119 66.15625,16.65625 L 64.84375,15.34375 C 64.64788,15.147881 64.352119,15.147881 64.15625,15.34375 L 60,19.5 L 55.84375,15.34375 C 55.745816,15.245816 55.627809,15.21875 55.5,15.21875 z" + style="opacity:1;fill:#000000;fill-opacity:0.50196078;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + style="opacity:1;fill:url(#linearGradient3197);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="M 55.5,13.71875 C 55.372191,13.71875 55.254184,13.745816 55.15625,13.84375 L 53.84375,15.15625 C 53.647881,15.352119 53.647881,15.647881 53.84375,15.84375 L 58,20 L 53.84375,24.15625 C 53.647883,24.352118 53.647881,24.647881 53.84375,24.84375 L 55.15625,26.15625 C 55.352118,26.352119 55.647881,26.352119 55.84375,26.15625 L 60,22 L 64.15625,26.15625 C 64.352119,26.352119 64.64788,26.352119 64.84375,26.15625 L 66.15625,24.84375 C 66.352119,24.647881 66.352117,24.352118 66.15625,24.15625 L 62,20 L 66.15625,15.84375 C 66.352119,15.647882 66.352119,15.352119 66.15625,15.15625 L 64.84375,13.84375 C 64.64788,13.647881 64.352119,13.647881 64.15625,13.84375 L 60,18 L 55.84375,13.84375 C 55.745816,13.745816 55.627809,13.71875 55.5,13.71875 z" + id="rect3173" /> + <rect + style="opacity:1;fill:#404040;fill-opacity:0.50196078;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + id="rect3179" + width="1" + height="30" + x="40" + y="5" + ry="0.5" + rx="0.5" /> + <rect + y="40" + x="0" + height="40" + width="80" + id="rect2391" + style="opacity:1;fill:#404040;fill-opacity:0.75294118;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <g + id="g3215"> + <rect + style="opacity:1;fill:#000000;fill-opacity:0.50196078000000000;fill-rule:nonzero;stroke:none;stroke-width:1.25000000000000000;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;color:#000000" + id="rect3200" + width="14" + height="14" + x="3" + y="84.5" + rx="2" + ry="2" /> + <path + style="fill:url(#linearGradient3176);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:0;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" + d="M 5,83 L 15,83 C 16.108,83 17,83.892 17,85 L 17,95 C 17,96.108 16.108,97 15,97 L 5,97 C 3.892,97 3,96.108 3,95 L 3,85 C 3,83.892 3.892,83 5,83 z" + id="rect3198" /> + <rect + ry="0.5" + rx="0.5" + y="87" + x="5" + height="8" + width="10" + id="rect3206" + style="opacity:1;fill:#000000;fill-opacity:0.1254902;fill-rule:nonzero;stroke:none;stroke-width:1.25000000000000000;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + <path + id="rect3208" + d="M 5.5 87 C 5.223 87 5 87.223 5 87.5 L 5 94 L 6 88 L 14 88 L 15 94 L 15 87.5 C 15 87.223 14.777 87 14.5 87 L 5.5 87 z " + style="opacity:1;fill:#000000;fill-opacity:0.1254902;fill-rule:nonzero;stroke:none;stroke-width:1.25;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" /> + </g> + <path + style="opacity:1;fill:url(#linearGradient3284);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.25000000000000000;stroke-linecap:square;stroke-linejoin:round;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:0;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;color:#000000" + d="M 30,83 C 27.8265,83 25.87799,84.006572 24.59375,85.5625 L 24.59375,85.59375 L 26,87 C 26.912944,85.791403 28.369734,85 30,85 C 32.044638,85 33.787894,86.240652 34.5625,88 L 31.5,88 L 35.5,92 L 39.5,88 L 39,88 L 36.6875,88 C 35.825628,85.115704 33.164199,83 30,83 z M 24.5,88 L 20.5,92 L 23.3125,92 C 24.174372,94.884296 26.835801,97 30,97 C 32.1735,97 34.12201,95.993428 35.40625,94.4375 C 35.411546,94.431083 35.400976,94.412686 35.40625,94.40625 L 34,93 C 33.087056,94.208597 31.630266,95 30,95 C 27.955362,95 26.212106,93.759348 25.4375,92 L 28.5,92 L 24.5,88 z" + id="path3231" + sodipodi:nodetypes="ccccsccccccccccssccsccc" /> + </g> +</svg> diff --git a/sites/all/modules/context/plugins/context_reaction_breadcrumb.inc b/sites/all/modules/context/plugins/context_reaction_breadcrumb.inc new file mode 100644 index 0000000000000000000000000000000000000000..caa1cad2f7ebaa8028a2c33315d5eaa7780468bd --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_breadcrumb.inc @@ -0,0 +1,39 @@ +<?php + +/** + * Set the breadcrumb using a context reaction. + */ +class context_reaction_breadcrumb extends context_reaction_menu { + /** + * Override of execute(). + */ + function execute(&$vars = NULL) { + if ($active_paths = $this->get_active_paths()) { + $breadcrumb = array(l(t('Home'), '<front>', array('purl' =>array('disabled' => TRUE)))); + foreach ($active_paths as $path) { + $result = db_select('menu_links') + ->fields('menu_links', array('p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8')) + ->condition('hidden', 0) + ->condition('link_path', $path) + ->execute(); + while ($parents = $result->fetchAssoc()) { + $set = FALSE; + foreach (array_filter($parents) as $plid) { + $parent = menu_link_load($plid); + if ($parent && $parent['access'] && empty($parent['hidden']) && !empty($parent['title'])) { + $set = TRUE; + $breadcrumb[] = l($parent['title'], $parent['href']); + } + } + // Only set the breadcrumb if one or more links were added to the + // trail. If not, continue iterating through possible menu links. + if ($set) { + drupal_set_breadcrumb($breadcrumb); + break; + } + } + } + } + } +} + diff --git a/sites/all/modules/context/plugins/context_reaction_css_injector.inc b/sites/all/modules/context/plugins/context_reaction_css_injector.inc new file mode 100644 index 0000000000000000000000000000000000000000..5fedb795ef27a13cf1005cc28d9e2607f92c64e8 --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_css_injector.inc @@ -0,0 +1,33 @@ +<?php + +class context_reaction_css_injector extends context_reaction { + + function options_form($context) { + $list = array(); + foreach (_css_injector_load_rule() as $css_rule) { + $list[$css_rule['crid']] = $css_rule['title']; + } + ksort($list); + + return array( + '#title' => $this->title, + '#description' => $this->description, + '#options' => $list, + '#type' => 'checkboxes', + '#default_value' => $this->fetch_from_context($context), + ); + } + + function execute() { + $contexts = $this->get_contexts(); + foreach ($contexts as $context) { + if (!empty($context->reactions[$this->plugin])) { + foreach ($context->reactions[$this->plugin] as $crid) { + if ($css_rule = _css_injector_load_rule($crid)) { + drupal_add_css(file_create_path($css_rule['file_path']), 'module', $css_rule['media'], $css_rule['preprocess']); + } + } + } + } + } +} \ No newline at end of file diff --git a/sites/all/modules/context/plugins/context_reaction_debug.inc b/sites/all/modules/context/plugins/context_reaction_debug.inc new file mode 100644 index 0000000000000000000000000000000000000000..a0577a054f6c77dd5500b1adc17d9da14b6aedda --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_debug.inc @@ -0,0 +1,32 @@ +<?php + +/** + * Output context debug information. + */ +class context_reaction_debug extends context_reaction { + function options_form($context) { + return array('debug' => array('#type' => 'value', '#value' => TRUE)); + } + + function options_form_submit($values) { + return array('debug' => 1); + } + + /** + * Output a list of active contexts. + */ + function execute() { + $contexts = context_active_contexts(); + foreach ($contexts as $context) { + if (!empty($context->reactions['debug'])) { + if (user_access('administer site configuration') && module_exists('context_ui')) { + $name = l($context->name, "admin/structure/context/list/{$context->name}", array('query' => array('destination' => $_GET['q']))); + } + else { + $name = check_plain($context->name); + } + drupal_set_message(t("Active context: !name", array('!name' => $name))); + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_reaction_menu.inc b/sites/all/modules/context/plugins/context_reaction_menu.inc new file mode 100644 index 0000000000000000000000000000000000000000..d285717112c6ab60751a1726bf487dedfc3f0e1e --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_menu.inc @@ -0,0 +1,135 @@ +<?php + +/** + * Expose menu items as context reactions. + */ +class context_reaction_menu extends context_reaction { + /** + * Provide a form element that allow the admin to chose a menu item. + */ + function options_form($context) { + if (module_exists('menu')) { + $menus = menu_parent_options(menu_get_menus(), array('mlid' => 0)); + $root_menus = array(); + foreach ($menus as $key => $name) { + $id = explode(':', $key); + if ($id[1] == '0') { + $root_menus[$id[0]] = check_plain($name); + } + else { + $link = menu_link_load($id[1]); + $identifier = $link['link_path']; + $root_menu = $root_menus[$id[0]]; + while (isset($menus[$root_menu][$identifier])) { + $identifier .= "'"; + } + $menus[$root_menu][$identifier] = $name; + } + unset($menus[$key]); + } + array_unshift($menus, "-- ". t('None') ." --"); + } + else { + $menus = array(); + } + return array( + '#title' => $this->title, + '#description' => $this->description, + '#options' => $menus, + '#type' => 'select', + '#default_value' => $this->fetch_from_context($context), + ); + } + + /** + * Override of options_form_submit(). + * Trim any identifier padding for non-unique path menu items. + */ + function options_form_submit($values) { + return trim($values, "'"); + } + + /** + * If primary + secondary links are pointed at the same menu, provide + * contextual trailing by default. + */ + function execute(&$vars = NULL) { + if (variable_get('menu_main_links_source', 'main-menu') == variable_get('menu_secondary_links_source', 'user-menu')) { + $vars['main_menu'] = theme_get_setting('toggle_main_menu') ? $this->menu_navigation_links(variable_get('menu_main_links_source', 'main-menu')) : $vars['main_menu']; + $vars['secondary_menu'] = theme_get_setting('toggle_secondary_menu') ? $this->menu_navigation_links(variable_get('menu_secondary_links_source', 'secondary-links'), 1) : $vars['secondary_menu']; + } + + $vars['main_menu'] = $this->menu_set_active($vars['main_menu']); + $vars['secondary_menu'] = $this->menu_set_active($vars['secondary_menu']); + } + + function get_active_paths() { + $active_paths = array(); + foreach ($this->get_contexts() as $context) { + if (isset($context->reactions[$this->plugin])) { + $active_paths[] = $context->reactions[$this->plugin]; + } + } + return $active_paths; + } + + /** + * Iterates through a provided links array for use with theme_links() + * (e.g. from menu_primary_links()) and provides an active class for + * any items that have a path that matches an active context. + * + * @param $links + * An array of links. + * @param $reset + * A boolean flag for resetting the static cache. + * + * @return + * A modified links array. + */ + function menu_set_active($links = array(), $reset = FALSE) { + $new_links = array(); + if (!empty($links)) { + $active_paths = $this->get_active_paths(); + + // Iterate through the provided links and build a new set of links + // that includes active classes + foreach ($links as $key => $link) { + if (!empty($link['href']) && in_array($link['href'], $active_paths)) { + $link['attributes']['class'][] = 'active'; + + if (strpos(' active', $key) === FALSE) { + $new_links[$key .' active'] = $link; + } + } + else { + $new_links[$key] = $link; + } + } + } + return $new_links; + } + + /** + * Wrapper around menu_navigation_links() that gives themers the option of + * building navigation links based on an active context trail. + */ + function menu_navigation_links($menu_name, $level = 0) { + // Retrieve original path so we can repair it after our hack. + $original_path = $_GET['q']; + + // Retrieve the first active menu path found. + if ($active_paths = $this->get_active_paths()) { + $path = current($active_paths); + if (menu_get_item($path)) { + menu_set_active_item($path); + } + } + + // Build the links requested + $links = menu_navigation_links($menu_name, $level); + + // Repair and get out + menu_set_active_item($original_path); + return $links; + } +} diff --git a/sites/all/modules/context/plugins/context_reaction_theme.inc b/sites/all/modules/context/plugins/context_reaction_theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..4d5b677ccb784e862e1c4e2f2aea2debebdc7974 --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_theme.inc @@ -0,0 +1,67 @@ +<?php + +/** + * Expose themes as context reactions. + */ +class context_reaction_theme extends context_reaction { + /** + * Editor form. + */ + function editor_form($context) { + $form = $this->options_form($context); + + // Hide descriptions which take up too much space. + unset($form['title']['#description']); + unset($form['subtitle']['#description']); + unset($form['class']['#description']); + return $form; + } + + /** + * Submit handler for editor form. + */ + function editor_form_submit($context, $values) { + return $values; + } + + /** + * Allow admins to provide a section title, section subtitle and section class. + */ + function options_form($context) { + $values = $this->fetch_from_context($context); + $form = array( + '#tree' => TRUE, + '#title' => t('Theme variables'), + 'title' => array( + '#title' => t('Section title'), + '#description' => t('Provides this text as a <strong>$section_title</strong> variable for display in page.tpl.php when this context is active.'), + '#type' => 'textfield', + '#maxlength' => 255, + '#default_value' => isset($values['title']) ? $values['title'] : '', + ), + 'subtitle' => array( + '#title' => t('Section subtitle'), + '#description' => t('Provides this text as a <strong>$section_subtitle</strong> variable for display in page.tpl.php when this context is active.'), + '#type' => 'textfield', + '#maxlength' => 255, + '#default_value' => isset($values['subtitle']) ? $values['subtitle'] : '', + ), + ); + return $form; + } + + /** + * Set 'section_title', and 'section_subtitle' if not set + */ + function execute(&$vars) { + $classes = array(); + foreach ($this->get_contexts() as $k => $v) { + if (!empty($v->reactions[$this->plugin]['title']) && !isset($vars['section_title'])) { + $vars['section_title'] = check_plain(t($v->reactions[$this->plugin]['title'])); + } + if (!empty($v->reactions[$this->plugin]['subtitle']) && !isset($vars['section_subtitle'])) { + $vars['section_subtitle'] = check_plain(t($v->reactions[$this->plugin]['subtitle'])); + } + } + } +} diff --git a/sites/all/modules/context/plugins/context_reaction_theme_html.inc b/sites/all/modules/context/plugins/context_reaction_theme_html.inc new file mode 100644 index 0000000000000000000000000000000000000000..42d17227abfe056adef4b1ba86afebbabc2611eb --- /dev/null +++ b/sites/all/modules/context/plugins/context_reaction_theme_html.inc @@ -0,0 +1,34 @@ +<?php +/** + * Expose themes as context reactions. + */ +class context_reaction_theme_html extends context_reaction_theme { + /** + * Allow admins to provide additional body classes. + */ + function options_form($context) { + $values = $this->fetch_from_context($context); + $form = array( + 'class' => array( + '#title' => t('Section class'), + '#description' => t('Provides this text as an additional body class (in <strong>$classes</strong> in html.tpl.php) when this section is active.'), + '#type' => 'textfield', + '#maxlength' => 64, + '#default_value' => isset($values['class']) ? $values['class'] : '', + ), + ); + return $form; + } + + /** + * Set additional classes onto the 'body_classes'. + */ + function execute(&$vars) { + $classes = array(); + foreach ($this->get_contexts() as $k => $v) { + if (!empty($v->reactions[$this->plugin]['class'])) { + $vars['classes_array'][] = $v->reactions[$this->plugin]['class']; + } + } + } +} diff --git a/sites/all/modules/context/tests/context.conditions.test b/sites/all/modules/context/tests/context.conditions.test new file mode 100644 index 0000000000000000000000000000000000000000..7ad3860e340060c46851df144466a9afa6b42718 --- /dev/null +++ b/sites/all/modules/context/tests/context.conditions.test @@ -0,0 +1,560 @@ +<?php + +class ContextConditionUserTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: user', + 'description' => 'Test user condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + $this->user1 = $this->drupalCreateUser(array('access content', 'administer site configuration')); + $this->user2 = $this->drupalCreateUser(array('access content')); + + // The role name is not reliably put on the user object. Retrive from + // user_roles(). + $role = ''; + foreach (array_keys($this->user1->roles) as $rid) { + if ($rid !== DRUPAL_AUTHENTICATED_RID) { + $role = user_role_load($rid)->name; + break; + } + } + + // Create test context. + ctools_include('export'); + $this->context = ctools_export_new_object('context'); + $this->context->name = 'testcontext'; + $this->context->conditions = array('user' => array('values' => array($role))); + $this->context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($this->context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + } + + function tearDown() { + parent::tearDown(); + context_delete($this->context); + user_delete($this->user1->uid); + user_delete($this->user2->uid); + } + + function test() { + // User 1 triggers the context. + $this->drupalLogin($this->user1); + $this->drupalGet('node'); + $this->assertText('Active context: testcontext'); + + // User 2 does not. + $this->drupalLogin($this->user2); + $this->drupalGet('node'); + $this->assertNoText('Active context: testcontext'); + } +} + +class ContextConditionUserPageTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: user page', + 'description' => 'Test user page condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + $this->user1 = $this->drupalCreateUser(array('access user profiles', 'access content', 'administer site configuration')); + $this->user2 = $this->drupalCreateUser(array('access user profiles', 'access content')); + + // Create test context. + ctools_include('export'); + $this->context = ctools_export_new_object('context'); + $this->context->name = 'testcontext'; + $this->context->conditions = array('user_page' => array('values' => array('view' => 'view'), 'options' => array('mode' => 'all'))); + $this->context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($this->context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + } + + function tearDown() { + parent::tearDown(); + context_delete($this->context); + $edit = array(); + user_delete($this->user1->uid); + user_delete($this->user2->uid); + } + + function test() { + // Viewing any user profile triggers context. + $this->drupalLogin($this->user1); + $this->drupalGet("user/{$this->user1->uid}"); + $this->assertText('Active context: testcontext'); + $this->drupalGet("user/{$this->user2->uid}"); + $this->assertText('Active context: testcontext'); + // User form does not. + $this->drupalGet("user/{$this->user1->uid}/edit"); + $this->assertNoText('Active context: testcontext'); + + // Test current user mode + $this->context->conditions['user_page']['options']['mode'] = 'current'; + $saved = context_save($this->context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + $this->drupalGet("user/{$this->user1->uid}"); + $this->assertText('Active context: testcontext'); + $this->drupalGet("user/{$this->user2->uid}"); + $this->assertNoText('Active context: testcontext'); + + // Test other user mode + $this->context->conditions['user_page']['options']['mode'] = 'other'; + $saved = context_save($this->context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + $this->drupalGet("user/{$this->user1->uid}"); + $this->assertNoText('Active context: testcontext'); + $this->drupalGet("user/{$this->user2->uid}"); + $this->assertText('Active context: testcontext'); + } +} + +class ContextConditionNodeTaxonomyTest extends DrupalWebTestCase { + // We want the default taxonomy and content types created + protected $profile = 'standard'; + + public static function getInfo() { + return array( + 'name' => 'Condition: taxonomy', + 'description' => 'Test taxonomy condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'taxonomy'); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'create article content')); + $this->drupalLogin($admin_user); + + // Create test terms. + $this->vocab = taxonomy_vocabulary_machine_name_load('tags'); + + $this->terms = array(); + $this->terms['apples'] = (object)array('name' => 'apples', 'vid' => $this->vocab->vid); + $this->terms['oranges'] = (object)array('name' => 'oranges', 'vid' => $this->vocab->vid); + taxonomy_term_save($this->terms['apples']); + taxonomy_term_save($this->terms['oranges']); + + // Create test context. + ctools_include('export'); + $this->context = ctools_export_new_object('context'); + $this->context->name = 'testcontext'; + $this->context->conditions = array('node_taxonomy' => array('values' => array($this->terms['apples']->tid))); + $this->context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($this->context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + } + + function tearDown() { + parent::tearDown(); + context_delete($this->context); + taxonomy_term_delete($this->terms['apples']->tid); + taxonomy_term_delete($this->terms['oranges']->tid); + } + + function test() { + // Apples does trigger the context. + $edit = array( + 'title' => 'Apples', + 'field_tags[und]' => $this->terms['apples']->name + ); + $this->drupalPost('node/add/article', $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->drupalGet('node/' . $node->nid); + $this->assertText('Active context: testcontext'); + + // Oranges does not trigger the context. + $edit = array( + 'title' => 'Oranges', + 'field_tags[und]' => $this->terms['oranges']->name + ); + $this->drupalPost('node/add/article', $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->drupalGet('node/' . $node->nid); + $this->assertNoText('Active context: testcontext'); + } +} + +class ContextConditionLanguageTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: language', + 'description' => 'Test language condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'locale'); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages')); + $this->drupalLogin($admin_user); + + // Set up Spanish as second language. + $this->drupalPost('admin/config/regional/language/add', array('langcode' => 'es'), t('Add language')); + $this->drupalPost('admin/config/regional/language/configure', array('language[enabled][locale-url]' => 1), t('Save settings')); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('language' => array('values' => array('es'))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet('node'); + $this->assertNoText('Active context: testcontext'); + + $this->drupalGet('es/node'); + $this->assertText('Active context: testcontext'); + + // Cleanup + context_delete($context); + } +} + +class ContextConditionSitewideTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: sitewide', + 'description' => 'Test sitewide condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + $admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('sitewide' => array('values' => array(1))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet('node'); + $this->assertText('Active context: testcontext'); + + // Cleanup + context_delete($context); + } +} + +class ContextConditionPathTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: path', + 'description' => 'Test path condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'path'); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer nodes')); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('path' => array('values' => array('admin', 'node/*'))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet('admin'); + $this->assertText('Active context: testcontext'); + + $node = $this->drupalCreateNode(); + $this->drupalGet("node/{$node->nid}"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet('node'); + $this->assertNoText('Active context: testcontext'); + + // Cleanup + context_delete($context); + + // @TODO: Test with path alias + // @TODO: Test with language prefixes + } +} + +class ContextConditionContextTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: context', + 'description' => 'Test context condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer nodes')); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('path' => array('values' => array('admin'))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $subcontext = ctools_export_new_object('context'); + $subcontext->name = 'subcontext'; + $subcontext->conditions = array('context' => array('values' => array('testcontext'))); + $subcontext->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($subcontext); + $this->assertTrue($saved, "Context 'subcontext' saved."); + + $this->drupalGet('admin'); + $this->assertText('Active context: testcontext'); + $this->assertText('Active context: subcontext'); + + // Cleanup + context_delete($context); + + // @TODO: Test exclusion + } +} + +class ContextConditionNodeTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: node', + 'description' => 'Test node condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'blog', 'book'); + $admin_user = $this->drupalCreateUser(array( + 'administer site configuration', + 'administer nodes', + 'create blog content', + 'create book content' + )); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('node' => array('values' => array('blog'))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet("node/add/blog"); + $this->assertNoText('Active context: testcontext'); + + $this->drupalGet("node/add/book"); + $this->assertNoText('Active context: testcontext'); + + $node = $this->drupalCreateNode(array('type' => 'blog')); + $this->drupalGet("node/{$node->nid}"); + $this->assertText('Active context: testcontext'); + + $node = $this->drupalCreateNode(array('type' => 'book')); + $this->drupalGet("node/{$node->nid}"); + $this->assertNoText('Active context: testcontext'); + + $context->conditions['node']['options']['node_form'] = 1; + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet("node/add/blog"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet("node/add/book"); + $this->assertNoText('Active context: testcontext'); + + // Cleanup + context_delete($context); + } +} + +class ContextConditionMenuTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: menu', + 'description' => 'Test menu condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'blog', 'menu'); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer nodes', 'create blog content')); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('menu' => array('values' => array('node/add'))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet("node/add/blog"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet("node/add"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet("node"); + $this->assertNoText('Active context: testcontext'); + + // Cleanup + context_delete($context); + } +} + +class ContextConditionBookTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: book', + 'description' => 'Test book condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'book', 'menu'); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer nodes')); + $this->drupalLogin($admin_user); + } + + function test() { + $book = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => 'new'))); + $child = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => $book->nid))); + $dummy = $this->drupalCreateNode(array('type' => 'book')); + + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('book' => array('values' => array(book_menu_name($book->book['bid'])))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet("node/{$book->nid}"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet("node/{$child->nid}"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet("node/{$dummy->nid}"); + $this->assertNoText('Active context: testcontext'); + + // Cleanup + context_delete($context); + } +} + +class ContextConditionBookroot extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Condition: bookroot', + 'description' => 'Test bookroot condition.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'book', 'menu'); + $admin_user = $this->drupalCreateUser(array( + 'administer site configuration', + 'administer nodes', + 'create book content', + 'edit any book content', + )); + $this->drupalLogin($admin_user); + variable_set('book_allowed_types', array('book', 'page')); + } + + function test() { + $book = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => 'new'))); + $child = $this->drupalCreateNode(array('type' => 'book', 'book' => array('bid' => $book->nid))); + + $dummy = $this->drupalCreateNode(array('type' => 'page', 'book' => array('bid' => 'new'))); + $dummy_child = $this->drupalCreateNode(array('type' => 'page', 'book' => array('bid' => $dummy->nid))); + + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('bookroot' => array('values' => array('book'))); + $context->reactions = array('debug' => array('debug' => TRUE)); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet("node/{$book->nid}"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet("node/{$child->nid}"); + $this->assertText('Active context: testcontext'); + + $this->drupalGet("node/{$dummy->nid}"); + $this->assertNoText('Active context: testcontext'); + + $this->drupalGet("node/{$dummy_child->nid}"); + $this->assertNoText('Active context: testcontext'); + + $this->drupalGet("node/{$book->nid}/edit"); + $this->assertNoText('Active context: testcontext'); + + $context->conditions['bookroot']['options']['node_form'] = 1; + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet("node/{$book->nid}/edit"); + $this->assertText('Active context: testcontext'); + + // Cleanup + context_delete($context); + } +} diff --git a/sites/all/modules/context/tests/context.reactions.test b/sites/all/modules/context/tests/context.reactions.test new file mode 100644 index 0000000000000000000000000000000000000000..42bcfa6f88b514085d4a216da246128cae2a2b3e --- /dev/null +++ b/sites/all/modules/context/tests/context.reactions.test @@ -0,0 +1,229 @@ +<?php + +class ContextReactionBlockTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Reaction: block', + 'description' => 'Test block reaction.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'block'); + $admin_user = $this->drupalCreateUser(array( + 'administer site configuration', + 'administer blocks' + )); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('sitewide' => array('values' => array(1))); + $context->reactions = array('block' => array('blocks' => array( + 'user-online' => array( + 'module' => 'user', + 'delta' => 'online', + 'region' => 'sidebar_first', + 'weight' => 0, + ), + ))); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + theme_enable(array('bartik')); + variable_set('theme_default', 'bartik'); + $this->refreshVariables(); + + $this->drupalGet('node'); + $this->assertText('Who\'s online'); + + // Test title override of code provided block + $edit = array('title' => 'Context Online Block'); + $this->drupalPost('admin/structure/block/manage/user/online/configure', $edit, t('Save block')); + $this->drupalGet('node'); + $this->assertText('Context Online Block'); + + // Test title of custom block + $edit = array( + 'info' => 'Context Custom Block Info', + 'title' => 'Context Custom Block Title', + 'body[value]' => $this->randomName(32), + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $edit['info']))->fetchField(); + + $context->reactions['block']['blocks']["block-{$bid}"] = array( + 'module' => 'block', + 'delta' => $bid, + 'region' => 'sidebar_first', + 'weight' => 2, + ); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalGet('node'); + $this->assertText('Context Custom Block Title'); + + // Cleanup + context_delete($context); + } +} + +class ContextReactionBlockAjaxTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Reaction: block ajax', + 'description' => 'Test block reaction ajax behavior.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + } + + function test() { + $this->drupalGet('node', array( + 'query' => array('context_block' => 'user-online,testcontext') + )); + + $this->assertText('"status":1'); + $this->assertText("Who's online"); + } +} + +class ContextReactionMenuTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Reaction: menu', + 'description' => 'Test menu reaction.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools', 'menu', 'blog'); + $admin_user = $this->drupalCreateUser(array( + 'administer menu', + 'administer nodes', + 'administer site configuration', + 'create blog content', + )); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('sitewide' => array('values' => array(1))); + $context->reactions = array('menu' => 'node/add'); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $this->drupalPost('admin/structure/menu/settings', array('menu_main_links_source' => 'navigation'), 'Save configuration'); + theme_enable(array('bartik')); + variable_set('theme_default', 'bartik'); + $this->refreshVariables(); + + $output = $this->drupalGet('user'); + $url = url('node/add'); + $active = $this->xpath('//li[contains(@class, "active")]/a[@href="'. $url .'"]'); + $this->assertTrue(!empty($active), t('Active menu item found.')); + + // Cleanup + context_delete($context); + } +} + +class ContextReactionBreadcrumbTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Reaction: breadcrumb', + 'description' => 'Test breadcrumb reaction.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + $admin_user = $this->drupalCreateUser(array( + 'access administration pages', + 'administer nodes', + 'administer site configuration' + )); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('path' => array('values' => array('node'))); + $context->reactions = array('breadcrumb' => 'admin/structure'); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + theme_enable(array('bartik')); + variable_set('theme_default', 'bartik'); + $this->refreshVariables(); + + $output = $this->drupalGet('node'); + $this->assertText('Home » Administration » Structure'); + $output = $this->drupalGet('user'); + $this->assertNoText('Home » Administration » Structure'); + + // Cleanup + context_delete($context); + } +} + +class ContextReactionThemeHtmlTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Reaction: theme html', + 'description' => 'Test theme html reaction.', + 'group' => 'Context', + ); + } + + function setUp() { + parent::setUp('context', 'ctools'); + $admin_user = $this->drupalCreateUser(array( + 'access administration pages', + 'administer nodes', + 'administer site configuration' + )); + $this->drupalLogin($admin_user); + } + + function test() { + ctools_include('export'); + $context = ctools_export_new_object('context'); + $context->name = 'testcontext'; + $context->conditions = array('sitewide' => array('values' => array(1))); + $context->reactions = array('theme_html' => array('class' => 'context-test-class')); + $saved = context_save($context); + $this->assertTrue($saved, "Context 'testcontext' saved."); + + $output = $this->drupalGet('node'); + $this->assertRaw('context-test-class'); + + // Cleanup + context_delete($context); + } +} diff --git a/sites/all/modules/context/tests/context.test b/sites/all/modules/context/tests/context.test new file mode 100644 index 0000000000000000000000000000000000000000..8b7c9d5a7cc937db58275a5e7ef8d9e641de28e2 --- /dev/null +++ b/sites/all/modules/context/tests/context.test @@ -0,0 +1,89 @@ +<?php + +class ContextUnitTest extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'API unit tests', + 'description' => 'Sets all possible context types and checks for integrity.', + 'group' => 'Context', + ); + } + + public function setUp() { + parent::setUp('context'); + } + + public function test() { + // define possible data types + $set_types = array( + 'bool' => TRUE, + 'int' => 1, + 'string' => 'lorem', + 'array' => array('lorem'), + 'object' => new stdClass(), + ); + $id_types = array('int', 'string'); + + // NAMESPACE + foreach ($set_types as $type => $val) { + $set = context_set($val); + // Test return value of context_set() + if (in_array($type, $id_types)) { + // test set integrity + $this->assertIdentical(true, $set, 'Space set successful.'); + // test get integrity + $this->assertIdentical(array(), context_get($val), 'Namespace get successful.'); + $this->assertIdentical(true, context_exists($val), 'Namespace exists successful.'); + } + else { + $this->assertIdentical(false, $set, 'Prohibited namespace not established.'); + } + context_clear(); + } + + // NAMESPACE+ATTRIBUTE + foreach ($set_types as $type => $val) { + foreach ($set_types as $type2 => $val2) { + // test set integrity + $set = context_set($val, $val2); + if (in_array($type, $id_types)) { + // test set integrity + if ($type2 != 'bool') { + $this->assertIdentical(true, $set, 'Namespace and attribute set successful.'); + } + else { + $this->assertIdentical(false, $set); + } + // test get + exists integrity + if (in_array($type2, $id_types)) { + $this->assertIdentical(true, (context_get($val, $val2) == $val2), 'Namespace and attribute get successful.'); + $this->assertIdentical(true, context_exists($val, $val2), 'Namespace and attribute exists.'); + } + else if (in_array($type2, array('array', 'object'))) { + $this->assertIdentical(true, (context_get($val) == $val2), 'Namespace and attribute get successful.'); + $this->assertIdentical(true, context_exists($val), 'Namespace and attribute exists.'); + } + } + } + context_clear(); + } + + // NAMESPACE+ATTRIBUTE+VALUE, o lord + foreach ($set_types as $type => $val) { + foreach ($set_types as $type2 => $val2) { + foreach ($set_types as $type3 => $val3) { + $set = context_set($val, $val2, $val3); + if (in_array($type, $id_types)) { + if (in_array($type2, $id_types)) { + $this->assertIdentical(true, (context_get($val, $val2, $val3) == $val3), 'Namespace, attribute and value get successful.'); + $this->assertIdentical(true, context_exists($val, $val2, $val3), 'Namespace, attribute and value exists.'); + } + } + context_clear(); + } + } + } + } +} diff --git a/sites/all/modules/context/theme/context-block-browser-item.tpl.php b/sites/all/modules/context/theme/context-block-browser-item.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..788f84c1c895b8f64bcfbfe18a3056beb64e2885 --- /dev/null +++ b/sites/all/modules/context/theme/context-block-browser-item.tpl.php @@ -0,0 +1,4 @@ +<div id='context-block-addable-<?php print $bid ?>' class='context-block-item context-block-addable clearfix'> + <span class='icon'></span> + <?php print $info ?> +</div> diff --git a/sites/all/modules/context/theme/context-block-browser.tpl.php b/sites/all/modules/context/theme/context-block-browser.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..2359951a1d21caa753ff27288a3eccf4b6f8ed03 --- /dev/null +++ b/sites/all/modules/context/theme/context-block-browser.tpl.php @@ -0,0 +1,17 @@ +<div class='context-block-browser clearfix'> + + <div class='categories'><?php print render($categories) ?></div> + + <?php foreach ($blocks as $module => $module_blocks): ?> + + <?php if (!empty($module_blocks)): ?> + <div class='category category-<?php print $module ?> clearfix'> + <?php foreach ($module_blocks as $block): ?> + <?php print theme('context_block_browser_item', array('block' => $block)); ?> + <?php endforeach; ?> + </div> + <?php endif; ?> + + <?php endforeach; ?> + +</div> diff --git a/sites/all/modules/context/theme/context_reaction_block.theme.inc b/sites/all/modules/context/theme/context_reaction_block.theme.inc new file mode 100644 index 0000000000000000000000000000000000000000..aeabda930e16b9474be765a2f8dc27e13a591d36 --- /dev/null +++ b/sites/all/modules/context/theme/context_reaction_block.theme.inc @@ -0,0 +1,104 @@ +<?php + +/** + * Block form. + */ +function theme_context_block_form($vars) { + $row = array( + array('data' => drupal_render($vars['form']['blocks']), 'class' => array('blocks')), + array('data' => drupal_render($vars['form']['selector']) . drupal_render($vars['form']['block']['help']), 'class' => array('selector')), + ); + + $output = drupal_render_children($vars['form']); + + $table = array( + 'rows' => array($row), + 'attributes' => array('id' => 'context-blockform'), + ); + $output .= theme('table', $table); + return $output; +} + +/** + * Generates the AJAX enabled block administration portion of the context_ui admin form. + */ +function theme_context_block_regions_form($vars) { + $form = $vars['form']; + + // Add draggable weights + drupal_add_js('misc/tableheader.js'); + drupal_add_js(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.js'); + drupal_add_css(drupal_get_path('module', 'context') .'/plugins/context_reaction_block.css'); + + $output = ''; + + foreach (element_children($form) as $region) { + $attr = array( + 'id' => "context-blockform-region-{$region}", + 'class' => array("context-blockform-region"), + ); + drupal_add_tabledrag($attr['id'], 'order', 'sibling', 'tabledrag-hide', NULL, NULL, FALSE); + $rows = array(); + foreach (element_children($form[$region]) as $id) { + $form[$region][$id]['weight']['#attributes'] = array('class' => array('tabledrag-hide')); + $label = $form[$region][$id]['#value']; + $remove = l('X', $_GET['q'], array('fragment' => 'remove', 'attributes' => array('class' => array('remove')))); + $rows[] = array( + 'data' => array($label, drupal_render($form[$region][$id]['weight']), $remove), + 'class' => array('draggable'), + 'id' => $id, + ); + } + $output .= "<div class='label context-blockform-regionlabel-{$region}'>"; + $output .= l('+ '. t('Add'), $_GET['q'], array('fragment' => $region, 'attributes' => array('class' => array('add-block')))); + $output .= $form[$region]['#title']; + $output .= "</div>"; + $output .= theme('table', array('rows' => $rows, 'attributes' => $attr)); + } + return $output; +} + +/** + * Use placeholder content for script tags that need to be replaced. + */ +function theme_context_block_script_placeholder($text = '') { + $message = t('Please reload the page to view this block.'); + return "<div class='script-placeholder'><strong>{$text}</strong><div class='description'>{$message}</div></div>"; +} + +/** + * Preprocessor for theme('context_block_browser'). + */ +function template_preprocess_context_block_browser(&$vars) { + $categories = array( + '#type' => 'select', + '#options' => array(0 => '<'. t('Choose a category') .'>'), + '#attributes' => array('class' => array('context-block-browser-categories')), + '#value' => 0, + '#size' => 1, + '#id' => '', + '#name' => '', + '#parents' => array(''), + '#multiple' => FALSE, + '#required' => FALSE, + ); + $blocks = array(); + // Group blocks by module. + foreach ($vars['blocks'] as $block) { + if (!isset($categories[$block->module])) { + $info = system_get_info('module', $block->module); + $categories['#options'][$block->module] = !empty($info['name']) ? $info['name'] : $block->module; + } + $blocks[$block->module][$block->bid] = $block; // Don't call theme('context_block_browser_item') to allow others to alter. + } + $vars['categories'] = $categories; // Don't call theme('select') here to allow further preprocesses to alter the element. + $vars['blocks'] = $blocks; +} + +/** + * Preprocessor for theme('context_block_browser_item'). + */ +function template_preprocess_context_block_browser_item(&$vars) { + $vars['bid'] = $vars['block']->bid; + $vars['info'] = check_plain($vars['block']->info); +} diff --git a/sites/all/modules/menu_block/CHANGELOG.txt b/sites/all/modules/menu_block/CHANGELOG.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f5b93fb088987d5165f24aa6199c6afeb662659 --- /dev/null +++ b/sites/all/modules/menu_block/CHANGELOG.txt @@ -0,0 +1,122 @@ +Menu Block 7.x-2.2, 2011-03-09 +------------------------------ +- #1086376: Custom menu fix doesn't work for new installations + +Menu Block 7.x-2.1, 2011-02-06 +------------------------------ +- #1051988: Fix wrong path in menu_block_help() +- #1050766: Improve usability of "Parent item" UI +- #1050040 by AgentRickard: Remove performance-killing variable_set() calls on non-admin pages + +Menu Block 7.x-2.0, 2011-01-11 +------------------------------ +- #1022478: Add "Edit book outline" contextual link for book menu blocks +- #1022428: Contextual links for all menu blocks disappear if book menu_block used +- #1017142: except for one menu, all other menus (including books) never receive an active trail +- #1017122 by becw, mfer and JohnAlbin: Core bug work-around: add active trail to custom menus +- Always show core menu blocks if they are in a region +- #993998 by tgf: Core menu suppression broken +- #993998 by jackinloadup: Delete links misplaced on menu list form +- #958166 by Simon Georges and JohnAlbin: secondary-menu removed from core; replace with user-menu +- #825132: Performance problem on sites with many books +- #945714 by pedrochristopher: theme override misidentified in README +- #593126: hook_get_menus() causes conflicts with Menu Access, og_menu, etc +- #825132: Add hook_menu_block_get_sort_menus() for improved performance and UX of book integration +- #957362 by blixxxa: Add Swedish translation +- Fix theme hook suggestions for non-numeric block deltas + +Menu Block 7.x-2.0-beta4, 2010-09-29 +------------------------------------ +- #891690: Never accept a fix not in patch form. And if you do, test it! + +Menu Block 7.x-2.0-beta3, 2010-09-29 +------------------------------------ +- #891698 by Chris Gillis: Incorrect link to configuration page +- #891690 by Chris Gillis: Undefined function db_fetch_array +- Updated menu_block_get_title() to return a renderable array +- Add #bid context to menu_link theme hook + +Menu Block 7.x-2.0-beta2, 2010-04-16 +------------------------------------ +- Add ability to suppress core's standard menu blocks on block admin page +- #693302: Add simple bulk export module +- Fixed import of exportable menu_blocks +- Fixed bug causing missing "delete" link for menu blocks +- Fixed configure link on modules page + +Menu Block 7.x-2.0-beta1, 2010-03-26 +------------------------------------ +- #693302: Add API for exportable menu_blocks +- #749838: Port to Drupal 7 + +Menu Block 6.x-2.3, 2010-03-24 +------------------------------ +- #739282: Users with "administer menu" privileges can exploit XSS vulnerability +- #343061 by sun and JohnAlbin: CSS styling breaks form layout +- #345552 by Dmitriy.trt: Inconsistent display of starting level set to children + of active item +- #474784: Menu title as link is incorrectly always marked as in active trail +- #540842 by JohnAlbin and agentrickard: Add option to use current page's + selected menu +- #580348: Add administrative title to config form to help organize blocks +- #350029: Add theme hook suggestions for all theme function calls +- #741284 by JohnAlbin, sdboyer, and hefox: Add "menu tree" content types to + Chaos Tools/Panels. +- #741284 by JohnAlbin and hefox: Add menu_block_get_config() and + menu_block_configure_form() to make the configuration form reusable by + separating it from the Block API functions. +- #553842 by apodran and JohnAlbin: split() is deprecated in PHP 5.3 +- #398888: "exanded" elements of a menu-item-rooted tree aren't expanded +- #703968 by hefox and JohnAlbin: Add menu_tree_build() to allow reuse of tree + building code outside of blocks +- Refactored API for menu_block_get_title() and menu_block_set_title() +- Added HOOK_menu_block_tree_alter() to allow direct tree manipulation +- by Denes Szabo and Zoltan Balogh: Added Hungarian translation +- Make default menu be "Primary links" instead of "Navigation" +- #378206 by sbordage: Added French translation +- #345419: Add option for menu title as link +- #347805: Add delta to variables and classes in menu-block-wrapper.tpl + +Menu Block 6.x-2.2, 2008-12-16 +------------------------------ +- #342498: Give unique class to depth-limited leaves that have children +- #341436: Depth-limited leaves that have children get "collapsed" icon +- #341345: WSOD on "Add menu block" page with some PHP versions + +Menu Block 6.x-2.1, 2008-12-01 +------------------------------ +- #300086: Add option to make starting level follow active menu item +- #340868: Clean up display of block configuration options +- #300094: Add option to sort active trail to top of menu tree +- #328238 by gorbeia: Add support for i18n menu translation +- #331934: Add option to select parent item of menu tree +- #330978: Add hook_get_menus() and implement Book module integration +- #331935: Replace admin/by-module hack with README.txt +- #332974: Collapsed menu items get "leaf" class when using "depth" option +- #333988: Create template for menu-block-wrapper +- #331933 by sun: Help links are displayed if Help module is disabled +- #338263: Migration from 5.x-1.x to 6.x-2.x is broken +- #305267: Menu blocks incorrectly cached per role + +Menu Block 6.x-2.0, 2008-08-25 +------------------------------ +- Added extensive documentation help text +- Added extended classes to menu trees +- Menu block's administrative interface now matches the block module's standard + add, configure, and delete interface +- #266230: Port Menu block to D6 + +Menu Block 5.x-2.0, 2008-11-24 +------------------------------ +- #304675: Port 6.x admin interface to 5.x +- Added missing dependency on menu module + +Menu Block 5.x-1.0, 2008-08-05 +------------------------------ +- Simplified block configuration + +Menu Block 5.x-0.9, 2008-08-05 +------------------------------ +- #266223: Add option to limit the depth of the tree to an arbitrary level +- Added block config to specify whether to expand all children +- Added settings to enable specific menu blocks diff --git a/sites/all/modules/menu_block/LICENSE.txt b/sites/all/modules/menu_block/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..2c095c8d3f42488e8168f9710a4ffbfc4125a159 --- /dev/null +++ b/sites/all/modules/menu_block/LICENSE.txt @@ -0,0 +1,274 @@ +GNU GENERAL PUBLIC LICENSE + + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, +Cambridge, MA 02139, USA. Everyone is permitted to copy and distribute +verbatim copies of this license document, but changing it is not allowed. + + Preamble + +The licenses for most software are designed to take away your freedom to +share and change it. By contrast, the GNU General Public License is +intended to guarantee your freedom to share and change free software--to +make sure the software is free for all its users. This General Public License +applies to most of the Free Software Foundation's software and to any other +program whose authors commit to using it. (Some other Free Software +Foundation software is covered by the GNU Library General Public License +instead.) You can apply it to your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our +General Public Licenses are designed to make sure that you have the +freedom to distribute copies of free software (and charge for this service if +you wish), that you receive source code or can get it if you want it, that you +can change the software or use pieces of it in new free programs; and that +you know you can do these things. + +To protect your rights, we need to make restrictions that forbid anyone to +deny you these rights or to ask you to surrender the rights. These restrictions +translate to certain responsibilities for you if you distribute copies of the +software, or if you modify it. + +For example, if you distribute copies of such a program, whether gratis or for +a fee, you must give the recipients all the rights that you have. You must make +sure that they, too, receive or can get the source code. And you must show +them these terms so they know their rights. + +We protect your rights with two steps: (1) copyright the software, and (2) +offer you this license which gives you legal permission to copy, distribute +and/or modify the software. + +Also, for each author's protection and ours, we want to make certain that +everyone understands that there is no warranty for this free software. If the +software is modified by someone else and passed on, we want its recipients +to know that what they have is not the original, so that any problems +introduced by others will not reflect on the original authors' reputations. + +Finally, any free program is threatened constantly by software patents. We +wish to avoid the danger that redistributors of a free program will individually +obtain patent licenses, in effect making the program proprietary. To prevent +this, we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + +The precise terms and conditions for copying, distribution and modification +follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND + MODIFICATION + +0. This License applies to any program or other work which contains a notice +placed by the copyright holder saying it may be distributed under the terms +of this General Public License. The "Program", below, refers to any such +program or work, and a "work based on the Program" means either the +Program or any derivative work under copyright law: that is to say, a work +containing the Program or a portion of it, either verbatim or with +modifications and/or translated into another language. (Hereinafter, translation +is included without limitation in the term "modification".) Each licensee is +addressed as "you". + +Activities other than copying, distribution and modification are not covered +by this License; they are outside its scope. The act of running the Program is +not restricted, and the output from the Program is covered only if its contents +constitute a work based on the Program (independent of having been made +by running the Program). Whether that is true depends on what the Program +does. + +1. You may copy and distribute verbatim copies of the Program's source +code as you receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice and +disclaimer of warranty; keep intact all the notices that refer to this License +and to the absence of any warranty; and give any other recipients of the +Program a copy of this License along with the Program. + +You may charge a fee for the physical act of transferring a copy, and you +may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion of it, +thus forming a work based on the Program, and copy and distribute such +modifications or work under the terms of Section 1 above, provided that you +also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices stating that +you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in whole or in +part contains or is derived from the Program or any part thereof, to be +licensed as a whole at no charge to all third parties under the terms of this +License. + +c) If the modified program normally reads commands interactively when run, +you must cause it, when started running for such interactive use in the most +ordinary way, to print or display an announcement including an appropriate +copyright notice and a notice that there is no warranty (or else, saying that +you provide a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this License. +(Exception: if the Program itself is interactive but does not normally print such +an announcement, your work based on the Program is not required to print +an announcement.) + +These requirements apply to the modified work as a whole. If identifiable +sections of that work are not derived from the Program, and can be +reasonably considered independent and separate works in themselves, then +this License, and its terms, do not apply to those sections when you distribute +them as separate works. But when you distribute the same sections as part +of a whole which is a work based on the Program, the distribution of the +whole must be on the terms of this License, whose permissions for other +licensees extend to the entire whole, and thus to each and every part +regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to +work written entirely by you; rather, the intent is to exercise the right to +control the distribution of derivative or collective works based on the +Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of a +storage or distribution medium does not bring the other work under the scope +of this License. + +3. You may copy and distribute the Program (or a work based on it, under +Section 2) in object code or executable form under the terms of Sections 1 +and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable source +code, which must be distributed under the terms of Sections 1 and 2 above +on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three years, to give +any third party, for a charge no more than your cost of physically performing +source distribution, a complete machine-readable copy of the corresponding +source code, to be distributed under the terms of Sections 1 and 2 above on +a medium customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer to distribute +corresponding source code. (This alternative is allowed only for +noncommercial distribution and only if you received the program in object +code or executable form with such an offer, in accord with Subsection b +above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source code +means all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation and +installation of the executable. However, as a special exception, the source +code distributed need not include anything that is normally distributed (in +either source or binary form) with the major components (compiler, kernel, +and so on) of the operating system on which the executable runs, unless that +component itself accompanies the executable. + +If distribution of executable or object code is made by offering access to +copy from a designated place, then offering equivalent access to copy the +source code from the same place counts as distribution of the source code, +even though third parties are not compelled to copy the source along with the +object code. + +4. You may not copy, modify, sublicense, or distribute the Program except as +expressly provided under this License. Any attempt otherwise to copy, +modify, sublicense or distribute the Program is void, and will automatically +terminate your rights under this License. However, parties who have received +copies, or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +5. You are not required to accept this License, since you have not signed it. +However, nothing else grants you permission to modify or distribute the +Program or its derivative works. These actions are prohibited by law if you +do not accept this License. Therefore, by modifying or distributing the +Program (or any work based on the Program), you indicate your acceptance +of this License to do so, and all its terms and conditions for copying, +distributing or modifying the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the original +licensor to copy, distribute or modify the Program subject to these terms and +conditions. You may not impose any further restrictions on the recipients' +exercise of the rights granted herein. You are not responsible for enforcing +compliance by third parties to this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), conditions +are imposed on you (whether by court order, agreement or otherwise) that +contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot distribute so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not distribute the Program at all. +For example, if a patent license would not permit royalty-free redistribution +of the Program by all those who receive copies directly or indirectly through +you, then the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply and +the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or +other property right claims or to contest validity of any such claims; this +section has the sole purpose of protecting the integrity of the free software +distribution system, which is implemented by public license practices. Many +people have made generous contributions to the wide range of software +distributed through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing to +distribute software through any other system and a licensee cannot impose +that choice. + +This section is intended to make thoroughly clear what is believed to be a +consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in certain +countries either by patents or by copyrighted interfaces, the original copyright +holder who places the Program under this License may add an explicit +geographical distribution limitation excluding those countries, so that +distribution is permitted only in or among countries not thus excluded. In such +case, this License incorporates the limitation as if written in the body of this +License. + +9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will be +similar in spirit to the present version, but may differ in detail to address new +problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies +a version number of this License which applies to it and "any later version", +you have the option of following the terms and conditions either of that +version or of any later version published by the Free Software Foundation. If +the Program does not specify a version number of this License, you may +choose any version ever published by the Free Software Foundation. + +10. If you wish to incorporate parts of the Program into other free programs +whose distribution conditions are different, write to the author to ask for +permission. For software which is copyrighted by the Free Software +Foundation, write to the Free Software Foundation; we sometimes make +exceptions for this. Our decision will be guided by the two goals of +preserving the free status of all derivatives of our free software and of +promoting the sharing and reuse of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT +PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE +STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT +WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND +PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL +NECESSARY SERVICING, REPAIR OR CORRECTION. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR +AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR +ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE +LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, +SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA +OR DATA BEING RENDERED INACCURATE OR LOSSES +SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE +PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN +IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF +THE POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/sites/all/modules/menu_block/README.txt b/sites/all/modules/menu_block/README.txt new file mode 100644 index 0000000000000000000000000000000000000000..75fa7131c21344d8e912f4e507d8a480b1147e87 --- /dev/null +++ b/sites/all/modules/menu_block/README.txt @@ -0,0 +1,140 @@ +ADDING MENU BLOCKS +------------------ + +To add new menu blocks, use the "Add menu block" link on the administer blocks +page, admin/structure/block. You will then be able to configure your menu block +before adding it. + + +CONFIGURING MENU BLOCKS +----------------------- + +When adding or configuring a menu block, several configuration options are +available: + +Basic Options: + +Block title + For menu trees that start with the 1st level, the default block title will be + the menu name. For menu trees that start with the 2nd level or deeper, the + default block title will be the title for the parent menu item of the + specified level. + + For example, if the active menu trail for the Management menu is: Administer > + Structure > Menus > Main menu, then a menu block configured to start with the + 1st level of the Management menu will display a block title of "Management". + And a menu block configured to start with the 3rd level of the Management menu + will display a block title of "Structure". + +Block title as link + For menu trees that start with the 2nd level or deeper, the default block + title will be the title for the parent menu item of the specified level. If + this option is checked, the block title will be a link to that menu item. + +Administrative title + To help identify the block on the administer blocks page, you can specify a + unique title to be used on that page. If blank, the regular title will be + used. + +Menu name + Select the menu to use for the tree of links. + +Starting level + Blocks that start with the 1st level will always be visible. Blocks that start + with the 2nd level or deeper will only be visible when the trail to the active + menu item is in the block's tree. + +Maximum depth + From the starting level, specify the maximum depth of the tree. Blocks with a + maximum depth of 1 will just be a single un-nested list of links with none of + those links' children displayed. + +Advanced options: + +Make the starting level follow the active menu item + If the active menu item is deeper than the level specified above, the starting + level will follow the active menu item. Otherwise, the starting level of the + tree will remain fixed. + +Expand + All children of this menu will be expanded. + +Sort + Sort each item in the active trail to the top of its level. When used on a + deep or wide menu tree, the active menu item's children will be easier to see + when the page is reloaded. + +Fixed parent item + If you select a specific menu item, you alter the "starting level" and + "maximum depth" options to be relative to the fixed parent item. The tree of + links will only contain children of the selected parent item. + + +STYLING MENU BLOCKS +------------------- + +Classes: + +Themers should look at the myriad of classes added to the <div>, <li> and <a> +elements. + +<div> + The <div> wrapped around the menu tree has a class for several of the + configurable options of the block: menu-block-[block id number] + menu-name-[menu name] parent-mlid-[menu link ID] menu-level-[level number] + +<li> + The <li> elements of the menu tree can have an extended list of classes + (compared to standard menu trees): first last menu-mlid-[menu link ID] + has-children active active-trail + +<a> + The <a> elements of the menu tree can have: active active-trail + +Templates: + +In addition, the wrapper <div> for the block is generated using the +menu-block-wrapper.tpl.php template. And Menu block provides several theme hook +suggestions for that template: +- menu-block-wrapper--[block id number].tpl.php +- menu-block-wrapper--[menu name].tpl.php + +For example, a file in your theme called +menu-block-wrapper--main-menu.tpl.php can be used to override the <div> for +just the "Primary links" menu blocks. + +Theme functions: + +Menu block uses Drupal core's menu theme functions. However, it also provides +theme hook suggestions that can be used to override any of the theme functions +called by it. + +- theme_menu_tree() can be overridden by creating one of: + - [theme]_menu_tree__[menu name]() + - [theme]_menu_tree__menu_block() + - [theme]_menu_tree__menu_block__[menu name]() + - [theme]_menu_tree__menu_block__[block id number]() + +- theme_menu_link() can be overridden by creating one of: + - [theme]_menu_link__[menu name]() + - [theme]_menu_link__menu_block() + - [theme]_menu_link__menu_block__[menu name]() + - [theme]_menu_link__menu_block__[block id number]() + +For example, if you created a bartik_menu_tree__menu_block() function, it would +override theme_menu_tree() any time it was used by this module, but not when +used by any other module. Similarly, a bartik_menu_link__menu_block__1() +function would override theme_menu_link(), but only for the first menu block in +your system (the menu block with an ID of 1). + + +MENU BLOCK API +-------------- + +Developers can use the API of this module to create their own menu trees outside +the confines of blocks. All of the publicly available API functions are +documented in the menu_block.module file. + +In addition, Menu block implements HOOK_menu_block_get_menus(), +HOOK_menu_block_get_sort_menus() and HOOK_menu_block_tree_alter(). See +menu_block.api.php for documentation. diff --git a/sites/all/modules/menu_block/custom_menu_active_trail.inc b/sites/all/modules/menu_block/custom_menu_active_trail.inc new file mode 100644 index 0000000000000000000000000000000000000000..9fd8eb831d26bf40784ea0aaea1ac7b83769e395 --- /dev/null +++ b/sites/all/modules/menu_block/custom_menu_active_trail.inc @@ -0,0 +1,185 @@ +<?php +/** + * @file + * Add custom menus to the active trail; required to fix Drupal 7.0 core. + * + * This file WILL be removed after this core bug is fixed: #942782. You have + * been warned! + */ + +/** + * Implements hook_menu_insert(). + */ +function menu_block_menu_insert($menu) { + _menu_block_add($menu); +} + +/** + * Implements hook_menu_update(). + */ +function menu_block_menu_update($menu) { + _menu_block_add($menu); +} + +/** + * Implements hook_menu_delete(). + */ +function menu_block_menu_delete($menu) { + $active_menus = variable_get('menu_default_active_menus', array_keys(menu_list_system_menus())); + if (in_array($menu['menu_name'], $active_menus)) { + $key = array_search($menu['menu_name'], $active_menus); + unset($active_menus[$key]); + variable_set('menu_default_active_menus', $active_menus); + } +} + +/** + * Add a custom menu to the active trail. + * + * @param array $menu + * A menu item to add to the active trail. + */ +function _menu_block_add($menu) { + // Make sure the menu is present in the active menus variable so that its + // items may appear in the menu active trail. + // @see menu_set_active_menu_names() + $active_menus = variable_get('menu_default_active_menus', array_keys(menu_list_system_menus())); + if (!in_array($menu['menu_name'], $active_menus)) { + $active_menus[] = $menu['menu_name']; + variable_set('menu_default_active_menus', $active_menus); + } +} + +/** + * Muck with the static cache of menu_link_get_preferred. + * + * In ensure all menus get an active trail, pre-set the static cache of + * menu_link_get_preferred() for the specific menu we want to render. + */ +function __menu_block_set_menu_trail($menu_name = FALSE) { + // If there's no menu item, no active trail hack is needed. + if (($item = menu_get_item()) && $item == FALSE) { + return; + } + + $preferred_links = &drupal_static('menu_link_get_preferred'); + $originals = &drupal_static(__FUNCTION__, array()); + $path = $_GET['q']; + + if ($menu_name) { + // Alter the preferred link so it always checks the requested menu. + $originals['preferred_link'] = isset($preferred_links[$path]) ? $preferred_links[$path] : NULL; + $preferred_links[$path] = _menu_link_get_preferred($path, $menu_name); + + // Ensure the active menu always includes the requested menu. + $originals['active_menus'] = variable_get('menu_default_active_menus', NULL); + $GLOBALS['conf']['menu_default_active_menus'] = array($menu_name); + } + else { + // Reset the original preferred_link static variables. + if (is_null($originals['preferred_link'])) { + unset($preferred_links[$path]); + } + else { + $preferred_links[$path] = $originals['preferred_link']; + } + // Reset the original menu_default_active_menus variable. + $GLOBALS['conf']['menu_default_active_menus'] = $originals['active_menus']; + } +} + +/** + * Lookup the preferred menu link for a given system path. + * + * @param $path + * The path, for example 'node/5'. The function will find the corresponding + * menu link ('node/5' if it exists, or fallback to 'node/%'). + * @param $menu_name + * The name of a menu used to restrict the search for a prefered menu link. + * If not specified, all the menus returned by menu_get_active_menu_names() + * will be used. + * + * @return + * A fully translated menu link, or NULL if not matching menu link was + * found. The most specific menu link ('node/5' preferred over 'node/%') in + * the most preferred menu (as defined by menu_get_active_menu_names()) is + * returned. + */ +function _menu_link_get_preferred($path = NULL, $menu_name = '') { + $preferred_links = &drupal_static(__FUNCTION__); + + if (!isset($path)) { + $path = $_GET['q']; + } + + if (!isset($preferred_links[$path][$menu_name])) { + $preferred_links[$path][$menu_name] = FALSE; + + // Look for the correct menu link by building a list of candidate paths, + // which are ordered by priority (translated hrefs are preferred over + // untranslated paths). Afterwards, the most relevant path is picked from + // the menus, ordered by menu preference. + $item = menu_get_item($path); + $path_candidates = array(); + // 1. The current item href. + $path_candidates[$item['href']] = $item['href']; + // 2. The tab root href of the current item (if any). + if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) { + $path_candidates[$tab_root['href']] = $tab_root['href']; + } + // 3. The current item path (with wildcards). + $path_candidates[$item['path']] = $item['path']; + // 4. The tab root path of the current item (if any). + if (!empty($tab_root)) { + $path_candidates[$tab_root['path']] = $tab_root['path']; + } + + // Retrieve a list of menu names, ordered by preference. + if ($menu_name !== '') { + $menu_names = array($menu_name); + } + else { + $menu_names = menu_get_active_menu_names(); + } + + $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); + $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); + $query->fields('ml'); + // Weight must be taken from {menu_links}, not {menu_router}. + $query->addField('ml', 'weight', 'link_weight'); + $query->fields('m'); + $query->condition('ml.link_path', $path_candidates, 'IN'); + + // Sort candidates by link path and menu name. + $candidates = array(); + foreach ($query->execute() as $candidate) { + $candidate['weight'] = $candidate['link_weight']; + $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; + } + + // Pick the most specific link, in the most preferred menu. + $preferred_link = FALSE; + foreach ($path_candidates as $link_path) { + if (isset($candidates[$link_path])) { + foreach ($menu_names as $menu) { + if (!$preferred_links[$path][$menu] && isset($candidates[$link_path][$menu])) { + $candidate_item = $candidates[$link_path][$menu]; + $map = explode('/', $path); + _menu_translate($candidate_item, $map); + if ($candidate_item['access']) { + $preferred_links[$path][$menu] = $candidate_item; + if (!$preferred_link) { + $preferred_link = $candidate_item; + } + } + } + } + } + } + if ($menu_name === '' && $preferred_link) { + $preferred_links[$path][$menu_name] = $preferred_link; + } + } + + return $preferred_links[$path][$menu_name]; +} diff --git a/sites/all/modules/menu_block/menu-block-admin.css b/sites/all/modules/menu_block/menu-block-admin.css new file mode 100644 index 0000000000000000000000000000000000000000..fb58280a3a9064237cb9b283c6d2996a60e2bfa9 --- /dev/null +++ b/sites/all/modules/menu_block/menu-block-admin.css @@ -0,0 +1,71 @@ +.menu-block-parent-wrapper { + margin-top: 0.5em; + margin-bottom: 0.5em; +} + +label#item-label { + font-weight: normal; +} + + +/* Proper buttonset styling is missing from Seven */ +.menu-block-processed { + padding: 0 15px 15px 15px; + border: 1px solid #ccc; +} +.form-type-radios.form-item-display-options { + display: none; + margin: 0 -15px; + padding: 5px 15px; + text-align: left; + background-color: #eee; +} +.form-type-radio.form-item-display-options { + float: left; +} +.form-item.form-type-radios.form-item-display-options label, +.form-item.form-type-radio.form-item-display-options label { + font-size: 12px; + line-height: 18px; + font-weight: bold; +} +.form-item-display-options .ui-buttonset .form-item { + padding: 0; /* Seven adds padding */ +} +.form-item-display-options .ui-buttonset label.ui-button { + display: inline-block; + padding: 2px 10px; + border: 1px solid #666; + color: #666; + font-weight: bold; + background-image: url(menu-block-background-display-options.png); + background-position: left top; + background-repeat: no-repeat; +} +.form-item-display-options .ui-buttonset label.ui-state-active { + color: #fff; + background-position: left -50px; +} +.form-item-display-options .ui-buttonset label.ui-button.ui-corner-right { + border-left: none; +} +.form-item-display-options .ui-button .ui-button-text { + display: inline; + padding: 0; +} +.ui-buttonset .ui-corner-left { + -moz-border-radius-topleft: 5px + -moz-border-radius-bottomleft: 5px; + -webkit-border-bottom-left-radius: 5px; + -webkit-border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + border-top-left-radius: 5px; +} +.ui-buttonset .ui-corner-right { + -moz-border-radius-topright: 5px + -moz-border-radius-bottomright: 5px; + -webkit-border-bottom-right-radius: 5px; + -webkit-border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; +} diff --git a/sites/all/modules/menu_block/menu-block-background-display-options.png b/sites/all/modules/menu_block/menu-block-background-display-options.png new file mode 100644 index 0000000000000000000000000000000000000000..4a7d4ad692bb8aba119497e00e7bb000782f6bb5 Binary files /dev/null and b/sites/all/modules/menu_block/menu-block-background-display-options.png differ diff --git a/sites/all/modules/menu_block/menu-block-wrapper.tpl.php b/sites/all/modules/menu_block/menu-block-wrapper.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..d98bca31225c7f321aa8ebb6dd348246e383f905 --- /dev/null +++ b/sites/all/modules/menu_block/menu-block-wrapper.tpl.php @@ -0,0 +1,23 @@ +<?php +/** + * @file + * Default theme implementation to wrap menu blocks. + * + * Available variables: + * - $content: The renderable array containing the menu. + * - $classes: A string containing the CSS classes for the DIV tag. Includes: + * menu-block-DELTA, menu-name-NAME, parent-mlid-MLID, and menu-level-LEVEL. + * - $classes_array: An array containing each of the CSS classes. + * + * The following variables are provided for contextual information. + * - $delta: (string) The menu_block's block delta. + * - $config: An array of the block's configuration settings. Includes + * menu_name, parent_mlid, title_link, admin_title, level, follow, depth, + * expanded, and sort. + * + * @see template_preprocess_menu_block_wrapper() + */ +?> +<div class="<?php print $classes; ?>"> + <?php print render($content); ?> +</div> diff --git a/sites/all/modules/menu_block/menu-block.js b/sites/all/modules/menu_block/menu-block.js new file mode 100644 index 0000000000000000000000000000000000000000..73a99adbb93f8a908f5d73d698bc1f8f1259dc6a --- /dev/null +++ b/sites/all/modules/menu_block/menu-block.js @@ -0,0 +1,36 @@ +(function ($) { + +Drupal.behaviors.menu_block = { + attach: function (context, settings) { + // This behavior attaches by ID, so is only valid once on a page. + if ($('#menu-block-settings.menu-block-processed').size()) { + return; + } + $('#menu-block-settings', context).addClass('menu-block-processed'); + + // Show the "display options" if javascript is on. + $('.form-item-display-options.form-type-radios>label', context).addClass('element-invisible'); + $('.form-item-display-options.form-type-radios', context).show(); + // Make the radio set into a jQuery UI buttonset. + $('#edit-display-options', context).buttonset(); + + // Override the default show/hide animation for Form API states. + $('#menu-block-settings', context).bind('state:visible', function(e) { + if (e.trigger) { + e.stopPropagation() /* Stop the handler further up the tree. */ + $(e.target).closest('.form-item, .form-wrapper')[e.value ? 'slideDown' : 'slideUp']('fast'); + } + }); + + // Syncronize the display of menu and parent item selects. + $('.menu-block-parent-mlid', context).change( function() { + var menuItem = $(this).val().split(':'); + $('.menu-block-menu-name').val(menuItem[0]); + }); + $('.menu-block-menu-name', context).change( function() { + $('.menu-block-parent-mlid').val($(this).val() + ':0'); + }); + } +}; + +})(jQuery); diff --git a/sites/all/modules/menu_block/menu_block.admin.inc b/sites/all/modules/menu_block/menu_block.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..0465e5317d5b9175952ba9f95d15d0c164c77a97 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.admin.inc @@ -0,0 +1,670 @@ +<?php +/** + * @file + * Provides infrequently used functions and hooks for menu_block. + */ + +/** + * Implements hook_menu(). + */ +function _menu_block_menu() { + $items['admin/structure/block/add-menu-block'] = array( + 'title' => 'Add menu block', + 'description' => 'Add a new menu block.', + 'access arguments' => array('administer blocks'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('menu_block_add_block_form'), + 'type' => MENU_LOCAL_ACTION, + 'file' => 'menu_block.admin.inc', + ); + $items['admin/structure/block/delete-menu-block'] = array( + 'title' => 'Delete menu block', + 'access arguments' => array('administer blocks'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('menu_block_delete'), + 'type' => MENU_CALLBACK, + 'file' => 'menu_block.admin.inc', + ); + $items['admin/config/user-interface/menu-block'] = array( + 'title' => 'Menu block', + 'description' => 'Configure menu block.', + 'access arguments' => array('administer blocks'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('menu_block_admin_settings_form'), + 'type' => MENU_NORMAL_ITEM, + 'file' => 'menu_block.admin.inc', + ); + return $items; +} + +/** + * Implements hook_menu_alter(). + */ +function menu_block_menu_alter(&$items) { + // Fake the necessary menu attributes necessary for a contextual link. + $items['admin/content/book/%node']['title'] = 'Edit book outline'; + $items['admin/content/book/%node']['type'] = MENU_LOCAL_TASK; + $items['admin/content/book/%node']['context'] = (MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE); + $items['admin/content/book/%node']['tab_root'] = 'admin/content/book'; +} + +/** + * Implements hook_theme(). + */ +function _menu_block_theme(&$existing, $type, $theme, $path) { + // Add theme hook suggestion patterns for the core theme functions used in + // this module. We can't add them during hook_theme_registry_alter() because + // we will already have missed the opportunity for the theme engine's + // theme_hook() to process the pattern. And we can't run the pattern ourselves + // because we aren't given the type, theme and path in that hook. + $existing['menu_tree']['pattern'] = 'menu_tree__'; + $existing['menu_link']['pattern'] = 'menu_link__'; + + return array( + 'menu_block_wrapper' => array( + 'template' => 'menu-block-wrapper', + 'variables' => array('content' => array(), 'config' => array(), 'delta' => NULL), + 'pattern' => 'menu_block_wrapper__', + ), + 'menu_block_menu_order' => array( + 'render element' => 'element', + 'file' => 'menu_block.admin.inc', + ), + ); +} + +/** + * Implements hook_ctools_plugin_directory(). + */ +function _menu_block_ctools_plugin_directory($module, $plugin) { + if ($plugin == 'content_types') { + return 'plugins/' . $plugin; + } +} + +/** + * Menu callback: display the menu block addition form. + * + * @see menu_block_add_block_form_submit() + */ +function menu_block_add_block_form($form, &$form_state) { + module_load_include('inc', 'block', 'block.admin'); + return block_admin_configure($form, $form_state, 'menu_block', NULL); +} + +/** + * Save the new menu block. + */ +function menu_block_add_block_form_submit($form, &$form_state) { + // Determine the delta of the new block. + $block_ids = variable_get('menu_block_ids', array()); + $delta = empty($block_ids) ? 1 : max($block_ids) + 1; + + // Save the new array of blocks IDs. + $block_ids[] = $delta; + variable_set('menu_block_ids', $block_ids); + + // Save the block configuration. + menu_block_block_save($delta, $form_state['values']); + + // Run the normal new block submission (borrowed from block_add_block_form_submit). + $query = db_insert('block')->fields(array('visibility', 'pages', 'custom', 'title', 'module', 'theme', 'region', 'status', 'weight', 'delta', 'cache')); + foreach (list_themes() as $key => $theme) { + if ($theme->status) { + $region = !empty($form_state['values']['regions'][$theme->name]) ? $form_state['values']['regions'][$theme->name] : BLOCK_REGION_NONE; + $query->values(array( + 'visibility' => (int) $form_state['values']['visibility'], + 'pages' => trim($form_state['values']['pages']), + 'custom' => (int) $form_state['values']['custom'], + 'title' => $form_state['values']['title'], + 'module' => $form_state['values']['module'], + 'theme' => $theme->name, + 'region' => ($region == BLOCK_REGION_NONE ? '' : $region), + 'status' => 0, + 'status' => (int) ($region != BLOCK_REGION_NONE), + 'weight' => 0, + 'delta' => $delta, + 'cache' => DRUPAL_NO_CACHE, + )); + } + } + $query->execute(); + + $query = db_insert('block_role')->fields(array('rid', 'module', 'delta')); + foreach (array_filter($form_state['values']['roles']) as $rid) { + $query->values(array( + 'rid' => $rid, + 'module' => $form_state['values']['module'], + 'delta' => $delta, + )); + } + $query->execute(); + + drupal_set_message(t('The block has been created.')); + cache_clear_all(); + $form_state['redirect'] = 'admin/structure/block'; +} + +/** + * Alters the block admin form to add delete links next to menu blocks. + */ +function _menu_block_form_block_admin_display_form_alter(&$form, $form_state) { + $blocks = module_invoke_all('menu_block_blocks'); + foreach (variable_get('menu_block_ids', array()) AS $delta) { + if (empty($blocks[$delta])) { + $form['blocks']['menu_block_' . $delta]['delete'] = array('#type' => 'link', '#title' => t('delete'), '#href' => 'admin/structure/block/delete-menu-block/' . $delta); + } + } + if (variable_get('menu_block_suppress_core')) { + foreach (array_keys(menu_get_menus(FALSE)) AS $delta) { + if (empty($form['blocks']['menu_' . $delta]['region']['#default_value'])) { + unset($form['blocks']['menu_' . $delta]); + } + } + foreach (array_keys(menu_list_system_menus()) AS $delta) { + if (empty($form['blocks']['system_' . $delta]['region']['#default_value'])) { + unset($form['blocks']['system_' . $delta]); + } + } + } +} + +/** + * Menu callback: confirm deletion of menu blocks. + */ +function menu_block_delete($form, &$form_state, $delta = 0) { + $title = _menu_block_format_title(menu_block_get_config($delta)); + $form['block_title'] = array('#type' => 'hidden', '#value' => $title); + $form['delta'] = array('#type' => 'hidden', '#value' => $delta); + + return confirm_form($form, t('Are you sure you want to delete the "%name" block?', array('%name' => $title)), 'admin/structure/block', NULL, t('Delete'), t('Cancel')); +} + +/** + * Deletion of menu blocks. + */ +function menu_block_delete_submit($form, &$form_state) { + // Remove the menu block configuration variables. + $delta = $form_state['values']['delta']; + $block_ids = variable_get('menu_block_ids', array()); + unset($block_ids[array_search($delta, $block_ids)]); + sort($block_ids); + variable_set('menu_block_ids', $block_ids); + variable_del("menu_block_{$delta}_title_link"); + variable_del("menu_block_{$delta}_admin_title"); + variable_del("menu_block_{$delta}_parent"); + variable_del("menu_block_{$delta}_level"); + variable_del("menu_block_{$delta}_follow"); + variable_del("menu_block_{$delta}_depth"); + variable_del("menu_block_{$delta}_expanded"); + variable_del("menu_block_{$delta}_sort"); + + db_delete('block') + ->condition('module', 'menu_block') + ->condition('delta', $delta) + ->execute(); + db_delete('block_role') + ->condition('module', 'menu_block') + ->condition('delta', $delta) + ->execute(); + drupal_set_message(t('The block "%name" has been removed.', array('%name' => $form_state['values']['block_title']))); + cache_clear_all(); + $form_state['redirect'] = 'admin/structure/block'; + return; +} + +/** + * Implements hook_block_info(). + */ +function _menu_block_block_info() { + $blocks = array(); + $deltas = variable_get('menu_block_ids', array()); + foreach (array_keys(module_invoke_all('menu_block_blocks')) as $delta) { + $deltas[] = $delta; + } + foreach ($deltas AS $delta) { + $blocks[$delta]['info'] = _menu_block_format_title(menu_block_get_config($delta)); + // Menu blocks can't be cached because each menu item can have + // a custom access callback. menu.inc manages its own caching. + $blocks[$delta]['cache'] = DRUPAL_NO_CACHE; + } + return $blocks; +} + +/** + * Return the title of the block. + * + * @param $config + * array The configuration of the menu block. + * @return + * string The title of the block. + */ +function _menu_block_format_title($config) { + // If an administrative title is specified, use it. + if (!empty($config['admin_title'])) { + return check_plain($config['admin_title']); + } + $menus = menu_block_get_all_menus(); + $menus[MENU_TREE__CURRENT_PAGE_MENU] = t('Current menu'); + if (empty($config['menu_name']) || empty($menus[$config['menu_name']])) { + $title = t('Unconfigured menu block'); + } + else { + // Show the configured levels in the block info + $replacements = array( + '@menu_name' => $menus[$config['menu_name']], + '@level1' => $config['level'], + '@level2' => $config['level'] + $config['depth'] - 1, + ); + if ($config['parent_mlid']) { + $parent_item = menu_link_load($config['parent_mlid']); + $replacements['@menu_name'] = $parent_item['title']; + } + if ($config['follow']) { + $title = t('@menu_name (active menu item)', $replacements); + } + elseif ($config['depth'] == 1) { + $title = t('@menu_name (level @level1)', $replacements); + } + elseif ($config['depth']) { + if ($config['expanded']) { + $title = t('@menu_name (expanded levels @level1-@level2)', $replacements); + } + else { + $title = t('@menu_name (levels @level1-@level2)', $replacements); + } + } + else { + if ($config['expanded']) { + $title = t('@menu_name (expanded levels @level1+)', $replacements); + } + else { + $title = t('@menu_name (levels @level1+)', $replacements); + } + } + } + return $title; +} + +/** + * Implements hook_block_configure(). + */ +function _menu_block_block_configure($delta = '') { + // Create a pseudo form state. + $form_state = array('values' => menu_block_get_config($delta)); + return menu_block_configure_form(array(), $form_state); +} + +/** + * Returns the configuration form for a menu tree. + * + * @param $form_state + * array An associated array of configuration options should be present in the + * 'values' key. If none are given, default configuration is assumed. + * @return + * array The form in Form API format. + */ +function menu_block_configure_form($form, &$form_state) { + $config = array(); + // Get the config from the form state. + if (!empty($form_state['values'])) { + $config = $form_state['values']; + if (!empty($config['parent'])) { + list($config['menu_name'], $config['parent_mlid']) = explode(':', $config['parent']); + } + } + // Merge in the default configuration. + $config += menu_block_get_config(); + + // Don't display the config form if this delta is exported to code. + if (!empty($config['exported_to_code'])) { + $form['exported_message'] = array( + '#markup' => '<p><em>' . t('Configuration is being provided by code.') . '</em></p>', + ); + return $form; + } + + // Build the standard form. + $form['#attached']['js'][] = drupal_get_path('module', 'menu_block') . '/menu-block.js'; + $form['#attached']['css'][] = drupal_get_path('module', 'menu_block') . '/menu-block-admin.css'; + $form['#attached']['library'][] = array('system', 'ui.button'); + + $form['menu-block-wrapper-start'] = array( + '#markup' => '<div id="menu-block-settings">', + '#weight' => -20, + ); + $form['display_options'] = array( + '#type' => 'radios', + '#title' => t('Display'), + '#default_value' => 'basic', + '#options' => array( + 'basic' => t('Basic options'), + 'advanced' => t('Advanced options'), + ), + '#attributes' => array('class' => array('clearfix')), + '#weight' => -19, + ); + $form['title_link'] = array( + '#type' => 'checkbox', + '#title' => t('Block title as link'), + '#default_value' => $config['title_link'], + '#description' => t('Make the default block title a link to that menu item. An overridden block title will not be a link.'), + '#states' => array( + 'visible' => array( + ':input[name=title]' => array('value' => ''), + ), + ), + ); + // We need a different state if the form is in a Panel overlay. + if (isset($form['override_title'])) { + $form['title_link']['#states'] = array( + 'visible' => array( + ':input[name=override_title]' => array('checked' => FALSE), + ), + ); + } + $form['admin_title'] = array( + '#type' => 'textfield', + '#default_value' => $config['admin_title'], + '#title' => t('Administrative title'), + '#description' => t('This title will be used administratively to identify this block. If blank, the regular title will be used.'), + ); + $menus = menu_block_get_all_menus(); + $form['menu_name'] = array( + '#type' => 'select', + '#title' => t('Menu'), + '#default_value' => $config['menu_name'], + '#options' => $menus, + '#description' => t('The preferred menus used by <em><the menu selected by the page></em> can be customized on the <a href="!url">Menu block settings page</a>.', array('!url' => url('admin/config/user-interface/menu-block'))), + '#attributes' => array('class' => array('menu-block-menu-name')), + ); + $form['level'] = array( + '#type' => 'select', + '#title' => t('Starting level'), + '#default_value' => $config['level'], + '#options' => array( + '1' => t('1st level (primary)'), + '2' => t('2nd level (secondary)'), + '3' => t('3rd level (tertiary)'), + '4' => t('4th level'), + '5' => t('5th level'), + '6' => t('6th level'), + '7' => t('7th level'), + '8' => t('8th level'), + '9' => t('9th level'), + ), + '#description' => t('Blocks that start with the 1st level will always be visible. Blocks that start with the 2nd level or deeper will only be visible when the trail to the active menu item is in the block’s tree.'), + ); + // The value of "follow" in the database/config array is either FALSE or the + // value of the "follow_parent" form element. + if ($follow = $config['follow']) { + $follow_parent = $follow; + $follow = 1; + } + else { + $follow_parent = 'active'; + } + $form['follow'] = array( + '#type' => 'checkbox', + '#title' => t('Make the starting level follow the active menu item.'), + '#default_value' => $follow, + '#description' => t('If the active menu item is deeper than the level specified above, the starting level will follow the active menu item. Otherwise, the starting level of the tree will remain fixed.'), + '#element_validate' => array('menu_block_configure_form_follow_validate'), + ); + $form['follow_parent'] = array( + '#type' => 'select', + '#title' => t('Starting level will be'), + '#default_value' => $follow_parent, + '#options' => array( + 'active' => t('Active menu item'), + 'child' => t('Children of active menu item'), + ), + '#description' => t('When following the active menu item, specify if the starting level should be the active menu item or its children.'), + '#states' => array( + 'visible' => array( + ':input[name=follow]' => array('checked' => TRUE), + ), + ), + ); + $form['depth'] = array( + '#type' => 'select', + '#title' => t('Maximum depth'), + '#default_value' => $config['depth'], + '#options' => array( + '1' => '1', + '2' => '2', + '3' => '3', + '4' => '4', + '5' => '5', + '6' => '6', + '7' => '7', + '8' => '8', + '9' => '9', + '0' => t('Unlimited'), + ), + '#description' => t('From the starting level, specify the maximum depth of the menu tree.'), + ); + $form['expanded'] = array( + '#type' => 'checkbox', + '#title' => t('<strong>Expand all children</strong> of this tree.'), + '#default_value' => $config['expanded'], + ); + $form['sort'] = array( + '#type' => 'checkbox', + '#title' => t('<strong>Sort</strong> menu tree by the active menu item’s trail.'), + '#default_value' => $config['sort'], + '#description' => t('Sort each item in the active trail to the top of its level. When used on a deep or wide menu tree, the active menu item’s children will be easier to see when the page is reloaded.'), + ); + $form['parent_mlid'] = array( + '#type' => 'select', + '#title' => t('Fixed parent item'), + '#default_value' => $config['menu_name'] . ':' . $config['parent_mlid'], + '#options' => menu_parent_options($menus, array('mlid' => 0)), + '#description' => t('Alter the “starting level” and “maximum depth” options to be relative to the fixed parent item. The tree of links will only contain children of the selected menu item.'), + '#attributes' => array('class' => array('menu-block-parent-mlid')), + '#element_validate' => array('menu_block_configure_form_parent_validate'), + ); + $form['parent_mlid']['#options'][MENU_TREE__CURRENT_PAGE_MENU . ':0'] = '<' . t('the menu selected by the page') . '>'; + $form['menu-block-wrapper-close'] = array('#markup' => '</div>'); + + // Set visibility of advanced options. + foreach (array('title_link', 'follow', 'follow_parent', 'expanded', 'sort', 'parent_mlid') as $key) { + $form[$key]['#states']['visible'][':input[name=display_options]'] = array('value' => 'advanced'); + } + if ($config['title_link'] || $follow || $config['expanded'] || $config['sort'] || $config['parent_mlid']) { + $form['display_options']['#default_value'] = 'advanced'; + } + + return $form; +} + +/** + * Validates the parent element of the block configuration form. + */ +function menu_block_configure_form_parent_validate($element, &$form_state) { + // Determine the fixed parent item's menu and mlid. + list($menu_name, $parent_mlid) = explode(':', $form_state['values']['parent_mlid']); + if ($parent_mlid) { + // If mlid is set, its menu overrides the menu_name option. + $form_state['values']['menu_name'] = $menu_name; + } + else { + // Otherwise the menu_name overrides the parent item option. + $form_state['values']['parent_mlid'] = $menu_name . ':0'; + } + // The value of "parent" stored in the database/config array is the menu name + // combined with the optional parent menu item's mlid. + $form_state['values']['parent'] = $form_state['values']['parent_mlid']; +} + +/** + * Validates the follow element of the block configuration form. + */ +function menu_block_configure_form_follow_validate($element, &$form_state) { + // The value of "follow" stored in the database/config array is either FALSE + // or the value of the "follow_parent" form element. + if ($form_state['values']['follow'] && !empty($form_state['values']['follow_parent'])) { + $form_state['values']['follow'] = $form_state['values']['follow_parent']; + } +} + +/** + * Implements hook_block_save(). + */ +function _menu_block_block_save($delta = '', $edit = array()) { + if (!empty($delta)) { + // Don't save values for an exported block. + $config = menu_block_get_config($delta); + if (empty($config['exported_to_code'])) { + variable_set("menu_block_{$delta}_title_link", $edit['title_link']); + variable_set("menu_block_{$delta}_admin_title", $edit['admin_title']); + variable_set("menu_block_{$delta}_parent", $edit['parent']); + variable_set("menu_block_{$delta}_level", $edit['level']); + variable_set("menu_block_{$delta}_follow", $edit['follow']); + variable_set("menu_block_{$delta}_depth", $edit['depth']); + variable_set("menu_block_{$delta}_expanded", $edit['expanded']); + variable_set("menu_block_{$delta}_sort", $edit['sort']); + } + } +} + +/** + * Menu callback: admin settings form. + * + * @return + * The settings form used by Menu block. + */ +function menu_block_admin_settings_form($form, &$form_state) { + // Option to suppress core's blocks of menus. + $form['menu_block_suppress_core'] = array( + '#type' => 'checkbox', + '#title' => t('Suppress Drupal’s standard menu blocks'), + '#default_value' => variable_get('menu_block_suppress_core', 0), + '#description' => t('On the blocks admin page, hide Drupal’s standard blocks of menus.'), + ); + + // Retrieve core's menus. + $menus = menu_get_menus(); + // Include book support. + if (module_exists('book')) { + module_load_include('inc', 'menu_block', 'menu_block.book'); + } + // Retrieve all the menu names provided by hook_menu_block_get_sort_menus(). + $menus = array_merge($menus, module_invoke_all('menu_block_get_sort_menus')); + asort($menus); + + // Load stored configuration. + $menu_order = variable_get('menu_block_menu_order', array('main-menu' => '', 'user-menu' => '')); + // Remove any menus no longer in the list of all menus. + foreach (array_keys($menu_order) as $menu) { + if (!isset($menus[$menu])) { + unset($menu_order[$menu]); + } + } + + // Merge the saved configuration with any un-configured menus. + $all_menus = $menu_order + $menus; + + $form['heading'] = array( + '#markup' => '<p>' . t('If a block is configured to use <em>"the menu selected by the page"</em>, the block will be generated from the first "available" menu that contains a link to the page.') . '</p>', + ); + + // Orderable list of menu selections. + $form['menu_order'] = array( + '#tree' => TRUE, + '#theme' => 'menu_block_menu_order', + ); + + $order = 0; + $total_menus = count($all_menus); + foreach (array_keys($all_menus) as $menu_name) { + $form['menu_order'][$menu_name] = array( + 'title' => array( + '#markup' => check_plain($menus[$menu_name]), + ), + 'available' => array( + '#type' => 'checkbox', + '#attributes' => array('title' => t('Select from the @menu_name menu', array('@menu_name' => $menus[$menu_name]))), + '#default_value' => isset($menu_order[$menu_name]), + ), + 'weight' => array( + '#type' => 'weight', + '#default_value' => $order - $total_menus, + '#delta' => $total_menus, + '#id' => 'edit-menu-block-menus-' . $menu_name, + ), + ); + $order++; + } + + $form['footer_note'] = array( + '#markup' => '<p>' . t('The above list will <em>not</em> affect menu blocks that are configured to use a specific menu.') . '</p>', + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} + +/** + * Form submission handler. + */ +function menu_block_admin_settings_form_submit($form, &$form_state) { + $menu_order = array(); + foreach ($form_state['values']['menu_order'] AS $menu_name => $row) { + if ($row['available']) { + // Add available menu and its weight to list. + $menu_order[$menu_name] = (int) $row['weight']; + } + } + // Sort the keys by the weight stored in the value. + asort($menu_order); + foreach ($menu_order AS $menu_name => $weight) { + // Now that the array is sorted, the weight is redundant data. + $menu_order[$menu_name] = ''; + } + variable_set('menu_block_menu_order', $menu_order); + if ($form_state['values']['menu_block_suppress_core']) { + variable_set('menu_block_suppress_core', 1); + } + else { + variable_del('menu_block_suppress_core'); + } + drupal_set_message('The configuration options have been saved.'); +} + +/** + * Theme a drag-to-reorder table of menu selection checkboxes. + */ +function theme_menu_block_menu_order($variables) { + $element = $variables['element']; + drupal_add_tabledrag('menu-block-menus', 'order', 'sibling', 'menu-weight'); + + $variables = array( + 'header' => array( + t('Menu'), + t('Available'), + t('Weight'), + ), + 'rows' => array(), + 'attributes' => array('id' => 'menu-block-menus'), + ); + + // Generate table of draggable menu names. + foreach (element_children($element) as $menu_name) { + $element[$menu_name]['weight']['#attributes']['class'] = array('menu-weight'); + $variables['rows'][] = array( + 'data' => array( + drupal_render($element[$menu_name]['title']), + drupal_render($element[$menu_name]['available']), + drupal_render($element[$menu_name]['weight']), + ), + 'class' => array('draggable'), + ); + } + + return theme('table', $variables); +} diff --git a/sites/all/modules/menu_block/menu_block.api.php b/sites/all/modules/menu_block/menu_block.api.php new file mode 100644 index 0000000000000000000000000000000000000000..3d7a4c9019d9793107896e25527e306f2e5095b0 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.api.php @@ -0,0 +1,97 @@ +<?php +/** + * @file + * Hooks provided by the Menu Block module. + */ + +/** + * @addtogroup hooks + * @{ + */ + +/** + * Alter the menu tree and its configuration before the tree is rendered. + * + * @param $tree + * An array containing the unrendered menu tree. + * @param $config + * An array containing the configuration of the tree. + */ +function hook_menu_block_tree_alter(&$tree, &$config) { +} + +/** + * Return a list of configurations for menu blocks. + * + * Modules that want to have menu block configurations exported to code should + * provide them using this hook. + * + * @see menu_tree_build() for a description of the config array. + */ +function hook_menu_block_blocks() { + return array( + // The array key is the block id used by menu block. + 'custom-nav' => array( + // Use the array keys/values described in menu_tree_build(). + 'menu_name' => 'primary-links', + 'parent_mlid' => 0, + 'title_link' => FALSE, + 'admin_title' => 'Drop-down navigation', + 'level' => 1, + 'follow' => 0, + 'depth' => 2, + 'expanded' => TRUE, + 'sort' => FALSE, + ), + // To prevent clobbering of the block id, it is recommended to prefix it + // with the module name. + 'custom-active' => array( + 'menu_name' => MENU_TREE__CURRENT_PAGE_MENU, + 'title_link' => TRUE, + 'admin_title' => 'Secondary navigation', + 'level' => 3, + 'depth' => 3, + // Any config options not specified will get the default value. + ), + ); +} + +/** + * Return a list of menus to use with the menu_block module. + * + * @return + * An array containing the menus' machine names as keys with their menu titles + * as values. + */ +function hook_menu_block_get_menus() { + $menus = array(); + // For each menu, add the following information: + $menus['menu_name'] = 'menu title'; + + return $menus; +} + +/** + * Return a list of menus to use on menu block's settings form. + * + * Menu block's settings page sorts menus for use with its "the menu selected by + * the page" option. + * + * @return + * An array containing the menus' machine names as keys with their menu titles + * as values. The key may optionally be a regular expression to match several + * menus at a time; see book_menu_block_get_sort_menus() for an example. + */ +function hook_menu_block_get_sort_menus() { + $menus = array(); + // For each menu, add the following information: + $menus['menu_name'] = 'menu title'; + // Optionally, add a regular expression to match several menus at once. + $menus['/^my\-menus\-.+/'] = t('My menus'); + + return $menus; +} + +/** + * @} End of "addtogroup hooks". + */ diff --git a/sites/all/modules/menu_block/menu_block.book.inc b/sites/all/modules/menu_block/menu_block.book.inc new file mode 100644 index 0000000000000000000000000000000000000000..667a9c94da655e3b7400a467546a1dd9dbee2252 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.book.inc @@ -0,0 +1,25 @@ +<?php +/** + * @file + * Provides book integration. + */ + +/** + * Implements hook_menu_block_get_menus(). + */ +function book_menu_block_get_menus() { + $menus = array(); + foreach (book_get_books() AS $book) { + $menus[$book['menu_name']] = $book['title']; + } + return $menus; +} + +/** + * Implements hook_menu_block_get_sort_menus(). + */ +function book_menu_block_get_sort_menus() { + return array( + '/^book\-toc\-.+/' => t('Book navigation'), + ); +} diff --git a/sites/all/modules/menu_block/menu_block.follow.inc b/sites/all/modules/menu_block/menu_block.follow.inc new file mode 100644 index 0000000000000000000000000000000000000000..c9be2511d4c2bf52ef337c713416f2c18be95d6a --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.follow.inc @@ -0,0 +1,59 @@ +<?php +/** + * @file + * Provides active menu item pruning. + */ + +/** + * Prune a tree so that it begins at the active menu item. + * + * @param $tree + * array The menu tree to prune. + * @param $level + * string The level which the tree will be pruned to: 'active' or 'child'. + * @return + * void + */ +function _menu_tree_prune_active_tree(&$tree, $level) { + do { + $found_active_trail = FALSE; + // Examine each element at this level for the active trail. + foreach ($tree AS $key => &$value) { + if ($tree[$key]['link']['in_active_trail']) { + $found_active_trail = TRUE; + // If the active trail item has children, examine them. + if ($tree[$key]['below']) { + // If we are pruning to the active menu item's level, check if this + // is the active menu item by checking its children. + if ($level == 'active') { + foreach ($tree[$key]['below'] AS $child_key => &$value) { + if ($tree[$key]['below'][$child_key]['link']['in_active_trail']) { + // Get the title for the pruned tree. + menu_block_set_title($tree[$key]['link']); + $tree = $tree[$key]['below']; + // Continue in the pruned tree. + break 2; + } + } + // If we've found the active item, we're done. + break 2; + } + // Set the title for the pruned tree. + menu_block_set_title($tree[$key]['link']); + // If we are pruning to the children of the active menu item, just + // prune the tree to the children of the item in the active trail. + $tree = $tree[$key]['below']; + // Continue in the pruned tree. + break; + } + // If the active menu item has no children, we're done. + else { + if ($level == 'child') { + $tree = array(); + } + break 2; + } + } + } + } while ($found_active_trail); +} diff --git a/sites/all/modules/menu_block/menu_block.info b/sites/all/modules/menu_block/menu_block.info new file mode 100644 index 0000000000000000000000000000000000000000..8ac699301796f66081055642daf4cff54fca27ad --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.info @@ -0,0 +1,23 @@ +name = "Menu Block" +description = "Provides configurable blocks of menu items." + +core = 7.x +dependencies[] = menu + +files[] = menu_block.module +files[] = menu_block.admin.inc +files[] = menu_block.book.inc +files[] = menu_block.follow.inc +files[] = menu_block.pages.inc +files[] = menu_block.sort.inc +files[] = menu_block.install +# files[] = plugins/content_types/menu_tree/menu_tree.inc + +configure = admin/config/user-interface/menu-block + +; Information added by drupal.org packaging script on 2011-03-09 +version = "7.x-2.2" +core = "7.x" +project = "menu_block" +datestamp = "1299683174" + diff --git a/sites/all/modules/menu_block/menu_block.install b/sites/all/modules/menu_block/menu_block.install new file mode 100644 index 0000000000000000000000000000000000000000..5d5ab9407b5ac3f4c49f6ef8176ae26e0a771051 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.install @@ -0,0 +1,200 @@ +<?php +/** + * @file + * Provides install, upgrade and un-install functions for menu_block. + */ + +/** + * Implements hook_uninstall(). + */ +function menu_block_uninstall() { + // Delete menu block variables. + foreach (variable_get('menu_block_ids', array()) AS $delta) { + variable_del("menu_block_{$delta}_title_link"); + variable_del("menu_block_{$delta}_admin_title"); + variable_del("menu_block_{$delta}_parent"); + variable_del("menu_block_{$delta}_level"); + variable_del("menu_block_{$delta}_follow"); + variable_del("menu_block_{$delta}_depth"); + variable_del("menu_block_{$delta}_expanded"); + variable_del("menu_block_{$delta}_sort"); + } + variable_del('menu_block_ids'); + variable_del('menu_block_suppress_core'); + variable_del('menu_block_menu_order'); + // Remove block configurations. + db_delete('block') + ->condition('module', 'menu_block') + ->execute(); + db_delete('block_role') + ->condition('module', 'menu_block') + ->execute(); + cache_clear_all(); +} + +/** + * Implements hook_enable(). + */ +function menu_block_enable() { + drupal_set_message(t('To use menu blocks, find the "Add menu block" link on the <a href="@url">administer blocks page</a>.', array('@url' => url('admin/structure/block')))); + // @TODO Remove this when after this core bug is fixed: #942782. + menu_block_fix_custom_menus(); +} + +/** + * Implements hook_install(). + */ +function menu_block_install() { + // No-op. +} + +/** + * Converts pre-5.x-1.0 block names to the new format. + */ +function menu_block_update_5100() { + $delta = 0; + $enabled_blocks = array(); + + // Find the old enabled blocks. + foreach (variable_get('menu_block_enabled_blocks', array()) AS $old_delta => $enabled) { + list($mid, $level) = explode('-', $old_delta); + if ($enabled) { + $enabled_blocks[++$delta] = TRUE; + variable_set("menu_block_{$delta}_mid", $mid); + variable_set("menu_block_{$delta}_level", $level); + variable_set("menu_block_{$delta}_depth", variable_get("menu_block_depth_{$mid}_{$level}", 0)); + variable_set("menu_block_{$delta}_expanded", variable_get("menu_block_expanded_{$mid}_{$level}", 0)); + } + // Remove any of the old-style variables. + variable_del("menu_block_depth_{$mid}_{$level}"); + variable_del("menu_block_expanded_{$mid}_{$level}"); + } + variable_set('menu_block_enabled_blocks', $enabled_blocks); + + return t('A pre-release version of Menu block has been detected. All menu blocks from the pre-release version have been given a new delta and are no longer placed in any block regions; their block placement should be re-configured immediately.'); +} + +/** + * Converts menu block-tracking data to new format. + */ +function menu_block_update_5200() { + $block_ids = array(); + foreach (variable_get('menu_block_enabled_blocks', array()) AS $delta => $enabled) { + if ($enabled) { + $block_ids[] = $delta; // Build new $menu_block_ids. + // Convert $menu_block_DELTA_mid to $menu_block_DELTA_menu_name. + $mid = variable_get("menu_block_{$delta}_mid", 1); + variable_set("menu_block_{$delta}_menu_name", $mid); + // If we weren't upgraded to 5.x-2.x before the Drupal 6 upgrade, the + // mid-to-menu_name conversion is not possible. + variable_set("menu_block_{$delta}_title", $mid == 2 ? 'primary-links' : 'navigation'); + variable_del("menu_block_{$delta}_mid"); + } + else { + // Delete un-enabled menu block. + variable_del("menu_block_{$delta}_mid"); + variable_del("menu_block_{$delta}_level"); + variable_del("menu_block_{$delta}_depth"); + variable_del("menu_block_{$delta}_expanded"); + db_delete('block') + ->condition('module', 'menu_block') + ->condition('delta', $delta) + ->execute(); + db_delete('block_role') + ->condition('module', 'menu_block') + ->condition('delta', $delta) + ->execute(); + } + } + // Finish conversion of $menu_block_enabled_blocks to $menu_block_ids. + sort($block_ids); + variable_set('menu_block_ids', $block_ids); + variable_del('menu_block_enabled_blocks'); + cache_clear_all(); + + return t('A 5.x-1.x version of Menu block has been detected and an attempt was made to upgrade it. Unfortunately, you should have upgraded to Menu block 5.x-2.x before your upgrade to Drupal 7. You may need to re-configure all your menu blocks.'); +} + +/** + * Converts to Drupal 6 menu names. + */ +function menu_block_update_6200() { + $menus = menu_get_menus(); + foreach (variable_get('menu_block_ids', array()) AS $delta) { + // Drupal 6 uses the menu title to create the new menu_name. + $menu_name = preg_replace('/[^a-zA-Z0-9]/', '-', strtolower(variable_get("menu_block_{$delta}_title", ''))); + // If we can't find the new menu_name, default to the navigation menu. + if (empty($menus[$menu_name])) { + $menu_name = 'navigation'; + } + variable_set("menu_block_{$delta}_menu_name", $menu_name); + variable_del("menu_block_{$delta}_title"); + } + return t('The 5.x-2.x version of Menu block has been upgraded to the 6.x-2.0 data storage format.'); +} + +/** + * Converts the menu names to parent items. + */ +function menu_block_update_6201() { + $menus = menu_get_menus(); + foreach (variable_get('menu_block_ids', array()) AS $delta) { + variable_set("menu_block_{$delta}_parent", variable_get("menu_block_{$delta}_menu_name", 'navigation') . ':0'); + variable_del("menu_block_{$delta}_menu_name"); + } + return t('The 6.x-2.0 version of Menu block has been upgraded to the 6.x-2.1 data storage format.'); +} + +/** + * Converts to Drupal 7 menu names. + */ +function menu_block_update_7200() { + foreach (variable_get('menu_block_ids', array()) AS $delta) { + $menu_name = ''; + list($old_menu_name, $parent_mlid) = explode(':', variable_get("menu_block_{$delta}_parent", ' : ')); + switch ($old_menu_name) { + case 'primary-links': + $menu_name = 'main-menu'; + break; + case 'secondary-links': + $menu_name = 'secondary-menu'; + break; + case 'navigation': + $menu_name = 'management'; + break; + } + if ($menu_name) { + variable_set("menu_block_{$delta}_parent", $menu_name . ':' . $parent_mlid); + } + } + return t('The 6.x-2.x version of Menu block has been upgraded to use the new menu names in Drupal 7. To use menu blocks in Drupal 7, find the "Add menu block" link on the <a href="@url">administer blocks page</a>.', array('@url' => url('admin/structure/block'))); +} + +/** + * Helper function to fix custom menus in Drupal 7.0. + */ +function menu_block_fix_custom_menus() { + // Make sure all custom menus are present in the active menus variable so that + // their items may appear in the active trail. + // @see menu_set_active_menu_names() + $active_menus = variable_get('menu_default_active_menus', array_keys(menu_list_system_menus())); + $update_variable = FALSE; + foreach (menu_get_names() as $menu_name) { + if (!in_array($menu_name, $active_menus) && (strpos($menu_name, 'menu-') === 0)) { + $active_menus[] = $menu_name; + $update_variable = TRUE; + } + } + if ($update_variable) { + variable_set('menu_default_active_menus', $active_menus); + } + // Clear the menu cache. + cache_clear_all(NULL, 'cache_menu'); +} + +/** + * Add missing custom menus to active menus list. + */ +function menu_block_update_7202() { + menu_block_fix_custom_menus(); +} diff --git a/sites/all/modules/menu_block/menu_block.module b/sites/all/modules/menu_block/menu_block.module new file mode 100644 index 0000000000000000000000000000000000000000..68402c04bf67bcb636cbfe220bbbe34be8ec8e9c --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.module @@ -0,0 +1,675 @@ +<?php +/** + * @file + * Provides configurable blocks of menu items. + */ + +// A core bug in Drupal 7.0 requires this fix. +module_load_include('inc', 'menu_block', 'custom_menu_active_trail'); + +/** + * Denotes that the tree should use the menu picked by the curent page. + */ +define('MENU_TREE__CURRENT_PAGE_MENU', '_active'); + +// Off-load the following infrequently called hooks to another file. +function menu_block_menu() { + module_load_include('inc', 'menu_block', 'menu_block.admin'); + return _menu_block_menu(); +} +function menu_block_theme(&$existing, $type, $theme, $path) { + module_load_include('inc', 'menu_block', 'menu_block.admin'); + return _menu_block_theme($existing, $type, $theme, $path); +} +function menu_block_block_info() { + module_load_include('inc', 'menu_block', 'menu_block.admin'); + return _menu_block_block_info(); +} +function menu_block_block_configure($delta = '') { + module_load_include('inc', 'menu_block', 'menu_block.admin'); + return _menu_block_block_configure($delta); +} +function menu_block_block_save($delta = '', $edit = array()) { + module_load_include('inc', 'menu_block', 'menu_block.admin'); + return _menu_block_block_save($delta, $edit); +} +function menu_block_form_block_admin_display_form_alter(&$form, $form_state) { + module_load_include('inc', 'menu_block', 'menu_block.admin'); + return _menu_block_form_block_admin_display_form_alter($form, $form_state); +} +/* -- temporarily remove until ctools is ported to D7. +function menu_block_ctools_plugin_directory($module, $plugin) { + module_load_include('inc', 'menu_block', 'menu_block.admin'); + return _menu_block_ctools_plugin_directory($module, $plugin); +} +*/ + + +/** + * Implements hook_help(). + */ +function menu_block_help($path, $arg) { + switch ($path) { + case 'admin/structure/block/manage/%/%': + if ($arg[4] != 'menu_block') { + break; + } + case 'admin/help#menu_block': + case 'admin/structure/block/add-menu-block': + module_load_include('inc', 'menu_block', 'menu_block.pages'); + return _menu_block_help($path, $arg); + } +} + +/** + * Implements hook_block_view(). + */ +function menu_block_block_view($delta = '') { + $config = menu_block_get_config($delta); + $data = menu_tree_build($config); + // Add contextual links for this block. + if (!empty($data['content'])) { + if (in_array($config['menu_name'], array_keys(menu_get_menus()))) { + $data['content']['#contextual_links']['menu_block'] = array('admin/structure/menu/manage', array($config['menu_name'])); + } + elseif (strpos($config['menu_name'], 'book-toc-') === 0) { + $node = str_replace('book-toc-', '', $config['menu_name']); + $data['content']['#contextual_links']['menu_block'] = array('admin/content/book', array($node)); + } + } + return $data; +} + +/** + * Process variables for menu-block-wrapper.tpl.php. + * + * @see menu-block-wrapper.tpl.php + */ +function template_preprocess_menu_block_wrapper(&$variables) { + $variables['classes_array'][] = 'menu-block-' . $variables['delta']; + $variables['classes_array'][] = 'menu-name-' . $variables['config']['menu_name']; + $variables['classes_array'][] = 'parent-mlid-' . $variables['config']['parent_mlid']; + $variables['classes_array'][] = 'menu-level-' . $variables['config']['level']; +} + +/** + * Returns a list of menu names implemented by all modules. + * + * @return + * array A list of menu names and titles. + */ +function menu_block_get_all_menus() { + $all_menus = &drupal_static(__FUNCTION__); + + if (!$all_menus) { + if ($cached = cache_get('menu_block_menus', 'cache_menu')) { + $all_menus = $cached->data; + } + else { + // Retrieve core's menus. + $all_menus = menu_get_menus(); + // Include book support. + if (module_exists('book')) { + module_load_include('inc', 'menu_block', 'menu_block.book'); + } + // Retrieve all the menu names provided by hook_menu_block_get_menus(). + $all_menus = array_merge($all_menus, module_invoke_all('menu_block_get_menus')); + // Add an option to use the menu for the active menu item. + $all_menus[MENU_TREE__CURRENT_PAGE_MENU] = '<' . t('the menu selected by the page') . '>'; + asort($all_menus); + cache_set('menu_block_menus', $all_menus, 'cache_menu'); + } + } + return $all_menus; +} + +/** + * Returns the configuration for the requested block delta. + * + * @param $delta + * string The delta that uniquely identifies the block in the block system. If + * not specified, the default configuration will be returned. + * @return + * array An associated array of configuration options. + */ +function menu_block_get_config($delta = NULL) { + $config = array( + 'delta' => $delta, + 'menu_name' => 'main-menu', + 'parent_mlid' => 0, + 'title_link' => 0, + 'admin_title' => '', + 'level' => 1, + 'follow' => 0, + 'depth' => 0, + 'expanded' => 0, + 'sort' => 0, + ); + + // Get the block configuration options. + if ($delta) { + static $blocks; + if (!isset($blocks)) { + $blocks = module_invoke_all('menu_block_blocks'); + } + if (!empty($blocks[$delta])) { + // Merge the default values. + $config = $blocks[$delta] + $config; + // Set the delta. + $config['delta'] = $delta; + // Flag the block as exported. + $config['exported_to_code'] = TRUE; + } + + $config['title_link'] = variable_get("menu_block_{$delta}_title_link", $config['title_link']); + $config['admin_title'] = variable_get("menu_block_{$delta}_admin_title", $config['admin_title']); + $config['level'] = variable_get("menu_block_{$delta}_level", $config['level']); + $config['follow'] = variable_get("menu_block_{$delta}_follow", $config['follow']); + $config['depth'] = variable_get("menu_block_{$delta}_depth", $config['depth']); + $config['expanded'] = variable_get("menu_block_{$delta}_expanded", $config['expanded']); + $config['sort'] = variable_get("menu_block_{$delta}_sort", $config['sort']); + list($config['menu_name'], $config['parent_mlid']) = explode(':', variable_get("menu_block_{$delta}_parent", $config['menu_name'] . ':' . $config['parent_mlid'])); + } + + return $config; +} + +/** + * Build a menu tree based on the provided configuration. + * + * @param $config + * array An array of configuration options that specifies how to build the + * menu tree and its title. + * - delta: (string) The menu_block's block delta. + * - menu_name: (string) The machine name of the requested menu. Can also be + * set to MENU_TREE__CURRENT_PAGE_MENU to use the menu selected by the page. + * - parent_mlid: (int) The mlid of the item that should root the tree. Use 0 + * to use the menu's root. + * - title_link: (boolean) Specifies if the title should be rendered as a link + * or a simple string. + * - admin_title: (string) An optional title to uniquely identify the block on + * the administer blocks page. + * - level: (int) The starting level of the tree. + * - follow: (string) Specifies if the starting level should follow the + * active menu item. Should be set to 0, 'active' or 'child'. + * - depth: (int) The maximum depth the tree should contain, relative to the + * starting level. + * - expanded: (boolean) Specifies if the entire tree be expanded or not. + * - sort: (boolean) Specifies if the tree should be sorted with the active + * trail at the top of the tree. + * @return + * array An associative array containing several pieces of data. + * - content: The tree as a renderable array. + * - subject: The title rendered as HTML. + * - subject_array: The title as a renderable array. + */ +function menu_tree_build($config) { + // Retrieve the active menu item from the database. + if ($config['menu_name'] == MENU_TREE__CURRENT_PAGE_MENU) { + // Retrieve the list of available menus. + $menu_order = variable_get('menu_block_menu_order', array('main-menu' => '', 'user-menu' => '')); + + // Check for regular expressions as menu keys. + $patterns = array(); + foreach (array_keys($menu_order) as $pattern) { + if ($pattern[0] == '/') { + $patterns[$pattern] = NULL; + } + } + + // Retrieve all the menus containing a link to the current page. + $result = db_query("SELECT menu_name FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $_GET['q'] ? $_GET['q'] : '<front>')); + foreach ($result as $item) { + // Check if the menu is in the list of available menus. + if (isset($menu_order[$item->menu_name])) { + // Mark the menu. + $menu_order[$item->menu_name] = MENU_TREE__CURRENT_PAGE_MENU; + } + else { + // Check if the menu matches one of the available patterns. + foreach (array_keys($patterns) as $pattern) { + if (preg_match($pattern, $item['menu_name'])) { + // Mark the menu. + $menu_order[$pattern] = MENU_TREE__CURRENT_PAGE_MENU; + // Store the actual menu name. + $patterns[$pattern] = $item['menu_name']; + } + } + } + } + // Find the first marked menu. + $config['menu_name'] = array_search(MENU_TREE__CURRENT_PAGE_MENU, $menu_order); + // If a pattern was matched, use the actual menu name instead of the pattern. + if (!empty($patterns[$config['menu_name']])) { + $config['menu_name'] = $patterns[$config['menu_name']]; + } + $config['parent_mlid'] = 0; + + // If no menu link was found, don't display the block. + if (empty($config['menu_name'])) { + return array(); + } + } + + // Get the default block name. + $menu_names = menu_block_get_all_menus(); + menu_block_set_title(t($menu_names[$config['menu_name']])); + + // @TODO: Remove work-around for core bug #942782. + __menu_block_set_menu_trail($config['menu_name']); + + if ($config['expanded'] || $config['parent_mlid']) { + // Get the full, un-pruned tree. + $tree = menu_tree_all_data($config['menu_name']); + // And add the active trail data back to the full tree. + menu_tree_add_active_path($tree); + } + else { + // Get the tree pruned for just the active trail. + $tree = menu_tree_page_data($config['menu_name']); + } + + // @TODO: Remove work-around for core bug #942782. + __menu_block_set_menu_trail(); + + // Allow alteration of the tree and config before we begin operations on it. + drupal_alter('menu_block_tree', $tree, $config); + + // Localize the tree. + if (module_exists('i18nmenu')) { + i18nmenu_localize_tree($tree); + } + + // Prune the tree along the active trail to the specified level. + if ($config['level'] > 1 || $config['parent_mlid']) { + if ($config['parent_mlid']) { + $parent_item = menu_link_load($config['parent_mlid']); + menu_tree_prune_tree($tree, $config['level'], $parent_item); + } + else { + menu_tree_prune_tree($tree, $config['level']); + } + } + + // Prune the tree to the active menu item. + if ($config['follow']) { + menu_tree_prune_active_tree($tree, $config['follow']); + } + + // If the menu-item-based tree is not "expanded", trim the tree to the active path. + if ($config['parent_mlid'] && !$config['expanded']) { + menu_tree_trim_active_path($tree); + } + + // Trim the branches that extend beyond the specified depth. + if ($config['depth'] > 0) { + menu_tree_depth_trim($tree, $config['depth']); + } + + // Sort the active path to the top of the tree. + if ($config['sort']) { + menu_tree_sort_active_path($tree); + } + + // Render the tree. + $data = array(); + $title = menu_block_get_title($config['title_link'], $config); + $data['subject_array'] = $title; + $data['subject'] = drupal_render($title); + $data['content']['#content'] = menu_block_tree_output($tree, $config); + if (!empty($data['content']['#content'])) { + $data['content']['#theme'] = array( + 'menu_block_wrapper__' . str_replace('-', '_', $config['delta']), + 'menu_block_wrapper__' . str_replace('-', '_', $config['menu_name']), + 'menu_block_wrapper' + ); + $data['content']['#config'] = $config; + $data['content']['#delta'] = $config['delta']; + } + else { + $data['content'] = ''; + } + + return $data; +} + +/** + * Retrieves the menu item to use for the tree's title. + * + * @param $render_title_as_link + * boolean A boolean that says whether to render the title as a link or a + * simple string. + * @return + * array A renderable array containing the tree's title. + */ +function menu_block_get_title($render_title_as_link = TRUE) { + $menu_item = menu_block_set_title(); + + // The tree's title is a menu title, a normal string. + if (is_string($menu_item)) { + $title = array('#markup' => check_plain($menu_item)); + } + // The tree's title is a menu item with a link. + elseif ($render_title_as_link) { + if (!empty($menu_item['in_active_trail'])) { + if (!empty($menu_item['localized_options']['attributes']['class'])) { + $menu_item['localized_options']['attributes']['class'][] = 'active-trail'; + } + else { + $menu_item['localized_options']['attributes']['class'][] = 'active-trail'; + } + } + $title = array( + '#type' => 'link', + '#title' => $menu_item['title'], + '#href' => $menu_item['href'], + '#options' => $menu_item['localized_options'], + ); + } + // The tree's title is a menu item. + else { + $title = array('#markup' => check_plain($menu_item['title'])); + } + return $title; +} + +/** + * Sets the menu item to use for the tree's title. + * + * @param $item + * array The menu item (an array) or the menu item's title as a string. + */ +function menu_block_set_title($item = NULL) { + $menu_item = &drupal_static(__FUNCTION__); + + // Save the menu item. + if (!is_null($item)) { + $menu_item = $item; + } + + return $menu_item; +} + +/** + * Add the active trail indicators into the tree. + * + * The data returned by menu_tree_page_data() has link['in_active_trail'] set to + * TRUE for each menu item in the active trail. The data returned from + * menu_tree_all_data() does not contain the active trail indicators. This is a + * helper function that adds it back in. + * + * @param $tree + * array The menu tree. + * @return + * void + */ +function menu_tree_add_active_path(&$tree) { + // Grab any menu item to find the menu_name for this tree. + $menu_item = current($tree); + $tree_with_trail = menu_tree_page_data($menu_item['link']['menu_name']); + + // To traverse the original tree down the active trail, we use a pointer. + $subtree_pointer =& $tree; + + // Find each key in the active trail. + while ($tree_with_trail) { + foreach ($tree_with_trail AS $key => &$value) { + if ($tree_with_trail[$key]['link']['in_active_trail']) { + // Set the active trail info in the original tree. + $subtree_pointer[$key]['link']['in_active_trail'] = TRUE; + // Continue in the subtree, if it exists. + $tree_with_trail =& $tree_with_trail[$key]['below']; + $subtree_pointer =& $subtree_pointer[$key]['below']; + break; + } + else { + unset($tree_with_trail[$key]); + } + } + } +} + +/** + * Trim everything but the active trail in the tree. + * + * @param $tree + * array The menu tree to trim. + * @return + * void + */ +function menu_tree_trim_active_path(&$tree) { + foreach ($tree AS $key => &$value) { + if (($tree[$key]['link']['in_active_trail'] || $tree[$key]['link']['expanded']) && $tree[$key]['below']) { + // Continue in the subtree, if it exists. + menu_tree_trim_active_path($tree[$key]['below']); + } + else { + // Trim anything not expanded or along the active trail. + $tree[$key]['below'] = FALSE; + } + } +} + +/** + * Sort the active trail to the top of the tree. + * + * @param $tree + * array The menu tree to sort. + * @return + * void + */ +function menu_tree_sort_active_path(&$tree) { + module_load_include('inc', 'menu_block', 'menu_block.sort'); + _menu_tree_sort_active_path($tree); +} + +/** + * Prune a tree so that it begins at the specified level. + * + * This function will follow the active menu trail to the specified level. + * + * @param $tree + * array The menu tree to prune. + * @param $level + * int The level of the original tree that will start the pruned tree. + * @param $parent_item + * array The menu item that should be used as the root of the tree. + * @return + * void + */ +function menu_tree_prune_tree(&$tree, $level, $parent_item = FALSE) { + if (!empty($parent_item)) { + // Prune the tree along the path to the menu item. + for ($i = 1; $i <= MENU_MAX_DEPTH && $parent_item["p$i"] != '0'; $i++) { + $plid = $parent_item["p$i"]; + $found_active_trail = FALSE; + // Examine each element at this level for the ancestor. + foreach ($tree AS $key => &$value) { + if ($tree[$key]['link']['mlid'] == $plid) { + menu_block_set_title($tree[$key]['link']); + // Prune the tree to the children of this ancestor. + $tree = $tree[$key]['below'] ? $tree[$key]['below'] : array(); + $found_active_trail = TRUE; + break; + } + } + // If we don't find the ancestor, bail out. + if (!$found_active_trail) { + $tree = array(); + break; + } + } + } + + // Trim the upper levels down to the one desired. + for ($i = 1; $i < $level; $i++) { + $found_active_trail = FALSE; + // Examine each element at this level for the active trail. + foreach ($tree AS $key => &$value) { + if ($tree[$key]['link']['in_active_trail']) { + // Get the title for the pruned tree. + menu_block_set_title($tree[$key]['link']); + // Prune the tree to the children of the item in the active trail. + $tree = $tree[$key]['below'] ? $tree[$key]['below'] : array(); + $found_active_trail = TRUE; + break; + } + } + // If we don't find the active trail, the active item isn't in the tree we want. + if (!$found_active_trail) { + $tree = array(); + break; + } + } +} + +/** + * Prune a tree so that it begins at the active menu item. + * + * @param $tree + * array The menu tree to prune. + * @param $level + * string The level which the tree will be pruned to: 'active' or 'child'. + * @return + * void + */ +function menu_tree_prune_active_tree(&$tree, $level) { + module_load_include('inc', 'menu_block', 'menu_block.follow'); + _menu_tree_prune_active_tree($tree, $level); +} + +/** + * Prune a tree so it does not extend beyond the specified depth limit. + * + * @param $tree + * array The menu tree to prune. + * @param $depth_limit + * int The maximum depth of the returned tree; must be a positive integer. + * @return + * void + */ +function menu_tree_depth_trim(&$tree, $depth_limit) { + // Prevent invalid input from returning a trimmed tree. + if ($depth_limit < 1) { + return; + } + + // Examine each element at this level to find any possible children. + foreach ($tree AS $key => &$value) { + if ($tree[$key]['below']) { + if ($depth_limit > 1) { + menu_tree_depth_trim($tree[$key]['below'], $depth_limit-1); + } + else { + // Remove the children items. + $tree[$key]['below'] = FALSE; + } + } + if ($depth_limit == 1 && $tree[$key]['link']['has_children']) { + // Turn off the menu styling that shows there were children. + $tree[$key]['link']['has_children'] = FALSE; + $tree[$key]['link']['leaf_has_children'] = TRUE; + } + } +} + +/** + * Returns a rendered menu tree. + * + * This is a copy of menu_tree_output() with additional classes added to the + * output. + * + * @param $tree + * array A data structure representing the tree as returned from menu_tree_data. + * @return + * string The rendered HTML of that data structure. + */ +function menu_block_tree_output(&$tree, $config = array()) { + $build = array(); + $items = array(); + + // Create context if no config was provided. + if (empty($config)) { + $config['delta'] = 0; + // Grab any menu item to find the menu_name for this tree. + $menu_item = current($tree); + $config['menu_name'] = $menu_item['link']['menu_name']; + } + $hook_delta = str_replace('-', '_', $config['delta']); + $hook_menu_name = str_replace('-', '_', $config['menu_name']); + + // Pull out just the menu items we are going to render so that we + // get an accurate count for the first/last classes. + foreach ($tree as $key => &$value) { + if (!$tree[$key]['link']['hidden']) { + $items[] = $tree[$key]; + } + } + + $num_items = count($items); + foreach ($items as $i => &$data) { + $class = array(); + if ($i == 0) { + $class[] = 'first'; + } + if ($i == $num_items - 1) { + $class[] = 'last'; + } + // Set a class if the link has children. + if ($data['below']) { + $class[] = 'expanded'; + } + elseif ($data['link']['has_children']) { + $class[] = 'collapsed'; + } + else { + $class[] = 'leaf'; + } + if (!empty($data['link']['leaf_has_children'])) { + $class[] = 'has-children'; + } + // Set a class if the link is in the active trail. + if ($data['link']['in_active_trail']) { + $class[] = 'active-trail'; + $data['link']['localized_options']['attributes']['class'][] = 'active-trail'; + } + if ($data['link']['href'] == $_GET['q'] || ($data['link']['href'] == '<front>' && drupal_is_front_page())) { + $class[] = 'active'; + } + // Set a menu link ID class. + $class[] = 'menu-mlid-' . $data['link']['mlid']; + + // Allow menu-specific theme overrides. + $element['#theme'] = array( + 'menu_link__menu_block__' . $hook_delta, + 'menu_link__menu_block__' . $hook_menu_name, + 'menu_link__menu_block', + 'menu_link__' . $hook_menu_name, + 'menu_link', + ); + $element['#attributes']['class'] = $class; + $element['#title'] = $data['link']['title']; + $element['#href'] = $data['link']['href']; + $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array(); + $element['#below'] = $data['below'] ? menu_block_tree_output($data['below'], $config) : $data['below']; + $element['#original_link'] = $data['link']; + $element['#bid'] = array('module' => 'menu_block', 'delta' => $config['delta']); + // Index using the link's unique mlid. + $build[$data['link']['mlid']] = $element; + } + if ($build) { + // Make sure drupal_render() does not re-order the links. + $build['#sorted'] = TRUE; + // Add the theme wrapper for outer markup. + // Allow menu-specific theme overrides. + $build['#theme_wrappers'][] = array( + 'menu_tree__menu_block__' . $hook_delta, + 'menu_tree__menu_block__' . $hook_menu_name, + 'menu_tree__menu_block', + 'menu_tree__' . $hook_menu_name, + 'menu_tree', + ); + } + + return $build; +} diff --git a/sites/all/modules/menu_block/menu_block.pages.inc b/sites/all/modules/menu_block/menu_block.pages.inc new file mode 100644 index 0000000000000000000000000000000000000000..b556780f3c487ef83a5ce39aac97dbc84853557f --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.pages.inc @@ -0,0 +1,61 @@ +<?php +/** + * @file + * Provides infrequently used pages for menu_block. + */ + +/** + * Implements hook_help(). + */ +function _menu_block_help($path, $arg) { + $output = ''; + switch ($path) { + case 'admin/structure/block/manage/%/%': + case 'admin/structure/block/add-menu-block': + if (module_exists('help')) { + $output = '<p>' . t('To learn more about configuring menu blocks, see <a href="!url">menu block’s detailed help</a>.', array('!url' => url('admin/help/menu_block'))) . '</p>'; + } + break; + + case 'admin/help#menu_block': + $output = + '<h3>' . t('Adding menu blocks') . '</h3>' + . '<p>' . t('To add new menu blocks, use the "<a href="!add-url">Add menu block</a>" link on the <a href="!block-url">administer blocks page</a>. You will then be able to configure your menu block before adding it.', array('!add-url' => url('admin/structure/block/add-menu-block'), '!block-url' => url('admin/structure/block'))) . '</p>' + . '<h3>' . t('Configuring menu blocks') . '</h3>' + . '<p>' . t('When adding or configuring a menu block, several configuration options are available:') . '</p>' + . '<h4>' . t('Basic options') . '</h4>' + . '<dl>' + . '<dt><strong>' . t('Block title') . '</strong></dt>' + . '<dd>' . t('For menu trees that start with the 1st level, the default block title will be the menu name. For menu trees that start with the 2nd level or deeper, the default block title will be the title for the parent menu item of the specified level.') + . '<p>' . t('For example, if the active menu trail for the Mangement menu is: Administer › Structure › Menus › Main menu, then a menu block configured to start with the 1st level of the Management menu will display a block title of “Management”. And a menu block configured to start with the 3rd level of the Management menu will display a block title of “Structure”.') . '</p></dd>' + . '<dt><strong>' . t('Block title as link') . '</strong></dt>' + . '<dd>' . t('For menu trees that start with the 2nd level or deeper, the default block title will be the title for the parent menu item of the specified level. If this option is checked, the block title will be a link to that menu item.') . '</dd>' + . '<dt><strong>' . t('Administrative title') . '</strong></dt>' + . '<dd>' . t('To help identify the block on the administer blocks page, you can specify a unique title to be used on that page. If blank, the regular title will be used.') . '</dd>' + . '<dt><strong>' . t('Menu name') . '</strong></dt>' + . '<dd>' . t('Select the menu to use for the tree of links.') . '</dd>' + . '<dt><strong>' . t('Starting level') . '</strong></dt>' + . '<dd>' . t('Blocks that start with the 1st level will always be visible. Blocks that start with the 2nd level or deeper will only be visible when the trail to the active menu item is in the block’s tree.') . '</dd>' + . '<dt><strong>' . t('Maximum depth') . '</strong></dt>' + . '<dd>' . t('From the starting level, specify the maximum depth of the tree. Blocks with a maximum depth of 1 will just be a single un-nested list of links with none of those links’ children displayed.') . '</dd>' + . '</dl>' + . '<h4>' . t('Advanced options') . '</h4>' + . '<dl>' + . '<dt><strong>' . t('Make the starting level follow the active menu item') . '</strong></dt>' + . '<dd>' . t('If the active menu item is deeper than the level specified above, the starting level will follow the active menu item. Otherwise, the starting level of the tree will remain fixed.') . '</dd>' + . '<dt><strong>' . t('Expand all children') . '</strong></dt>' + . '<dd>' . t('All children of this menu will be expanded.') . '</dd>' + . '<dt><strong>' . t('Sort') . '</strong></dt>' + . '<dd>' . t('Sort each item in the active trail to the top of its level. When used on a deep or wide menu tree, the active menu item’s children will be easier to see when the page is reloaded.') . '</dd>' + . '<dt><strong>' . t('Fixed parent item') . '</strong></dt>' + . '<dd>' . t('If you select a specific menu item, you alter the “starting level” and “maximum depth” options to be relative to the fixed parent item. The tree of links will only contain children of the selected parent item.') . '</dd>' + . '</dl>' + . '<h3>' . t('Styling menu blocks') . '</h3>' + . '<p>' . t('Themers should be aware of the myriad of classes, templates and theme functions available to them. See the <a href="!url">online documentation</a> or the README.txt file for detailed information about each of them.', array('!url' => url('http://drupal.org/node/748022'))) . '</p>' + . '<h3>' . t('Menu block API') . '</h3>' + . '<p>' . t('Developers can use the API of this module to create their own menu trees outside the confines of blocks. All of the publicly available API functions are documented in the menu_block.module file.') . '</p>' + . '<p>' . t('In addition, Menu block implements HOOK_get_menus() and HOOK_menu_block_tree_alter(). See menu_block.api.php for documentation.') . '</p>'; + break; + } + return $output; +} diff --git a/sites/all/modules/menu_block/menu_block.sort.inc b/sites/all/modules/menu_block/menu_block.sort.inc new file mode 100644 index 0000000000000000000000000000000000000000..bb224b4ef2eaf2d1135023dbf3752f48be426ce1 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block.sort.inc @@ -0,0 +1,54 @@ +<?php +/** + * @file + * Provides optional sorting of the active trail in the menu tree. + */ + +/** + * Sort the active trail to the top of the tree. + * + * @param $tree + * array The menu tree to sort. + * @return + * void + */ +function _menu_tree_sort_active_path(&$tree) { + // To traverse the original tree down the active trail, we use a pointer. + $current_level =& $tree; + + // Traverse the tree along the active trail. + do { + $next_level = $sort = $first_key = FALSE; + foreach ($current_level AS $key => &$value) { + // Save the first key for later use. + if (!$first_key) { + $first_key = $key; + } + if ($current_level[$key]['link']['in_active_trail'] && $current_level[$key]['below']) { + // Don't re-sort if its already sorted. + if ($key != $first_key) { + // Create a new key that will come before the first key. + list($first_key, ) = explode(' ', $first_key); + $first_key--; + list(, $new_key) = explode(' ', $key, 2); + $new_key = "$first_key $new_key"; + // Move the item to the new key. + $current_level[$new_key] = $current_level[$key]; + unset($current_level[$key]); + $key = $new_key; + $sort = TRUE; // Flag sorting. + } + $next_level = $key; // Flag subtree. + break; + } + } + // Sort this level. + if ($sort) { + ksort($current_level); + } + // Continue in the subtree, if it exists. + if ($next_level) { + $current_level =& $current_level[$next_level]['below']; + } + } while ($next_level); +} diff --git a/sites/all/modules/menu_block/menu_block_export.admin.inc b/sites/all/modules/menu_block/menu_block_export.admin.inc new file mode 100644 index 0000000000000000000000000000000000000000..48ade57b1e1277bb3d1deed5947b0c94891e9fe9 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block_export.admin.inc @@ -0,0 +1,138 @@ +<?php + +/** + * @file + * Provides infrequently used functions and hooks for menu_block_export. + */ + +/** + * Implements hook_menu(). + */ +function _menu_block_export_menu() { + $items['admin/config/user-interface/menu-block/config'] = array( + 'title' => 'Configure', + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['admin/config/user-interface/menu-block/export'] = array( + 'title' => 'export', + 'description' => 'Export menu blocks.', + 'access arguments' => array('administer blocks'), + 'page callback' => 'menu_block_export_export', + 'type' => MENU_LOCAL_TASK, + 'file' => 'menu_block_export.admin.inc', + ); + $items['admin/config/user-interface/menu-block/export/results'] = array( + 'title' => 'Menu block bulk export results', + 'description' => 'Exported menu blocks.', + 'access arguments' => array('administer blocks'), + 'page callback' => 'menu_block_export_export', + 'type' => MENU_CALLBACK, + 'file' => 'menu_block_export.admin.inc', + ); + return $items; +} + +/** + * Page callback to export menu blocks in bulk. + */ +function menu_block_export_export() { + $blocks = variable_get('menu_block_ids', array()); + + if (!empty($blocks)) { + $form_state = array( + 'no_redirect' => TRUE, + ); + + $output = drupal_build_form('menu_block_export_form', $form_state); + if (!empty($form_state['output'])) { + $output = $form_state['output']; + } + return $output; + } + else { + return t('There are no menu blocks to be exported at this time.'); + } +} + +/** + * Menu callback; export form. + * + * @return + * The export form used by Menu block. + */ +function menu_block_export_form($form, &$form_state) { + $form['name'] = array( + '#type' => 'textfield', + '#title' => t('Module name'), + '#default_value' => 'custom', + '#description' => t('Enter the module name to export code to.'), + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Export'), + ); + + $form['#action'] = url('admin/config/user-interface/menu-block/export/results'); + return $form; +} + +/** + * Submit callback for menu_block_export_form(). + */ +function menu_block_export_form_submit(&$form, &$form_state) { + $output = ''; + $module = check_plain($form_state['values']['name']); + + foreach (variable_get('menu_block_ids', array()) AS $delta) { + $config = menu_block_get_config($delta); + // Use the constant instead of the constant's value. + if ($config['menu_name'] == MENU_TREE__CURRENT_PAGE_MENU) { + $config['menu_name'] = 'MENU_TREE__CURRENT_PAGE_MENU'; + } + else { + // If it's not the constant, wrap value in quotes. + $config['menu_name'] = "'" . $config['menu_name'] . "'"; + } + $output .= <<<END_OF_CONFIG + + '$module-$delta' => array( + 'menu_name' => {$config['menu_name']}, + 'parent_mlid' => {$config['parent_mlid']}, + 'title_link' => {$config['title_link']}, + 'admin_title' => '{$config['admin_title']}', + 'level' => {$config['level']}, + 'follow' => {$config['follow']}, + 'depth' => {$config['depth']}, + 'expanded' => {$config['expanded']}, + 'sort' => {$config['sort']}, + ), +END_OF_CONFIG; + } + + $output = <<<END_OF_CONFIG +/** + * Implements hook_menu_block_blocks(). + */ +function {$module}_menu_block_blocks() { + // The array key is the block delta used by menu block. + return array($output + ); +} + +END_OF_CONFIG; + + $element = array( + '#type' => 'textarea', + '#title' => t('Use this in your !module.module file:', array('!module' => $module)), + '#value' => $output, + '#rows' => 20, + // Since this isn't a real form, manually add additional required properties. + '#id' => 'menu-block-export-textarea', + '#name' => 'export', + '#required' => FALSE, + '#attributes' => array('style' => 'font-family: monospace;'), + '#title_display' => 'before', + '#parents' => array('dummy'), + ); + $form_state['output'] = drupal_render($element); +} diff --git a/sites/all/modules/menu_block/menu_block_export.info b/sites/all/modules/menu_block/menu_block_export.info new file mode 100644 index 0000000000000000000000000000000000000000..d9e5afbd4540c931c83fd62c99f0a33067a955a5 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block_export.info @@ -0,0 +1,18 @@ + +name = "Menu Block Export" +description = "Provides export interface for Menu block module." + +core = 7.x +dependencies[] = menu_block + +files[] = menu_block_export.module +files[] = menu_block_export.admin.inc + +configure = admin/config/user-interface/menu-block/export + +; Information added by drupal.org packaging script on 2011-03-09 +version = "7.x-2.2" +core = "7.x" +project = "menu_block" +datestamp = "1299683174" + diff --git a/sites/all/modules/menu_block/menu_block_export.module b/sites/all/modules/menu_block/menu_block_export.module new file mode 100644 index 0000000000000000000000000000000000000000..4dc0f4a6d4c8944a669d9e8b2a99b4f099c202d9 --- /dev/null +++ b/sites/all/modules/menu_block/menu_block_export.module @@ -0,0 +1,12 @@ +<?php + +/** + * @file + * Provides export interface for Menu block. + */ + +// Off-load the following infrequently called hooks to another file. +function menu_block_export_menu() { + module_load_include('inc', 'menu_block_export', 'menu_block_export.admin'); + return _menu_block_export_menu(); +} diff --git a/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_booknavigation.png b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_booknavigation.png new file mode 100644 index 0000000000000000000000000000000000000000..29cd838e405edb2c52369c3dc762d4034e2cb0f3 Binary files /dev/null and b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_booknavigation.png differ diff --git a/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_main_menu.png b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_main_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..53f10e49e98d35fec9775cc5b0185fb3678faef1 Binary files /dev/null and b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_main_menu.png differ diff --git a/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_management.png b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_management.png new file mode 100644 index 0000000000000000000000000000000000000000..8bc3fe3a74d681b7602521560d977c3a054203e1 Binary files /dev/null and b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_management.png differ diff --git a/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_menu.png b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..38cf72090abaa16000c9dceedee8196df1c5251e Binary files /dev/null and b/sites/all/modules/menu_block/plugins/content_types/menu_tree/icon_contrib_menu.png differ diff --git a/sites/all/modules/menu_block/plugins/content_types/menu_tree/menu_tree.inc b/sites/all/modules/menu_block/plugins/content_types/menu_tree/menu_tree.inc new file mode 100644 index 0000000000000000000000000000000000000000..8572ed7192bd4507a82a7762b8aeff7344416402 --- /dev/null +++ b/sites/all/modules/menu_block/plugins/content_types/menu_tree/menu_tree.inc @@ -0,0 +1,169 @@ +<?php +/** + * @file + * Provides ctools integration for "Menu block" trees. + * + * "Menu block" trees operate with no context at all. They are basically the + * same as a 'custom content' block, but not even that sophisticated. + */ + +/** + * Plugins are described by creating a $plugin array which will be used + * by the system that includes this file. + */ +$plugin = array( + // And this is just the administrative title. + // All our callbacks are named according to the standard pattern and can be deduced. + 'title' => t('Menu tree'), +); + +/** + * Supplies a list of menu tree content sub-types. + */ +function menu_block_menu_tree_content_type_content_types() { + $common_plugin = array( + 'description' => t('A configurable tree provided by Menu block.'), + 'icon' => 'icon_contrib_menu.png', + 'category' => array(t('Menus'), -9), + + // The default context. + 'defaults' => menu_block_get_config(), + + // JavaScript and CSS for the config form. + 'js' => array(drupal_get_path('module', 'menu_block') . '/menu-block.js'), + 'css' => array(drupal_get_path('module', 'menu_block') . '/menu-block-admin.css'), + ); + unset($common_plugin['defaults']['delta']); + + $menus = menu_block_get_all_menus(); + $items = array(); + foreach ($menus as $menu_name => $title) { + $items[$menu_name] = $common_plugin; + $items[$menu_name]['title'] = t('%menu menu tree', array('%menu' => $title)); + $items[$menu_name]['defaults']['menu_name'] = $menu_name; + $items[$menu_name]['menu_title'] = $title; + // Custom icons/titles for some menus. + switch ($menu_name) { + case MENU_TREE__CURRENT_PAGE_MENU: + $items[$menu_name]['title'] = t('menu tree of %menu', array('%menu' => 'the menu selected by the page')); + break; + case 'main-menu': + case 'secondary-menu': + $items[$menu_name]['icon'] = 'icon_contrib_main_menu.png'; + break; + case 'management': + $items[$menu_name]['icon'] = 'icon_contrib_management.png'; + break; + } + if (strpos($menu_name, 'book-toc-') === 0) { + $items[$menu_name]['icon'] = 'icon_contrib_booknavigation.png'; + } + } + return $items; +} + +/** + * Renders a menu_tree content type based on the delta supplied in the configuration. + * + * @param $subtype + * @param $conf + * Configuration as done at admin time. + * @param $args + * @param $context + * Context; in this case we don't have any. + * @return + * object An object with at least title and content members. + */ +function menu_block_menu_tree_content_type_render($subtype, $conf, $args, $context) { + // Ensure the delta is unique. + $ids = &drupal_static(__FUNCTION__, array()); + if (empty($ids[$conf['menu_name']])) { + $ids[$conf['menu_name']] = 0; + } + $delta = ++$ids[$conf['menu_name']]; + $conf['delta'] = 'ctools-' . $conf['menu_name'] . '-' . $delta; + + $tree = menu_tree_build($conf); + + $block = new stdClass(); + $block->subtype = $conf['menu_name']; + $block->title = $tree['subject']; + $block->title_array = $tree['subject_array']; + $block->content = $tree['content']; + + return $block; +} + +/** + * 'Edit form' callback for the content type. + */ +function menu_block_menu_tree_content_type_edit_form(&$form, &$form_state) { + $conf = $form_state['conf']; + + // Load the standard config form. + module_load_include('inc', 'menu_block', 'menu_block.admin'); + // Create a pseudo form state. + $sub_form_state = array('values' => $conf); + $form += menu_block_configure_form($form, $sub_form_state); + + // Set the options to a simple list of menu links for the configured menu. + $menus = menu_block_get_all_menus(); + $form['parent']['#options'] = menu_parent_options(array($conf['menu_name'] => $menus[$conf['menu_name']]), array('mlid' => 0)); + // Hide the Parent item option for the special "active" menu. + if ($conf['menu_name'] == MENU_TREE__CURRENT_PAGE_MENU) { + $form['parent']['#type'] = 'hidden'; + $form['admin_title']['#suffix'] = '<div id="edit-parent-wrapper"><strong>' . t('Parent item:') . '</strong><br />' . t('<em>The menu selected by the page</em> can be customized on the <a href="!url">Menu block settings page</a>.', array('!url' => url('admin/config/user-interface/menu-block'))) . '</div>'; + } + // Remove CSS class hooks for jQuery script on parent select. + unset($form['parent']['#attributes']); +} + +/** + * Submit callback for content type editing form. + */ +function menu_block_menu_tree_content_type_edit_form_submit(&$form, &$form_state) { + foreach (array_keys($form_state['subtype']['defaults']) as $key) { + if (!empty($form_state['values'][$key])) { + $form_state['conf'][$key] = $form_state['values'][$key]; + } + } +} + +/** + * Return the tree's title with an admin-sensitive prefix. + */ +function menu_block_menu_tree_content_type_admin_title($subtype, $conf, $context = NULL) { + if (!empty($conf['admin_title'])) { + $output = filter_xss_admin($conf['admin_title']); + } + else { + // Build the menu tree. + module_load_include('inc', 'menu_block', 'menu_block.admin'); + $output = _menu_block_format_title($conf); + } + return $output; +} + +/** + * Callback to provide administrative info (the preview in panels when building a panel). + */ +function menu_block_menu_tree_content_type_admin_info($subtype, $conf, $context = NULL) { + // Ensure the delta is unique. + $ids = &drupal_static(__FUNCTION__, array()); + if (empty($ids[$conf['menu_name']])) { + $ids[$conf['menu_name']] = 0; + } + $delta = ++$ids[$conf['menu_name']]; + $conf['delta'] = 'ctools-' . $conf['menu_name'] . '-' . $delta; + + // Force the title to not be a link. + $conf['title_link'] = 0; + $tree = menu_tree_build($conf); + + $block = new stdClass(); + $block->subtype = $conf['menu_name']; + $block->title = $tree['subject']; + $block->content = $tree['content']; + + return $block; +} diff --git a/sites/all/modules/unl/plugins/unl_context_reaction_blockremove.inc b/sites/all/modules/unl/plugins/unl_context_reaction_blockremove.inc new file mode 100644 index 0000000000000000000000000000000000000000..67eba0ffb67bdfb13bc57b413a9b1a86666324a8 --- /dev/null +++ b/sites/all/modules/unl/plugins/unl_context_reaction_blockremove.inc @@ -0,0 +1,41 @@ +<?php + +class unl_context_reaction_blockremove extends context_reaction { + function options_form($context) { + $values = $this->fetch_from_context($context); + $form = array( + '#tree' => TRUE, + '#title' => t('Block Remove variables'), + 'block' => array( + '#title' => t('Block'), + '#description' => t('Not much you can do with this. Currently used to remove main-menu from nav.'), + '#type' => 'textfield', + '#maxlength' => 255, + '#default_value' => isset($values['block']) ? $values['block'] : 'main-menu', + ), + ); + return $form; + } + + function execute(&$page) { + global $theme; + + // The theme system might not yet be initialized. We need $theme. + drupal_theme_initialize(); + + // Populate all block regions + $all_regions = system_region_list($theme); + + foreach ($this->get_contexts() as $k => $v) { + if (!empty($v->reactions[$this->plugin]['block'])) { + foreach (array_keys($all_regions) as $region) { + foreach ($page[$region] as $key => $item) { + if (isset($item['#block']->delta) && !empty($item['#block']->delta) && $item['#block']->delta == $v->reactions[$this->plugin]['block']) { + unset($page[$region][$key]); + } + } + } + } + } + } +} diff --git a/sites/all/modules/unl/plugins/unl_context_reaction_linkrelhome.inc b/sites/all/modules/unl/plugins/unl_context_reaction_linkrelhome.inc new file mode 100644 index 0000000000000000000000000000000000000000..04416871d303e0e205a82b799f073b1d68789c68 --- /dev/null +++ b/sites/all/modules/unl/plugins/unl_context_reaction_linkrelhome.inc @@ -0,0 +1,31 @@ +<?php + +class unl_context_reaction_linkrelhome extends context_reaction { + function options_form($context) { + $values = $this->fetch_from_context($context); + $form = array( + '#tree' => TRUE, + '#title' => t('link rel="home" tag variables'), + 'url' => array( + '#title' => t('URL'), + '#description' => t('Address to set as the href attribute in the \'link rel="home" tag\' for the purpose of setting the default selected breadcrumb in the UNL template.'), + '#type' => 'textfield', + '#maxlength' => 255, + '#default_value' => isset($values['url']) ? $values['url'] : '', + ), + ); + return $form; + } + + function execute() { + foreach ($this->get_contexts() as $k => $v) { + if (!empty($v->reactions[$this->plugin]['url'])) { + $attributes = array( + 'rel' => 'home', + 'href' => check_plain($v->reactions[$this->plugin]['url']), + ); + drupal_add_html_head_link($attributes); + } + } + } +} diff --git a/sites/all/modules/unl/unl.module b/sites/all/modules/unl/unl.module index 423e5827524ee7de3d174e5ad10b318d7ad1c92d..c8ddc6f07df419df7886d9d7680a4c47d9b20c24 100644 --- a/sites/all/modules/unl/unl.module +++ b/sites/all/modules/unl/unl.module @@ -3,7 +3,94 @@ require_once dirname(__FILE__) . '/includes/common.php'; /** - * Implements hook_field_attach_view_alter(). + * Implementation of hook_html_head_alter(). + */ +function unl_html_head_alter(&$head_elements) { + // If <link rel="home"> has already been set elsewhere (in a Context for example) then return... + foreach ($head_elements as $key => $element) { + if ($element["#tag"] == 'link' && isset($element['#attributes']['rel']) && $element['#attributes']['rel'] == 'home') { + return; + } + } + // ...otherwise add a <link rel="home"> tag with the front page as the href attribute + $element = array( + '#tag' => 'link', + '#attributes' => array( + 'rel' => 'home', + 'href' => url('<front>', array('absolute' => TRUE)), + ), + '#type' => 'html_tag' + ); + $head_elements['drupal_add_html_head_link:home'] = $element; +} + +/** + * Implementation of hook_context_plugins(). (Context module) + */ +function unl_context_plugins() { + $plugins = array(); + $plugins['unl_context_reaction_linkrelhome'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'unl') .'/plugins', + 'file' => 'unl_context_reaction_linkrelhome.inc', + 'class' => 'unl_context_reaction_linkrelhome', + 'parent' => 'context_reaction', + ), + ); + $plugins['unl_context_reaction_blockremove'] = array( + 'handler' => array( + 'path' => drupal_get_path('module', 'unl') .'/plugins', + 'file' => 'unl_context_reaction_blockremove.inc', + 'class' => 'unl_context_reaction_blockremove', + 'parent' => 'context_reaction', + ), + ); + return $plugins; +} + +/** + * Implementation of hook_context_registry(). (Context module) + */ +function unl_context_registry() { + return array( + 'reactions' => array( + 'linkrelhome' => array( + 'title' => t('link rel="home" tag'), + 'plugin' => 'unl_context_reaction_linkrelhome', + ), + 'blockremove' => array( + 'title' => t('Block Remove'), + 'plugin' => 'unl_context_reaction_blockremove', + ), + ), + ); +} + +/** + * Implementation of hook_context_page_reaction(). (Context module) + */ +function unl_context_page_reaction() { + if ($plugin = context_get_plugin('reaction', 'linkrelhome')) { + $plugin->execute(); + } +} + +/** + * Implementation of hook_page_build(). + */ +function unl_page_build(&$page) { + if (module_exists('context')) { + if ($plugin = context_get_plugin('reaction', 'blockremove')) { + $plugin->execute($page); + } + // See block_page_build. Clear static cache b/c in overlay form submissions + // hook_page_build can get called more than once per page load. + drupal_static_reset('context_reaction_block_list'); + } +} + +/** + * Implementation of hook_field_attach_view_alter(). */ function unl_field_attach_view_alter(&$output, $context) { // Replace the field named field_hrorgunit containing an org unit number with that unit's listing from the UNL directory diff --git a/sites/all/themes/unl_wdn/html.tpl.php b/sites/all/themes/unl_wdn/html.tpl.php index 02b2252dc442cfee9fd7beb53dc754749e971598..7ed1cea4d9af5f02acb901f700f2fd060cdd7b3c 100644 --- a/sites/all/themes/unl_wdn/html.tpl.php +++ b/sites/all/themes/unl_wdn/html.tpl.php @@ -1,8 +1,7 @@ <?php /** * @file - * Default theme implementation to display the basic html structure of a single - * Drupal page. + * unl_wdn theme implementation to display the basic html structure of a single Drupal page. * * Variables: * - $css: An array of CSS files for the current page. @@ -44,7 +43,6 @@ $t->head .= PHP_EOL . $head . PHP_EOL . $styles . PHP_EOL . $scripts . PHP_EOL - . '<link href="' . url('<front>', array('absolute' => TRUE)) . '" rel="home" />' . PHP_EOL . '<link rel="logout" href="user/logout" />' . PHP_EOL . '<link rel="login" href="' . url('user', array('query' => drupal_get_destination())) . '" />' . PHP_EOL . theme_get_setting('head_html') . PHP_EOL diff --git a/sites/all/themes/unl_wdn/menu-block-wrapper.tpl.php b/sites/all/themes/unl_wdn/menu-block-wrapper.tpl.php new file mode 100644 index 0000000000000000000000000000000000000000..39abf4a1938a32faf4d6a97ce482299572cf8de9 --- /dev/null +++ b/sites/all/themes/unl_wdn/menu-block-wrapper.tpl.php @@ -0,0 +1,21 @@ +<?php +/** + * @file + * unl_wdn theme implementation to wrap menu blocks. + * + * Available variables: + * - $content: The renderable array containing the menu. + * - $classes: A string containing the CSS classes for the DIV tag. Includes: + * menu-block-DELTA, menu-name-NAME, parent-mlid-MLID, and menu-level-LEVEL. + * - $classes_array: An array containing each of the CSS classes. + * + * The following variables are provided for contextual information. + * - $delta: (string) The menu_block's block delta. + * - $config: An array of the block's configuration settings. Includes + * menu_name, parent_mlid, title_link, admin_title, level, follow, depth, + * expanded, and sort. + * + * @see template_preprocess_menu_block_wrapper() + */ +?> +<?php print render($content); ?>